Code

Fixed serialisation problem by moving the serialisation step out of the
authorrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Wed, 3 Apr 2002 05:54:31 +0000 (05:54 +0000)
committerrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Wed, 3 Apr 2002 05:54:31 +0000 (05:54 +0000)
hyperdb.Class (get, set) into the hyperdb.Database.

Also fixed htmltemplate after the showid changes I made yesterday.

Unit tests for all of the above written.

git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@690 57a73879-2fb5-44c3-a270-3262357dd7e2

CHANGES.txt
roundup/backends/back_anydbm.py
roundup/backends/back_bsddb.py
roundup/htmltemplate.py
roundup/hyperdb.py
test/test_db.py
test/test_htmltemplate.py

index 1f3b81b93fcbd153dbeb2723aefb6bf9379ac848..fe640b13a82c0a1dd200486d94e9c93fd3d208fc 100644 (file)
@@ -1,7 +1,7 @@
 This file contains the changes to the Roundup system over time. The entries
 are given with the most recent entry first.
 
-0.4.2
+2002-04-?? 0.4.2
 Feature:
  . link() htmltemplate function now has a "showid" option for links and
    multilinks. When true, it only displays the linked node id as the anchor
@@ -18,6 +18,7 @@ Feature:
 
 Fixed:
  . stop sending blank (whitespace-only) notes
+ . cleanup of serialisation for database storage
 
 
 2002-03-25 - 0.4.1
