Code

Ran it through pychecker, made fixes
[roundup.git] / roundup / hyperdb.py
index c221d0adf3346e722f1bcc01af63f971551fa56c..9aa575ac63813004cce38c19c62f53cf26501a31 100644 (file)
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: hyperdb.py,v 1.48 2002-01-14 06:32:34 richard Exp $
+# $Id: hyperdb.py,v 1.58 2002-02-27 03:23:16 richard Exp $
 
 __doc__ = """
 Hyperdatabase implementation, especially field types.
 """
 
 # standard python modules
-import cPickle, re, string, weakref
+import re, string, weakref, os
 
 # roundup modules
 import date, password
 
+DEBUG = os.environ.get('HYPERDBDEBUG', '')
 
 #
 # Types
@@ -54,17 +55,24 @@ class Interval:
 class Link:
     """An object designating a Link property that links to a
        node in a specified class."""
-    def __init__(self, classname):
+    def __init__(self, classname, do_journal='no'):
         self.classname = classname
+        self.do_journal = do_journal == 'yes'
     def __repr__(self):
         return '<%s to "%s">'%(self.__class__, self.classname)
 
 class Multilink:
     """An object designating a Multilink property that links
        to nodes in a specified class.
+
+       "classname" indicates the class to link to
+
+       "do_journal" indicates whether the linked-to nodes should have
+                    'link' and 'unlink' events placed in their journal
     """
-    def __init__(self, classname):
+    def __init__(self, classname, do_journal='no'):
         self.classname = classname
+        self.do_journal = do_journal == 'yes'
     def __repr__(self):
         return '<%s to "%s">'%(self.__class__, self.classname)
 
@@ -204,6 +212,11 @@ transaction.
         '''
         raise NotImplementedError
 
+    def pack(self, pack_before):
+        ''' pack the database
+        '''
+        raise NotImplementedError
+
     def commit(self):
         ''' Commit the current transactions.
 
@@ -309,8 +322,9 @@ class Class:
                 propvalues[key] = value
 
                 # register the link with the newly linked node
-                self.db.addjournal(link_class, value, 'link',
-                    (self.classname, newid, key))
+                if self.properties[key].do_journal:
+                    self.db.addjournal(link_class, value, 'link',
+                        (self.classname, newid, key))
 
             elif isinstance(prop, Multilink):
                 if type(value) != type([]):
@@ -336,8 +350,9 @@ class Class:
                     if not self.db.hasnode(link_class, id):
                         raise IndexError, '%s has no node %s'%(link_class, id)
                     # register the link with the newly linked node
-                    self.db.addjournal(link_class, id, 'link',
-                        (self.classname, newid, key))
+                    if self.properties[key].do_journal:
+                        self.db.addjournal(link_class, id, 'link',
+                            (self.classname, newid, key))
 
             elif isinstance(prop, String):
                 if type(value) != type(''):
@@ -348,11 +363,11 @@ class Class:
                     raise TypeError, 'new property "%s" not a Password'%key
 
             elif isinstance(prop, Date):
-                if not isinstance(value, date.Date):
+                if value is not None and not isinstance(value, date.Date):
                     raise TypeError, 'new property "%s" not a Date'%key
 
             elif isinstance(prop, Interval):
-                if not isinstance(value, date.Interval):
+                if value is not None and not isinstance(value, date.Interval):
                     raise TypeError, 'new property "%s" not an Interval'%key
 
         # make sure there's data where there needs to be
@@ -370,9 +385,11 @@ class Class:
         # convert all data to strings
         for key, prop in self.properties.items():
             if isinstance(prop, Date):
-                propvalues[key] = propvalues[key].get_tuple()
+                if propvalues[key] is not None:
+                    propvalues[key] = propvalues[key].get_tuple()
             elif isinstance(prop, Interval):
-                propvalues[key] = propvalues[key].get_tuple()
+                if propvalues[key] is not None:
+                    propvalues[key] = propvalues[key].get_tuple()
             elif isinstance(prop, Password):
                 propvalues[key] = str(propvalues[key])
 
@@ -414,8 +431,12 @@ class Class:
 
         # 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()
@@ -484,6 +505,13 @@ class Class:
             # the writeable properties.
             prop = self.properties[key]
 
+            # if the value's the same as the existing value, no sense in
+            # doing anything
+            if node.has_key(key) and value == node[key]:
+                del propvalues[key]
+                continue
+
+            # do stuff based on the prop type
             if isinstance(prop, Link):
                 link_class = self.properties[key].classname
                 # if it isn't a number, it's a key
@@ -499,15 +527,16 @@ class Class:
                 if not self.db.hasnode(link_class, value):
                     raise IndexError, '%s has no node %s'%(link_class, value)
 
-                # register the unlink with the old linked node
-                if node[key] is not None:
-                    self.db.addjournal(link_class, node[key], 'unlink',
-                        (self.classname, nodeid, key))
+                if self.properties[key].do_journal:
+                    # register the unlink with the old linked node
+                    if node[key] is not None:
+                        self.db.addjournal(link_class, node[key], 'unlink',
+                            (self.classname, nodeid, key))
 
