Code

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