comparison roundup/hyperdb.py @ 602:c242455d9b46 config-0-4-0-branch

Brought the config branch up to date with HEAD
author Richard Jones <richard@users.sourceforge.net>
date Wed, 06 Feb 2002 04:05:55 +0000
parents 05a46da7293a
children
comparison
equal deleted inserted replaced
601:912029653c1c 602:c242455d9b46
13 # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 13 # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
14 # FOR A PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" 14 # FOR A PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS"
15 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, 15 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
16 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. 16 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
17 # 17 #
18 # $Id: hyperdb.py,v 1.45 2002-01-02 04:18:17 richard Exp $ 18 # $Id: hyperdb.py,v 1.45.2.1 2002-02-06 04:05:53 richard Exp $
19 19
20 __doc__ = """ 20 __doc__ = """
21 Hyperdatabase implementation, especially field types. 21 Hyperdatabase implementation, especially field types.
22 """ 22 """
23 23
24 # standard python modules 24 # standard python modules
25 import cPickle, re, string, weakref 25 import cPickle, re, string, weakref, os
26 26
27 # roundup modules 27 # roundup modules
28 import date, password 28 import date, password
29 29
30 DEBUG = os.environ.get('HYPERDBDEBUG', '')
30 31
31 # 32 #
32 # Types 33 # Types
33 # 34 #
34 class String: 35 class String:
52 return '<%s>'%self.__class__ 53 return '<%s>'%self.__class__
53 54
54 class Link: 55 class Link:
55 """An object designating a Link property that links to a 56 """An object designating a Link property that links to a
56 node in a specified class.""" 57 node in a specified class."""
57 def __init__(self, classname): 58 def __init__(self, classname, do_journal='no'):
58 self.classname = classname 59 self.classname = classname
60 self.do_journal = do_journal == 'yes'
59 def __repr__(self): 61 def __repr__(self):
60 return '<%s to "%s">'%(self.__class__, self.classname) 62 return '<%s to "%s">'%(self.__class__, self.classname)
61 63
62 class Multilink: 64 class Multilink:
63 """An object designating a Multilink property that links 65 """An object designating a Multilink property that links
64 to nodes in a specified class. 66 to nodes in a specified class.
67
68 "classname" indicates the class to link to
69
70 "do_journal" indicates whether the linked-to nodes should have
71 'link' and 'unlink' events placed in their journal
65 """ 72 """
66 def __init__(self, classname): 73 def __init__(self, classname, do_journal='no'):
67 self.classname = classname 74 self.classname = classname
75 self.do_journal = do_journal == 'yes'
68 def __repr__(self): 76 def __repr__(self):
69 return '<%s to "%s">'%(self.__class__, self.classname) 77 return '<%s to "%s">'%(self.__class__, self.classname)
70 78
71 class DatabaseError(ValueError): 79 class DatabaseError(ValueError):
72 pass 80 pass
96 ''' 104 '''
97 105
98 # flag to set on retired entries 106 # flag to set on retired entries
99 RETIRED_FLAG = '__hyperdb_retired' 107 RETIRED_FLAG = '__hyperdb_retired'
100 108
101 def __init__(self, storagelocator, journaltag=None): 109 # XXX deviates from spec: storagelocator is obtained from the config
110 def __init__(self, config, journaltag=None):
102 """Open a hyperdatabase given a specifier to some storage. 111 """Open a hyperdatabase given a specifier to some storage.
103 112
113 The 'storagelocator' is obtained from config.DATABASE.
104 The meaning of 'storagelocator' depends on the particular 114 The meaning of 'storagelocator' depends on the particular
105 implementation of the hyperdatabase. It could be a file name, 115 implementation of the hyperdatabase. It could be a file name,
106 a directory path, a socket descriptor for a connection to a 116 a directory path, a socket descriptor for a connection to a
107 database over the network, etc. 117 database over the network, etc.
108 118
197 ''' 207 '''
198 raise NotImplementedError 208 raise NotImplementedError
199 209
200 def getjournal(self, classname, nodeid): 210 def getjournal(self, classname, nodeid):
201 ''' get the journal for id 211 ''' get the journal for id
212 '''
213 raise NotImplementedError
214
215 def pack(self, pack_before):
216 ''' pack the database
202 ''' 217 '''
203 raise NotImplementedError 218 raise NotImplementedError
204 219
205 def commit(self): 220 def commit(self):
206 ''' Commit the current transactions. 221 ''' Commit the current transactions.
305 320
306 # save off the value 321 # save off the value
307 propvalues[key] = value 322 propvalues[key] = value
308 323
309 # register the link with the newly linked node 324 # register the link with the newly linked node
310 self.db.addjournal(link_class, value, 'link', 325 if self.properties[key].do_journal:
311 (self.classname, newid, key)) 326 self.db.addjournal(link_class, value, 'link',
327 (self.classname, newid, key))
312 328
313 elif isinstance(prop, Multilink): 329 elif isinstance(prop, Multilink):
314 if type(value) != type([]): 330 if type(value) != type([]):
315 raise TypeError, 'new property "%s" not a list of ids'%key 331 raise TypeError, 'new property "%s" not a list of ids'%key
316 link_class = self.properties[key].classname 332 link_class = self.properties[key].classname
332 # handle additions 348 # handle additions
333 for id in value: 349 for id in value:
334 if not self.db.hasnode(link_class, id): 350 if not self.db.hasnode(link_class, id):
335 raise IndexError, '%s has no node %s'%(link_class, id) 351 raise IndexError, '%s has no node %s'%(link_class, id)
336 # register the link with the newly linked node 352 # register the link with the newly linked node
337 self.db.addjournal(link_class, id, 'link', 353 if self.properties[key].do_journal:
338 (self.classname, newid, key)) 354 self.db.addjournal(link_class, id, 'link',
355 (self.classname, newid, key))
339 356
340 elif isinstance(prop, String): 357 elif isinstance(prop, String):
341 if type(value) != type(''): 358 if type(value) != type(''):
342 raise TypeError, 'new property "%s" not a string'%key 359 raise TypeError, 'new property "%s" not a string'%key
343 360
344 elif isinstance(prop, Password): 361 elif isinstance(prop, Password):
345 if not isinstance(value, password.Password): 362 if not isinstance(value, password.Password):
346 raise TypeError, 'new property "%s" not a Password'%key 363 raise TypeError, 'new property "%s" not a Password'%key
347 364
348 elif isinstance(prop, Date): 365 elif isinstance(prop, Date):
349 if not isinstance(value, date.Date): 366 if value is not None and not isinstance(value, date.Date):
350 raise TypeError, 'new property "%s" not a Date'%key 367 raise TypeError, 'new property "%s" not a Date'%key
351 368
352 elif isinstance(prop, Interval): 369 elif isinstance(prop, Interval):
353 if not isinstance(value, date.Interval): 370 if value is not None and not isinstance(value, date.Interval):
354 raise TypeError, 'new property "%s" not an Interval'%key 371 raise TypeError, 'new property "%s" not an Interval'%key
355 372
356 # make sure there's data where there needs to be 373 # make sure there's data where there needs to be
357 for key, prop in self.properties.items(): 374 for key, prop in self.properties.items():
358 if propvalues.has_key(key): 375 if propvalues.has_key(key):
360 if key == self.key: 377 if key == self.key:
361 raise ValueError, 'key property "%s" is required'%key 378 raise ValueError, 'key property "%s" is required'%key
362 if isinstance(prop, Multilink): 379 if isinstance(prop, Multilink):
363 propvalues[key] = [] 380 propvalues[key] = []
364 else: 381 else:
382 # TODO: None isn't right here, I think...
365 propvalues[key] = None 383 propvalues[key] = None
366 384
367 # convert all data to strings 385 # convert all data to strings
368 for key, prop in self.properties.items(): 386 for key, prop in self.properties.items():
369 if isinstance(prop, Date): 387 if isinstance(prop, Date):
370 propvalues[key] = propvalues[key].get_tuple() 388 if propvalues[key] is not None:
389 propvalues[key] = propvalues[key].get_tuple()
371 elif isinstance(prop, Interval): 390 elif isinstance(prop, Interval):
372 propvalues[key] = propvalues[key].get_tuple() 391 if propvalues[key] is not None:
392 propvalues[key] = propvalues[key].get_tuple()
373 elif isinstance(prop, Password): 393 elif isinstance(prop, Password):
374 propvalues[key] = str(propvalues[key]) 394 propvalues[key] = str(propvalues[key])
375 395
376 # done 396 # done
377 self.db.addnode(self.classname, newid, propvalues) 397 self.db.addnode(self.classname, newid, propvalues)
391 set cache=0. 411 set cache=0.
392 """ 412 """
393 if propname == 'id': 413 if propname == 'id':
394 return nodeid 414 return nodeid
395 415
416 # get the property (raises KeyErorr if invalid)
417 prop = self.properties[propname]
418
396 # get the node's dict 419 # get the node's dict
397 d = self.db.getnode(self.classname, nodeid, cache=cache) 420 d = self.db.getnode(self.classname, nodeid, cache=cache)
398 if not d.has_key(propname) and default is not _marker: 421
399 return default 422 if not d.has_key(propname):
400 423 if default is _marker:
401 # get the value 424 if isinstance(prop, Multilink):
402 prop = self.properties[propname] 425 return []
426 else:
427 # TODO: None isn't right here, I think...
428 return None
429 else:
430 return default
403 431
404 # possibly convert the marshalled data to instances 432 # possibly convert the marshalled data to instances
405 if isinstance(prop, Date): 433 if isinstance(prop, Date):
434 if d[propname] is None:
435 return None
406 return date.Date(d[propname]) 436 return date.Date(d[propname])
407 elif isinstance(prop, Interval): 437 elif isinstance(prop, Interval):
438 if d[propname] is None:
439 return None
408 return date.Interval(d[propname]) 440 return date.Interval(d[propname])
409 elif isinstance(prop, Password): 441 elif isinstance(prop, Password):
410 p = password.Password() 442 p = password.Password()
411 p.unpack(d[propname]) 443 p.unpack(d[propname])
412 return p 444 return p
486 key, value, self.properties[key].classname) 518 key, value, self.properties[key].classname)
487 519
488 if not self.db.hasnode(link_class, value): 520 if not self.db.hasnode(link_class, value):
489 raise IndexError, '%s has no node %s'%(link_class, value) 521 raise IndexError, '%s has no node %s'%(link_class, value)
490 522
491 # register the unlink with the old linked node 523 if self.properties[key].do_journal:
492 if node[key] is not None: 524 # register the unlink with the old linked node
493 self.db.addjournal(link_class, node[key], 'unlink', 525 if node[key] is not None:
494 (self.classname, nodeid, key)) 526 self.db.addjournal(link_class, node[key], 'unlink',
495 527 (self.classname, nodeid, key))
496 # register the link with the newly linked node 528
497 if value is not None: 529 # register the link with the newly linked node
498 self.db.addjournal(link_class, value, 'link', 530 if value is not None:
499 (self.classname, nodeid, key)) 531 self.db.addjournal(link_class, value, 'link',
532 (self.classname, nodeid, key))
500 533
501 elif isinstance(prop, Multilink): 534 elif isinstance(prop, Multilink):
502 if type(value) != type([]): 535 if type(value) != type([]):
503 raise TypeError, 'new property "%s" not a list of ids'%key 536 raise TypeError, 'new property "%s" not a list of ids'%key
504 link_class = self.properties[key].classname 537 link_class = self.properties[key].classname
515 key, entry, self.properties[key].classname) 548 key, entry, self.properties[key].classname)
516 l.append(entry) 549 l.append(entry)
517 value = l 550 value = l
518 propvalues[key] = value 551 propvalues[key] = value
519 552
520 #handle removals 553 # handle removals
521 l = node[key] 554 if node.has_key(key):
555 l = node[key]
556 else:
557 l = []
522 for id in l[:]: 558 for id in l[:]:
523 if id in value: 559 if id in value:
524 continue 560 continue
525 # register the unlink with the old linked node 561 # register the unlink with the old linked node
526 self.db.addjournal(link_class, id, 'unlink', 562 if self.properties[key].do_journal:
527 (self.classname, nodeid, key)) 563 self.db.addjournal(link_class, id, 'unlink',
564 (self.classname, nodeid, key))
528 l.remove(id) 565 l.remove(id)
529 566
530 # handle additions 567 # handle additions
531 for id in value: 568 for id in value:
532 if not self.db.hasnode(link_class, id): 569 if not self.db.hasnode(link_class, id):
533 raise IndexError, '%s has no node %s'%(link_class, id) 570 raise IndexError, '%s has no node %s'%(
571 link_class, id)
534 if id in l: 572 if id in l:
535 continue 573 continue
536 # register the link with the newly linked node 574 # register the link with the newly linked node
537 self.db.addjournal(link_class, id, 'link', 575 if self.properties[key].do_journal:
538 (self.classname, nodeid, key)) 576 self.db.addjournal(link_class, id, 'link',
577 (self.classname, nodeid, key))
539 l.append(id) 578 l.append(id)
540 579
541 elif isinstance(prop, String): 580 elif isinstance(prop, String):
542 if value is not None and type(value) != type(''): 581 if value is not None and type(value) != type(''):
543 raise TypeError, 'new property "%s" not a string'%key 582 raise TypeError, 'new property "%s" not a string'%key
545 elif isinstance(prop, Password): 584 elif isinstance(prop, Password):
546 if not isinstance(value, password.Password): 585 if not isinstance(value, password.Password):
547 raise TypeError, 'new property "%s" not a Password'% key 586 raise TypeError, 'new property "%s" not a Password'% key
548 propvalues[key] = value = str(value) 587 propvalues[key] = value = str(value)
549 588
550 elif isinstance(prop, Date): 589 elif value is not None and isinstance(prop, Date):
551 if not isinstance(value, date.Date): 590 if not isinstance(value, date.Date):
552 raise TypeError, 'new property "%s" not a Date'% key 591 raise TypeError, 'new property "%s" not a Date'% key
553 propvalues[key] = value = value.get_tuple() 592 propvalues[key] = value = value.get_tuple()
554 593
555 elif isinstance(prop, Interval): 594 elif value is not None and isinstance(prop, Interval):
556 if not isinstance(value, date.Interval): 595 if not isinstance(value, date.Interval):
557 raise TypeError, 'new property "%s" not an Interval'% key 596 raise TypeError, 'new property "%s" not an Interval'% key
558 propvalues[key] = value = value.get_tuple() 597 propvalues[key] = value = value.get_tuple()
559 598
560 node[key] = value 599 node[key] = value
976 ''' A convenience wrapper for the given node 1015 ''' A convenience wrapper for the given node
977 ''' 1016 '''
978 def __init__(self, cl, nodeid, cache=1): 1017 def __init__(self, cl, nodeid, cache=1):
979 self.__dict__['cl'] = cl 1018 self.__dict__['cl'] = cl
980 self.__dict__['nodeid'] = nodeid 1019 self.__dict__['nodeid'] = nodeid
981 self.cache = cache 1020 self.__dict__['cache'] = cache
982 def keys(self, protected=1): 1021 def keys(self, protected=1):
983 return self.cl.getprops(protected=protected).keys() 1022 return self.cl.getprops(protected=protected).keys()
984 def values(self, protected=1): 1023 def values(self, protected=1):
985 l = [] 1024 l = []
986 for name in self.cl.getprops(protected=protected).keys(): 1025 for name in self.cl.getprops(protected=protected).keys():
1025 cl.create(name=option[i], order=i) 1064 cl.create(name=option[i], order=i)
1026 return hyperdb.Link(name) 1065 return hyperdb.Link(name)
1027 1066
1028 # 1067 #
1029 # $Log: not supported by cvs2svn $ 1068 # $Log: not supported by cvs2svn $
1069 # Revision 1.53 2002/01/22 07:21:13 richard
1070 # . fixed back_bsddb so it passed the journal tests
1071 #
1072 # ... it didn't seem happy using the back_anydbm _open method, which is odd.
1073 # Yet another occurrance of whichdb not being able to recognise older bsddb
1074 # databases. Yadda yadda. Made the HYPERDBDEBUG stuff more sane in the
1075 # process.
1076 #
1077 # Revision 1.52 2002/01/21 16:33:19 rochecompaan
1078 # You can now use the roundup-admin tool to pack the database
1079 #
1080 # Revision 1.51 2002/01/21 03:01:29 richard
1081 # brief docco on the do_journal argument
1082 #
1083 # Revision 1.50 2002/01/19 13:16:04 rochecompaan
1084 # Journal entries for link and multilink properties can now be switched on
1085 # or off.
1086 #
1087 # Revision 1.49 2002/01/16 07:02:57 richard
1088 # . lots of date/interval related changes:
1089 # - more relaxed date format for input
1090 #
1091 # Revision 1.48 2002/01/14 06:32:34 richard
1092 # . #502951 ] adding new properties to old database
1093 #
1094 # Revision 1.47 2002/01/14 02:20:15 richard
1095 # . changed all config accesses so they access either the instance or the
1096 # config attriubute on the db. This means that all config is obtained from
1097 # instance_config instead of the mish-mash of classes. This will make
1098 # switching to a ConfigParser setup easier too, I hope.
1099 #
1100 # At a minimum, this makes migration a _little_ easier (a lot easier in the
1101 # 0.5.0 switch, I hope!)
1102 #
1103 # Revision 1.46 2002/01/07 10:42:23 richard
1104 # oops
1105 #
1106 # Revision 1.45 2002/01/02 04:18:17 richard
1107 # hyperdb docstrings
1108 #
1030 # Revision 1.44 2002/01/02 02:31:38 richard 1109 # Revision 1.44 2002/01/02 02:31:38 richard
1031 # Sorry for the huge checkin message - I was only intending to implement #496356 1110 # Sorry for the huge checkin message - I was only intending to implement #496356
1032 # but I found a number of places where things had been broken by transactions: 1111 # but I found a number of places where things had been broken by transactions:
1033 # . modified ROUNDUPDBSENDMAILDEBUG to be SENDMAILDEBUG and hold a filename 1112 # . modified ROUNDUPDBSENDMAILDEBUG to be SENDMAILDEBUG and hold a filename
1034 # for _all_ roundup-generated smtp messages to be sent to. 1113 # for _all_ roundup-generated smtp messages to be sent to.

Roundup Issue Tracker: http://roundup-tracker.org/