-                # register the link with the newly linked node
-                if value is not None:
-                    self.db.addjournal(link_class, value, 'link',
-                        (self.classname, nodeid, key))
+                    # register the link with the newly linked node
+                    if value is not None:
+                        self.db.addjournal(link_class, value, 'link',
+                            (self.classname, nodeid, key))
 
             elif isinstance(prop, Multilink):
                 if type(value) != type([]):
@@ -537,19 +566,22 @@ class Class:
                     if id in value:
                         continue
                     # register the unlink with the old linked node
-                    self.db.addjournal(link_class, id, 'unlink',
-                        (self.classname, nodeid, key))
+                    if self.properties[key].do_journal:
+                        self.db.addjournal(link_class, id, 'unlink',
+                            (self.classname, nodeid, key))
                     l.remove(id)
 
                 # handle additions
                 for id in value:
                     if not self.db.hasnode(link_class, id):
-                        raise IndexError, '%s has no node %s'%(link_class, id)
+                        raise IndexError, '%s has no node %s'%(
+                            link_class, id)
                     if id in l:
                         continue
                     # register the link with the newly linked node
-                    self.db.addjournal(link_class, id, 'link',
-                        (self.classname, nodeid, key))
+                    if self.properties[key].do_journal:
+                        self.db.addjournal(link_class, id, 'link',
+                            (self.classname, nodeid, key))
                     l.append(id)
 
             elif isinstance(prop, String):
@@ -561,18 +593,23 @@ class Class:
                     raise TypeError, 'new property "%s" not a Password'% key
                 propvalues[key] = value = str(value)
 
-            elif isinstance(prop, Date):
+            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()
 
-            elif isinstance(prop, Interval):
+            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()
 
             node[key] = value
 
+        # nothing to do?
+        if not propvalues:
+            return
+
+        # do the set, and journal it
         self.db.setnode(self.classname, nodeid, node)
         self.db.addjournal(self.classname, nodeid, 'set', propvalues)
 
@@ -608,6 +645,10 @@ class Class:
         return self.db.getjournal(self.classname, nodeid)
 
     # Locating nodes:
+    def hasnode(self, nodeid):
+        '''Determine if the given nodeid actually exists
+        '''
+        return self.db.hasnode(self.classname, nodeid)
 
     def setkey(self, propname):
         """Select a String property of this class to be the key property.
@@ -821,7 +862,7 @@ class Class:
                     else:
                         continue
                     break
-                elif t == 2 and not v.search(node[k]):
+                elif t == 2 and (node[k] is None or not v.search(node[k])):
                     # RE search
                     break
                 elif t == 6 and node[k] != v:
@@ -1033,14 +1074,57 @@ class Node:
         return self.cl.retire(self.nodeid)
 
 
-def Choice(name, *options):
-    cl = Class(db, name, name=hyperdb.String(), order=hyperdb.String())
+def Choice(name, db, *options):
+    '''Quick helper to create a simple class with choices
+    '''
+    cl = Class(db, name, name=String(), order=String())
     for i in range(len(options)):
-        cl.create(name=option[i], order=i)
+        cl.create(name=options[i], order=i)
     return hyperdb.Link(name)
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.57  2002/02/20 05:23:24  richard
+# Didn't accomodate new values for new properties
+#
+# Revision 1.56  2002/02/20 05:05:28  richard
+#  . Added simple editing for classes that don't define a templated interface.
+#    - access using the admin "class list" interface
+#    - limited to admin-only
+#    - requires the csv module from object-craft (url given if it's missing)
+#
+# Revision 1.55  2002/02/15 07:27:12  richard
+# Oops, precedences around the way w0rng.
+#
+# Revision 1.54  2002/02/15 07:08:44  richard
+#  . Alternate email addresses are now available for users. See the MIGRATION
+#    file for info on how to activate the feature.
+#
+# Revision 1.53  2002/01/22 07:21:13  richard
+# . fixed back_bsddb so it passed the journal tests
+#
+# ... it didn't seem happy using the back_anydbm _open method, which is odd.
+# Yet another occurrance of whichdb not being able to recognise older bsddb
+# databases. Yadda yadda. Made the HYPERDBDEBUG stuff more sane in the
+# process.
+#
+# Revision 1.52  2002/01/21 16:33:19  rochecompaan
+# You can now use the roundup-admin tool to pack the database
+#
+# Revision 1.51  2002/01/21 03:01:29  richard
+# brief docco on the do_journal argument
+#
+# Revision 1.50  2002/01/19 13:16:04  rochecompaan
+# Journal entries for link and multilink properties can now be switched on
+# or off.
+#
+# Revision 1.49  2002/01/16 07:02:57  richard
+#  . lots of date/interval related changes:
+#    - more relaxed date format for input
+#
+# Revision 1.48  2002/01/14 06:32:34  richard
+#  . #502951 ] adding new properties to old database
+#
 # Revision 1.47  2002/01/14 02:20:15  richard
 #  . changed all config accesses so they access either the instance or the
 #    config attriubute on the db. This means that all config is obtained from