diff --git a/roundup/hyperdb.py b/roundup/hyperdb.py
index 15f5c80d217a47d666d6c61348c81ed9deda0ac5..5761e1d872ece31fda8fbd6bd86e3b6260e8a679 100644 (file)
--- a/roundup/hyperdb.py
+++ b/roundup/hyperdb.py
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
-# $Id: hyperdb.py,v 1.50 2002-01-19 13:16:04 rochecompaan Exp $
+# $Id: hyperdb.py,v 1.64 2002-05-15 06:21:21 richard Exp $
__doc__ = """
Hyperdatabase implementation, especially field types.
"""
# standard python modules
-import cPickle, re, string, weakref
+import re, string, weakref, os, time
# roundup modules
import date, password
+# configure up the DEBUG and TRACE captures
+class Sink:
+ def write(self, content):
+ pass
+DEBUG = os.environ.get('HYPERDBDEBUG', '')
+if DEBUG and __debug__:
+ DEBUG = open(DEBUG, 'a')
+else:
+ DEBUG = Sink()
+TRACE = os.environ.get('HYPERDBTRACE', '')
+if TRACE and __debug__:
+ TRACE = open(TRACE, 'w')
+else:
+ TRACE = Sink()
+def traceMark():
+ print >>TRACE, '**MARK', time.ctime()
+del Sink
#
# Types
class String:
"""An object designating a String property."""
def __repr__(self):
+ ' more useful for dumps '
return '<%s>'%self.__class__
class Password:
"""An object designating a Password property."""
def __repr__(self):
+ ' more useful for dumps '
return '<%s>'%self.__class__
class Date:
"""An object designating a Date property."""
def __repr__(self):
+ ' more useful for dumps '
return '<%s>'%self.__class__
class Interval:
"""An object designating an Interval property."""
def __repr__(self):
+ ' more useful for dumps '
return '<%s>'%self.__class__
class Link:
"""An object designating a Link property that links to a
node in a specified class."""
def __init__(self, classname, do_journal='no'):
+ ''' Default is to not journal link and unlink events
+ '''
self.classname = classname
self.do_journal = do_journal == 'yes'
def __repr__(self):
+ ' more useful for dumps '
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, do_journal='no'):
+ ''' Default is to not journal link and unlink events
+ '''
self.classname = classname
self.do_journal = do_journal == 'yes'
def __repr__(self):
+ ' more useful for dumps '
return '<%s to "%s">'%(self.__class__, self.classname)
class DatabaseError(ValueError):
+ '''Error to be raised when there is some problem in the database code
+ '''
pass
'''
raise NotImplementedError
+ def serialise(self, classname, node):
+ '''Copy the node contents, converting non-marshallable data into
+ marshallable data.
+ '''
+ if __debug__:
+ print >>DEBUG, 'serialise', classname, node
+ properties = self.getclass(classname).getprops()
+ d = {}
+ for k, v in node.items():
+ # if the property doesn't exist, or is the "retired" flag then
+ # it won't be in the properties dict
+ if not properties.has_key(k):
+ d[k] = v
+ continue
+
+ # get the property spec
+ 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 >>DEBUG, 'unserialise', classname, node
+ properties = self.getclass(classname).getprops()
+ d = {}
+ for k, v in node.items():
+ # if the property doesn't exist, or is the "retired" flag then
+ # it won't be in the properties dict
+ if not properties.has_key(k):
+ d[k] = v
+ continue
+
+ # get the property spec
+ 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.
'''
'''
raise NotImplementedError
+ def pack(self, pack_before):
+ ''' pack the database
+ '''
+ raise NotImplementedError
+
def commit(self):
''' Commit the current transactions.
db.addclass(self)
def __repr__(self):
+ '''Slightly more useful representation
+ '''
return '<hypderdb.Class "%s">'%self.classname
# Editing nodes:
raise DatabaseError, 'Database open read-only'
# new node's id
- newid = str(self.count() + 1)
+ newid = self.db.newid(self.classname)
# validate propvalues
num_re = re.compile('^\d+$')
# 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)
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
# 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
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
+ # 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)
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.
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:
raise ValueError, key
self.properties.update(properties)
-
# XXX not in spec
class Node:
''' A convenience wrapper for the given 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.63 2002/04/15 23:25:15 richard
+# . node ids are now generated from a lockable store - no more race conditions
+#
+# We're using the portalocker code by Jonathan Feinberg that was contributed
+# to the ASPN Python cookbook. This gives us locking across Unix and Windows.
+#
+# Revision 1.62 2002/04/03 07:05:50 richard
+# d'oh! killed retirement of nodes :(
+# all better now...
+#
+# Revision 1.61 2002/04/03 06:11:51 richard
+# Fix for old databases that contain properties that don't exist any more.
+#
+# Revision 1.60 2002/04/03 05:54:31 richard
+# Fixed serialisation problem by moving the serialisation step out of the
+# 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.
+#
+# 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
+#
+# 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