index df287d8295a3df930643ec6db4a093465ca3ad08..65607c8e1c619db9bca4a093abf6d8994b665a1c 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-#$Id: back_anydbm.py,v 1.30 2002-02-27 03:40:59 richard Exp $
+#$Id: back_anydbm.py,v 1.31 2002-04-03 05:54:31 richard Exp $
 '''
 This module defines a backend that saves the hyperdatabase in a database
 chosen by anydbm. It is guaranteed to always be available in python
@@ -177,6 +177,7 @@ class Database(FileStorage, hyperdb.Database):
         if hyperdb.DEBUG:
             print 'setnode', (self, classname, nodeid, node)
         self.dirtynodes.setdefault(classname, {})[nodeid] = 1
+
         # can't set without having already loaded the node
         self.cache[classname][nodeid] = node
         self.savenode(classname, nodeid, node)
@@ -204,9 +205,17 @@ class Database(FileStorage, hyperdb.Database):
             db = self.getclassdb(classname)
         if not db.has_key(nodeid):
             raise IndexError, "no such %s %s"%(classname, nodeid)
+
+        # decode
         res = marshal.loads(db[nodeid])
+
+        # reverse the serialisation
+        res = self.unserialise(classname, res)
+
+        # store off in the cache
         if cache:
             cache[nodeid] = res
+
         return res
 
     def hasnode(self, classname, nodeid, db=None):
@@ -380,11 +389,17 @@ class Database(FileStorage, hyperdb.Database):
             db = self.databases[db_name] = self.getclassdb(classname, 'c')
 
         # now save the marshalled data
-        db[nodeid] = marshal.dumps(node)
+        db[nodeid] = marshal.dumps(self.serialise(classname, node))
 
     def _doSaveJournal(self, classname, nodeid, action, params):
+        # serialise first
+        if action in ('set', 'create'):
+            params = self.serialise(classname, params)
+
+        # create the journal entry
         entry = (nodeid, date.Date().get_tuple(), self.journaltag, action,
             params)
+
         if hyperdb.DEBUG:
             print '_doSaveJournal', entry
 
@@ -397,11 +412,13 @@ class Database(FileStorage, hyperdb.Database):
 
         # now insert the journal entry
         if db.has_key(nodeid):
+            # append to existing
             s = db[nodeid]
             l = marshal.loads(s)
             l.append(entry)
         else:
             l = [entry]
+
         db[nodeid] = marshal.dumps(l)
 
     def _doStoreFile(self, name, **databases):
@@ -425,6 +442,9 @@ class Database(FileStorage, hyperdb.Database):
 
 #
 #$Log: not supported by cvs2svn $
+#Revision 1.30  2002/02/27 03:40:59  richard
+#Ran it through pychecker, made fixes
+#
 #Revision 1.29  2002/02/25 14:34:31  grubert
 # . use blobfiles in back_anydbm which is used in back_bsddb.
 #   change test_db as dirlist does not work for subdirectories.
index 973e7bc8cb8e47728a427e4ce6177c4abd62a48d..ea5e1b7cc34714b634e6460c3fc9b0fb805fe169 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-#$Id: back_bsddb.py,v 1.16 2002-02-27 03:40:59 richard Exp $
+#$Id: back_bsddb.py,v 1.17 2002-04-03 05:54:31 richard Exp $
 '''
 This module defines a backend that saves the hyperdatabase in BSDDB.
 '''
@@ -95,20 +95,33 @@ class Database(back_anydbm.Database):
         return res
 
     def _doSaveJournal(self, classname, nodeid, action, params):
+        # serialise first
+        if action in ('set', 'create'):
+            params = self.serialise(classname, params)
+
         entry = (nodeid, date.Date().get_tuple(), self.journaltag, action,
             params)
+
+        if hyperdb.DEBUG:
+            print '_doSaveJournal', entry
+
         db = bsddb.btopen(os.path.join(self.dir, 'journals.%s'%classname), 'c')
+
         if db.has_key(nodeid):
             s = db[nodeid]
             l = marshal.loads(s)
             l.append(entry)
         else:
             l = [entry]
+
         db[nodeid] = marshal.dumps(l)
         db.close()
 
 #
 #$Log: not supported by cvs2svn $
+#Revision 1.16  2002/02/27 03:40:59  richard
+#Ran it through pychecker, made fixes
+#
 #Revision 1.15  2002/02/16 09:15:33  richard
 #forgot to patch bsddb backend too
 #
index 59ce26cc19cf92fd964d59f47e7e4eaa2aa6e7de..03553280b4ba90916432cd3354231f8338ee29cd 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: htmltemplate.py,v 1.85 2002-04-02 01:40:58 richard Exp $
+# $Id: htmltemplate.py,v 1.86 2002-04-03 05:54:31 richard Exp $
 
 __doc__ = """
 Template engine.
@@ -357,10 +357,11 @@ class TemplateFunctions:
             linkvalue = cgi.escape(linkcl.get(value, k))
             if showid:
                 label = value
-               title = ' title="%s"'%linkvalue
-               # note ... this should be urllib.quote(linkcl.get(value, k))
+                title = ' title="%s"'%linkvalue
+                # note ... this should be urllib.quote(linkcl.get(value, k))
             else:
                 label = linkvalue
+                title = ''
             if is_download:
                 return '<a href="%s%s/%s"%s>%s</a>'%(linkname, value,
                     linkvalue, title, label)
@@ -376,9 +377,10 @@ class TemplateFunctions:
                 if showid:
                     label = value
                     title = ' title="%s"'%linkvalue
-                   # note ... this should be urllib.quote(linkcl.get(value, k))
+                    # note ... this should be urllib.quote(linkcl.get(value, k))
                 else:
                     label = linkvalue
+                    title = ''
                 if is_download:
                     l.append('<a href="%s%s/%s"%s>%s</a>'%(linkname, value,
                         linkvalue, title, label))
@@ -1126,6 +1128,12 @@ class NewItemTemplate(TemplateFunctions):
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.85  2002/04/02 01:40:58  richard
+#  . link() htmltemplate function now has a "showid" option for links and
+#    multilinks. When true, it only displays the linked node id as the anchor
+#    text. The link value is displayed as a tooltip using the title anchor
+#    attribute.
+#
 # Revision 1.84  2002/03/29 19:41:48  rochecompaan
 #  . Fixed display of mutlilink properties when using the template
 #    functions, menu and plain.
index 3d3c97bbae48ce7552b9f6de5ec83bbead7ef603..7319f1a227b010f77e7eacf9c00d322229381bc8 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: hyperdb.py,v 1.59 2002-03-12 22:52:26 richard Exp $
+# $Id: hyperdb.py,v 1.60 2002-04-03 05:54:31 richard Exp $
 
 __doc__ = """
 Hyperdatabase implementation, especially field types.
@@ -171,11 +171,51 @@ transaction.
         '''
         raise NotImplementedError
 
