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

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