index fee6d95dd6d9f967886ba3938a5cbb919cb2e7a8..91f8a76a4221d7ee7be304f19af475493bdf89e1 100644 (file)
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
-#$Id: back_anydbm.py,v 1.65 2002-08-30 08:35:45 richard Exp $
+#$Id: back_anydbm.py,v 1.79 2002-09-15 23:06:20 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
'''
if __debug__:
print >>hyperdb.DEBUG, 'getclass', (self, classname)
- return self.classes[classname]
+ try:
+ return self.classes[classname]
+ except KeyError:
+ raise KeyError, 'There is no class called "%s"'%classname
#
# Class DBs
if os.path.exists(path):
db_type = whichdb.whichdb(path)
if not db_type:
- raise hyperdb.DatabaseError, "Couldn't identify database type"
+ raise DatabaseError, "Couldn't identify database type"
elif os.path.exists(path+'.db'):
# if the path ends in '.db', it's a dbm database, whether
# anydbm says it's dbhash or not!
# new database? let anydbm pick the best dbm
if not db_type:
if __debug__:
- print >>hyperdb.DEBUG, "opendb anydbm.open(%r, 'n')"%path
- return anydbm.open(path, 'n')
+ print >>hyperdb.DEBUG, "opendb anydbm.open(%r, 'c')"%path
+ return anydbm.open(path, 'c')
# open the database with the correct module
try:
dbm = __import__(db_type)
except ImportError:
- raise hyperdb.DatabaseError, \
+ raise DatabaseError, \
"Couldn't open database - the required module '%s'"\
" is not available"%db_type
if __debug__:
#
# Journal
#
- def addjournal(self, classname, nodeid, action, params):
+ def addjournal(self, classname, nodeid, action, params, creator=None,
+ creation=None):
''' Journal the Action
'action' may be:
'''
if __debug__:
print >>hyperdb.DEBUG, 'addjournal', (self, classname, nodeid,
- action, params)
+ action, params, creator, creation)
self.transactions.append((self.doSaveJournal, (classname, nodeid,
- action, params)))
+ action, params, creator, creation)))
def getjournal(self, classname, nodeid):
''' get the journal for id
self.databases[db_name] = self.opendb(db_name, 'c')
return self.databases[db_name]
- def doSaveJournal(self, classname, nodeid, action, params):
- # handle supply of the special journalling parameters (usually
- # supplied on importing an existing database)
+ def doSaveJournal(self, classname, nodeid, action, params, creator,
+ creation):
+ # serialise the parameters now if necessary
if isinstance(params, type({})):
- if params.has_key('creator'):
- journaltag = self.user.get(params['creator'], 'username')
- del params['creator']
- else:
- journaltag = self.journaltag
- if params.has_key('created'):
- journaldate = params['created'].serialise()
- del params['created']
- else:
- journaldate = date.Date().serialise()
- if params.has_key('activity'):
- del params['activity']
-
- # serialise the parameters now
if action in ('set', 'create'):
params = self.serialise(classname, params)
+
+ # handle supply of the special journalling parameters (usually
+ # supplied on importing an existing database)
+ if creator:
+ journaltag = creator
else:
journaltag = self.journaltag
+ if creation:
+ journaldate = creation.serialise()
+ else:
journaldate = date.Date().serialise()
# create the journal entry
self.destroyednodes = {}
self.transactions = []
+ def close(self):
+ ''' Nothing to do
+ '''
+ pass
+
_marker = []
class Class(hyperdb.Class):
'''The handle to a particular class of nodes in a hyperdatabase.'''
l = []
for entry in value:
if type(entry) != type(''):
- raise ValueError, '"%s" link value (%s) must be '\
- 'String'%(key, value)
+ raise ValueError, '"%s" multilink value (%r) '\
+ 'must contain Strings'%(key, value)
# if it isn't a number, it's a key
if not num_re.match(entry):
try:
proptype = properties[prop]
value = self.get(nodeid, prop)
# "marshal" data where needed
- if isinstance(proptype, hyperdb.Date):
+ if value is None:
+ pass
+ elif isinstance(proptype, hyperdb.Date):
value = value.get_tuple()
elif isinstance(proptype, hyperdb.Interval):
value = value.get_tuple()
if propname == 'id':
newid = value
continue
+ elif value is None:
+ # don't set Nones
+ continue
elif isinstance(prop, hyperdb.Date):
value = date.Date(value)
elif isinstance(prop, hyperdb.Interval):
pwd = password.Password()
pwd.unpack(value)
value = pwd
- if value is not None:
- d[propname] = value
+ d[propname] = value
+
+ # extract the extraneous journalling gumpf and nuke it
+ if d.has_key('creator'):
+ creator = d['creator']
+ del d['creator']
+ else:
+ creator = None
+ if d.has_key('creation'):
+ creation = d['creation']
+ del d['creation']
+ else:
+ creation = None
+ if d.has_key('activity'):
+ del d['activity']
- # add
+ # add the node and journal
self.db.addnode(self.classname, newid, d)
- self.db.addjournal(self.classname, newid, 'create', d)
+ self.db.addjournal(self.classname, newid, 'create', d, creator,
+ creation)
return newid
def get(self, nodeid, propname, default=_marker, cache=1):
raise ValueError, 'Journalling is disabled for this class'
journal = self.db.getjournal(self.classname, nodeid)
if journal:
- name = self.db.getjournal(self.classname, nodeid)[0][2]
+ return self.db.getjournal(self.classname, nodeid)[0][2]
else:
- return None
- return self.db.user.lookup(name)
+ return self.db.journaltag
# get the property (raises KeyErorr if invalid)
prop = self.properties[propname]
else:
return default
+ # return a dupe of the list so code doesn't get confused
+ if isinstance(prop, Multilink):
+ return d[propname][:]
+
return d[propname]
- # XXX not in spec
+ # not in spec
def getnode(self, nodeid, cache=1):
''' Return a convenience wrapper for the node.
otherwise a KeyError is raised.
'''
if not self.key:
- raise TypeError, 'No key property set'
+ raise TypeError, 'No key property set for class %s'%self.classname
cldb = self.db.getclassdb(self.classname)
try:
for nodeid in self.db.getnodeids(self.classname, cldb):
cldb.close()
raise KeyError, keyvalue
- # XXX: change from spec - allows multiple props to match
+ # change from spec - allows multiple props to match
def find(self, **propspec):
'''Get the ids of nodes in this class which link to the given nodes.
sort spec.
"filterspec" is {propname: value(s)}
- "sort" is ['+propname', '-propname', 'propname', ...]
- "group is ['+propname', '-propname', 'propname', ...]
+ "sort" and "group" are (dir, prop) where dir is '+', '-' or None
+ and prop is a prop name or None
"search_matches" is {nodeid: marker}
'''
cn = self.classname
k.append(v)
l = k
- # optimise sort
- m = []
- for entry in sort:
- if entry[0] != '-':
- m.append(('+', entry))
- else:
- m.append((entry[0], entry[1:]))
- sort = m
-
- # optimise group
- m = []
- for entry in group:
- if entry[0] != '-':
- m.append(('+', entry))
- else:
- m.append((entry[0], entry[1:]))
- group = m
# now, sort the result
def sortfun(a, b, sort=sort, group=group, properties=self.getprops(),
db = self.db, cl=self):
a_id, an = a
b_id, bn = b
# sort by group and then sort
- for list in group, sort:
- for dir, prop in list:
- # sorting is class-specific
- propclass = properties[prop]
+ for dir, prop in group, sort:
+ if dir is None or prop is None: continue
- # handle the properties that might be "faked"
- # also, handle possible missing properties
- try:
- if not an.has_key(prop):
- an[prop] = cl.get(a_id, prop)
- av = an[prop]
- except KeyError:
- # the node doesn't have a value for this property
- if isinstance(propclass, Multilink): av = []
- else: av = ''
+ # sorting is class-specific
+ propclass = properties[prop]
+
+ # handle the properties that might be "faked"
+ # also, handle possible missing properties
+ try:
+ if not an.has_key(prop):
+ an[prop] = cl.get(a_id, prop)
+ av = an[prop]
+ except KeyError:
+ # the node doesn't have a value for this property
+ if isinstance(propclass, Multilink): av = []
+ else: av = ''
+ try:
+ if not bn.has_key(prop):
+ bn[prop] = cl.get(b_id, prop)
+ bv = bn[prop]
+ except KeyError:
+ # the node doesn't have a value for this property
+ if isinstance(propclass, Multilink): bv = []
+ else: bv = ''
+
+ # String and Date values are sorted in the natural way
+ if isinstance(propclass, String):
+ # clean up the strings
+ if av and av[0] in string.uppercase:
+ av = an[prop] = av.lower()
+ if bv and bv[0] in string.uppercase:
+ bv = bn[prop] = bv.lower()
+ if (isinstance(propclass, String) or
+ isinstance(propclass, Date)):
+ # it might be a string that's really an integer
try:
- if not bn.has_key(prop):
- bn[prop] = cl.get(b_id, prop)
- bv = bn[prop]
- except KeyError:
- # the node doesn't have a value for this property
- if isinstance(propclass, Multilink): bv = []
- else: bv = ''
-
- # String and Date values are sorted in the natural way
- if isinstance(propclass, String):
- # clean up the strings
- if av and av[0] in string.uppercase:
- av = an[prop] = av.lower()
- if bv and bv[0] in string.uppercase:
- bv = bn[prop] = bv.lower()
- if (isinstance(propclass, String) or
- isinstance(propclass, Date)):
- # it might be a string that's really an integer
- try:
- av = int(av)
- bv = int(bv)
- except:
- pass
+ av = int(av)
+ bv = int(bv)
+ except:
+ pass
+ if dir == '+':
+ r = cmp(av, bv)
+ if r != 0: return r
+ elif dir == '-':
+ r = cmp(bv, av)
+ if r != 0: return r
+
+ # Link properties are sorted according to the value of
+ # the "order" property on the linked nodes if it is
+ # present; or otherwise on the key string of the linked
+ # nodes; or finally on the node ids.
+ elif isinstance(propclass, Link):
+ link = db.classes[propclass.classname]
+ if av is None and bv is not None: return -1
+ if av is not None and bv is None: return 1
+ if av is None and bv is None: continue
+ if link.getprops().has_key('order'):
if dir == '+':
- r = cmp(av, bv)
+ r = cmp(link.get(av, 'order'),
+ link.get(bv, 'order'))
if r != 0: return r
elif dir == '-':
- r = cmp(bv, av)
+ r = cmp(link.get(bv, 'order'),
+ link.get(av, 'order'))
if r != 0: return r
-
- # Link properties are sorted according to the value of
- # the "order" property on the linked nodes if it is
- # present; or otherwise on the key string of the linked
- # nodes; or finally on the node ids.
- elif isinstance(propclass, Link):
- link = db.classes[propclass.classname]
- if av is None and bv is not None: return -1
- if av is not None and bv is None: return 1
- if av is None and bv is None: continue
- if link.getprops().has_key('order'):
- if dir == '+':
- r = cmp(link.get(av, 'order'),
- link.get(bv, 'order'))
- if r != 0: return r
- elif dir == '-':
- r = cmp(link.get(bv, 'order'),
- link.get(av, 'order'))
- if r != 0: return r
- elif link.getkey():
- key = link.getkey()
- if dir == '+':
- r = cmp(link.get(av, key), link.get(bv, key))
- if r != 0: return r
- elif dir == '-':
- r = cmp(link.get(bv, key), link.get(av, key))
- if r != 0: return r
- else:
- if dir == '+':
- r = cmp(av, bv)
- if r != 0: return r
- elif dir == '-':
- r = cmp(bv, av)
- if r != 0: return r
-
- # Multilink properties are sorted according to how many
- # links are present.
- elif isinstance(propclass, Multilink):
+ elif link.getkey():
+ key = link.getkey()
if dir == '+':
- r = cmp(len(av), len(bv))
+ r = cmp(link.get(av, key), link.get(bv, key))
if r != 0: return r
elif dir == '-':
- r = cmp(len(bv), len(av))
+ r = cmp(link.get(bv, key), link.get(av, key))
if r != 0: return r
- elif isinstance(propclass, Number) or isinstance(propclass, Boolean):
+ else:
if dir == '+':
r = cmp(av, bv)
+ if r != 0: return r
elif dir == '-':
r = cmp(bv, av)
-
- # end for dir, prop in list:
- # end for list in sort, group:
+ if r != 0: return r
+
+ # Multilink properties are sorted according to how many
+ # links are present.
+ elif isinstance(propclass, Multilink):
+ if dir == '+':
+ r = cmp(len(av), len(bv))
+ if r != 0: return r
+ elif dir == '-':
+ r = cmp(len(bv), len(av))
+ if r != 0: return r
+ elif isinstance(propclass, Number) or isinstance(propclass, Boolean):
+ if dir == '+':
+ r = cmp(av, bv)
+ elif dir == '-':
+ r = cmp(bv, av)
+
+ # end for dir, prop in sort, group:
# if all else fails, compare the ids
return cmp(a[0], b[0])
d['id'] = String()
d['creation'] = hyperdb.Date()
d['activity'] = hyperdb.Date()
- d['creator'] = hyperdb.Link("user")
+ # can't be a link to user because the user might have been
+ # retired since the journal entry was created
+ d['creator'] = hyperdb.String()
return d
def addprop(self, **properties):
# extract the "content" property from the proplist
i = propnames.index('content')
- content = proplist[i]
+ content = eval(proplist[i])
del propnames[i]
del proplist[i]
self.db.indexer.add_text((self.classname, nodeid, 'content'), content,
mime_type)
-# XXX deviation from spec - was called ItemClass
+# deviation from spec - was called ItemClass
class IssueClass(Class, roundupdb.IssueClass):
# Overridden methods:
def __init__(self, db, classname, **properties):
if not properties.has_key('files'):
properties['files'] = hyperdb.Multilink("file")
if not properties.has_key('nosy'):
- properties['nosy'] = hyperdb.Multilink("user")
+ # note: journalling is turned off as it really just wastes
+ # space. this behaviour may be overridden in an instance
+ properties['nosy'] = hyperdb.Multilink("user", do_journal="no")
if not properties.has_key('superseder'):
properties['superseder'] = hyperdb.Multilink(classname)
Class.__init__(self, db, classname, **properties)
#
-#$Log: not supported by cvs2svn $
-#Revision 1.64 2002/08/22 07:57:11 richard
-#Consistent quoting
-#
-#Revision 1.63 2002/08/22 04:42:28 richard
-#use more robust date stamp comparisons in pack(), make journal smaller too
-#
-#Revision 1.62 2002/08/21 07:07:27 richard
-#In preparing to turn back on link/unlink journal events (by default these
-#are turned off) I've:
-#- fixed back_anydbm so it can journal those events again (had broken it
-# with recent changes)
-#- changed the serialisation format for dates and intervals to use a
-# numbers-only (and sign for Intervals) string instead of tuple-of-ints.
-# Much smaller.
-#
-#Revision 1.61 2002/08/19 02:53:27 richard
-#full database export and import is done
-#
-#Revision 1.60 2002/08/19 00:23:19 richard
-#handle "unset" initial Link values (!)
-#
-#Revision 1.59 2002/08/16 04:28:13 richard
-#added is_retired query to Class
-#
-#Revision 1.58 2002/08/01 15:06:24 gmcm
-#Use same regex to split search terms as used to index text.
-#Fix to back_metakit for not changing journaltag on reopen.
-#Fix htmltemplate's do_link so [No <whatever>] strings are href'd.
-#Fix bogus "nosy edited ok" msg - the **d syntax does NOT share d between caller and callee.
-#
-#Revision 1.57 2002/07/31 23:57:36 richard
-# . web forms may now unset Link values (like assignedto)
-#
-#Revision 1.56 2002/07/31 22:04:33 richard
-#cleanup
-#
-#Revision 1.55 2002/07/30 08:22:38 richard
-#Session storage in the hyperdb was horribly, horribly inefficient. We use
-#a simple anydbm wrapper now - which could be overridden by the metakit
-#backend or RDB backend if necessary.
-#Much, much better.
-#
-#Revision 1.54 2002/07/26 08:26:59 richard
-#Very close now. The cgi and mailgw now use the new security API. The two
-#templates have been migrated to that setup. Lots of unit tests. Still some
-#issue in the web form for editing Roles assigned to users.
-#
-#Revision 1.53 2002/07/25 07:14:06 richard
-#Bugger it. Here's the current shape of the new security implementation.
-#Still to do:
-# . call the security funcs from cgi and mailgw
-# . change shipped templates to include correct initialisation and remove
-# the old config vars
-#... that seems like a lot. The bulk of the work has been done though. Honest :)
-#
-#Revision 1.52 2002/07/19 03:36:34 richard
-#Implemented the destroy() method needed by the session database (and possibly
-#others). At the same time, I removed the leading underscores from the hyperdb
-#methods that Really Didn't Need Them.
-#The journal also raises IndexError now for all situations where there is a
-#request for the journal of a node that doesn't have one. It used to return
-#[] in _some_ situations, but not all. This _may_ break code, but the tests
-#pass...
-#
-#Revision 1.51 2002/07/18 23:07:08 richard
-#Unit tests and a few fixes.
-#
-#Revision 1.50 2002/07/18 11:50:58 richard
-#added tests for number type too
-#
-#Revision 1.49 2002/07/18 11:41:10 richard
-#added tests for boolean type, and fixes to anydbm backend
-#
-#Revision 1.48 2002/07/18 11:17:31 gmcm
-#Add Number and Boolean types to hyperdb.
-#Add conversion cases to web, mail & admin interfaces.
-#Add storage/serialization cases to back_anydbm & back_metakit.
-#
-#Revision 1.47 2002/07/14 23:18:20 richard
-#. fixed the journal bloat from multilink changes - we just log the add or
-# remove operations, not the whole list
-#
-#Revision 1.46 2002/07/14 06:06:34 richard
-#Did some old TODOs
-#
-#Revision 1.45 2002/07/14 04:03:14 richard
-#Implemented a switch to disable journalling for a Class. CGI session
-#database now uses it.
-#
-#Revision 1.44 2002/07/14 02:05:53 richard
-#. all storage-specific code (ie. backend) is now implemented by the backends
-#
-#Revision 1.43 2002/07/10 06:30:30 richard
-#...except of course it's nice to use valid Python syntax
-#
-#Revision 1.42 2002/07/10 06:21:38 richard
-#Be extra safe
-#
-#Revision 1.41 2002/07/10 00:21:45 richard
-#explicit database closing
-#
-#Revision 1.40 2002/07/09 04:19:09 richard
-#Added reindex command to roundup-admin.
-#Fixed reindex on first access.
-#Also fixed reindexing of entries that change.
-#
-#Revision 1.39 2002/07/09 03:02:52 richard
-#More indexer work:
-#- all String properties may now be indexed too. Currently there's a bit of
-# "issue" specific code in the actual searching which needs to be
-# addressed. In a nutshell:
-# + pass 'indexme="yes"' as a String() property initialisation arg, eg:
-# file = FileClass(db, "file", name=String(), type=String(),
-# comment=String(indexme="yes"))
-# + the comment will then be indexed and be searchable, with the results
-# related back to the issue that the file is linked to
-#- as a result of this work, the FileClass has a default MIME type that may
-# be overridden in a subclass, or by the use of a "type" property as is
-# done in the default templates.
-#- the regeneration of the indexes (if necessary) is done once the schema is
-# set up in the dbinit.
-#
-#Revision 1.38 2002/07/08 06:58:15 richard
-#cleaned up the indexer code:
-# - it splits more words out (much simpler, faster splitter)
-# - removed code we'll never use (roundup.roundup_indexer has the full
-# implementation, and replaces roundup.indexer)
-# - only index text/plain and rfc822/message (ideas for other text formats to
-# index are welcome)
-# - added simple unit test for indexer. Needs more tests for regression.
-#
-#Revision 1.37 2002/06/20 23:52:35 richard
-#More informative error message
-#
-#Revision 1.36 2002/06/19 03:07:19 richard
-#Moved the file storage commit into blobfiles where it belongs.
-#
-#Revision 1.35 2002/05/25 07:16:24 rochecompaan
-#Merged search_indexing-branch with HEAD
-#
-#Revision 1.34 2002/05/15 06:21:21 richard
-# . node caching now works, and gives a small boost in performance
-#
-#As a part of this, I cleaned up the DEBUG output and implemented TRACE
-#output (HYPERDBTRACE='file to trace to') with checkpoints at the start of
-#CGI requests. Run roundup with python -O to skip all the DEBUG/TRACE stuff
-#(using if __debug__ which is compiled out with -O)
-#
-#Revision 1.33 2002/04/24 10:38:26 rochecompaan
-#All database files are now created group readable and writable.
-#
-#Revision 1.32 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.31 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.30.2.1 2002/04/03 11:55:57 rochecompaan
-# . Added feature #526730 - search for messages capability
-#
-#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.
-# ATTENTION: blobfiles now creates subdirectories for files.
-#
-#Revision 1.28 2002/02/16 09:14:17 richard
-# . #514854 ] History: "User" is always ticket creator
-#
-#Revision 1.27 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.26 2002/01/22 05:18:38 rochecompaan
-#last_set_entry was referenced before assignment
-#
-#Revision 1.25 2002/01/22 05:06:08 rochecompaan
-#We need to keep the last 'set' entry in the journal to preserve
-#information on 'activity' for nodes.
-#
-#Revision 1.24 2002/01/21 16:33:20 rochecompaan
-#You can now use the roundup-admin tool to pack the database
-#
-#Revision 1.23 2002/01/18 04:32:04 richard
-#Rollback was breaking because a message hadn't actually been written to the file. Needs
-#more investigation.
-#
-#Revision 1.22 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
-# instance_config instead of the mish-mash of classes. This will make
-# switching to a ConfigParser setup easier too, I hope.
-#
-#At a minimum, this makes migration a _little_ easier (a lot easier in the
-#0.5.0 switch, I hope!)
-#
-#Revision 1.21 2002/01/02 02:31:38 richard
-#Sorry for the huge checkin message - I was only intending to implement #496356
-#but I found a number of places where things had been broken by transactions:
-# . modified ROUNDUPDBSENDMAILDEBUG to be SENDMAILDEBUG and hold a filename
-# for _all_ roundup-generated smtp messages to be sent to.
-# . the transaction cache had broken the roundupdb.Class set() reactors
-# . newly-created author users in the mailgw weren't being committed to the db
-#
-#Stuff that made it into CHANGES.txt (ie. the stuff I was actually working
-#on when I found that stuff :):
-# . #496356 ] Use threading in messages
-# . detectors were being registered multiple times
-# . added tests for mailgw
-# . much better attaching of erroneous messages in the mail gateway
-#
-#Revision 1.20 2001/12/18 15:30:34 rochecompaan
-#Fixed bugs:
-# . Fixed file creation and retrieval in same transaction in anydbm
-# backend
-# . Cgi interface now renders new issue after issue creation
-# . Could not set issue status to resolved through cgi interface
-# . Mail gateway was changing status back to 'chatting' if status was
-# omitted as an argument
-#
-#Revision 1.19 2001/12/17 03:52:48 richard
-#Implemented file store rollback. As a bonus, the hyperdb is now capable of
-#storing more than one file per node - if a property name is supplied,
-#the file is called designator.property.
-#I decided not to migrate the existing files stored over to the new naming
-#scheme - the FileClass just doesn't specify the property name.
-#
-#Revision 1.18 2001/12/16 10:53:38 richard
-#take a copy of the node dict so that the subsequent set
-#operation doesn't modify the oldvalues structure
-#
-#Revision 1.17 2001/12/14 23:42:57 richard
-#yuck, a gdbm instance tests false :(
-#I've left the debugging code in - it should be removed one day if we're ever
-#_really_ anal about performace :)
-#
-#Revision 1.16 2001/12/12 03:23:14 richard
-#Cor blimey this anydbm/whichdb stuff is yecchy. Turns out that whichdb
-#incorrectly identifies a dbm file as a dbhash file on my system. This has
-#been submitted to the python bug tracker as issue #491888:
-#https://sourceforge.net/tracker/index.php?func=detail&aid=491888&group_id=5470&atid=105470
-#
-#Revision 1.15 2001/12/12 02:30:51 richard
-#I fixed the problems with people whose anydbm was using the dbm module at the
-#backend. It turns out the dbm module modifies the file name to append ".db"
-#and my check to determine if we're opening an existing or new db just
-#tested os.path.exists() on the filename. Well, no longer! We now perform a
-#much better check _and_ cope with the anydbm implementation module changing
-#too!
-#I also fixed the backends __init__ so only ImportError is squashed.
-#
-#Revision 1.14 2001/12/10 22:20:01 richard
-#Enabled transaction support in the bsddb backend. It uses the anydbm code
-#where possible, only replacing methods where the db is opened (it uses the
-#btree opener specifically.)
-#Also cleaned up some change note generation.
-#Made the backends package work with pydoc too.
-#
-#Revision 1.13 2001/12/02 05:06:16 richard
-#. We now use weakrefs in the Classes to keep the database reference, so
-# the close() method on the database is no longer needed.
-# I bumped the minimum python requirement up to 2.1 accordingly.
-#. #487480 ] roundup-server
-#. #487476 ] INSTALL.txt
-#
-#I also cleaned up the change message / post-edit stuff in the cgi client.
-#There's now a clearly marked "TODO: append the change note" where I believe
-#the change note should be added there. The "changes" list will obviously
-#have to be modified to be a dict of the changes, or somesuch.
-#
-#More testing needed.
-#
-#Revision 1.12 2001/12/01 07:17:50 richard
-#. We now have basic transaction support! Information is only written to
-# the database when the commit() method is called. Only the anydbm
-# backend is modified in this way - neither of the bsddb backends have been.
-# The mail, admin and cgi interfaces all use commit (except the admin tool
-# doesn't have a commit command, so interactive users can't commit...)
-#. Fixed login/registration forwarding the user to the right page (or not,
-# on a failure)
-#
-#Revision 1.11 2001/11/21 02:34:18 richard
-#Added a target version field to the extended issue schema
-#
-#Revision 1.10 2001/10/09 23:58:10 richard
-#Moved the data stringification up into the hyperdb.Class class' get, set
-#and create methods. This means that the data is also stringified for the
-#journal call, and removes duplication of code from the backends. The
-#backend code now only sees strings.
-#
-#Revision 1.9 2001/10/09 07:25:59 richard
-#Added the Password property type. See "pydoc roundup.password" for
-#implementation details. Have updated some of the documentation too.
-#
-#Revision 1.8 2001/09/29 13:27:00 richard
-#CGI interfaces now spit up a top-level index of all the instances they can
-#serve.
-#
-#Revision 1.7 2001/08/12 06:32:36 richard
-#using isinstance(blah, Foo) now instead of isFooType
-#
-#Revision 1.6 2001/08/07 00:24:42 richard
-#stupid typo
-#
-#Revision 1.5 2001/08/07 00:15:51 richard
-#Added the copyright/license notice to (nearly) all files at request of
-#Bizar Software.
-#
-#Revision 1.4 2001/07/30 01:41:36 richard
-#Makes schema changes mucho easier.
-#
-#Revision 1.3 2001/07/25 01:23:07 richard
-#Added the Roundup spec to the new documentation directory.
-#
-#Revision 1.2 2001/07/23 08:20:44 richard
-#Moved over to using marshal in the bsddb and anydbm backends.
-#roundup-admin now has a "freshen" command that'll load/save all nodes (not
-# retired - mod hyperdb.Class.list() so it lists retired nodes)
-#
-#