+    def serialise(self, classname, node):
+        '''Copy the node contents, converting non-marshallable data into
+           marshallable data.
+        '''
+        if DEBUG: print 'serialise', classname, node
+        properties = self.getclass(classname).getprops()
+        d = {}
+        for k, v in node.items():
+            prop = properties[k]
+
+            if isinstance(prop, Password):
+                d[k] = str(v)
+            elif isinstance(prop, Date) and v is not None:
+                d[k] = v.get_tuple()
+            elif isinstance(prop, Interval) and v is not None:
+                d[k] = v.get_tuple()
+            else:
+                d[k] = v
+        return d
+
     def setnode(self, classname, nodeid, node):
         '''Change the specified node.
         '''
         raise NotImplementedError
 
+    def unserialise(self, classname, node):
+        '''Decode the marshalled node data
+        '''
+        if DEBUG: print 'unserialise', classname, node
+        properties = self.getclass(classname).getprops()
+        d = {}
+        for k, v in node.items():
+            prop = properties[k]
+            if isinstance(prop, Date) and v is not None:
+                d[k] = date.Date(v)
+            elif isinstance(prop, Interval) and v is not None:
+                d[k] = date.Interval(v)
+            elif isinstance(prop, Password):
+                p = password.Password()
+                p.unpack(v)
+                d[k] = p
+            else:
+                d[k] = v
+        return d
+
     def getnode(self, classname, nodeid, db=None, cache=1):
         '''Get a node from the database.
         '''
@@ -396,17 +436,6 @@ class Class:
                 # TODO: None isn't right here, I think...
                 propvalues[key] = None
 
-        # convert all data to strings
-        for key, prop in self.properties.items():
-            if isinstance(prop, Date):
-                if propvalues[key] is not None:
-                    propvalues[key] = propvalues[key].get_tuple()
-            elif isinstance(prop, Interval):
-                if propvalues[key] is not None:
-                    propvalues[key] = propvalues[key].get_tuple()
-            elif isinstance(prop, Password):
-                propvalues[key] = str(propvalues[key])
-
         # done
         self.db.addnode(self.classname, newid, propvalues)
         self.db.addjournal(self.classname, newid, 'create', propvalues)
@@ -443,20 +472,6 @@ class Class:
             else:
                 return default
 
-        # possibly convert the marshalled data to instances
-        if isinstance(prop, Date):
-            if d[propname] is None:
-                return None
-            return date.Date(d[propname])
-        elif isinstance(prop, Interval):
-            if d[propname] is None:
-                return None
-            return date.Interval(d[propname])
-        elif isinstance(prop, Password):
-            p = password.Password()
-            p.unpack(d[propname])
-            return p
-
         return d[propname]
 
     # XXX not in spec
@@ -605,17 +620,17 @@ class Class:
             elif isinstance(prop, Password):
                 if not isinstance(value, password.Password):
                     raise TypeError, 'new property "%s" not a Password'% key
-                propvalues[key] = value = str(value)
+                propvalues[key] = value
 
             elif value is not None and isinstance(prop, Date):
                 if not isinstance(value, date.Date):
                     raise TypeError, 'new property "%s" not a Date'% key
-                propvalues[key] = value = value.get_tuple()
+                propvalues[key] = value
 
             elif value is not None and isinstance(prop, Interval):
                 if not isinstance(value, date.Interval):
                     raise TypeError, 'new property "%s" not an Interval'% key
-                propvalues[key] = value = value.get_tuple()
+                propvalues[key] = value
 
             node[key] = value
 
@@ -1097,6 +1112,9 @@ def Choice(name, db, *options):
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.59  2002/03/12 22:52:26  richard
+# more pychecker warnings removed
+#
 # Revision 1.58  2002/02/27 03:23:16  richard
 # Ran it through pychecker, made fixes
 #
index dc08709cc447092623c9c99610f39281eb5e5b74..19dcd1682393729e1ed6482803638ad226018043 100644 (file)
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: test_db.py,v 1.19 2002-02-25 14:34:31 grubert Exp $ 
+# $Id: test_db.py,v 1.20 2002-04-03 05:54:31 richard Exp $ 
 
 import unittest, os, shutil
 
 from roundup.hyperdb import String, Password, Link, Multilink, Date, \
     Interval, Class, DatabaseError
 from roundup.roundupdb import FileClass
