Mercurial > p > roundup > code
comparison roundup/hyperdb.py @ 224:ad2c98faec97
using isinstance(blah, Foo) now instead of isFooType
| author | Richard Jones <richard@users.sourceforge.net> |
|---|---|
| date | Sun, 12 Aug 2001 06:32:36 +0000 |
| parents | 18134bffab37 |
| children | 1d1848c99abe |
comparison
equal
deleted
inserted
replaced
| 223:5ce5fc22ac6c | 224:ad2c98faec97 |
|---|---|
| 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.14 2001-08-07 00:24:42 richard Exp $ | 18 # $Id: hyperdb.py,v 1.15 2001-08-12 06:32:36 richard Exp $ |
| 19 | 19 |
| 20 # standard python modules | 20 # standard python modules |
| 21 import cPickle, re, string | 21 import cPickle, re, string |
| 22 | 22 |
| 23 # roundup modules | 23 # roundup modules |
| 25 | 25 |
| 26 | 26 |
| 27 # | 27 # |
| 28 # Types | 28 # Types |
| 29 # | 29 # |
| 30 class BaseType: | 30 class String: |
| 31 isStringType = 0 | 31 """An object designating a String property.""" |
| 32 isDateType = 0 | |
| 33 isIntervalType = 0 | |
| 34 isLinkType = 0 | |
| 35 isMultilinkType = 0 | |
| 36 | |
| 37 class String(BaseType): | |
| 38 def __init__(self): | |
| 39 """An object designating a String property.""" | |
| 40 pass | |
| 41 def __repr__(self): | 32 def __repr__(self): |
| 42 return '<%s>'%self.__class__ | 33 return '<%s>'%self.__class__ |
| 43 isStringType = 1 | 34 |
| 44 | 35 class Date: |
| 45 class Date(BaseType, String): | 36 """An object designating a Date property.""" |
| 46 isDateType = 1 | 37 def __repr__(self): |
| 47 | 38 return '<%s>'%self.__class__ |
| 48 class Interval(BaseType, String): | 39 |
| 49 isIntervalType = 1 | 40 class Interval: |
| 50 | 41 """An object designating an Interval property.""" |
| 51 class Link(BaseType): | 42 def __repr__(self): |
| 43 return '<%s>'%self.__class__ | |
| 44 | |
| 45 class Link: | |
| 46 """An object designating a Link property that links to a | |
| 47 node in a specified class.""" | |
| 52 def __init__(self, classname): | 48 def __init__(self, classname): |
| 53 """An object designating a Link property that links to | |
| 54 nodes in a specified class.""" | |
| 55 self.classname = classname | 49 self.classname = classname |
| 56 def __repr__(self): | 50 def __repr__(self): |
| 57 return '<%s to "%s">'%(self.__class__, self.classname) | 51 return '<%s to "%s">'%(self.__class__, self.classname) |
| 58 isLinkType = 1 | 52 |
| 59 | 53 class Multilink: |
| 60 class Multilink(BaseType, Link): | |
| 61 """An object designating a Multilink property that links | 54 """An object designating a Multilink property that links |
| 62 to nodes in a specified class. | 55 to nodes in a specified class. |
| 63 """ | 56 """ |
| 64 isMultilinkType = 1 | 57 def __init__(self, classname): |
| 58 self.classname = classname | |
| 59 def __repr__(self): | |
| 60 return '<%s to "%s">'%(self.__class__, self.classname) | |
| 65 | 61 |
| 66 class DatabaseError(ValueError): | 62 class DatabaseError(ValueError): |
| 67 pass | 63 pass |
| 68 | 64 |
| 69 | 65 |
| 141 prop = self.properties[key] | 137 prop = self.properties[key] |
| 142 except KeyError: | 138 except KeyError: |
| 143 raise KeyError, '"%s" has no property "%s"'%(self.classname, | 139 raise KeyError, '"%s" has no property "%s"'%(self.classname, |
| 144 key) | 140 key) |
| 145 | 141 |
| 146 if prop.isLinkType: | 142 if isinstance(prop, Link): |
| 147 if type(value) != type(''): | 143 if type(value) != type(''): |
| 148 raise ValueError, 'link value must be String' | 144 raise ValueError, 'link value must be String' |
| 149 # value = str(value) | |
| 150 link_class = self.properties[key].classname | 145 link_class = self.properties[key].classname |
| 151 # if it isn't a number, it's a key | 146 # if it isn't a number, it's a key |
| 152 if not num_re.match(value): | 147 if not num_re.match(value): |
| 153 try: | 148 try: |
| 154 value = self.db.classes[link_class].lookup(value) | 149 value = self.db.classes[link_class].lookup(value) |
| 161 | 156 |
| 162 # register the link with the newly linked node | 157 # register the link with the newly linked node |
| 163 self.db.addjournal(link_class, value, 'link', | 158 self.db.addjournal(link_class, value, 'link', |
| 164 (self.classname, newid, key)) | 159 (self.classname, newid, key)) |
| 165 | 160 |
| 166 elif prop.isMultilinkType: | 161 elif isinstance(prop, Multilink): |
| 167 if type(value) != type([]): | 162 if type(value) != type([]): |
| 168 raise TypeError, 'new property "%s" not a list of ids'%key | 163 raise TypeError, 'new property "%s" not a list of ids'%key |
| 169 link_class = self.properties[key].classname | 164 link_class = self.properties[key].classname |
| 170 l = [] | 165 l = [] |
| 171 for entry in value: | 166 for entry in value: |
| 188 raise IndexError, '%s has no node %s'%(link_class, id) | 183 raise IndexError, '%s has no node %s'%(link_class, id) |
| 189 # register the link with the newly linked node | 184 # register the link with the newly linked node |
| 190 self.db.addjournal(link_class, id, 'link', | 185 self.db.addjournal(link_class, id, 'link', |
| 191 (self.classname, newid, key)) | 186 (self.classname, newid, key)) |
| 192 | 187 |
| 193 elif prop.isStringType: | 188 elif isinstance(prop, String): |
| 194 if type(value) != type(''): | 189 if type(value) != type(''): |
| 195 raise TypeError, 'new property "%s" not a string'%key | 190 raise TypeError, 'new property "%s" not a string'%key |
| 196 | 191 |
| 197 elif prop.isDateType: | 192 elif isinstance(prop, Date): |
| 198 if not hasattr(value, 'isDate'): | 193 if not hasattr(value, 'isDate'): |
| 199 raise TypeError, 'new property "%s" not a Date'% key | 194 raise TypeError, 'new property "%s" not a Date'% key |
| 200 | 195 |
| 201 elif prop.isIntervalType: | 196 elif isinstance(prop, Interval): |
| 202 if not hasattr(value, 'isInterval'): | 197 if not hasattr(value, 'isInterval'): |
| 203 raise TypeError, 'new property "%s" not an Interval'% key | 198 raise TypeError, 'new property "%s" not an Interval'% key |
| 204 | 199 |
| 205 for key, prop in self.properties.items(): | 200 for key, prop in self.properties.items(): |
| 206 if propvalues.has_key(key): | 201 if propvalues.has_key(key): |
| 207 continue | 202 continue |
| 208 if prop.isMultilinkType: | 203 if isinstance(prop, Multilink): |
| 209 propvalues[key] = [] | 204 propvalues[key] = [] |
| 210 else: | 205 else: |
| 211 propvalues[key] = None | 206 propvalues[key] = None |
| 212 | 207 |
| 213 # done | 208 # done |
| 222 IndexError is raised. 'propname' must be the name of a property | 217 IndexError is raised. 'propname' must be the name of a property |
| 223 of this class or a KeyError is raised. | 218 of this class or a KeyError is raised. |
| 224 """ | 219 """ |
| 225 if propname == 'id': | 220 if propname == 'id': |
| 226 return nodeid | 221 return nodeid |
| 227 # nodeid = str(nodeid) | |
| 228 d = self.db.getnode(self.classname, nodeid) | 222 d = self.db.getnode(self.classname, nodeid) |
| 229 if not d.has_key(propname) and default is not _marker: | 223 if not d.has_key(propname) and default is not _marker: |
| 230 return default | 224 return default |
| 231 return d[propname] | 225 return d[propname] |
| 232 | 226 |
| 261 raise KeyError, '"id" is reserved' | 255 raise KeyError, '"id" is reserved' |
| 262 | 256 |
| 263 if self.db.journaltag is None: | 257 if self.db.journaltag is None: |
| 264 raise DatabaseError, 'Database open read-only' | 258 raise DatabaseError, 'Database open read-only' |
| 265 | 259 |
| 266 # nodeid = str(nodeid) | |
| 267 node = self.db.getnode(self.classname, nodeid) | 260 node = self.db.getnode(self.classname, nodeid) |
| 268 if node.has_key(self.db.RETIRED_FLAG): | 261 if node.has_key(self.db.RETIRED_FLAG): |
| 269 raise IndexError | 262 raise IndexError |
| 270 num_re = re.compile('^\d+$') | 263 num_re = re.compile('^\d+$') |
| 271 for key, value in propvalues.items(): | 264 for key, value in propvalues.items(): |
| 280 else: | 273 else: |
| 281 raise ValueError, 'node with key "%s" exists'%value | 274 raise ValueError, 'node with key "%s" exists'%value |
| 282 | 275 |
| 283 prop = self.properties[key] | 276 prop = self.properties[key] |
| 284 | 277 |
| 285 if prop.isLinkType: | 278 if isinstance(prop, Link): |
| 286 # value = str(value) | |
| 287 link_class = self.properties[key].classname | 279 link_class = self.properties[key].classname |
| 288 # if it isn't a number, it's a key | 280 # if it isn't a number, it's a key |
| 289 if type(value) != type(''): | 281 if type(value) != type(''): |
| 290 raise ValueError, 'link value must be String' | 282 raise ValueError, 'link value must be String' |
| 291 if not num_re.match(value): | 283 if not num_re.match(value): |
| 306 # register the link with the newly linked node | 298 # register the link with the newly linked node |
| 307 if value is not None: | 299 if value is not None: |
| 308 self.db.addjournal(link_class, value, 'link', | 300 self.db.addjournal(link_class, value, 'link', |
| 309 (self.classname, nodeid, key)) | 301 (self.classname, nodeid, key)) |
| 310 | 302 |
| 311 elif prop.isMultilinkType: | 303 elif isinstance(prop, Multilink): |
| 312 if type(value) != type([]): | 304 if type(value) != type([]): |
| 313 raise TypeError, 'new property "%s" not a list of ids'%key | 305 raise TypeError, 'new property "%s" not a list of ids'%key |
| 314 link_class = self.properties[key].classname | 306 link_class = self.properties[key].classname |
| 315 l = [] | 307 l = [] |
| 316 for entry in value: | 308 for entry in value: |
| 346 # register the link with the newly linked node | 338 # register the link with the newly linked node |
| 347 self.db.addjournal(link_class, id, 'link', | 339 self.db.addjournal(link_class, id, 'link', |
| 348 (self.classname, nodeid, key)) | 340 (self.classname, nodeid, key)) |
| 349 l.append(id) | 341 l.append(id) |
| 350 | 342 |
| 351 elif prop.isStringType: | 343 elif isinstance(prop, String): |
| 352 if value is not None and type(value) != type(''): | 344 if value is not None and type(value) != type(''): |
| 353 raise TypeError, 'new property "%s" not a string'%key | 345 raise TypeError, 'new property "%s" not a string'%key |
| 354 | 346 |
| 355 elif prop.isDateType: | 347 elif isinstance(prop, Date): |
| 356 if not hasattr(value, 'isDate'): | 348 if not hasattr(value, 'isDate'): |
| 357 raise TypeError, 'new property "%s" not a Date'% key | 349 raise TypeError, 'new property "%s" not a Date'% key |
| 358 | 350 |
| 359 elif prop.isIntervalType: | 351 elif isinstance(prop, Interval): |
| 360 if not hasattr(value, 'isInterval'): | 352 if not hasattr(value, 'isInterval'): |
| 361 raise TypeError, 'new property "%s" not an Interval'% key | 353 raise TypeError, 'new property "%s" not an Interval'% key |
| 362 | 354 |
| 363 node[key] = value | 355 node[key] = value |
| 364 | 356 |
| 372 and the node's id is never reused. | 364 and the node's id is never reused. |
| 373 | 365 |
| 374 Retired nodes are not returned by the find(), list(), or lookup() | 366 Retired nodes are not returned by the find(), list(), or lookup() |
| 375 methods, and other nodes may reuse the values of their key properties. | 367 methods, and other nodes may reuse the values of their key properties. |
| 376 """ | 368 """ |
| 377 # nodeid = str(nodeid) | |
| 378 if self.db.journaltag is None: | 369 if self.db.journaltag is None: |
| 379 raise DatabaseError, 'Database open read-only' | 370 raise DatabaseError, 'Database open read-only' |
| 380 node = self.db.getnode(self.classname, nodeid) | 371 node = self.db.getnode(self.classname, nodeid) |
| 381 node[self.db.RETIRED_FLAG] = 1 | 372 node[self.db.RETIRED_FLAG] = 1 |
| 382 self.db.setnode(self.classname, nodeid, node) | 373 self.db.setnode(self.classname, nodeid, node) |
| 467 'nodeid' must be the id of an existing node in the class linked | 458 'nodeid' must be the id of an existing node in the class linked |
| 468 to by the given property, or an IndexError is raised. | 459 to by the given property, or an IndexError is raised. |
| 469 """ | 460 """ |
| 470 propspec = propspec.items() | 461 propspec = propspec.items() |
| 471 for propname, nodeid in propspec: | 462 for propname, nodeid in propspec: |
| 472 # nodeid = str(nodeid) | |
| 473 # check the prop is OK | 463 # check the prop is OK |
| 474 prop = self.properties[propname] | 464 prop = self.properties[propname] |
| 475 if not prop.isLinkType and not prop.isMultilinkType: | 465 if not isinstance(prop, Link) and not isinstance(prop, Multilink): |
| 476 raise TypeError, "'%s' not a Link/Multilink property"%propname | 466 raise TypeError, "'%s' not a Link/Multilink property"%propname |
| 477 if not self.db.hasnode(prop.classname, nodeid): | 467 if not self.db.hasnode(prop.classname, nodeid): |
| 478 raise ValueError, '%s has no node %s'%(link_class, nodeid) | 468 raise ValueError, '%s has no node %s'%(link_class, nodeid) |
| 479 | 469 |
| 480 # ok, now do the find | 470 # ok, now do the find |
| 483 for id in self.db.getnodeids(self.classname, cldb): | 473 for id in self.db.getnodeids(self.classname, cldb): |
| 484 node = self.db.getnode(self.classname, id, cldb) | 474 node = self.db.getnode(self.classname, id, cldb) |
| 485 if node.has_key(self.db.RETIRED_FLAG): | 475 if node.has_key(self.db.RETIRED_FLAG): |
| 486 continue | 476 continue |
| 487 for propname, nodeid in propspec: | 477 for propname, nodeid in propspec: |
| 488 # nodeid = str(nodeid) | |
| 489 property = node[propname] | 478 property = node[propname] |
| 490 if prop.isLinkType and nodeid == property: | 479 if isinstance(prop, Link) and nodeid == property: |
| 491 l.append(id) | 480 l.append(id) |
| 492 elif prop.isMultilinkType and nodeid in property: | 481 elif isinstance(prop, Multilink) and nodeid in property: |
| 493 l.append(id) | 482 l.append(id) |
| 494 cldb.close() | 483 cldb.close() |
| 495 return l | 484 return l |
| 496 | 485 |
| 497 def stringFind(self, **requirements): | 486 def stringFind(self, **requirements): |
| 501 | 490 |
| 502 The return is a list of the id of all nodes that match. | 491 The return is a list of the id of all nodes that match. |
| 503 """ | 492 """ |
| 504 for propname in requirements.keys(): | 493 for propname in requirements.keys(): |
| 505 prop = self.properties[propname] | 494 prop = self.properties[propname] |
| 506 if not prop.isStringType: | 495 if isinstance(not prop, String): |
| 507 raise TypeError, "'%s' not a String property"%propname | 496 raise TypeError, "'%s' not a String property"%propname |
| 508 l = [] | 497 l = [] |
| 509 cldb = self.db.getclassdb(self.classname) | 498 cldb = self.db.getclassdb(self.classname) |
| 510 for nodeid in self.db.getnodeids(self.classname, cldb): | 499 for nodeid in self.db.getnodeids(self.classname, cldb): |
| 511 node = self.db.getnode(self.classname, nodeid, cldb) | 500 node = self.db.getnode(self.classname, nodeid, cldb) |
| 544 # optimise filterspec | 533 # optimise filterspec |
| 545 l = [] | 534 l = [] |
| 546 props = self.getprops() | 535 props = self.getprops() |
| 547 for k, v in filterspec.items(): | 536 for k, v in filterspec.items(): |
| 548 propclass = props[k] | 537 propclass = props[k] |
| 549 if propclass.isLinkType: | 538 if isinstance(propclass, Link): |
| 550 if type(v) is not type([]): | 539 if type(v) is not type([]): |
| 551 v = [v] | 540 v = [v] |
| 552 # replace key values with node ids | 541 # replace key values with node ids |
| 553 u = [] | 542 u = [] |
| 554 link_class = self.db.classes[propclass.classname] | 543 link_class = self.db.classes[propclass.classname] |
| 560 raise ValueError, 'new property "%s": %s not a %s'%( | 549 raise ValueError, 'new property "%s": %s not a %s'%( |
| 561 k, entry, self.properties[k].classname) | 550 k, entry, self.properties[k].classname) |
| 562 u.append(entry) | 551 u.append(entry) |
| 563 | 552 |
| 564 l.append((0, k, u)) | 553 l.append((0, k, u)) |
| 565 elif propclass.isMultilinkType: | 554 elif isinstance(propclass, Multilink): |
| 566 if type(v) is not type([]): | 555 if type(v) is not type([]): |
| 567 v = [v] | 556 v = [v] |
| 568 # replace key values with node ids | 557 # replace key values with node ids |
| 569 u = [] | 558 u = [] |
| 570 link_class = self.db.classes[propclass.classname] | 559 link_class = self.db.classes[propclass.classname] |
| 575 except: | 564 except: |
| 576 raise ValueError, 'new property "%s": %s not a %s'%( | 565 raise ValueError, 'new property "%s": %s not a %s'%( |
| 577 k, entry, self.properties[k].classname) | 566 k, entry, self.properties[k].classname) |
| 578 u.append(entry) | 567 u.append(entry) |
| 579 l.append((1, k, u)) | 568 l.append((1, k, u)) |
| 580 elif propclass.isStringType: | 569 elif isinstance(propclass, String): |
| 581 if '*' in v or '?' in v: | 570 if '*' in v or '?' in v: |
| 582 # simple glob searching | 571 # simple glob searching |
| 583 v = v.replace('?', '.') | 572 v = v.replace('?', '.') |
| 584 v = v.replace('*', '.*?') | 573 v = v.replace('*', '.*?') |
| 585 v = re.compile(v) | 574 v = re.compile(v) |
| 678 | 667 |
| 679 # sorting is class-specific | 668 # sorting is class-specific |
| 680 propclass = properties[prop] | 669 propclass = properties[prop] |
| 681 | 670 |
| 682 # String and Date values are sorted in the natural way | 671 # String and Date values are sorted in the natural way |
| 683 if propclass.isStringType: | 672 if isinstance(propclass, String): |
| 684 # clean up the strings | 673 # clean up the strings |
| 685 if av and av[0] in string.uppercase: | 674 if av and av[0] in string.uppercase: |
| 686 av = an[prop] = av.lower() | 675 av = an[prop] = av.lower() |
| 687 if bv and bv[0] in string.uppercase: | 676 if bv and bv[0] in string.uppercase: |
| 688 bv = bn[prop] = bv.lower() | 677 bv = bn[prop] = bv.lower() |
| 689 if propclass.isStringType or propclass.isDateType: | 678 if isinstance(propclass.isStringType or propclass, Date): |
| 690 if dir == '+': | 679 if dir == '+': |
| 691 r = cmp(av, bv) | 680 r = cmp(av, bv) |
| 692 if r != 0: return r | 681 if r != 0: return r |
| 693 elif dir == '-': | 682 elif dir == '-': |
| 694 r = cmp(bv, av) | 683 r = cmp(bv, av) |
| 696 | 685 |
| 697 # Link properties are sorted according to the value of | 686 # Link properties are sorted according to the value of |
| 698 # the "order" property on the linked nodes if it is | 687 # the "order" property on the linked nodes if it is |
| 699 # present; or otherwise on the key string of the linked | 688 # present; or otherwise on the key string of the linked |
| 700 # nodes; or finally on the node ids. | 689 # nodes; or finally on the node ids. |
| 701 elif propclass.isLinkType: | 690 elif isinstance(propclass, Link): |
| 702 link = db.classes[propclass.classname] | 691 link = db.classes[propclass.classname] |
| 703 if av is None and bv is not None: return -1 | 692 if av is None and bv is not None: return -1 |
| 704 if av is not None and bv is None: return 1 | 693 if av is not None and bv is None: return 1 |
| 705 if av is None and bv is None: return 0 | 694 if av is None and bv is None: return 0 |
| 706 if link.getprops().has_key('order'): | 695 if link.getprops().has_key('order'): |
| 728 r = cmp(bv, av) | 717 r = cmp(bv, av) |
| 729 if r != 0: return r | 718 if r != 0: return r |
| 730 | 719 |
| 731 # Multilink properties are sorted according to how many | 720 # Multilink properties are sorted according to how many |
| 732 # links are present. | 721 # links are present. |
| 733 elif propclass.isMultilinkType: | 722 elif isinstance(propclass, Multilink): |
| 734 if dir == '+': | 723 if dir == '+': |
| 735 r = cmp(len(av), len(bv)) | 724 r = cmp(len(av), len(bv)) |
| 736 if r != 0: return r | 725 if r != 0: return r |
| 737 elif dir == '-': | 726 elif dir == '-': |
| 738 r = cmp(len(bv), len(av)) | 727 r = cmp(len(bv), len(av)) |
| 815 cl.create(name=option[i], order=i) | 804 cl.create(name=option[i], order=i) |
| 816 return hyperdb.Link(name) | 805 return hyperdb.Link(name) |
| 817 | 806 |
| 818 # | 807 # |
| 819 # $Log: not supported by cvs2svn $ | 808 # $Log: not supported by cvs2svn $ |
| 809 # Revision 1.14 2001/08/07 00:24:42 richard | |
| 810 # stupid typo | |
| 811 # | |
| 820 # Revision 1.13 2001/08/07 00:15:51 richard | 812 # Revision 1.13 2001/08/07 00:15:51 richard |
| 821 # Added the copyright/license notice to (nearly) all files at request of | 813 # Added the copyright/license notice to (nearly) all files at request of |
| 822 # Bizar Software. | 814 # Bizar Software. |
| 823 # | 815 # |
| 824 # Revision 1.12 2001/08/02 06:38:17 richard | 816 # Revision 1.12 2001/08/02 06:38:17 richard |
