X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=roundup%2Fhyperdb.py;h=365921577741ca7efe2442a285e47d4744cddbb6;hb=dbc35d39450d413b1eb0aa2f9d4580dd0e34b2ac;hp=856f2d4e040a1ad84fe6ae47babef5843d0c790e;hpb=6314ef18bd925ad0d16b7e47439cf8d384eb8be4;p=roundup.git diff --git a/roundup/hyperdb.py b/roundup/hyperdb.py index 856f2d4..3659215 100644 --- a/roundup/hyperdb.py +++ b/roundup/hyperdb.py @@ -22,7 +22,8 @@ __docformat__ = 'restructuredtext' # standard python modules import os, re, shutil, weakref -from sets import Set +# Python 2.3 ... 2.6 compatibility: +from roundup.anypy.sets_ import set # roundup modules import date, password @@ -71,24 +72,12 @@ class Password(_Type): def from_raw(self, value, **kw): if not value: return None - m = password.Password.pwre.match(value) - if m: - # password is being given to us encrypted - p = password.Password() - p.scheme = m.group(1) - if p.scheme not in 'SHA crypt plaintext'.split(): - raise HyperdbValueError, \ - ('property %s: unknown encryption scheme %r') %\ - (kw['propname'], p.scheme) - p.password = m.group(2) - value = p - else: - try: - value = password.Password(value) - except password.PasswordValueError, message: - raise HyperdbValueError, \ - _('property %s: %s')%(kw['propname'], message) - return value + try: + return password.Password(encrypted=value, strict=True) + except password.PasswordValueError, message: + raise HyperdbValueError, \ + _('property %s: %s')%(kw['propname'], message) + def sort_repr (self, cls, val, name): if not val: return val @@ -193,7 +182,7 @@ class Multilink(_Pointer): # definitely in the new list (in case of e.g. # =A,+B, which should replace the old # list with A,B) - set = 1 + do_set = 1 newvalue = [] for item in value: item = item.strip() @@ -206,10 +195,10 @@ class Multilink(_Pointer): if item.startswith('-'): remove = 1 item = item[1:] - set = 0 + do_set = 0 elif item.startswith('+'): item = item[1:] - set = 0 + do_set = 0 # look up the value itemid = convertLinkValue(db, propname, self, item) @@ -228,7 +217,7 @@ class Multilink(_Pointer): # that's it, set the new Multilink property value, # or overwrite it completely - if set: + if do_set: value = newvalue else: value = curvalue @@ -283,18 +272,17 @@ class Proptree(object): """ Simple tree data structure for optimizing searching of properties. Each node in the tree represents a roundup Class Property that has to be navigated for finding the given search - or sort properties. The sort_type attribute is used for - distinguishing nodes in the tree used for sorting or searching: If - it is 0 for a node, that node is not used for sorting. If it is 1, - it is used for both, sorting and searching. If it is 2 it is used - for sorting only. + or sort properties. The need_for attribute is used for + distinguishing nodes in the tree used for sorting, searching or + retrieval: The attribute is a dictionary containing one or several + of the values 'sort', 'search', 'retrieve'. The Proptree is also used for transitively searching attributes for backends that do not support transitive search (e.g. anydbm). The _val attribute with set_val is used for this. """ - def __init__(self, db, cls, name, props, parent = None): + def __init__(self, db, cls, name, props, parent=None, retr=False): self.db = db self.name = name self.props = props @@ -307,7 +295,7 @@ class Proptree(object): self.children = [] self.sortattr = [] self.propdict = {} - self.sort_type = 0 + self.need_for = {'search' : True} self.sort_direction = None self.sort_ids = None self.sort_ids_needed = False @@ -316,6 +304,7 @@ class Proptree(object): self.tree_sort_done = False self.propclass = None self.orderby = [] + self.sql_idx = None # index of retrieved column in sql result if parent: self.root = parent.root self.depth = parent.depth + 1 @@ -323,7 +312,7 @@ class Proptree(object): self.root = self self.seqno = 1 self.depth = 0 - self.sort_type = 1 + self.need_for['sort'] = True self.id = self.root.seqno self.root.seqno += 1 if self.cls: @@ -331,15 +320,18 @@ class Proptree(object): self.uniqname = '%s%s' % (self.cls.classname, self.id) if not self.parent: self.uniqname = self.cls.classname + if retr: + self.append_retr_props() - def append(self, name, sort_type = 0): + def append(self, name, need_for='search', retr=False): """Append a property to self.children. Will create a new propclass for the child. """ if name in self.propdict: pt = self.propdict[name] - if sort_type and not pt.sort_type: - pt.sort_type = 1 + pt.need_for[need_for] = True + if retr and isinstance(pt.propclass, Link): + pt.append_retr_props() return pt propclass = self.props[name] cls = None @@ -348,15 +340,24 @@ class Proptree(object): cls = self.db.getclass(propclass.classname) props = cls.getprops() child = self.__class__(self.db, cls, name, props, parent = self) - child.sort_type = sort_type + child.need_for = {need_for : True} child.propclass = propclass self.children.append(child) self.propdict[name] = child + if retr and isinstance(child.propclass, Link): + child.append_retr_props() return child + def append_retr_props(self): + """Append properties for retrieval.""" + for name, prop in self.cls.getprops(protected=1).iteritems(): + if isinstance(prop, Multilink): + continue + self.append(name, need_for='retrieve') + def compute_sort_done(self, mlseen=False): """ Recursively check if attribute is needed for sorting - (self.sort_type > 0) or all children have tree_sort_done set and + ('sort' in self.need_for) or all children have tree_sort_done set and sort_ids_needed unset: set self.tree_sort_done if one of the conditions holds. Also remove sort_ids_needed recursively once having seen a Multilink. @@ -370,7 +371,7 @@ class Proptree(object): p.compute_sort_done(mlseen) if not p.tree_sort_done: self.tree_sort_done = False - if not self.sort_type: + if 'sort' not in self.need_for: self.tree_sort_done = True if mlseen: self.tree_sort_done = False @@ -388,7 +389,7 @@ class Proptree(object): """ filterspec = {} for p in self.children: - if p.sort_type < 2: + if 'search' in p.need_for: if p.children: p.search(sort = False) filterspec[p.name] = p.val @@ -412,7 +413,7 @@ class Proptree(object): too. """ return [p for p in self.children - if p.sort_type > 0 and (intermediate or p.sort_direction)] + if 'sort' in p.need_for and (intermediate or p.sort_direction)] def __iter__(self): """ Yield nodes in depth-first order -- visited nodes first """ @@ -487,7 +488,7 @@ class Proptree(object): v = self._val if not isinstance(self._val, type([])): v = [self._val] - vals = Set(v) + vals = set(v) vals.intersection_update(val) self._val = [v for v in vals] else: @@ -533,7 +534,6 @@ class Proptree(object): curdir = sa.sort_direction idx += 1 sortattr.append (val) - #print >> sys.stderr, "\nsortattr", sortattr sortattr = zip (*sortattr) for dir, i in reversed(zip(directions, dir_idx)): rev = dir == '-' @@ -759,6 +759,16 @@ All methods except __repr__ must be implemented by a concrete backend Database. """ +def iter_roles(roles): + ''' handle the text processing of turning the roles list + into something python can use more easily + ''' + if not roles or not roles.strip(): + raise StopIteration, "Empty roles given" + for role in [x.lower().strip() for x in roles.split(',')]: + yield role + + # # The base Class class # @@ -1044,27 +1054,40 @@ class Class: """ raise NotImplementedError - def _proptree(self, filterspec, sortattr=[]): + def _proptree(self, filterspec, sortattr=[], retr=False): """Build a tree of all transitive properties in the given filterspec. + If we retrieve (retr is True) linked items we don't follow + across multilinks. We also don't follow if the searched value + can contain NULL values. """ - proptree = Proptree(self.db, self, '', self.getprops()) + proptree = Proptree(self.db, self, '', self.getprops(), retr=retr) for key, v in filterspec.iteritems(): keys = key.split('.') p = proptree + mlseen = False for k in keys: - p = p.append(k) + if isinstance (p.propclass, Multilink): + mlseen = True + isnull = v == '-1' or v is None + nullin = isinstance(v, type([])) and ('-1' in v or None in v) + r = retr and not mlseen and not isnull and not nullin + p = p.append(k, retr=r) p.val = v multilinks = {} for s in sortattr: keys = s[1].split('.') p = proptree + mlseen = False for k in keys: - p = p.append(k, sort_type = 2) + if isinstance (p.propclass, Multilink): + mlseen = True + r = retr and not mlseen + p = p.append(k, need_for='sort', retr=r) if isinstance (p.propclass, Multilink): multilinks[p] = True if p.cls: - p = p.append(p.cls.orderprop(), sort_type = 2) + p = p.append(p.cls.orderprop(), need_for='sort') if p.sort_direction: # if an orderprop is also specified explicitly continue p.sort_direction = s[0] @@ -1090,7 +1113,7 @@ class Class: for k in propname_path.split('.'): try: prop = props[k] - except KeyError, TypeError: + except (KeyError, TypeError): return default cl = getattr(prop, 'classname', None) props = None @@ -1147,7 +1170,7 @@ class Class: This implements a non-optimized version of Transitive search using _filter implemented in a backend class. A more efficient version can be implemented in the individual backends -- e.g., - an SQL backen will want to create a single SQL statement and + an SQL backend will want to create a single SQL statement and override the filter method instead of implementing _filter. """ sortattr = self._sortattr(sort = sort, group = group) @@ -1155,6 +1178,13 @@ class Class: proptree.search(search_matches) return proptree.sort() + # non-optimized filter_iter, a backend may chose to implement a + # better version that provides a real iterator that pre-fills the + # cache for each id returned. Note that the filter_iter doesn't + # promise to correctly sort by multilink (which isn't sane to do + # anyway). + filter_iter = filter + def count(self): """Get the number of nodes in this class. @@ -1227,6 +1257,83 @@ class Class: propnames.sort() return propnames + def import_journals(self, entries): + """Import a class's journal. + + Uses setjournal() to set the journal for each item. + Strategy for import: Sort first by id, then import journals for + each id, this way the memory footprint is a lot smaller than the + initial implementation which stored everything in a big hash by + id and then proceeded to import journals for each id.""" + properties = self.getprops() + a = [] + for l in entries: + # first element in sorted list is the (numeric) id + # in python2.4 and up we would use sorted with a key... + a.append ((int (l [0].strip ("'")), l)) + a.sort () + + + last = 0 + r = [] + for n, l in a: + nodeid, jdate, user, action, params = map(eval, l) + assert (str(n) == nodeid) + if n != last: + if r: + self.db.setjournal(self.classname, str(last), r) + last = n + r = [] + + if action == 'set': + for propname, value in params.iteritems(): + prop = properties[propname] + if value is None: + pass + elif isinstance(prop, Date): + value = date.Date(value) + elif isinstance(prop, Interval): + value = date.Interval(value) + elif isinstance(prop, Password): + value = password.Password(encrypted=value) + params[propname] = value + elif action == 'create' and params: + # old tracker with data stored in the create! + params = {} + r.append((nodeid, date.Date(jdate), user, action, params)) + if r: + self.db.setjournal(self.classname, nodeid, r) + + # + # convenience methods + # + def get_roles(self, nodeid): + """Return iterator for all roles for this nodeid. + + Yields string-processed roles. + This method can be overridden to provide a hook where we can + insert other permission models (e.g. get roles from database) + In standard schemas only a user has a roles property but + this may be different in customized schemas. + Note that this is the *central place* where role + processing happens! + """ + node = self.db.getnode(self.classname, nodeid) + return iter_roles(node['roles']) + + def has_role(self, nodeid, *roles): + '''See if this node has any roles that appear in roles. + + For convenience reasons we take a list. + In standard schemas only a user has a roles property but + this may be different in customized schemas. + ''' + roles = dict.fromkeys ([r.strip().lower() for r in roles]) + for role in self.get_roles(nodeid): + if role in roles: + return True + return False + class HyperdbValueError(ValueError): """ Error converting a raw value into a Hyperdb value """