Code

ea76be7dd458e5d735766245ec452615430591d5
[roundup.git] / test / memorydb.py
1 # $Id: test_memorydb.py,v 1.4 2004-11-03 01:34:21 richard Exp $ 
2 '''Implement an in-memory hyperdb for testing purposes.
3 '''
5 import shutil
7 from roundup import hyperdb
8 from roundup import roundupdb
9 from roundup import security
10 from roundup import password
11 from roundup import configuration
12 from roundup.backends import back_anydbm
13 from roundup.backends import indexer_dbm
14 from roundup.backends import indexer_common
15 from roundup.hyperdb import *
17 def new_config():
18     config = configuration.CoreConfig()
19     config.DATABASE = "db"
20     #config.logging = MockNull()
21     # these TRACKER_WEB and MAIL_DOMAIN values are used in mailgw tests
22     config.MAIL_DOMAIN = "your.tracker.email.domain.example"
23     config.TRACKER_WEB = "http://tracker.example/cgi-bin/roundup.cgi/bugs/"
24     return config
26 def create(journaltag, create=True):
27     db = Database(new_config(), journaltag)
29     # load standard schema
30     schema = os.path.join(os.path.dirname(__file__),
31         '../share/roundup/templates/classic/schema.py')
32     vars = dict(globals())
33     vars['db'] = db
34     execfile(schema, vars)
35     initial_data = os.path.join(os.path.dirname(__file__),
36         '../share/roundup/templates/classic/initial_data.py')
37     vars = dict(db=db, admin_email='admin@test.com',
38         adminpw=password.Password('sekrit'))
39     execfile(initial_data, vars)
41     # load standard detectors
42     dirname = os.path.join(os.path.dirname(__file__),
43         '../share/roundup/templates/classic/detectors')
44     for fn in os.listdir(dirname):
45         if not fn.endswith('.py'): continue
46         vars = {}
47         execfile(os.path.join(dirname, fn), vars)
48         vars['init'](db)
50     '''
51     status = Class(db, "status", name=String())
52     status.setkey("name")
53     priority = Class(db, "priority", name=String(), order=String())
54     priority.setkey("name")
55     keyword = Class(db, "keyword", name=String(), order=String())
56     keyword.setkey("name")
57     user = Class(db, "user", username=String(), password=Password(),
58         assignable=Boolean(), age=Number(), roles=String(), address=String(),
59         supervisor=Link('user'),realname=String(),alternate_addresses=String())
60     user.setkey("username")
61     file = FileClass(db, "file", name=String(), type=String(),
62         comment=String(indexme="yes"), fooz=Password())
63     file_nidx = FileClass(db, "file_nidx", content=String(indexme='no'))
64     issue = IssueClass(db, "issue", title=String(indexme="yes"),
65         status=Link("status"), nosy=Multilink("user"), deadline=Date(),
66         foo=Interval(), files=Multilink("file"), assignedto=Link('user'),
67         priority=Link('priority'), spam=Multilink('msg'),
68         feedback=Link('msg'))
69     stuff = Class(db, "stuff", stuff=String())
70     session = Class(db, 'session', title=String())
71     msg = FileClass(db, "msg", date=Date(),
72                            author=Link("user", do_journal='no'),
73                            files=Multilink('file'), inreplyto=String(),
74                            messageid=String(), summary=String(),
75                            content=String(),
76                            recipients=Multilink("user", do_journal='no')
77                            )
78     '''
79     if create:
80         db.user.create(username="fred", roles='User',
81             password=password.Password('sekrit'), address='fred@example.com')
83     db.security.addPermissionToRole('User', 'Email Access')
84     '''
85     db.security.addPermission(name='Register', klass='user')
86     db.security.addPermissionToRole('User', 'Web Access')
87     db.security.addPermissionToRole('Anonymous', 'Email Access')
88     db.security.addPermissionToRole('Anonymous', 'Register', 'user')
89     for cl in 'issue', 'file', 'msg', 'keyword':
90         db.security.addPermissionToRole('User', 'View', cl)
91         db.security.addPermissionToRole('User', 'Edit', cl)
92         db.security.addPermissionToRole('User', 'Create', cl)
93     for cl in 'priority', 'status':
94         db.security.addPermissionToRole('User', 'View', cl)
95     '''
96     return db
98 class cldb(dict):
99     def close(self):
100         pass
102 class BasicDatabase(dict):
103     ''' Provide a nice encapsulation of an anydbm store.
105         Keys are id strings, values are automatically marshalled data.
106     '''
107     def __getitem__(self, key):
108         if key not in self:
109             d = self[key] = {}
110             return d
111         return super(BasicDatabase, self).__getitem__(key)
112     def exists(self, infoid):
113         return infoid in self
114     def get(self, infoid, value, default=None):
115         return self[infoid].get(value, default)
116     def getall(self, infoid):
117         return self[infoid]
118     def set(self, infoid, **newvalues):
119         self[infoid].update(newvalues)
120     def list(self):
121         return self.keys()
122     def destroy(self, infoid):
123         del self[infoid]
124     def commit(self):
125         pass
126     def close(self):
127         pass
128     def updateTimestamp(self, sessid):
129         pass
130     def clean(self):
131         pass
133 class Sessions(BasicDatabase):
134     name = 'sessions'
136 class OneTimeKeys(BasicDatabase):
137     name = 'otks'
139 class Indexer(indexer_dbm.Indexer):
140     def __init__(self, db):
141         indexer_common.Indexer.__init__(self, db)
142         self.reindex = 0
143         self.quiet = 9
144         self.changed = 0
146     def load_index(self, reload=0, wordlist=None):
147         # Unless reload is indicated, do not load twice
148         if self.index_loaded() and not reload:
149             return 0
150         self.words = {}
151         self.files = {'_TOP':(0,None)}
152         self.fileids = {}
153         self.changed = 0
155     def save_index(self):
156         pass
158 class Database(hyperdb.Database, roundupdb.Database):
159     """A database for storing records containing flexible data types.
161     Transaction stuff TODO:
163     - check the timestamp of the class file and nuke the cache if it's
164       modified. Do some sort of conflict checking on the dirty stuff.
165     - perhaps detect write collisions (related to above)?
166     """
167     def __init__(self, config, journaltag=None):
168         self.config, self.journaltag = config, journaltag
169         self.classes = {}
170         self.items = {}
171         self.ids = {}
172         self.journals = {}
173         self.files = {}
174         self.security = security.Security(self)
175         self.stats = {'cache_hits': 0, 'cache_misses': 0, 'get_items': 0,
176             'filtering': 0}
177         self.sessions = Sessions()
178         self.otks = OneTimeKeys()
179         self.indexer = Indexer(self)
182     def filename(self, classname, nodeid, property=None, create=0):
183         shutil.copyfile(__file__, __file__+'.dummy')
184         return __file__+'.dummy'
186     def filesize(self, classname, nodeid, property=None, create=0):
187         return len(self.getnode(classname, nodeid)[property or 'content'])
189     def post_init(self):
190         pass
192     def refresh_database(self):
193         pass
195     def getSessionManager(self):
196         return self.sessions
198     def getOTKManager(self):
199         return self.otks
201     def reindex(self, classname=None, show_progress=False):
202         pass
204     def __repr__(self):
205         return '<memorydb instance at %x>'%id(self)
207     def storefile(self, classname, nodeid, property, content):
208         self.files[classname, nodeid, property] = content
210     def getfile(self, classname, nodeid, property):
211         return self.files[classname, nodeid, property]
213     def numfiles(self):
214         return len(self.files)
216     #
217     # Classes
218     #
219     def __getattr__(self, classname):
220         """A convenient way of calling self.getclass(classname)."""
221         if self.classes.has_key(classname):
222             return self.classes[classname]
223         raise AttributeError, classname
225     def addclass(self, cl):
226         cn = cl.classname
227         if self.classes.has_key(cn):
228             raise ValueError, cn
229         self.classes[cn] = cl
230         self.items[cn] = cldb()
231         self.ids[cn] = 0
233         # add default Edit and View permissions
234         self.security.addPermission(name="Create", klass=cn,
235             description="User is allowed to create "+cn)
236         self.security.addPermission(name="Edit", klass=cn,
237             description="User is allowed to edit "+cn)
238         self.security.addPermission(name="View", klass=cn,
239             description="User is allowed to access "+cn)
241     def getclasses(self):
242         """Return a list of the names of all existing classes."""
243         l = self.classes.keys()
244         l.sort()
245         return l
247     def getclass(self, classname):
248         """Get the Class object representing a particular class.
250         If 'classname' is not a valid class name, a KeyError is raised.
251         """
252         try:
253             return self.classes[classname]
254         except KeyError:
255             raise KeyError, 'There is no class called "%s"'%classname
257     #
258     # Class DBs
259     #
260     def clear(self):
261         self.items = {}
263     def getclassdb(self, classname):
264         """ grab a connection to the class db that will be used for
265             multiple actions
266         """
267         return self.items[classname]
269     #
270     # Node IDs
271     #
272     def newid(self, classname):
273         self.ids[classname] += 1
274         return str(self.ids[classname])
275     def setid(self, classname, id):
276         self.ids[classname] = id
278     #
279     # Nodes
280     #
281     def addnode(self, classname, nodeid, node):
282         self.getclassdb(classname)[nodeid] = node
284     def setnode(self, classname, nodeid, node):
285         self.getclassdb(classname)[nodeid] = node
287     def getnode(self, classname, nodeid, db=None):
288         if db is not None:
289             return db[nodeid]
290         d = self.getclassdb(classname)
291         if nodeid not in d:
292             raise IndexError(nodeid)
293         return d[nodeid]
295     def destroynode(self, classname, nodeid):
296         del self.getclassdb(classname)[nodeid]
298     def hasnode(self, classname, nodeid):
299         return nodeid in self.getclassdb(classname)
301     def countnodes(self, classname, db=None):
302         return len(self.getclassdb(classname))
304     #
305     # Journal
306     #
307     def addjournal(self, classname, nodeid, action, params, creator=None,
308             creation=None):
309         if creator is None:
310             creator = self.getuid()
311         if creation is None:
312             creation = date.Date()
313         self.journals.setdefault(classname, {}).setdefault(nodeid,
314             []).append((nodeid, creation, creator, action, params))
316     def setjournal(self, classname, nodeid, journal):
317         self.journals.setdefault(classname, {})[nodeid] = journal
319     def getjournal(self, classname, nodeid):
320         return self.journals.get(classname, {}).get(nodeid, [])
322     def pack(self, pack_before):
323         TODO
325     #
326     # Basic transaction support
327     #
328     def commit(self, fail_ok=False):
329         pass
331     def rollback(self):
332         TODO
334     def close(self):
335         pass
337 class Class(back_anydbm.Class):
338     def getnodeids(self, db=None, retired=None):
339         d = self.db.getclassdb(self.classname)
340         if retired is None:
341             return d.keys()
342         return [k for k in d if d[k].get(self.db.RETIRED_FLAG, False) == retired]
344 class FileClass(back_anydbm.Class):
345     def __init__(self, db, classname, **properties):
346         if not properties.has_key('content'):
347             properties['content'] = hyperdb.String(indexme='yes')
348         if not properties.has_key('type'):
349             properties['type'] = hyperdb.String()
350         back_anydbm.Class.__init__(self, db, classname, **properties)
352     def getnodeids(self, db=None, retired=None):
353         return self.db.getclassdb(self.classname).keys()
355 # deviation from spec - was called ItemClass
356 class IssueClass(Class, roundupdb.IssueClass):
357     # Overridden methods:
358     def __init__(self, db, classname, **properties):
359         """The newly-created class automatically includes the "messages",
360         "files", "nosy", and "superseder" properties.  If the 'properties'
361         dictionary attempts to specify any of these properties or a
362         "creation" or "activity" property, a ValueError is raised.
363         """
364         if not properties.has_key('title'):
365             properties['title'] = hyperdb.String(indexme='yes')
366         if not properties.has_key('messages'):
367             properties['messages'] = hyperdb.Multilink("msg")
368         if not properties.has_key('files'):
369             properties['files'] = hyperdb.Multilink("file")
370         if not properties.has_key('nosy'):
371             # note: journalling is turned off as it really just wastes
372             # space. this behaviour may be overridden in an instance
373             properties['nosy'] = hyperdb.Multilink("user", do_journal="no")
374         if not properties.has_key('superseder'):
375             properties['superseder'] = hyperdb.Multilink(classname)
376         Class.__init__(self, db, classname, **properties)
378 # vim: set et sts=4 sw=4 :