From a9dac81a48599be6adfa220d37013f41ef14f6b5 Mon Sep 17 00:00:00 2001 From: richard Date: Thu, 4 Sep 2003 00:47:01 +0000 Subject: [PATCH] Initial implementaion (half-baked) at new Tracker instance. Cleaned up caching API / comments in backends. Fixes to docs. git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@1847 57a73879-2fb5-44c3-a270-3262357dd7e2 --- CHANGES.txt | 7 ++- doc/announcement.txt | 57 +++++++++----------- doc/customizing.txt | 4 +- roundup/backends/back_anydbm.py | 52 ++++++++---------- roundup/backends/back_metakit.py | 9 ++-- roundup/backends/rdbms_common.py | 22 ++++---- roundup/hyperdb.py | 25 ++++----- roundup/instance.py | 47 ++++++++++++---- templates/classic/detectors/nosyreaction.py | 6 +-- templates/classic/detectors/statusauditor.py | 4 +- test/test_db.py | 11 +++- 11 files changed, 129 insertions(+), 115 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 2e227b4..48fe014 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,7 +1,12 @@ This file contains the changes to the Roundup system over time. The entries are given with the most recent entry first. -2003-08-?? 0.6.1 +2003-09-?? 0.6.2 +Fixed: +- cleaned up, clarified internal caching API in *dbm backends + + +2003-08-31 0.6.1 Fixed: - Add note about installing cgi-bin with a different interpreter - Importing wasn't setting None values explicitly when it should have been diff --git a/doc/announcement.txt b/doc/announcement.txt index 51dc012..3a53e03 100644 --- a/doc/announcement.txt +++ b/doc/announcement.txt @@ -1,47 +1,38 @@ ================================================= -SC-Track Roundup 0.6.0 - an issue tracking system +SC-Track Roundup 0.6.1 - an issue tracking system ================================================= -I'm pleased to announce the latest feature-packed release of Roundup. See -below for a list of some of the goodies included in this release. +I'm pleased to announce this maintenance release of Roundup. This fix +introduces Python2.3 compatibility. My thanks to Paul Dubois for +contributing the csv module compatibility layer. If you're upgrading from an older version of Roundup you *must* follow the "Software Upgrade" guidelines given in the maintenance documentation. Unfortunately, the Zope frontend for Roundup is currently broken. I hope to -revive it in a future 0.6 bugfix release. - -The gadfly backend has now been removed, having served its purpose as a -template for other RDBMS implementations. It is replaced by the sqlite and -mysql backends. +revive it in a future 0.6 maintenance release. Roundup requires python 2.1.3 or later for correct operation. -The 0.6 release has lots of new goodies including: - -- new instant-gratification Demo Mode ("python demo.py" :) -- added mysql backend (see doc/mysql.txt for details) -- web interface cleanups including nicer history display, nicer index - navigation and nicer popup list windows -- searching of date ranges -- better international support, including utf-8 email handling and ability - to display localized dates in web interface. -- more documentation including revamped design document, unix manual pages - and some FAQ entries -- significantly more powerful form handling allowing editing of multiple - items and creation of multiple items -- tracker templates can contain subdirectories and static files (e.g. - images) and we may now distribute templates separately from Roundup. - Template HTML files now have a .html extension too. -- user registration is now a two-step process, with confirmation from the - email address supplied in the registration form, and we also have a - password reset feature for forgotten password / login -- Windows Service mode for roundup-server when daemonification is - attempted on Windows -- lots of speed enhancements, making the web interface much more responsive -- fixed issues with dumb email or web clients -- email system handles more SMTP and POP features (TLS, APOP, ...) -- lots more little tweaks and back-end work... +This release fixes some bugs: + +- Add note about installing cgi-bin with a different interpreter +- Importing wasn't setting None values explicitly when it should have been +- Fixed import warning regarding 0xffff0000 literal, finally, really this + time. Checked on win2k. (sf bug 786711) +- Fix CGI editCSV action to handle metakit's integer itemids +- Apply fix for "remove" links from Klamer Schutte +- Added permission check on "remove" link while I was there.. +- Applied CSV fix for python2.3 (sf bug 790363) +- Fixed form padding in LHS menu (sf bug 790502) +- Fixed upgrading docs for timezones (sf bug 790498) +- Set the content type on page templates (can have XML templates now) +- Various cosmetic fixes (thanks James Kew for being persistent :) +- Applied patch 739314 (sorry John!) + +To give Roundup a try, just download (see below), unpack and run:: + + python demo.py Source and documentation is available at the website: http://roundup.sourceforge.net/ diff --git a/doc/customizing.txt b/doc/customizing.txt index 5df11ba..9ac41f5 100644 --- a/doc/customizing.txt +++ b/doc/customizing.txt @@ -2,7 +2,7 @@ Customising Roundup =================== -:Version: $Revision: 1.95 $ +:Version: $Revision: 1.96 $ .. This document borrows from the ZopeBook section on ZPT. The original is at: http://www.zope.org/Documentation/Books/ZopeBook/current/ZPT.stx @@ -1483,7 +1483,7 @@ _value the value of the property if any - this is the actual There are several methods available on these wrapper objects: =========== ================================================================ -Method Description +Method Description =========== ================================================================ plain render a "plain" representation of the property. This method may take two arguments: diff --git a/roundup/backends/back_anydbm.py b/roundup/backends/back_anydbm.py index e0f516c..b0a8556 100644 --- a/roundup/backends/back_anydbm.py +++ b/roundup/backends/back_anydbm.py @@ -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.123 2003-08-26 00:06:55 richard Exp $ +#$Id: back_anydbm.py,v 1.124 2003-09-04 00:47:01 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 @@ -283,17 +283,20 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database): def getnode(self, classname, nodeid, db=None, cache=1): ''' get a node from the database + + Note the "cache" parameter is not used, and exists purely for + backward compatibility! ''' if __debug__: print >>hyperdb.DEBUG, 'getnode', (self, classname, nodeid, db) - if cache: - # try the cache - cache_dict = self.cache.setdefault(classname, {}) - if cache_dict.has_key(nodeid): - if __debug__: - print >>hyperdb.TRACE, 'get %s %s cached'%(classname, - nodeid) - return cache_dict[nodeid] + + # try the cache + cache_dict = self.cache.setdefault(classname, {}) + if cache_dict.has_key(nodeid): + if __debug__: + print >>hyperdb.TRACE, 'get %s %s cached'%(classname, + nodeid) + return cache_dict[nodeid] if __debug__: print >>hyperdb.TRACE, 'get %s %s'%(classname, nodeid) @@ -1008,10 +1011,7 @@ class Class(hyperdb.Class): IndexError is raised. 'propname' must be the name of a property of this class or a KeyError is raised. - 'cache' indicates whether the transaction cache should be queried - for the node. If the node has been modified and you need to - determine what its values prior to modification are, you need to - set cache=0. + 'cache' exists for backward compatibility, and is not used. Attempts to get the "creation" or "activity" properties should do the right thing. @@ -1020,7 +1020,7 @@ class Class(hyperdb.Class): return nodeid # get the node's dict - d = self.db.getnode(self.classname, nodeid, cache=cache) + d = self.db.getnode(self.classname, nodeid) # check for one of the special props if propname == 'creation': @@ -1091,12 +1091,9 @@ class Class(hyperdb.Class): 'nodeid' must be the id of an existing node of this class or an IndexError is raised. - 'cache' indicates whether the transaction cache should be queried - for the node. If the node has been modified and you need to - determine what its values prior to modification are, you need to - set cache=0. + 'cache' exists for backwards compatibility, and is not used. ''' - return Node(self, nodeid, cache=cache) + return Node(self, nodeid) def set(self, nodeid, **propvalues): '''Modify a property on an existing node of this class. @@ -1134,14 +1131,7 @@ class Class(hyperdb.Class): self.fireAuditors('set', nodeid, propvalues) # Take a copy of the node dict so that the subsequent set # operation doesn't modify the oldvalues structure. - try: - # try not using the cache initially - oldvalues = copy.deepcopy(self.db.getnode(self.classname, nodeid, - cache=0)) - except IndexError: - # this will be needed if somone does a create() and set() - # with no intervening commit() - oldvalues = copy.deepcopy(self.db.getnode(self.classname, nodeid)) + oldvalues = copy.deepcopy(self.db.getnode(self.classname, nodeid)) node = self.db.getnode(self.classname, nodeid) if node.has_key(self.db.RETIRED_FLAG): @@ -2040,7 +2030,9 @@ class FileClass(Class, hyperdb.FileClass): return newid def get(self, nodeid, propname, default=_marker, cache=1): - ''' trap the content propname and get it from the file + ''' Trap the content propname and get it from the file + + 'cache' exists for backwards compatibility, and is not used. ''' poss_msg = 'Possibly an access right configuration problem.' if propname == 'content': @@ -2051,9 +2043,9 @@ class FileClass(Class, hyperdb.FileClass): return 'ERROR reading file: %s%s\n%s\n%s'%( self.classname, nodeid, poss_msg, strerror) if default is not _marker: - return Class.get(self, nodeid, propname, default, cache=cache) + return Class.get(self, nodeid, propname, default) else: - return Class.get(self, nodeid, propname, cache=cache) + return Class.get(self, nodeid, propname) def getprops(self, protected=1): ''' In addition to the actual properties on the node, these methods diff --git a/roundup/backends/back_metakit.py b/roundup/backends/back_metakit.py index 60997df..64ab3d7 100755 --- a/roundup/backends/back_metakit.py +++ b/roundup/backends/back_metakit.py @@ -1,4 +1,4 @@ -# $Id: back_metakit.py,v 1.48 2003-08-26 00:06:56 richard Exp $ +# $Id: back_metakit.py,v 1.49 2003-09-04 00:47:01 richard Exp $ ''' Metakit backend for Roundup, originally by Gordon McMillan. @@ -373,8 +373,9 @@ class Class: return str(newid) def get(self, nodeid, propname, default=_marker, cache=1): - # default and cache aren't in the spec - # cache=0 means "original value" + ''' + 'cache' exists for backwards compatibility, and is not used. + ''' view = self.getview() id = int(nodeid) @@ -1407,7 +1408,7 @@ class FileClass(Class, hyperdb.FileClass): Class.__init__(self, db, classname, **properties) def get(self, nodeid, propname, default=_marker, cache=1): - x = Class.get(self, nodeid, propname, default, cache) + x = Class.get(self, nodeid, propname, default) poss_msg = 'Possibly an access right configuration problem.' if propname == 'content': if x.startswith('file:'): diff --git a/roundup/backends/rdbms_common.py b/roundup/backends/rdbms_common.py index 2bbe0e0..1bdec79 100644 --- a/roundup/backends/rdbms_common.py +++ b/roundup/backends/rdbms_common.py @@ -1,4 +1,4 @@ -# $Id: rdbms_common.py,v 1.59 2003-08-26 00:06:56 richard Exp $ +# $Id: rdbms_common.py,v 1.60 2003-09-04 00:47:01 richard Exp $ ''' Relational database (SQL) backend common code. Basics: @@ -1183,10 +1183,7 @@ class Class(hyperdb.Class): IndexError is raised. 'propname' must be the name of a property of this class or a KeyError is raised. - 'cache' indicates whether the transaction cache should be queried - for the node. If the node has been modified and you need to - determine what its values prior to modification are, you need to - set cache=0. + 'cache' exists for backwards compatibility, and is not used. ''' if propname == 'id': return nodeid @@ -1234,12 +1231,9 @@ class Class(hyperdb.Class): 'nodeid' must be the id of an existing node of this class or an IndexError is raised. - 'cache' indicates whether the transaction cache should be queried - for the node. If the node has been modified and you need to - determine what its values prior to modification are, you need to - set cache=0. + 'cache' exists for backwards compatibility, and is not used. ''' - return Node(self, nodeid, cache=cache) + return Node(self, nodeid) def set(self, nodeid, **propvalues): '''Modify a property on an existing node of this class. @@ -2094,7 +2088,9 @@ class FileClass(Class, hyperdb.FileClass): _marker = [] def get(self, nodeid, propname, default=_marker, cache=1): - ''' trap the content propname and get it from the file + ''' Trap the content propname and get it from the file + + 'cache' exists for backwards compatibility, and is not used. ''' poss_msg = 'Possibly a access right configuration problem.' if propname == 'content': @@ -2105,9 +2101,9 @@ class FileClass(Class, hyperdb.FileClass): return 'ERROR reading file: %s%s\n%s\n%s'%( self.classname, nodeid, poss_msg, strerror) if default is not self._marker: - return Class.get(self, nodeid, propname, default, cache=cache) + return Class.get(self, nodeid, propname, default) else: - return Class.get(self, nodeid, propname, cache=cache) + return Class.get(self, nodeid, propname) def getprops(self, protected=1): ''' In addition to the actual properties on the node, these methods diff --git a/roundup/hyperdb.py b/roundup/hyperdb.py index 4ab2226..78ca20e 100644 --- a/roundup/hyperdb.py +++ b/roundup/hyperdb.py @@ -15,7 +15,7 @@ # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # -# $Id: hyperdb.py,v 1.87 2003-03-17 22:03:03 kedder Exp $ +# $Id: hyperdb.py,v 1.88 2003-09-04 00:47:01 richard Exp $ """ Hyperdatabase implementation, especially field types. @@ -245,6 +245,8 @@ concrete backend Class. def getnode(self, classname, nodeid, db=None, cache=1): '''Get a node from the database. + + 'cache' exists for backwards compatibility, and is not used. ''' raise NotImplementedError @@ -365,10 +367,7 @@ class Class: IndexError is raised. 'propname' must be the name of a property of this class or a KeyError is raised. - 'cache' indicates whether the transaction cache should be queried - for the node. If the node has been modified and you need to - determine what its values prior to modification are, you need to - set cache=0. + 'cache' exists for backwards compatibility, and is not used. """ raise NotImplementedError @@ -378,12 +377,9 @@ class Class: 'nodeid' must be the id of an existing node of this class or an IndexError is raised. - 'cache' indicates whether the transaction cache should be queried - for the node. If the node has been modified and you need to - determine what its values prior to modification are, you need to - set cache=0. + 'cache' exists for backwards compatibility, and is not used. ''' - return Node(self, nodeid, cache=cache) + return Node(self, nodeid) def set(self, nodeid, **propvalues): """Modify a property on an existing node of this class. @@ -580,18 +576,17 @@ class Node: def __init__(self, cl, nodeid, cache=1): self.__dict__['cl'] = cl self.__dict__['nodeid'] = nodeid - self.__dict__['cache'] = cache def keys(self, protected=1): return self.cl.getprops(protected=protected).keys() def values(self, protected=1): l = [] for name in self.cl.getprops(protected=protected).keys(): - l.append(self.cl.get(self.nodeid, name, cache=self.cache)) + l.append(self.cl.get(self.nodeid, name)) return l def items(self, protected=1): l = [] for name in self.cl.getprops(protected=protected).keys(): - l.append((name, self.cl.get(self.nodeid, name, cache=self.cache))) + l.append((name, self.cl.get(self.nodeid, name))) return l def has_key(self, name): return self.cl.getprops().has_key(name) @@ -604,7 +599,7 @@ class Node: if self.__dict__.has_key(name): return self.__dict__[name] try: - return self.cl.get(self.nodeid, name, cache=self.cache) + return self.cl.get(self.nodeid, name) except KeyError, value: # we trap this but re-raise it as AttributeError - all other # exceptions should pass through untrapped @@ -612,7 +607,7 @@ class Node: # nope, no such attribute raise AttributeError, str(value) def __getitem__(self, name): - return self.cl.get(self.nodeid, name, cache=self.cache) + return self.cl.get(self.nodeid, name) def __setattr__(self, name, value): try: return self.cl.set(self.nodeid, **{name: value}) diff --git a/roundup/instance.py b/roundup/instance.py index b873340..ec87f38 100644 --- a/roundup/instance.py +++ b/roundup/instance.py @@ -15,21 +15,46 @@ # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # -# $Id: instance.py,v 1.9 2002-09-20 01:20:31 richard Exp $ +# $Id: instance.py,v 1.10 2003-09-04 00:47:01 richard Exp $ __doc__ = ''' Tracker handling (open tracker). -Currently this module provides one function: open. This function opens -a tracker. Note that trackers used to be called instances. +Backwards compatibility for the old-style "imported" trackers. ''' -import imp, os +import os + +class Vars: + ''' I'm just a container ''' + +class Tracker: + def __init__(self, tracker_home): + self.tracker_home = tracker_home + self.select_db = self._load_python('select_db.py') + self.config = self._load_config('config.py') + raise NotImplemented, 'this is *so* not finished' + self.init = XXX + self.Client = XXX + self.MailGW = XXX + + def open(self): + return self._load_config('schema.py').db + self._load_config('security.py', db=db) + + + def __load_python(self, file): + file = os.path.join(tracker_home, file) + vars = Vars() + execfile(file, vars.__dict__) + return vars + class TrackerError(Exception): pass -class Opener: + +class OldStyleTrackers: def __init__(self): self.number = 0 self.trackers = {} @@ -39,6 +64,7 @@ class Opener: Raise ValueError if the tracker home doesn't exist. ''' + import imp # sanity check existence of tracker home if not os.path.exists(tracker_home): raise ValueError, 'no such directory: "%s"'%tracker_home @@ -67,11 +93,12 @@ class Opener: return tracker -opener = Opener() -open = opener.open - -del Opener -del opener +OldStyleTrackers = OldStyleTrackers() +def open(tracker_home): + if os.path.exists(os.path.join(tracker_home, 'dbinit.py')): + # user should upgrade... + return OldStyleTrackers.open(tracker_home) + return Tracker(tracker_home) # vim: set filetype=python ts=4 sw=4 et si diff --git a/templates/classic/detectors/nosyreaction.py b/templates/classic/detectors/nosyreaction.py index 2c29204..7506211 100644 --- a/templates/classic/detectors/nosyreaction.py +++ b/templates/classic/detectors/nosyreaction.py @@ -15,7 +15,7 @@ # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # -#$Id: nosyreaction.py,v 1.1 2003-04-17 03:26:38 richard Exp $ +#$Id: nosyreaction.py,v 1.2 2003-09-04 00:47:01 richard Exp $ from roundup import roundupdb, hyperdb @@ -107,8 +107,8 @@ def updatenosy(db, cl, nodeid, newvalues): else: ok = ('yes',) # figure which of the messages now on the issue weren't - # there before - make sure we don't get a cached version! - oldmessages = cl.get(nodeid, 'messages', cache=0) + # there before + oldmessages = cl.get(nodeid, 'messages') messages = [] for msgid in newvalues['messages']: if msgid not in oldmessages: diff --git a/templates/classic/detectors/statusauditor.py b/templates/classic/detectors/statusauditor.py index f1ce29b..e7aa6e9 100644 --- a/templates/classic/detectors/statusauditor.py +++ b/templates/classic/detectors/statusauditor.py @@ -18,7 +18,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # -#$Id: statusauditor.py,v 1.2 2003-06-25 09:49:34 neaj Exp $ +#$Id: statusauditor.py,v 1.3 2003-09-04 00:47:01 richard Exp $ def chatty(db, cl, nodeid, newvalues): ''' If the issue is currently 'unread', 'resolved' or 'done-cbb', then set @@ -27,7 +27,7 @@ def chatty(db, cl, nodeid, newvalues): # don't fire if there's no new message (ie. chat) if not newvalues.has_key('messages'): return - if newvalues['messages'] == cl.get(nodeid, 'messages', cache=0): + if newvalues['messages'] == cl.get(nodeid, 'messages'): return # get the chatting state ID diff --git a/test/test_db.py b/test/test_db.py index 2994213..9b16434 100644 --- a/test/test_db.py +++ b/test/test_db.py @@ -15,7 +15,7 @@ # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # -# $Id: test_db.py,v 1.90 2003-08-12 02:22:22 richard Exp $ +# $Id: test_db.py,v 1.91 2003-09-04 00:47:01 richard Exp $ import unittest, os, shutil, time @@ -296,7 +296,14 @@ class anydbmDBTestCase(MyTestCase): self.assertNotEqual(a, self.db.status.list()) # try to restore retired node self.db.status.restore('1') - self.assertEqual(a, self.db.status.list()) + + def testCacheCreateSet(self): + self.db.issue.create(title="spam", status='1') + a = self.db.issue.get('1', 'title') + self.assertEqual(a, 'spam') + self.db.issue.set('1', title='ham') + b = self.db.issue.get('1', 'title') + self.assertEqual(b, 'ham') def testSerialisation(self): nid = self.db.issue.create(title="spam", status='1', -- 2.30.2