-from roundup import date
+from roundup import date, password
 
 def setupSchema(db, create):
     status = Class(db, "status", name=String())
@@ -84,7 +84,11 @@ class anydbmDBTestCase(MyTestCase):
 
         a = self.db.issue.get('5', "deadline")
         self.db.issue.set('5', deadline=date.Date())
-        self.assertNotEqual(a, self.db.issue.get('5', "deadline"))
+        b = self.db.issue.get('5', "deadline")
+        self.db.commit()
+        self.assertNotEqual(a, b)
+        self.assertNotEqual(b, date.Date('1970-1-1 00:00:00'))
+        self.db.issue.set('5', deadline=date.Date())
 
         a = self.db.issue.get('5', "foo")
         self.db.issue.set('5', foo=date.Interval('-1d'))
@@ -98,6 +102,17 @@ class anydbmDBTestCase(MyTestCase):
         self.db.status.history('1')
         self.db.status.history('2')
 
+    def testSerialisation(self):
+        self.db.issue.create(title="spam", status='1',
+            deadline=date.Date(), foo=date.Interval('-1d'))
+        self.db.commit()
+        assert isinstance(self.db.issue.get('1', 'deadline'), date.Date)
+        assert isinstance(self.db.issue.get('1', 'foo'), date.Interval)
+        self.db.user.create(username="fozzy",
+            password=password.Password('t. bear'))
+        self.db.commit()
+        assert isinstance(self.db.user.get('1', 'password'), password.Password)
+
     def testTransactions(self):
         # remember the number of items we started
         num_issues = len(self.db.issue.list())
@@ -324,7 +339,8 @@ class bsddb3ReadOnlyDBTestCase(anydbmReadOnlyDBTestCase):
 
 
 def suite():
-    l = [unittest.makeSuite(anydbmDBTestCase, 'test'),
+    l = [
+         unittest.makeSuite(anydbmDBTestCase, 'test'),
          unittest.makeSuite(anydbmReadOnlyDBTestCase, 'test')
     ]
 
@@ -346,6 +362,11 @@ def suite():
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.19  2002/02/25 14:34:31  grubert
+#  . use blobfiles in back_anydbm which is used in back_bsddb.
+#    change test_db as dirlist does not work for subdirectories.
+#    ATTENTION: blobfiles now creates subdirectories for files.
+#
 # Revision 1.18  2002/01/22 07:21:13  richard
 # . fixed back_bsddb so it passed the journal tests
 #
index 3fd109c5edf7f1bf067d0898cfccb1e876cf2a07..f216db7e23cf15583b3423d9797f13bab97c78de 100644 (file)
@@ -8,7 +8,7 @@
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 #
-# $Id: test_htmltemplate.py,v 1.12 2002-03-29 19:41:48 rochecompaan Exp $ 
+# $Id: test_htmltemplate.py,v 1.13 2002-04-03 05:54:31 richard Exp $ 
 
 import unittest, cgi, time
 
@@ -234,10 +234,18 @@ class NodeCase(unittest.TestCase):
         self.assertEqual(self.tf.do_link('link'),
             '<a href="other1">the key1</a>')
 
+    def testLink_link_id(self):
+        self.assertEqual(self.tf.do_link('link', showid=1),
+            '<a href="other1" title="the key1">1</a>')
+
     def testLink_multilink(self):
         self.assertEqual(self.tf.do_link('multilink'),
             '<a href="other1">the key1</a>, <a href="other2">the key2</a>')
 
+    def testLink_multilink_id(self):
+        self.assertEqual(self.tf.do_link('multilink', showid=1),
+            '<a href="other1" title="the key1">1</a>, <a href="other2" title="the key2">2</a>')
+
 #    def do_count(self, property, **args):
     def testCount_nonlinks(self):
         s = _('[Count: not a Multilink]')
@@ -344,6 +352,10 @@ def suite():
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.12  2002/03/29 19:41:48  rochecompaan
+#  . Fixed display of mutlilink properties when using the template
+#    functions, menu and plain.
+#
 # Revision 1.11  2002/02/21 23:11:45  richard
 #  . fixed some problems in date calculations (calendar.py doesn't handle over-
 #    and under-flow). Also, hour/minute/second intervals may now be more than