Mercurial > p > roundup > code
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. |
