Mercurial > p > roundup > code
comparison roundup/hyperdb.py @ 7045:ca90f7270cd4 issue2550923_computed_property
merge from main tip. This should fix test failure in Markdown2TestCase.test_string_markdown_code_block_attribute by merging html diff method used in tests.
| author | John Rouillard <rouilj@ieee.org> |
|---|---|
| date | Mon, 07 Nov 2022 22:58:38 -0500 |
| parents | e1588ae185dc 8473039648da |
| children | 82bbb95e5690 |
comparison
equal
deleted
inserted
replaced
| 6638:e1588ae185dc | 7045:ca90f7270cd4 |
|---|---|
| 19 """Hyperdatabase implementation, especially field types. | 19 """Hyperdatabase implementation, especially field types. |
| 20 """ | 20 """ |
| 21 __docformat__ = 'restructuredtext' | 21 __docformat__ = 'restructuredtext' |
| 22 | 22 |
| 23 # standard python modules | 23 # standard python modules |
| 24 import os, re, shutil, sys, weakref | 24 import logging |
| 25 import os | |
| 26 import re | |
| 27 import shutil | |
| 28 import sys | |
| 25 import traceback | 29 import traceback |
| 26 import logging | 30 import weakref |
| 27 | 31 |
| 28 # roundup modules | 32 # roundup modules |
| 29 from . import date, password | 33 from . import date, password |
| 30 from .support import ensureParentsExist, PrioList | 34 from .support import ensureParentsExist, PrioList |
| 31 from roundup.mlink_expr import Expression | 35 from roundup.mlink_expr import Expression |
| 56 | 60 |
| 57 def get_default_value(self): | 61 def get_default_value(self): |
| 58 """The default value when creating a new instance of this property.""" | 62 """The default value when creating a new instance of this property.""" |
| 59 return self.__default_value | 63 return self.__default_value |
| 60 | 64 |
| 61 def register (self, cls, propname): | 65 def register(self, cls, propname): |
| 62 """Register myself to the class of which we are a property | 66 """Register myself to the class of which we are a property |
| 63 the given propname is the name we have in our class. | 67 the given propname is the name we have in our class. |
| 64 """ | 68 """ |
| 65 assert not getattr(self, 'cls', None) | 69 assert not getattr(self, 'cls', None) |
| 66 self.name = propname | 70 self.name = propname |
| 67 self.cls = cls | 71 self.cls = cls |
| 68 | 72 |
| 69 def sort_repr(self, cls, val, name): | 73 def sort_repr(self, cls, val, name): |
| 70 """Representation used for sorting. This should be a python | 74 """Representation used for sorting. This should be a python |
| 71 built-in type, otherwise sorting will take ages. Note that | 75 built-in type, otherwise sorting will take ages. Note that |
| 72 individual backends may chose to use something different for | 76 individual backends may chose to use something different for |
| 344 do_journal, | 348 do_journal, |
| 345 required=required, | 349 required=required, |
| 346 default_value=[], quiet=quiet, | 350 default_value=[], quiet=quiet, |
| 347 try_id_parsing=try_id_parsing, | 351 try_id_parsing=try_id_parsing, |
| 348 rev_multilink=rev_multilink) | 352 rev_multilink=rev_multilink) |
| 349 self.rev_property = rev_property | 353 self.rev_property = rev_property |
| 350 self.rev_classname = None | 354 self.rev_classname = None |
| 351 self.rev_propname = None | 355 self.rev_propname = None |
| 352 self.table_name = None # computed in 'register' below | 356 self.table_name = None # computed in 'register' below |
| 353 self.linkid_name = 'linkid' | 357 self.linkid_name = 'linkid' |
| 354 self.nodeid_name = 'nodeid' | 358 self.nodeid_name = 'nodeid' |
| 355 if self.rev_property: | 359 if self.rev_property: |
| 356 # Do not allow updates if this is a reverse multilink | 360 # Do not allow updates if this is a reverse multilink |
| 357 self.computed = True | 361 self.computed = True |
| 358 self.rev_classname = rev_property.cls.classname | 362 self.rev_classname = rev_property.cls.classname |
| 359 self.rev_propname = rev_property.name | 363 self.rev_propname = rev_property.name |
| 360 if isinstance(self.rev_property, Link): | 364 if isinstance(self.rev_property, Link): |
| 361 self.table_name = '_' + self.rev_classname | 365 self.table_name = '_' + self.rev_classname |
| 362 self.linkid_name = 'id' | 366 self.linkid_name = 'id' |
| 363 self.nodeid_name = '_' + self.rev_propname | 367 self.nodeid_name = '_' + self.rev_propname |
| 364 else: | 368 else: |
| 365 self.table_name = self.rev_classname + '_' + self.rev_propname | 369 self.table_name = self.rev_classname + '_' + self.rev_propname |
| 366 self.linkid_name = 'nodeid' | 370 self.linkid_name = 'nodeid' |
| 367 self.nodeid_name = 'linkid' | 371 self.nodeid_name = 'linkid' |
| 368 | 372 |
| 369 def from_raw(self, value, db, klass, propname, itemid, **kw): | 373 def from_raw(self, value, db, klass, propname, itemid, **kw): |
| 370 if not value: | 374 if not value: |
| 389 newvalue = [] | 393 newvalue = [] |
| 390 for item in value: | 394 for item in value: |
| 391 item = item.strip() | 395 item = item.strip() |
| 392 | 396 |
| 393 # skip blanks | 397 # skip blanks |
| 394 if not item: continue | 398 if not item: continue # noqa: E701 |
| 395 | 399 |
| 396 # handle +/- | 400 # handle +/- |
| 397 remove = 0 | 401 remove = 0 |
| 398 if item.startswith('-'): | 402 if item.startswith('-'): |
| 399 remove = 1 | 403 remove = 1 |
| 501 # | 505 # |
| 502 class DesignatorError(ValueError): | 506 class DesignatorError(ValueError): |
| 503 pass | 507 pass |
| 504 | 508 |
| 505 | 509 |
| 510 dre = re.compile(r'^([A-Za-z](?:[A-Za-z_0-9]*[A-Za-z_]+)?)(\d+)$') | |
| 511 | |
| 512 | |
| 506 def splitDesignator(designator, | 513 def splitDesignator(designator, |
| 507 dre=re.compile(r'^([A-Za-z](?:[A-Za-z_0-9]*[A-Za-z_]+)?)(\d+)$')): | 514 dre=dre): |
| 508 """ Take a foo123 and return ('foo', 123) | 515 """ Take a foo123 and return ('foo', 123) |
| 509 """ | 516 """ |
| 510 m = dre.match(designator) | 517 m = dre.match(designator) |
| 511 if m is None: | 518 if m is None: |
| 512 raise DesignatorError(_('"%s" not a node designator') % designator) | 519 raise DesignatorError(_('"%s" not a node designator') % designator) |
| 583 """ | 590 """ |
| 584 if name in self.propdict: | 591 if name in self.propdict: |
| 585 pt = self.propdict[name] | 592 pt = self.propdict[name] |
| 586 pt.need_for[need_for] = True | 593 pt.need_for[need_for] = True |
| 587 # For now we do not recursively retrieve Link properties | 594 # For now we do not recursively retrieve Link properties |
| 588 #if retr and isinstance(pt.propclass, Link): | 595 # if retr and isinstance(pt.propclass, Link): |
| 589 # pt.append_retr_props() | 596 # pt.append_retr_props() |
| 590 return pt | 597 return pt |
| 591 propclass = self.props[name] | 598 propclass = self.props[name] |
| 592 cls = None | 599 cls = None |
| 593 props = None | 600 props = None |
| 603 else: | 610 else: |
| 604 child.need_child_retired = True | 611 child.need_child_retired = True |
| 605 self.children.append(child) | 612 self.children.append(child) |
| 606 self.propdict[name] = child | 613 self.propdict[name] = child |
| 607 # For now we do not recursively retrieve Link properties | 614 # For now we do not recursively retrieve Link properties |
| 608 #if retr and isinstance(child.propclass, Link): | 615 # if retr and isinstance(child.propclass, Link): |
| 609 # child.append_retr_props() | 616 # child.append_retr_props() |
| 610 return child | 617 return child |
| 611 | 618 |
| 612 def append_retr_props(self): | 619 def append_retr_props(self): |
| 613 """Append properties for retrieval.""" | 620 """Append properties for retrieval.""" |
| 653 for p in self.children: | 660 for p in self.children: |
| 654 if 'search' in p.need_for: | 661 if 'search' in p.need_for: |
| 655 x = [c for c in p.children if 'search' in c.need_for] | 662 x = [c for c in p.children if 'search' in c.need_for] |
| 656 if x: | 663 if x: |
| 657 p.search(sort=False) | 664 p.search(sort=False) |
| 658 if getattr(p.propclass,'rev_property',None): | 665 if getattr(p.propclass, 'rev_property', None): |
| 659 pn = p.propclass.rev_property.name | 666 pn = p.propclass.rev_property.name |
| 660 cl = p.propclass.rev_property.cls | 667 cl = p.propclass.rev_property.cls |
| 661 if not isinstance(p.val, type([])): | 668 if not isinstance(p.val, type([])): |
| 662 p.val = [p.val] | 669 p.val = [p.val] |
| 663 nval = [int(i) for i in p.val] | 670 nval = [int(i) for i in p.val] |
| 675 else: | 682 else: |
| 676 s2.add(node[pn]) | 683 s2.add(node[pn]) |
| 677 items |= s1.difference(s2) | 684 items |= s1.difference(s2) |
| 678 if isinstance(p.propclass.rev_property, Link): | 685 if isinstance(p.propclass.rev_property, Link): |
| 679 items |= set(cl.get(x, pn) for x in pval | 686 items |= set(cl.get(x, pn) for x in pval |
| 680 if not cl.is_retired(x)) | 687 if not cl.is_retired(x)) |
| 681 else: | 688 else: |
| 682 items |= set().union(*(cl.get(x, pn) for x in pval | 689 items |= set().union(*(cl.get(x, pn) for x in pval |
| 683 if not cl.is_retired(x))) | 690 if not cl.is_retired(x))) |
| 684 else: | 691 else: |
| 685 # Expression: materialize rev multilinks and run | 692 # Expression: materialize rev multilinks and run |
| 686 # expression on them | 693 # expression on them |
| 687 expr = Expression(nval) | 694 expr = Expression(nval) |
| 688 by_id = {} | 695 by_id = {} |
| 719 subst.append(v) | 726 subst.append(v) |
| 720 if exact: | 727 if exact: |
| 721 exact_match_spec[p.name] = exact | 728 exact_match_spec[p.name] = exact |
| 722 if subst: | 729 if subst: |
| 723 filterspec[p.name] = subst | 730 filterspec[p.name] = subst |
| 724 elif not exact: # don't set if we have exact criteria | 731 elif not exact: # don't set if we have exact criteria |
| 725 if p.has_result: | 732 if p.has_result: |
| 726 # A subquery already has found nothing. So | 733 # A subquery already has found nothing. So |
| 727 # it doesn't make sense to search further. | 734 # it doesn't make sense to search further. |
| 728 self.set_val([], force=True) | 735 self.set_val([], force=True) |
| 729 return self.val | 736 return self.val |
| 730 else: | 737 else: |
| 731 filterspec[p.name] = ['-1'] # no match was found | 738 filterspec[p.name] = ['-1'] # no match was found |
| 732 else: | 739 else: |
| 733 assert not isinstance(p.val, Exact_Match) | 740 assert not isinstance(p.val, Exact_Match) |
| 734 filterspec[p.name] = p.val | 741 filterspec[p.name] = p.val |
| 735 self.set_val(self.cls._filter(search_matches, filterspec, sort and self, | 742 self.set_val(self.cls._filter(search_matches, filterspec, sort and self, |
| 736 retired=retired, | 743 retired=retired, |
| 873 is_expression = \ | 880 is_expression = \ |
| 874 self.val and min(int(i) for i in self.val) < -1 | 881 self.val and min(int(i) for i in self.val) < -1 |
| 875 if is_expression: | 882 if is_expression: |
| 876 # Tag on the ORed values with an AND | 883 # Tag on the ORed values with an AND |
| 877 l = val | 884 l = val |
| 878 for i in range(len(val)-1): | 885 for _i in range(len(val)-1): |
| 879 l.append('-4') | 886 l.append('-4') |
| 880 l.append('-3') | 887 l.append('-3') |
| 881 self.val = self.val + l | 888 self.val = self.val + l |
| 882 else: | 889 else: |
| 883 vals.intersection_update(val) | 890 vals.intersection_update(val) |
| 1021 # This will change properties if a back-multilink happens to | 1028 # This will change properties if a back-multilink happens to |
| 1022 # have the same class, so we need to iterate over a list made | 1029 # have the same class, so we need to iterate over a list made |
| 1023 # from .keys() | 1030 # from .keys() |
| 1024 for p in list(cl.properties.keys()): | 1031 for p in list(cl.properties.keys()): |
| 1025 prop = cl.properties[p] | 1032 prop = cl.properties[p] |
| 1026 if not isinstance (prop, (Link, Multilink)): | 1033 if not isinstance(prop, (Link, Multilink)): |
| 1027 continue | 1034 continue |
| 1028 if prop.rev_multilink: | 1035 if prop.rev_multilink: |
| 1029 linkcls = self.getclass(prop.classname) | 1036 linkcls = self.getclass(prop.classname) |
| 1030 if prop.rev_multilink in linkcls.properties: | 1037 if prop.rev_multilink in linkcls.properties: |
| 1031 if not done: | 1038 if not done: |
| 1032 raise ValueError( | 1039 raise ValueError( |
| 1033 "%s already a property of class %s"% | 1040 "%s already a property of class %s" % |
| 1034 (prop.rev_multilink, linkcls.classname)) | 1041 (prop.rev_multilink, linkcls.classname)) |
| 1035 else: | 1042 else: |
| 1036 linkcls.properties[prop.rev_multilink] = Multilink( | 1043 linkcls.properties[prop.rev_multilink] = Multilink( |
| 1037 cl.classname, rev_property=prop) | 1044 cl.classname, rev_property=prop) |
| 1038 self.post_init_done = True | 1045 self.post_init_done = True |
| 1167 | 1174 |
| 1168 This method must be called at the end of processing. | 1175 This method must be called at the end of processing. |
| 1169 | 1176 |
| 1170 """ | 1177 """ |
| 1171 raise NotImplementedError | 1178 raise NotImplementedError |
| 1179 | |
| 1172 | 1180 |
| 1173 def iter_roles(roles): | 1181 def iter_roles(roles): |
| 1174 ''' handle the text processing of turning the roles list | 1182 ''' handle the text processing of turning the roles list |
| 1175 into something python can use more easily | 1183 into something python can use more easily |
| 1176 ''' | 1184 ''' |
| 1236 """Slightly more useful representation | 1244 """Slightly more useful representation |
| 1237 Note that an error message can be raised at a point | 1245 Note that an error message can be raised at a point |
| 1238 where self.classname isn't known yet if the error | 1246 where self.classname isn't known yet if the error |
| 1239 occurs during schema parsing. | 1247 occurs during schema parsing. |
| 1240 """ | 1248 """ |
| 1241 cn = getattr (self, 'classname', 'Unknown') | 1249 cn = getattr(self, 'classname', 'Unknown') |
| 1242 return '<hyperdb.Class "%s">' % cn | 1250 return '<hyperdb.Class "%s">' % cn |
| 1243 | 1251 |
| 1244 # Editing nodes: | 1252 # Editing nodes: |
| 1245 | 1253 |
| 1246 def create(self, **propvalues): | 1254 def create(self, **propvalues): |
| 1627 """For some backends this implements the non-transitive | 1635 """For some backends this implements the non-transitive |
| 1628 search, for more information see the filter method. | 1636 search, for more information see the filter method. |
| 1629 """ | 1637 """ |
| 1630 raise NotImplementedError | 1638 raise NotImplementedError |
| 1631 | 1639 |
| 1632 def _proptree(self, filterspec, exact_match_spec={}, sortattr=[], | 1640 def _proptree(self, filterspec, exact_match_spec=None, sortattr=None, |
| 1633 retr=False): | 1641 retr=False): |
| 1634 """Build a tree of all transitive properties in the given | 1642 """Build a tree of all transitive properties in the given |
| 1635 exact_match_spec/filterspec. | 1643 exact_match_spec/filterspec. |
| 1636 If we retrieve (retr is True) linked items we don't follow | 1644 If we retrieve (retr is True) linked items we don't follow |
| 1637 across multilinks or links. | 1645 across multilinks or links. |
| 1638 """ | 1646 """ |
| 1647 if filterspec is None: | |
| 1648 filterspec = {} | |
| 1649 if exact_match_spec is None: | |
| 1650 exact_match_spec = {} | |
| 1651 if sortattr is None: | |
| 1652 sortattr = [] | |
| 1653 | |
| 1639 proptree = Proptree(self.db, self, '', self.getprops(), retr=retr) | 1654 proptree = Proptree(self.db, self, '', self.getprops(), retr=retr) |
| 1640 for exact, spec in enumerate((filterspec, exact_match_spec)): | 1655 for exact, spec in enumerate((filterspec, exact_match_spec)): |
| 1641 for key, v in spec.items(): | 1656 for key, v in spec.items(): |
| 1642 keys = key.split('.') | 1657 keys = key.split('.') |
| 1643 p = proptree | 1658 p = proptree |
| 1710 def _sortattr(self, sort=[], group=[]): | 1725 def _sortattr(self, sort=[], group=[]): |
| 1711 """Build a single list of sort attributes in the correct order | 1726 """Build a single list of sort attributes in the correct order |
| 1712 with sanity checks (no duplicate properties) included. Always | 1727 with sanity checks (no duplicate properties) included. Always |
| 1713 sort last by id -- if id is not already in sortattr. | 1728 sort last by id -- if id is not already in sortattr. |
| 1714 """ | 1729 """ |
| 1730 if sort is None: | |
| 1731 sort = [(None, None)] | |
| 1732 if group is None: | |
| 1733 group = [(None, None)] | |
| 1734 | |
| 1715 seen = {} | 1735 seen = {} |
| 1716 sortattr = [] | 1736 sortattr = [] |
| 1717 for srt in group, sort: | 1737 for srt in group, sort: |
| 1718 if not isinstance(srt, list): | 1738 if not isinstance(srt, list): |
| 1719 srt = [srt] | 1739 srt = [srt] |
| 1758 respectively. These can be used when displaying a number of | 1778 respectively. These can be used when displaying a number of |
| 1759 items in a pagination application or similar. A common use-case | 1779 items in a pagination application or similar. A common use-case |
| 1760 is returning the first item of a sorted search by specifying | 1780 is returning the first item of a sorted search by specifying |
| 1761 limit=1 (i.e. the maximum or minimum depending on sort order). | 1781 limit=1 (i.e. the maximum or minimum depending on sort order). |
| 1762 | 1782 |
| 1763 The filter must match all properties specificed. If the property | 1783 The filter must match all properties specified. If the property |
| 1764 value to match is a list: | 1784 value to match is a list: |
| 1765 | 1785 |
| 1766 1. String properties must match all elements in the list, and | 1786 1. String properties must match all elements in the list, and |
| 1767 2. Other properties must match any of the elements in the list. | 1787 2. Other properties must match any of the elements in the list. |
| 1768 | 1788 |
| 1838 If the "protected" flag is true, we include protected properties - | 1858 If the "protected" flag is true, we include protected properties - |
| 1839 those which may not be modified. | 1859 those which may not be modified. |
| 1840 """ | 1860 """ |
| 1841 raise NotImplementedError | 1861 raise NotImplementedError |
| 1842 | 1862 |
| 1843 def get_required_props(self, propnames=[]): | 1863 def get_required_props(self, propnames=None): |
| 1844 """Return a dict of property names mapping to property objects. | 1864 """Return a dict of property names mapping to property objects. |
| 1845 All properties that have the "required" flag set will be | 1865 All properties that have the "required" flag set will be |
| 1846 returned in addition to all properties in the propnames | 1866 returned in addition to all properties in the propnames |
| 1847 parameter. | 1867 parameter. |
| 1848 """ | 1868 """ |
| 1869 if propnames is None: | |
| 1870 propnames = [] | |
| 1849 props = self.getprops(protected=False) | 1871 props = self.getprops(protected=False) |
| 1850 pdict = dict([(p, props[p]) for p in propnames]) | 1872 pdict = dict([(p, props[p]) for p in propnames]) |
| 1851 pdict.update([(k, v) for k, v in props.items() if v.required]) | 1873 pdict.update([(k, v) for k, v in props.items() if v.required]) |
| 1852 return pdict | 1874 return pdict |
| 1853 | 1875 |
| 1920 each id, this way the memory footprint is a lot smaller than the | 1942 each id, this way the memory footprint is a lot smaller than the |
| 1921 initial implementation which stored everything in a big hash by | 1943 initial implementation which stored everything in a big hash by |
| 1922 id and then proceeded to import journals for each id.""" | 1944 id and then proceeded to import journals for each id.""" |
| 1923 properties = self.getprops() | 1945 properties = self.getprops() |
| 1924 a = [] | 1946 a = [] |
| 1925 for l in entries: | 1947 for entry in entries: |
| 1926 # first element in sorted list is the (numeric) id | 1948 # first element in sorted list is the (numeric) id |
| 1927 # in python2.4 and up we would use sorted with a key... | 1949 # in python2.4 and up we would use sorted with a key... |
| 1928 a.append((int(l[0].strip("'")), l)) | 1950 a.append((int(entry[0].strip("'")), entry)) |
| 1929 a.sort() | 1951 a.sort() |
| 1930 | 1952 |
| 1931 last = 0 | 1953 last = 0 |
| 1932 r = [] | 1954 r = [] |
| 1933 for n, l in a: | 1955 for n, l in a: |
| 1992 class HyperdbValueError(ValueError): | 2014 class HyperdbValueError(ValueError): |
| 1993 """ Error converting a raw value into a Hyperdb value """ | 2015 """ Error converting a raw value into a Hyperdb value """ |
| 1994 pass | 2016 pass |
| 1995 | 2017 |
| 1996 | 2018 |
| 1997 def convertLinkValue(db, propname, prop, value, idre=re.compile(r'^\d+$')): | 2019 id_regex = re.compile(r'^\d+$') |
| 2020 | |
| 2021 | |
| 2022 def convertLinkValue(db, propname, prop, value, idre=id_regex): | |
| 1998 """ Convert the link value (may be id or key value) to an id value. """ | 2023 """ Convert the link value (may be id or key value) to an id value. """ |
| 1999 linkcl = db.classes[prop.classname] | 2024 linkcl = db.classes[prop.classname] |
| 2000 if not idre or not idre.match(value): | 2025 if not idre or not idre.match(value): |
| 2001 if linkcl.getkey(): | 2026 if linkcl.getkey(): |
| 2002 try: | 2027 try: |
| 2127 | 2152 |
| 2128 def keys(self, protected=1): | 2153 def keys(self, protected=1): |
| 2129 return list(self.cl.getprops(protected=protected).keys()) | 2154 return list(self.cl.getprops(protected=protected).keys()) |
| 2130 | 2155 |
| 2131 def values(self, protected=1): | 2156 def values(self, protected=1): |
| 2132 l = [] | 2157 value_list = [] |
| 2133 for name in self.cl.getprops(protected=protected).keys(): | 2158 for name in self.cl.getprops(protected=protected).keys(): |
| 2134 l.append(self.cl.get(self.nodeid, name)) | 2159 value_list.append(self.cl.get(self.nodeid, name)) |
| 2135 return l | 2160 return value_list |
| 2136 | 2161 |
| 2137 def items(self, protected=1): | 2162 def items(self, protected=1): |
| 2138 l = [] | 2163 item_list = [] |
| 2139 for name in self.cl.getprops(protected=protected).keys(): | 2164 for name in self.cl.getprops(protected=protected).keys(): |
| 2140 l.append((name, self.cl.get(self.nodeid, name))) | 2165 item_list.append((name, self.cl.get(self.nodeid, name))) |
| 2141 return l | 2166 return item_list |
| 2142 | 2167 |
| 2143 def has_key(self, name): | 2168 def has_key(self, name): |
| 2144 return name in self.cl.getprops() | 2169 return name in self.cl.getprops() |
| 2145 | 2170 |
| 2146 def get(self, name, default=None): | 2171 def get(self, name, default=None): |
| 2187 """ | 2212 """ |
| 2188 cl = Class(db, name, name=String(), order=String()) | 2213 cl = Class(db, name, name=String(), order=String()) |
| 2189 for i in range(len(options)): | 2214 for i in range(len(options)): |
| 2190 cl.create(name=options[i], order=i) | 2215 cl.create(name=options[i], order=i) |
| 2191 return Link(name) | 2216 return Link(name) |
| 2192 |
