Mercurial > p > roundup > code
comparison roundup/hyperdb.py @ 6006:cf800f1febe6
Flake8 fixes; use isinstance rather than type equality.
Also remove assignment for unused variable. Replace x with _x in for
loop variables that are not used but required to unpack tuple.
| author | John Rouillard <rouilj@ieee.org> |
|---|---|
| date | Sat, 28 Dec 2019 14:41:55 -0500 |
| parents | acc4a128ab9b |
| children | 8497bf3f23a1 |
comparison
equal
deleted
inserted
replaced
| 6005:292c9dfd06bd | 6006:cf800f1febe6 |
|---|---|
| 33 from roundup.anypy.cmp_ import NoneAndDictComparable | 33 from roundup.anypy.cmp_ import NoneAndDictComparable |
| 34 from roundup.anypy.strings import eval_import | 34 from roundup.anypy.strings import eval_import |
| 35 | 35 |
| 36 logger = logging.getLogger('roundup.hyperdb') | 36 logger = logging.getLogger('roundup.hyperdb') |
| 37 | 37 |
| 38 | |
| 38 # | 39 # |
| 39 # Types | 40 # Types |
| 40 # | 41 # |
| 41 class _Type(object): | 42 class _Type(object): |
| 42 """A roundup property type.""" | 43 """A roundup property type.""" |
| 43 def __init__(self, required=False, default_value = None, quiet=False): | 44 def __init__(self, required=False, default_value=None, quiet=False): |
| 44 self.required = required | 45 self.required = required |
| 45 self.__default_value = default_value | 46 self.__default_value = default_value |
| 46 self.quiet = quiet | 47 self.quiet = quiet |
| 48 | |
| 47 def __repr__(self): | 49 def __repr__(self): |
| 48 ' more useful for dumps ' | 50 ' more useful for dumps ' |
| 49 return '<%s.%s>'%(self.__class__.__module__, self.__class__.__name__) | 51 return '<%s.%s>' % (self.__class__.__module__, self.__class__.__name__) |
| 52 | |
| 50 def get_default_value(self): | 53 def get_default_value(self): |
| 51 """The default value when creating a new instance of this property.""" | 54 """The default value when creating a new instance of this property.""" |
| 52 return self.__default_value | 55 return self.__default_value |
| 53 def sort_repr (self, cls, val, name): | 56 |
| 57 def sort_repr(self, cls, val, name): | |
| 54 """Representation used for sorting. This should be a python | 58 """Representation used for sorting. This should be a python |
| 55 built-in type, otherwise sorting will take ages. Note that | 59 built-in type, otherwise sorting will take ages. Note that |
| 56 individual backends may chose to use something different for | 60 individual backends may chose to use something different for |
| 57 sorting as long as the outcome is the same. | 61 sorting as long as the outcome is the same. |
| 58 """ | 62 """ |
| 59 return val | 63 return val |
| 60 | 64 |
| 65 | |
| 61 class String(_Type): | 66 class String(_Type): |
| 62 """An object designating a String property.""" | 67 """An object designating a String property.""" |
| 63 def __init__(self, indexme='no', required=False, default_value = "", quiet=False): | 68 def __init__(self, indexme='no', required=False, default_value="", |
| 69 quiet=False): | |
| 64 super(String, self).__init__(required, default_value, quiet) | 70 super(String, self).__init__(required, default_value, quiet) |
| 65 self.indexme = indexme == 'yes' | 71 self.indexme = indexme == 'yes' |
| 72 | |
| 66 def from_raw(self, value, propname='', **kw): | 73 def from_raw(self, value, propname='', **kw): |
| 67 """fix the CRLF/CR -> LF stuff""" | 74 """fix the CRLF/CR -> LF stuff""" |
| 68 if propname == 'content': | 75 if propname == 'content': |
| 69 # Why oh why wasn't the FileClass content property a File | 76 # Why oh why wasn't the FileClass content property a File |
| 70 # type from the beginning? | 77 # type from the beginning? |
| 71 return value | 78 return value |
| 72 return fixNewlines(value) | 79 return fixNewlines(value) |
| 73 def sort_repr (self, cls, val, name): | 80 |
| 81 def sort_repr(self, cls, val, name): | |
| 74 if not val: | 82 if not val: |
| 75 return val | 83 return val |
| 76 if name == 'id': | 84 if name == 'id': |
| 77 return int(val) | 85 return int(val) |
| 78 return val.lower() | 86 return val.lower() |
| 79 | 87 |
| 88 | |
| 80 class Password(_Type): | 89 class Password(_Type): |
| 81 """An object designating a Password property.""" | 90 """An object designating a Password property.""" |
| 82 def __init__(self, scheme=None, required=False, default_value = None, quiet=False): | 91 def __init__(self, scheme=None, required=False, default_value=None, |
| 92 quiet=False): | |
| 83 super(Password, self).__init__(required, default_value, quiet) | 93 super(Password, self).__init__(required, default_value, quiet) |
| 84 self.scheme = scheme | 94 self.scheme = scheme |
| 85 | 95 |
| 86 def from_raw(self, value, **kw): | 96 def from_raw(self, value, **kw): |
| 87 if not value: | 97 if not value: |
| 88 return None | 98 return None |
| 89 try: | 99 try: |
| 90 return password.Password(encrypted=value, scheme=self.scheme, strict=True) | 100 return password.Password(encrypted=value, scheme=self.scheme, |
| 101 strict=True) | |
| 91 except password.PasswordValueError as message: | 102 except password.PasswordValueError as message: |
| 92 raise HyperdbValueError(_('property %s: %s')%(kw['propname'], message)) | 103 raise HyperdbValueError(_('property %s: %s') % |
| 93 | 104 (kw['propname'], message)) |
| 94 def sort_repr (self, cls, val, name): | 105 |
| 106 def sort_repr(self, cls, val, name): | |
| 95 if not val: | 107 if not val: |
| 96 return val | 108 return val |
| 97 return str(val) | 109 return str(val) |
| 98 | 110 |
| 111 | |
| 99 class Date(_Type): | 112 class Date(_Type): |
| 100 """An object designating a Date property.""" | 113 """An object designating a Date property.""" |
| 101 def __init__(self, offset=None, required=False, default_value = None, quiet=False): | 114 def __init__(self, offset=None, required=False, default_value=None, |
| 102 super(Date, self).__init__(required = required, | 115 quiet=False): |
| 103 default_value = default_value, | 116 super(Date, self).__init__(required=required, |
| 117 default_value=default_value, | |
| 104 quiet=quiet) | 118 quiet=quiet) |
| 105 self._offset = offset | 119 self._offset = offset |
| 120 | |
| 106 def offset(self, db): | 121 def offset(self, db): |
| 107 if self._offset is not None: | 122 if self._offset is not None: |
| 108 return self._offset | 123 return self._offset |
| 109 return db.getUserTimezone() | 124 return db.getUserTimezone() |
| 125 | |
| 110 def from_raw(self, value, db, **kw): | 126 def from_raw(self, value, db, **kw): |
| 111 try: | 127 try: |
| 112 value = date.Date(value, self.offset(db)) | 128 value = date.Date(value, self.offset(db)) |
| 113 except ValueError as message: | 129 except ValueError as message: |
| 114 raise HyperdbValueError(_('property %s: %r is an invalid '\ | 130 raise HyperdbValueError(_('property %s: %r is an invalid ' |
| 115 'date (%s)')%(kw['propname'], value, message)) | 131 'date (%s)') % (kw['propname'], |
| 132 value, message)) | |
| 116 return value | 133 return value |
| 134 | |
| 117 def range_from_raw(self, value, db): | 135 def range_from_raw(self, value, db): |
| 118 """return Range value from given raw value with offset correction""" | 136 """return Range value from given raw value with offset correction""" |
| 119 return date.Range(value, date.Date, offset=self.offset(db)) | 137 return date.Range(value, date.Date, offset=self.offset(db)) |
| 120 def sort_repr (self, cls, val, name): | 138 |
| 139 def sort_repr(self, cls, val, name): | |
| 121 if not val: | 140 if not val: |
| 122 return val | 141 return val |
| 123 return str(val) | 142 return str(val) |
| 143 | |
| 124 | 144 |
| 125 class Interval(_Type): | 145 class Interval(_Type): |
| 126 """An object designating an Interval property.""" | 146 """An object designating an Interval property.""" |
| 127 def from_raw(self, value, **kw): | 147 def from_raw(self, value, **kw): |
| 128 try: | 148 try: |
| 129 value = date.Interval(value) | 149 value = date.Interval(value) |
| 130 except ValueError as message: | 150 except ValueError as message: |
| 131 raise HyperdbValueError(_('property %s: %r is an invalid '\ | 151 raise HyperdbValueError(_('property %s: %r is an invalid ' |
| 132 'date interval (%s)')%(kw['propname'], value, message)) | 152 'date interval (%s)') % |
| 153 (kw['propname'], value, message)) | |
| 133 return value | 154 return value |
| 134 def sort_repr (self, cls, val, name): | 155 |
| 156 def sort_repr(self, cls, val, name): | |
| 135 if not val: | 157 if not val: |
| 136 return val | 158 return val |
| 137 return val.as_seconds() | 159 return val.as_seconds() |
| 160 | |
| 138 | 161 |
| 139 class _Pointer(_Type): | 162 class _Pointer(_Type): |
| 140 """An object designating a Pointer property that links or multilinks | 163 """An object designating a Pointer property that links or multilinks |
| 141 to a node in a specified class.""" | 164 to a node in a specified class.""" |
| 142 def __init__(self, classname, do_journal='yes', try_id_parsing='yes', | 165 def __init__(self, classname, do_journal='yes', try_id_parsing='yes', |
| 143 required=False, default_value=None, | 166 required=False, default_value=None, |
| 144 msg_header_property = None, quiet=False): | 167 msg_header_property=None, quiet=False): |
| 145 """ Default is to journal link and unlink events. | 168 """ Default is to journal link and unlink events. |
| 146 When try_id_parsing is false, we don't allow IDs in input | 169 When try_id_parsing is false, we don't allow IDs in input |
| 147 fields (the key of the Link or Multilink property must be | 170 fields (the key of the Link or Multilink property must be |
| 148 given instead). This is useful when the name of a property | 171 given instead). This is useful when the name of a property |
| 149 can be numeric. It will only work if the linked item has a | 172 can be numeric. It will only work if the linked item has a |
| 161 'X-Roundup-issue-assigned_to: joe_user'. | 184 'X-Roundup-issue-assigned_to: joe_user'. |
| 162 """ | 185 """ |
| 163 super(_Pointer, self).__init__(required, default_value, quiet) | 186 super(_Pointer, self).__init__(required, default_value, quiet) |
| 164 self.classname = classname | 187 self.classname = classname |
| 165 self.do_journal = do_journal == 'yes' | 188 self.do_journal = do_journal == 'yes' |
| 166 self.try_id_parsing = try_id_parsing == 'yes' | 189 self.try_id_parsing = try_id_parsing == 'yes' |
| 167 self.msg_header_property = msg_header_property | 190 self.msg_header_property = msg_header_property |
| 191 | |
| 168 def __repr__(self): | 192 def __repr__(self): |
| 169 """more useful for dumps. But beware: This is also used in schema | 193 """more useful for dumps. But beware: This is also used in schema |
| 170 storage in SQL backends! | 194 storage in SQL backends! |
| 171 """ | 195 """ |
| 172 return '<%s.%s to "%s">'%(self.__class__.__module__, | 196 return '<%s.%s to "%s">' % (self.__class__.__module__, |
| 173 self.__class__.__name__, self.classname) | 197 self.__class__.__name__, self.classname) |
| 198 | |
| 174 | 199 |
| 175 class Link(_Pointer): | 200 class Link(_Pointer): |
| 176 """An object designating a Link property that links to a | 201 """An object designating a Link property that links to a |
| 177 node in a specified class.""" | 202 node in a specified class.""" |
| 178 def from_raw(self, value, db, propname, **kw): | 203 def from_raw(self, value, db, propname, **kw): |
| 182 if self.try_id_parsing: | 207 if self.try_id_parsing: |
| 183 value = convertLinkValue(db, propname, self, value) | 208 value = convertLinkValue(db, propname, self, value) |
| 184 else: | 209 else: |
| 185 value = convertLinkValue(db, propname, self, value, None) | 210 value = convertLinkValue(db, propname, self, value, None) |
| 186 return value | 211 return value |
| 187 def sort_repr (self, cls, val, name): | 212 |
| 213 def sort_repr(self, cls, val, name): | |
| 188 if not val: | 214 if not val: |
| 189 return val | 215 return val |
| 190 op = cls.labelprop() | 216 op = cls.labelprop() |
| 191 if op == 'id': | 217 if op == 'id': |
| 192 return int(cls.get(val, op)) | 218 return int(cls.get(val, op)) |
| 193 return cls.get(val, op) | 219 return cls.get(val, op) |
| 194 | 220 |
| 221 | |
| 195 class Multilink(_Pointer): | 222 class Multilink(_Pointer): |
| 196 """An object designating a Multilink property that links | 223 """An object designating a Multilink property that links |
| 197 to nodes in a specified class. | 224 to nodes in a specified class. |
| 198 | 225 |
| 199 "classname" indicates the class to link to | 226 "classname" indicates the class to link to |
| 200 | 227 |
| 201 "do_journal" indicates whether the linked-to nodes should have | 228 "do_journal" indicates whether the linked-to nodes should have |
| 202 'link' and 'unlink' events placed in their journal | 229 'link' and 'unlink' events placed in their journal |
| 203 """ | 230 """ |
| 204 | 231 |
| 205 def __init__(self, classname, do_journal = 'yes', required = False, quiet=False, try_id_parsing='yes'): | 232 def __init__(self, classname, do_journal='yes', required=False, |
| 233 quiet=False, try_id_parsing='yes'): | |
| 206 | 234 |
| 207 super(Multilink, self).__init__(classname, | 235 super(Multilink, self).__init__(classname, |
| 208 do_journal, | 236 do_journal, |
| 209 required = required, | 237 required=required, |
| 210 default_value = [], quiet=quiet, | 238 default_value=[], quiet=quiet, |
| 211 try_id_parsing=try_id_parsing) | 239 try_id_parsing=try_id_parsing) |
| 212 | 240 |
| 213 def from_raw(self, value, db, klass, propname, itemid, **kw): | 241 def from_raw(self, value, db, klass, propname, itemid, **kw): |
| 214 if not value: | 242 if not value: |
| 215 return [] | 243 return [] |
| 280 value = [int(x) for x in value] | 308 value = [int(x) for x in value] |
| 281 value.sort() | 309 value.sort() |
| 282 value = [str(x) for x in value] | 310 value = [str(x) for x in value] |
| 283 return value | 311 return value |
| 284 | 312 |
| 285 def sort_repr (self, cls, val, name): | 313 def sort_repr(self, cls, val, name): |
| 286 if not val: | 314 if not val: |
| 287 return val | 315 return val |
| 288 op = cls.labelprop() | 316 op = cls.labelprop() |
| 289 if op == 'id': | 317 if op == 'id': |
| 290 return [int(cls.get(v, op)) for v in val] | 318 return [int(cls.get(v, op)) for v in val] |
| 291 return [cls.get(v, op) for v in val] | 319 return [cls.get(v, op) for v in val] |
| 320 | |
| 292 | 321 |
| 293 class Boolean(_Type): | 322 class Boolean(_Type): |
| 294 """An object designating a boolean property""" | 323 """An object designating a boolean property""" |
| 295 def from_raw(self, value, **kw): | 324 def from_raw(self, value, **kw): |
| 296 value = value.strip() | 325 value = value.strip() |
| 297 # checked is a common HTML checkbox value | 326 # checked is a common HTML checkbox value |
| 298 value = value.lower() in ('checked', 'yes', 'true', 'on', '1') | 327 value = value.lower() in ('checked', 'yes', 'true', 'on', '1') |
| 299 return value | 328 return value |
| 300 | 329 |
| 330 | |
| 301 class Number(_Type): | 331 class Number(_Type): |
| 302 """An object designating a numeric property""" | 332 """An object designating a numeric property""" |
| 303 def __init__(self, use_double = False, **kw): | 333 def __init__(self, use_double=False, **kw): |
| 304 """ The value use_double tells the database backend to use a | 334 """ The value use_double tells the database backend to use a |
| 305 floating-point format with more precision than the default. | 335 floating-point format with more precision than the default. |
| 306 Usually implemented by type 'double precision' in the sql | 336 Usually implemented by type 'double precision' in the sql |
| 307 backend. The default is to use single-precision float (aka | 337 backend. The default is to use single-precision float (aka |
| 308 'real') in the db. Note that sqlite already uses 8-byte for | 338 'real') in the db. Note that sqlite already uses 8-byte for |
| 309 floating point numbers. | 339 floating point numbers. |
| 310 """ | 340 """ |
| 311 self.use_double = use_double | 341 self.use_double = use_double |
| 312 super(Number, self).__init__(**kw) | 342 super(Number, self).__init__(**kw) |
| 343 | |
| 313 def from_raw(self, value, **kw): | 344 def from_raw(self, value, **kw): |
| 314 value = value.strip() | 345 value = value.strip() |
| 315 try: | 346 try: |
| 316 value = float(value) | 347 value = float(value) |
| 317 except ValueError: | 348 except ValueError: |
| 318 raise HyperdbValueError(_('property %s: %r is not a number')%( | 349 raise HyperdbValueError(_('property %s: %r is not a number') % |
| 319 kw['propname'], value)) | 350 (kw['propname'], value)) |
| 320 return value | 351 return value |
| 352 | |
| 321 | 353 |
| 322 class Integer(_Type): | 354 class Integer(_Type): |
| 323 """An object designating an integer property""" | 355 """An object designating an integer property""" |
| 324 def from_raw(self, value, **kw): | 356 def from_raw(self, value, **kw): |
| 325 value = value.strip() | 357 value = value.strip() |
| 326 try: | 358 try: |
| 327 value = int(value) | 359 value = int(value) |
| 328 except ValueError: | 360 except ValueError: |
| 329 raise HyperdbValueError(_('property %s: %r is not an integer')%( | 361 raise HyperdbValueError(_('property %s: %r is not an integer') % |
| 330 kw['propname'], value)) | 362 (kw['propname'], value)) |
| 331 return value | 363 return value |
| 364 | |
| 365 | |
| 332 # | 366 # |
| 333 # Support for splitting designators | 367 # Support for splitting designators |
| 334 # | 368 # |
| 335 class DesignatorError(ValueError): | 369 class DesignatorError(ValueError): |
| 336 pass | 370 pass |
| 371 | |
| 372 | |
| 337 def splitDesignator(designator, dre=re.compile(r'([^\d]+)(\d+)')): | 373 def splitDesignator(designator, dre=re.compile(r'([^\d]+)(\d+)')): |
| 338 """ Take a foo123 and return ('foo', 123) | 374 """ Take a foo123 and return ('foo', 123) |
| 339 """ | 375 """ |
| 340 m = dre.match(designator) | 376 m = dre.match(designator) |
| 341 if m is None: | 377 if m is None: |
| 342 raise DesignatorError(_('"%s" not a node designator')%designator) | 378 raise DesignatorError(_('"%s" not a node designator') % designator) |
| 343 return m.group(1), m.group(2) | 379 return m.group(1), m.group(2) |
| 380 | |
| 344 | 381 |
| 345 class Exact_Match(object): | 382 class Exact_Match(object): |
| 346 """ Used to encapsulate exact match semantics search values | 383 """ Used to encapsulate exact match semantics search values |
| 347 """ | 384 """ |
| 348 def __init__(self, value): | 385 def __init__(self, value): |
| 349 self.value = value | 386 self.value = value |
| 387 | |
| 350 | 388 |
| 351 class Proptree(object): | 389 class Proptree(object): |
| 352 """ Simple tree data structure for property lookup. Each node in | 390 """ Simple tree data structure for property lookup. Each node in |
| 353 the tree is a roundup Class Property that has to be navigated to | 391 the tree is a roundup Class Property that has to be navigated to |
| 354 find given property. The need_for attribute is used to mark nodes | 392 find given property. The need_for attribute is used to mark nodes |
| 372 self.classname = None | 410 self.classname = None |
| 373 self.uniqname = None | 411 self.uniqname = None |
| 374 self.children = [] | 412 self.children = [] |
| 375 self.sortattr = [] | 413 self.sortattr = [] |
| 376 self.propdict = {} | 414 self.propdict = {} |
| 377 self.need_for = {'search' : True} | 415 self.need_for = {'search': True} |
| 378 self.sort_direction = None | 416 self.sort_direction = None |
| 379 self.sort_ids = None | 417 self.sort_ids = None |
| 380 self.sort_ids_needed = False | 418 self.sort_ids_needed = False |
| 381 self.sort_result = None | 419 self.sort_result = None |
| 382 self.attr_sort_done = False | 420 self.attr_sort_done = False |
| 383 self.tree_sort_done = False | 421 self.tree_sort_done = False |
| 384 self.propclass = None | 422 self.propclass = None |
| 385 self.orderby = [] | 423 self.orderby = [] |
| 386 self.sql_idx = None # index of retrieved column in sql result | 424 self.sql_idx = None # index of retrieved column in sql result |
| 387 if parent: | 425 if parent: |
| 388 self.root = parent.root | 426 self.root = parent.root |
| 389 self.depth = parent.depth + 1 | 427 self.depth = parent.depth + 1 |
| 390 else: | 428 else: |
| 391 self.root = self | 429 self.root = self |
| 416 cls = None | 454 cls = None |
| 417 props = None | 455 props = None |
| 418 if isinstance(propclass, (Link, Multilink)): | 456 if isinstance(propclass, (Link, Multilink)): |
| 419 cls = self.db.getclass(propclass.classname) | 457 cls = self.db.getclass(propclass.classname) |
| 420 props = cls.getprops() | 458 props = cls.getprops() |
| 421 child = self.__class__(self.db, cls, name, props, parent = self) | 459 child = self.__class__(self.db, cls, name, props, parent=self) |
| 422 child.need_for = {need_for : True} | 460 child.need_for = {need_for: True} |
| 423 child.propclass = propclass | 461 child.propclass = propclass |
| 424 self.children.append(child) | 462 self.children.append(child) |
| 425 self.propdict[name] = child | 463 self.propdict[name] = child |
| 426 if retr and isinstance(child.propclass, Link): | 464 if retr and isinstance(child.propclass, Link): |
| 427 child.append_retr_props() | 465 child.append_retr_props() |
| 439 ('sort' in self.need_for) or all children have tree_sort_done set and | 477 ('sort' in self.need_for) or all children have tree_sort_done set and |
| 440 sort_ids_needed unset: set self.tree_sort_done if one of the conditions | 478 sort_ids_needed unset: set self.tree_sort_done if one of the conditions |
| 441 holds. Also remove sort_ids_needed recursively once having seen a | 479 holds. Also remove sort_ids_needed recursively once having seen a |
| 442 Multilink that is used for sorting. | 480 Multilink that is used for sorting. |
| 443 """ | 481 """ |
| 444 if isinstance (self.propclass, Multilink) and 'sort' in self.need_for: | 482 if isinstance(self.propclass, Multilink) and 'sort' in self.need_for: |
| 445 mlseen = True | 483 mlseen = True |
| 446 if mlseen: | 484 if mlseen: |
| 447 self.sort_ids_needed = False | 485 self.sort_ids_needed = False |
| 448 self.tree_sort_done = True | 486 self.tree_sort_done = True |
| 449 for p in self.children: | 487 for p in self.children: |
| 469 filterspec = {} | 507 filterspec = {} |
| 470 exact_match_spec = {} | 508 exact_match_spec = {} |
| 471 for p in self.children: | 509 for p in self.children: |
| 472 if 'search' in p.need_for: | 510 if 'search' in p.need_for: |
| 473 if p.children: | 511 if p.children: |
| 474 p.search(sort = False) | 512 p.search(sort=False) |
| 475 if isinstance(p.val, type([])): | 513 if isinstance(p.val, type([])): |
| 476 exact = [] | 514 exact = [] |
| 477 subst = [] | 515 subst = [] |
| 478 for v in p.val: | 516 for v in p.val: |
| 479 if isinstance(v, Exact_Match): | 517 if isinstance(v, Exact_Match): |
| 490 self.val = self.cls._filter(search_matches, filterspec, sort and self, | 528 self.val = self.cls._filter(search_matches, filterspec, sort and self, |
| 491 retired=retired, | 529 retired=retired, |
| 492 exact_match_spec=exact_match_spec) | 530 exact_match_spec=exact_match_spec) |
| 493 return self.val | 531 return self.val |
| 494 | 532 |
| 495 def sort (self, ids=None): | 533 def sort(self, ids=None): |
| 496 """ Sort ids by the order information stored in self. With | 534 """ Sort ids by the order information stored in self. With |
| 497 optimisations: Some order attributes may be precomputed (by the | 535 optimisations: Some order attributes may be precomputed (by the |
| 498 backend) and some properties may already be sorted. | 536 backend) and some properties may already be sorted. |
| 499 """ | 537 """ |
| 500 if ids is None: | 538 if ids is None: |
| 501 ids = self.val | 539 ids = self.val |
| 502 if self.sortattr and [s for s in self.sortattr if not s.attr_sort_done]: | 540 if self.sortattr and [s for s in self.sortattr |
| 541 if not s.attr_sort_done]: | |
| 503 return self._searchsort(ids, True, True) | 542 return self._searchsort(ids, True, True) |
| 504 return ids | 543 return ids |
| 505 | 544 |
| 506 def sortable_children(self, intermediate=False): | 545 def sortable_children(self, intermediate=False): |
| 507 """ All children needed for sorting. If intermediate is True, | 546 """ All children needed for sorting. If intermediate is True, |
| 516 for p in self.children: | 555 for p in self.children: |
| 517 yield p | 556 yield p |
| 518 for c in p: | 557 for c in p: |
| 519 yield c | 558 yield c |
| 520 | 559 |
| 521 def _get (self, ids): | 560 def _get(self, ids): |
| 522 """Lookup given ids -- possibly a list of list. We recurse until | 561 """Lookup given ids -- possibly a list of list. We recurse until |
| 523 we have a list of ids. | 562 we have a list of ids. |
| 524 """ | 563 """ |
| 525 if not ids: | 564 if not ids: |
| 526 return ids | 565 return ids |
| 527 if isinstance (ids[0], list): | 566 if isinstance(ids[0], list): |
| 528 cids = [self._get(i) for i in ids] | 567 cids = [self._get(i) for i in ids] |
| 529 else: | 568 else: |
| 530 cids = [i and self.parent.cls.get(i, self.name) for i in ids] | 569 cids = [i and self.parent.cls.get(i, self.name) for i in ids] |
| 531 if self.sortattr: | 570 if self.sortattr: |
| 532 cids = [self._searchsort(i, False, True) for i in cids] | 571 cids = [self._searchsort(i, False, True) for i in cids] |
| 551 issues are sorted by author and then by the time of the messages | 590 issues are sorted by author and then by the time of the messages |
| 552 *of this author*. Probably what the user intends in that case, | 591 *of this author*. Probably what the user intends in that case, |
| 553 so we do *not* use two sorted lists of messages, one sorted by | 592 so we do *not* use two sorted lists of messages, one sorted by |
| 554 author and one sorted by date for sorting issues. | 593 author and one sorted by date for sorting issues. |
| 555 """ | 594 """ |
| 556 for pt in self.sortable_children(intermediate = True): | 595 for pt in self.sortable_children(intermediate=True): |
| 557 # ids can be an empty list | 596 # ids can be an empty list |
| 558 if pt.tree_sort_done or not ids: | 597 if pt.tree_sort_done or not ids: |
| 559 continue | 598 continue |
| 560 if pt.sort_ids: # cached or computed by backend | 599 if pt.sort_ids: # cached or computed by backend |
| 561 cids = pt.sort_ids | 600 cids = pt.sort_ids |
| 562 else: | 601 else: |
| 563 cids = pt._get(ids) | 602 cids = pt._get(ids) |
| 564 if pt.sort_direction and not pt.sort_result: | 603 if pt.sort_direction and not pt.sort_result: |
| 565 sortrep = pt.propclass.sort_repr | 604 sortrep = pt.propclass.sort_repr |
| 568 if pt.children: | 607 if pt.children: |
| 569 pt._searchsort(cids, update, False) | 608 pt._searchsort(cids, update, False) |
| 570 if self.sortattr and dosort: | 609 if self.sortattr and dosort: |
| 571 ids = self._sort(ids) | 610 ids = self._sort(ids) |
| 572 if not update: | 611 if not update: |
| 573 for pt in self.sortable_children(intermediate = True): | 612 for pt in self.sortable_children(intermediate=True): |
| 574 pt.sort_ids = None | 613 pt.sort_ids = None |
| 575 for pt in self.sortattr: | 614 for pt in self.sortattr: |
| 576 pt.sort_result = None | 615 pt.sort_result = None |
| 577 return ids | 616 return ids |
| 578 | 617 |
| 634 for sa in self.sortattr: | 673 for sa in self.sortattr: |
| 635 if sa.attr_sort_done: | 674 if sa.attr_sort_done: |
| 636 break | 675 break |
| 637 if sortattr: | 676 if sortattr: |
| 638 assert len(sortattr[0]) == len(sa.sort_result) | 677 assert len(sortattr[0]) == len(sa.sort_result) |
| 639 sortattr.append (sa.sort_result) | 678 sortattr.append(sa.sort_result) |
| 640 if curdir != sa.sort_direction: | 679 if curdir != sa.sort_direction: |
| 641 dir_idx.append (idx) | 680 dir_idx.append(idx) |
| 642 directions.append (sa.sort_direction) | 681 directions.append(sa.sort_direction) |
| 643 curdir = sa.sort_direction | 682 curdir = sa.sort_direction |
| 644 idx += 1 | 683 idx += 1 |
| 645 sortattr.append (val) | 684 sortattr.append(val) |
| 646 sortattr = zip (*sortattr) | 685 sortattr = zip(*sortattr) |
| 647 for dir, i in reversed(list(zip(directions, dir_idx))): | 686 for dir, i in reversed(list(zip(directions, dir_idx))): |
| 648 rev = dir == '-' | 687 rev = dir == '-' |
| 649 sortattr = sorted (sortattr, | 688 sortattr = sorted(sortattr, |
| 650 key = lambda x: NoneAndDictComparable(x[i:idx]), | 689 key=lambda x: NoneAndDictComparable(x[i:idx]), |
| 651 reverse = rev) | 690 reverse=rev) |
| 652 idx = i | 691 idx = i |
| 653 return [x[-1] for x in sortattr] | 692 return [x[-1] for x in sortattr] |
| 654 | 693 |
| 655 def _sort_repr(self, sortrep, ids): | 694 def _sort_repr(self, sortrep, ids): |
| 656 """Call sortrep for given ids -- possibly a list of list. We | 695 """Call sortrep for given ids -- possibly a list of list. We |
| 657 recurse until we have a list of ids. | 696 recurse until we have a list of ids. |
| 658 """ | 697 """ |
| 659 if not ids: | 698 if not ids: |
| 660 return ids | 699 return ids |
| 661 if isinstance (ids[0], list): | 700 if isinstance(ids[0], list): |
| 662 res = [self._sort_repr(sortrep, i) for i in ids] | 701 res = [self._sort_repr(sortrep, i) for i in ids] |
| 663 else: | 702 else: |
| 664 res = [sortrep(self.cls, i, self.name) for i in ids] | 703 res = [sortrep(self.cls, i, self.name) for i in ids] |
| 665 return res | 704 return res |
| 666 | 705 |
| 668 r = ["proptree:" + self.name] | 707 r = ["proptree:" + self.name] |
| 669 for n in self: | 708 for n in self: |
| 670 r.append("proptree:" + " " * n.depth + n.name) | 709 r.append("proptree:" + " " * n.depth + n.name) |
| 671 return '\n'.join(r) | 710 return '\n'.join(r) |
| 672 __str__ = __repr__ | 711 __str__ = __repr__ |
| 712 | |
| 673 | 713 |
| 674 # | 714 # |
| 675 # the base Database class | 715 # the base Database class |
| 676 # | 716 # |
| 677 class DatabaseError(ValueError): | 717 class DatabaseError(ValueError): |
| 678 """Error to be raised when there is some problem in the database code | 718 """Error to be raised when there is some problem in the database code |
| 679 """ | 719 """ |
| 680 pass | 720 pass |
| 721 | |
| 722 | |
| 681 class Database: | 723 class Database: |
| 682 """A database for storing records containing flexible data types. | 724 """A database for storing records containing flexible data types. |
| 683 | 725 |
| 684 This class defines a hyperdatabase storage layer, which the Classes use to | 726 This class defines a hyperdatabase storage layer, which the Classes use to |
| 685 store their data. | 727 store their data. |
| 863 """Close the database. | 905 """Close the database. |
| 864 | 906 |
| 865 This method must be called at the end of processing. | 907 This method must be called at the end of processing. |
| 866 | 908 |
| 867 """ | 909 """ |
| 910 | |
| 868 | 911 |
| 869 def iter_roles(roles): | 912 def iter_roles(roles): |
| 870 ''' handle the text processing of turning the roles list | 913 ''' handle the text processing of turning the roles list |
| 871 into something python can use more easily | 914 into something python can use more easily |
| 872 ''' | 915 ''' |
| 893 or a ValueError is raised. The keyword arguments in 'properties' | 936 or a ValueError is raised. The keyword arguments in 'properties' |
| 894 must map names to property objects, or a TypeError is raised. | 937 must map names to property objects, or a TypeError is raised. |
| 895 """ | 938 """ |
| 896 for name in 'creation activity creator actor'.split(): | 939 for name in 'creation activity creator actor'.split(): |
| 897 if name in properties: | 940 if name in properties: |
| 898 raise ValueError('"creation", "activity", "creator" and '\ | 941 raise ValueError('"creation", "activity", "creator" and ' |
| 899 '"actor" are reserved') | 942 '"actor" are reserved') |
| 900 | 943 |
| 901 self.classname = classname | 944 self.classname = classname |
| 902 self.properties = properties | 945 self.properties = properties |
| 903 self.db = weakref.proxy(db) # use a weak ref to avoid circularity | 946 self.db = weakref.proxy(db) # use a weak ref to avoid circularity |
| 904 self.key = '' | 947 self.key = '' |
| 914 self.reactors = dict([(a, PrioList()) for a in actions]) | 957 self.reactors = dict([(a, PrioList()) for a in actions]) |
| 915 | 958 |
| 916 def __repr__(self): | 959 def __repr__(self): |
| 917 """Slightly more useful representation | 960 """Slightly more useful representation |
| 918 """ | 961 """ |
| 919 return '<hyperdb.Class "%s">'%self.classname | 962 return '<hyperdb.Class "%s">' % self.classname |
| 920 | 963 |
| 921 # Editing nodes: | 964 # Editing nodes: |
| 922 | 965 |
| 923 def create(self, **propvalues): | 966 def create(self, **propvalues): |
| 924 """Create a new node of this class and return its id. | 967 """Create a new node of this class and return its id. |
| 938 node, an IndexError is raised. | 981 node, an IndexError is raised. |
| 939 """ | 982 """ |
| 940 raise NotImplementedError | 983 raise NotImplementedError |
| 941 | 984 |
| 942 _marker = [] | 985 _marker = [] |
| 986 | |
| 943 def get(self, nodeid, propname, default=_marker, cache=1): | 987 def get(self, nodeid, propname, default=_marker, cache=1): |
| 944 """Get the value of a property on an existing node of this class. | 988 """Get the value of a property on an existing node of this class. |
| 945 | 989 |
| 946 'nodeid' must be the id of an existing node of this class or an | 990 'nodeid' must be the id of an existing node of this class or an |
| 947 IndexError is raised. 'propname' must be the name of a property | 991 IndexError is raised. 'propname' must be the name of a property |
| 1059 raise ValueError('Journalling is disabled for this class') | 1103 raise ValueError('Journalling is disabled for this class') |
| 1060 | 1104 |
| 1061 perm = self.db.security.hasPermission | 1105 perm = self.db.security.hasPermission |
| 1062 journal = [] | 1106 journal = [] |
| 1063 | 1107 |
| 1064 uid=self.db.getuid() # id of the person requesting the history | 1108 uid = self.db.getuid() # id of the person requesting the history |
| 1065 | 1109 |
| 1066 # Roles of the user and the configured obsolete_history_roles | 1110 # Roles of the user and the configured obsolete_history_roles |
| 1067 hr = set(iter_roles(self.db.config.OBSOLETE_HISTORY_ROLES)) | 1111 hr = set(iter_roles(self.db.config.OBSOLETE_HISTORY_ROLES)) |
| 1068 ur = set(self.db.user.get_roles(uid)) | 1112 ur = set(self.db.user.get_roles(uid)) |
| 1069 allow_obsolete = bool(hr & ur) | 1113 allow_obsolete = bool(hr & ur) |
| 1073 # property is quiet | 1117 # property is quiet |
| 1074 # property is not (viewable or editable) | 1118 # property is not (viewable or editable) |
| 1075 # property is obsolete and not allow_obsolete | 1119 # property is obsolete and not allow_obsolete |
| 1076 id, evt_date, user, action, args = j | 1120 id, evt_date, user, action, args = j |
| 1077 if logger.isEnabledFor(logging.DEBUG): | 1121 if logger.isEnabledFor(logging.DEBUG): |
| 1078 j_repr = "%s"%(j,) | 1122 j_repr = "%s" % (j,) |
| 1079 else: | 1123 else: |
| 1080 j_repr='' | 1124 j_repr = '' |
| 1081 if args and type(args) == type({}): | 1125 if args and isinstance(args, type({})): |
| 1082 for key in list(args.keys()): | 1126 for key in list(args.keys()): |
| 1083 if key not in self.properties : | 1127 if key not in self.properties: |
| 1084 if enforceperm and not allow_obsolete: | 1128 if enforceperm and not allow_obsolete: |
| 1085 del args[key] | 1129 del args[key] |
| 1086 continue | 1130 continue |
| 1087 if skipquiet and self.properties[key].quiet: | 1131 if skipquiet and self.properties[key].quiet: |
| 1088 logger.debug("skipping quiet property" | 1132 logger.debug("skipping quiet property" |
| 1089 " %s::%s in %s", | 1133 " %s::%s in %s", |
| 1090 self.classname, key, j_repr) | 1134 self.classname, key, j_repr) |
| 1091 del args[key] | 1135 del args[key] |
| 1092 continue | 1136 continue |
| 1093 if enforceperm and not ( perm("View", | 1137 if enforceperm and not (perm("View", |
| 1094 uid, | 1138 uid, |
| 1095 self.classname, | 1139 self.classname, |
| 1096 property=key ) or perm("Edit", | 1140 property=key) or |
| 1097 uid, | 1141 perm("Edit", |
| 1098 self.classname, | 1142 uid, |
| 1099 property=key )): | 1143 self.classname, |
| 1144 property=key)): | |
| 1100 logger.debug("skipping unaccessible property " | 1145 logger.debug("skipping unaccessible property " |
| 1101 "%s::%s seen by user%s in %s", | 1146 "%s::%s seen by user%s in %s", |
| 1102 self.classname, key, uid, j_repr) | 1147 self.classname, key, uid, j_repr) |
| 1103 del args[key] | 1148 del args[key] |
| 1104 continue | 1149 continue |
| 1105 if not args: | 1150 if not args: |
| 1106 logger.debug("Omitting journal entry for %s%s" | 1151 logger.debug("Omitting journal entry for %s%s" |
| 1107 " all props removed in: %s", | 1152 " all props removed in: %s", |
| 1108 self.classname, nodeid, j_repr) | 1153 self.classname, nodeid, j_repr) |
| 1109 continue | 1154 continue |
| 1110 journal.append(j) | 1155 journal.append(j) |
| 1111 elif action in ['link', 'unlink' ] and type(args) == type(()): | 1156 elif action in ['link', 'unlink'] and isinstance(args, type(())): |
| 1112 # definitions: | 1157 # definitions: |
| 1113 # myself - object whose history is being filtered | 1158 # myself - object whose history is being filtered |
| 1114 # linkee - object/class whose property is changing to | 1159 # linkee - object/class whose property is changing to |
| 1115 # include/remove myself | 1160 # include/remove myself |
| 1116 # link property - property of the linkee class that is changing | 1161 # link property - property of the linkee class that is changing |
| 1138 if not enforceperm or allow_obsolete: | 1183 if not enforceperm or allow_obsolete: |
| 1139 journal.append(j) | 1184 journal.append(j) |
| 1140 continue | 1185 continue |
| 1141 # obsolete linked-to item | 1186 # obsolete linked-to item |
| 1142 try: | 1187 try: |
| 1143 k = cls.get (linkid, key) | 1188 cls.get(linkid, key) # does linkid exist |
| 1144 except IndexError: | 1189 except IndexError: |
| 1145 if not enforceperm or allow_obsolete: | 1190 if not enforceperm or allow_obsolete: |
| 1146 journal.append(j) | 1191 journal.append(j) |
| 1147 continue | 1192 continue |
| 1148 # is the updated property quiet? | 1193 # is the updated property quiet? |
| 1151 "%s %sed %s%s", | 1196 "%s %sed %s%s", |
| 1152 j_repr, action, self.classname, nodeid) | 1197 j_repr, action, self.classname, nodeid) |
| 1153 continue | 1198 continue |
| 1154 # can user view the property in linkee class | 1199 # can user view the property in linkee class |
| 1155 if enforceperm and not (perm("View", | 1200 if enforceperm and not (perm("View", |
| 1156 uid, | 1201 uid, |
| 1157 linkcl, | 1202 linkcl, |
| 1158 property=key) or perm("Edit", | 1203 property=key) or |
| 1159 uid, | 1204 perm("Edit", |
| 1160 linkcl, | 1205 uid, |
| 1161 property=key)): | 1206 linkcl, |
| 1207 property=key)): | |
| 1162 logger.debug("skipping unaccessible property: " | 1208 logger.debug("skipping unaccessible property: " |
| 1163 "%s with uid %s %sed %s%s", | 1209 "%s with uid %s %sed %s%s", |
| 1164 j_repr, uid, action, | 1210 j_repr, uid, action, |
| 1165 self.classname, nodeid) | 1211 self.classname, nodeid) |
| 1166 continue | 1212 continue |
| 1167 # check access to linkee object | 1213 # check access to linkee object |
| 1168 if enforceperm and not (perm("View", | 1214 if enforceperm and not (perm("View", |
| 1169 uid, | 1215 uid, |
| 1170 cls.classname, | 1216 cls.classname, |
| 1171 itemid=linkid) or perm("Edit", | 1217 itemid=linkid) or |
| 1172 uid, | 1218 perm("Edit", |
| 1173 cls.classname, | 1219 uid, |
| 1174 itemid=linkid)): | 1220 cls.classname, |
| 1221 itemid=linkid)): | |
| 1175 logger.debug("skipping unaccessible object: " | 1222 logger.debug("skipping unaccessible object: " |
| 1176 "%s uid %s %sed %s%s", | 1223 "%s uid %s %sed %s%s", |
| 1177 j_repr, uid, action, | 1224 j_repr, uid, action, |
| 1178 self.classname, nodeid) | 1225 self.classname, nodeid) |
| 1179 continue | 1226 continue |
| 1236 4. first property from the sorted property name list | 1283 4. first property from the sorted property name list |
| 1237 """ | 1284 """ |
| 1238 if hasattr(self, '_labelprop'): | 1285 if hasattr(self, '_labelprop'): |
| 1239 return self._labelprop | 1286 return self._labelprop |
| 1240 k = self.getkey() | 1287 k = self.getkey() |
| 1241 if k: | 1288 if k: |
| 1242 return k | 1289 return k |
| 1243 props = self.getprops() | 1290 props = self.getprops() |
| 1244 if 'name' in props: | 1291 if 'name' in props: |
| 1245 return 'name' | 1292 return 'name' |
| 1246 elif 'title' in props: | 1293 elif 'title' in props: |
| 1293 | 1340 |
| 1294 db.issue.find(messages={'1':1,'3':1}, files={'7':1}) | 1341 db.issue.find(messages={'1':1,'3':1}, files={'7':1}) |
| 1295 """ | 1342 """ |
| 1296 raise NotImplementedError | 1343 raise NotImplementedError |
| 1297 | 1344 |
| 1298 def _filter(self, search_matches, filterspec, sort=(None,None), | 1345 def _filter(self, search_matches, filterspec, sort=(None, None), |
| 1299 group=(None,None), retired=False, exact_match_spec={}): | 1346 group=(None, None), retired=False, exact_match_spec={}): |
| 1300 """For some backends this implements the non-transitive | 1347 """For some backends this implements the non-transitive |
| 1301 search, for more information see the filter method. | 1348 search, for more information see the filter method. |
| 1302 """ | 1349 """ |
| 1303 raise NotImplementedError | 1350 raise NotImplementedError |
| 1304 | 1351 |
| 1315 for key, v in spec.items(): | 1362 for key, v in spec.items(): |
| 1316 keys = key.split('.') | 1363 keys = key.split('.') |
| 1317 p = proptree | 1364 p = proptree |
| 1318 mlseen = False | 1365 mlseen = False |
| 1319 for k in keys: | 1366 for k in keys: |
| 1320 if isinstance (p.propclass, Multilink): | 1367 if isinstance(p.propclass, Multilink): |
| 1321 mlseen = True | 1368 mlseen = True |
| 1322 isnull = v == '-1' or v is None | 1369 isnull = v == '-1' or v is None |
| 1323 islist = isinstance(v, type([])) | 1370 islist = isinstance(v, type([])) |
| 1324 nullin = islist and ('-1' in v or None in v) | 1371 nullin = islist and ('-1' in v or None in v) |
| 1325 r = retr and not mlseen and not isnull and not nullin | 1372 r = retr and not mlseen and not isnull and not nullin |
| 1338 for s in sortattr: | 1385 for s in sortattr: |
| 1339 keys = s[1].split('.') | 1386 keys = s[1].split('.') |
| 1340 p = proptree | 1387 p = proptree |
| 1341 mlseen = False | 1388 mlseen = False |
| 1342 for k in keys: | 1389 for k in keys: |
| 1343 if isinstance (p.propclass, Multilink): | 1390 if isinstance(p.propclass, Multilink): |
| 1344 mlseen = True | 1391 mlseen = True |
| 1345 r = retr and not mlseen | 1392 r = retr and not mlseen |
| 1346 p = p.append(k, need_for='sort', retr=r) | 1393 p = p.append(k, need_for='sort', retr=r) |
| 1347 if isinstance (p.propclass, Multilink): | 1394 if isinstance(p.propclass, Multilink): |
| 1348 multilinks[p] = True | 1395 multilinks[p] = True |
| 1349 if p.cls: | 1396 if p.cls: |
| 1350 p = p.append(p.cls.orderprop(), need_for='sort') | 1397 p = p.append(p.cls.orderprop(), need_for='sort') |
| 1351 if p.sort_direction: # if an orderprop is also specified explicitly | 1398 if p.sort_direction: # if orderprop is also specified explicitly |
| 1352 continue | 1399 continue |
| 1353 p.sort_direction = s[0] | 1400 p.sort_direction = s[0] |
| 1354 proptree.sortattr.append (p) | 1401 proptree.sortattr.append(p) |
| 1355 for p in multilinks.keys(): | 1402 for p in multilinks.keys(): |
| 1356 sattr = {} | 1403 sattr = {} |
| 1357 for c in p: | 1404 for c in p: |
| 1358 if c.sort_direction: | 1405 if c.sort_direction: |
| 1359 sattr [c] = True | 1406 sattr[c] = True |
| 1360 for sa in proptree.sortattr: | 1407 for sa in proptree.sortattr: |
| 1361 if sa in sattr: | 1408 if sa in sattr: |
| 1362 p.sortattr.append (sa) | 1409 p.sortattr.append(sa) |
| 1363 return proptree | 1410 return proptree |
| 1364 | 1411 |
| 1365 def get_transitive_prop(self, propname_path, default = None): | 1412 def get_transitive_prop(self, propname_path, default=None): |
| 1366 """Expand a transitive property (individual property names | 1413 """Expand a transitive property (individual property names |
| 1367 separated by '.' into a new property at the end of the path. If | 1414 separated by '.' into a new property at the end of the path. If |
| 1368 one of the names does not refer to a valid property, we return | 1415 one of the names does not refer to a valid property, we return |
| 1369 None. | 1416 None. |
| 1370 Example propname_path (for class issue): "messages.author" | 1417 Example propname_path (for class issue): "messages.author" |
| 1393 srt = [srt] | 1440 srt = [srt] |
| 1394 for s in srt: | 1441 for s in srt: |
| 1395 if s[1] and s[1] not in seen: | 1442 if s[1] and s[1] not in seen: |
| 1396 sortattr.append((s[0] or '+', s[1])) | 1443 sortattr.append((s[0] or '+', s[1])) |
| 1397 seen[s[1]] = True | 1444 seen[s[1]] = True |
| 1398 if 'id' not in seen : | 1445 if 'id' not in seen: |
| 1399 sortattr.append(('+', 'id')) | 1446 sortattr.append(('+', 'id')) |
| 1400 return sortattr | 1447 return sortattr |
| 1401 | 1448 |
| 1402 def filter(self, search_matches, filterspec, sort=[], group=[], | 1449 def filter(self, search_matches, filterspec, sort=[], group=[], |
| 1403 retired=False, exact_match_spec={}, limit=None, offset=None): | 1450 retired=False, exact_match_spec={}, limit=None, offset=None): |
| 1456 using _filter implemented in a backend class. A more efficient | 1503 using _filter implemented in a backend class. A more efficient |
| 1457 version can be implemented in the individual backends -- e.g., | 1504 version can be implemented in the individual backends -- e.g., |
| 1458 an SQL backend will want to create a single SQL statement and | 1505 an SQL backend will want to create a single SQL statement and |
| 1459 override the filter method instead of implementing _filter. | 1506 override the filter method instead of implementing _filter. |
| 1460 """ | 1507 """ |
| 1461 sortattr = self._sortattr(sort = sort, group = group) | 1508 sortattr = self._sortattr(sort=sort, group=group) |
| 1462 proptree = self._proptree(filterspec, exact_match_spec, sortattr) | 1509 proptree = self._proptree(filterspec, exact_match_spec, sortattr) |
| 1463 proptree.search(search_matches, retired=retired) | 1510 proptree.search(search_matches, retired=retired) |
| 1464 if offset is not None or limit is not None: | 1511 if offset is not None or limit is not None: |
| 1465 items = proptree.sort() | 1512 items = proptree.sort() |
| 1466 if limit and offset: | 1513 if limit and offset: |
| 1469 return items[offset:] | 1516 return items[offset:] |
| 1470 else: | 1517 else: |
| 1471 return items[:limit] | 1518 return items[:limit] |
| 1472 return proptree.sort() | 1519 return proptree.sort() |
| 1473 | 1520 |
| 1474 | |
| 1475 # non-optimized filter_iter, a backend may chose to implement a | 1521 # non-optimized filter_iter, a backend may chose to implement a |
| 1476 # better version that provides a real iterator that pre-fills the | 1522 # better version that provides a real iterator that pre-fills the |
| 1477 # cache for each id returned. Note that the filter_iter doesn't | 1523 # cache for each id returned. Note that the filter_iter doesn't |
| 1478 # promise to correctly sort by multilink (which isn't sane to do | 1524 # promise to correctly sort by multilink (which isn't sane to do |
| 1479 # anyway). | 1525 # anyway). |
| 1494 If the "protected" flag is true, we include protected properties - | 1540 If the "protected" flag is true, we include protected properties - |
| 1495 those which may not be modified. | 1541 those which may not be modified. |
| 1496 """ | 1542 """ |
| 1497 raise NotImplementedError | 1543 raise NotImplementedError |
| 1498 | 1544 |
| 1499 def get_required_props(self, propnames = []): | 1545 def get_required_props(self, propnames=[]): |
| 1500 """Return a dict of property names mapping to property objects. | 1546 """Return a dict of property names mapping to property objects. |
| 1501 All properties that have the "required" flag set will be | 1547 All properties that have the "required" flag set will be |
| 1502 returned in addition to all properties in the propnames | 1548 returned in addition to all properties in the propnames |
| 1503 parameter. | 1549 parameter. |
| 1504 """ | 1550 """ |
| 1505 props = self.getprops(protected = False) | 1551 props = self.getprops(protected=False) |
| 1506 pdict = dict([(p, props[p]) for p in propnames]) | 1552 pdict = dict([(p, props[p]) for p in propnames]) |
| 1507 pdict.update([(k, v) for k, v in props.items() if v.required]) | 1553 pdict.update([(k, v) for k, v in props.items() if v.required]) |
| 1508 return pdict | 1554 return pdict |
| 1509 | 1555 |
| 1510 def addprop(self, **properties): | 1556 def addprop(self, **properties): |
| 1522 raise NotImplementedError | 1568 raise NotImplementedError |
| 1523 | 1569 |
| 1524 # | 1570 # |
| 1525 # Detector interface | 1571 # Detector interface |
| 1526 # | 1572 # |
| 1527 def audit(self, event, detector, priority = 100): | 1573 def audit(self, event, detector, priority=100): |
| 1528 """Register an auditor detector""" | 1574 """Register an auditor detector""" |
| 1529 self.auditors[event].append((priority, detector.__name__, detector)) | 1575 self.auditors[event].append((priority, detector.__name__, detector)) |
| 1530 | 1576 |
| 1531 def fireAuditors(self, event, nodeid, newvalues): | 1577 def fireAuditors(self, event, nodeid, newvalues): |
| 1532 """Fire all registered auditors""" | 1578 """Fire all registered auditors""" |
| 1533 for prio, name, audit in self.auditors[event]: | 1579 for _prio, _name, audit in self.auditors[event]: |
| 1534 try: | 1580 try: |
| 1535 audit(self.db, self, nodeid, newvalues) | 1581 audit(self.db, self, nodeid, newvalues) |
| 1536 except (EnvironmentError, ArithmeticError) as e: | 1582 except (EnvironmentError, ArithmeticError) as e: |
| 1537 tb = traceback.format_exc() | 1583 tb = traceback.format_exc() |
| 1538 html = ("<h1>Traceback</h1>" + str(tb).replace('\n', '<br>'). | 1584 html = ("<h1>Traceback</h1>" + str(tb).replace('\n', '<br>'). |
| 1540 txt = 'Caught exception %s: %s\n%s' % (str(type(e)), e, tb) | 1586 txt = 'Caught exception %s: %s\n%s' % (str(type(e)), e, tb) |
| 1541 exc_info = sys.exc_info() | 1587 exc_info = sys.exc_info() |
| 1542 subject = "Error: %s" % exc_info[1] | 1588 subject = "Error: %s" % exc_info[1] |
| 1543 raise DetectorError(subject, html, txt) | 1589 raise DetectorError(subject, html, txt) |
| 1544 | 1590 |
| 1545 def react(self, event, detector, priority = 100): | 1591 def react(self, event, detector, priority=100): |
| 1546 """Register a reactor detector""" | 1592 """Register a reactor detector""" |
| 1547 self.reactors[event].append((priority, detector.__name__, detector)) | 1593 self.reactors[event].append((priority, detector.__name__, detector)) |
| 1548 | 1594 |
| 1549 def fireReactors(self, event, nodeid, oldvalues): | 1595 def fireReactors(self, event, nodeid, oldvalues): |
| 1550 """Fire all registered reactors""" | 1596 """Fire all registered reactors""" |
| 1551 for prio, name, react in self.reactors[event]: | 1597 for _prio, _name, react in self.reactors[event]: |
| 1552 try: | 1598 try: |
| 1553 react(self.db, self, nodeid, oldvalues) | 1599 react(self.db, self, nodeid, oldvalues) |
| 1554 except (EnvironmentError, ArithmeticError) as e: | 1600 except (EnvironmentError, ArithmeticError) as e: |
| 1555 tb = traceback.format_exc() | 1601 tb = traceback.format_exc() |
| 1556 html = ("<h1>Traceback</h1>" + str(tb).replace('\n', '<br>'). | 1602 html = ("<h1>Traceback</h1>" + str(tb).replace('\n', '<br>'). |
| 1579 properties = self.getprops() | 1625 properties = self.getprops() |
| 1580 a = [] | 1626 a = [] |
| 1581 for l in entries: | 1627 for l in entries: |
| 1582 # first element in sorted list is the (numeric) id | 1628 # first element in sorted list is the (numeric) id |
| 1583 # in python2.4 and up we would use sorted with a key... | 1629 # in python2.4 and up we would use sorted with a key... |
| 1584 a.append ((int (l [0].strip ("'")), l)) | 1630 a.append((int(l[0].strip("'")), l)) |
| 1585 a.sort () | 1631 a.sort() |
| 1586 | |
| 1587 | 1632 |
| 1588 last = 0 | 1633 last = 0 |
| 1589 r = [] | 1634 r = [] |
| 1590 for n, l in a: | 1635 for n, l in a: |
| 1591 nodeid, jdate, user, action, params = map(eval_import, l) | 1636 nodeid, jdate, user, action, params = map(eval_import, l) |
| 1637 | 1682 |
| 1638 For convenience reasons we take a list. | 1683 For convenience reasons we take a list. |
| 1639 In standard schemas only a user has a roles property but | 1684 In standard schemas only a user has a roles property but |
| 1640 this may be different in customized schemas. | 1685 this may be different in customized schemas. |
| 1641 ''' | 1686 ''' |
| 1642 roles = dict.fromkeys ([r.strip().lower() for r in roles]) | 1687 roles = dict.fromkeys([r.strip().lower() for r in roles]) |
| 1643 for role in self.get_roles(nodeid): | 1688 for role in self.get_roles(nodeid): |
| 1644 if role in roles: | 1689 if role in roles: |
| 1645 return True | 1690 return True |
| 1646 return False | 1691 return False |
| 1647 | 1692 |
| 1648 | 1693 |
| 1649 class HyperdbValueError(ValueError): | 1694 class HyperdbValueError(ValueError): |
| 1650 """ Error converting a raw value into a Hyperdb value """ | 1695 """ Error converting a raw value into a Hyperdb value """ |
| 1651 pass | 1696 pass |
| 1697 | |
| 1652 | 1698 |
| 1653 def convertLinkValue(db, propname, prop, value, idre=re.compile(r'^\d+$')): | 1699 def convertLinkValue(db, propname, prop, value, idre=re.compile(r'^\d+$')): |
| 1654 """ Convert the link value (may be id or key value) to an id value. """ | 1700 """ Convert the link value (may be id or key value) to an id value. """ |
| 1655 linkcl = db.classes[prop.classname] | 1701 linkcl = db.classes[prop.classname] |
| 1656 if not idre or not idre.match(value): | 1702 if not idre or not idre.match(value): |
| 1657 if linkcl.getkey(): | 1703 if linkcl.getkey(): |
| 1658 try: | 1704 try: |
| 1659 value = linkcl.lookup(value) | 1705 value = linkcl.lookup(value) |
| 1660 except KeyError as message: | 1706 except KeyError: |
| 1661 raise HyperdbValueError(_('property %s: %r is not a %s.')%( | 1707 raise HyperdbValueError(_('property %s: %r is not a %s.') % ( |
| 1662 propname, value, prop.classname)) | 1708 propname, value, prop.classname)) |
| 1663 else: | 1709 else: |
| 1664 raise HyperdbValueError(_('you may only enter ID values '\ | 1710 raise HyperdbValueError(_('you may only enter ID values ' |
| 1665 'for property %s')%propname) | 1711 'for property %s') % propname) |
| 1666 return value | 1712 return value |
| 1713 | |
| 1667 | 1714 |
| 1668 def fixNewlines(text): | 1715 def fixNewlines(text): |
| 1669 """ Homogenise line endings. | 1716 """ Homogenise line endings. |
| 1670 | 1717 |
| 1671 Different web clients send different line ending values, but | 1718 Different web clients send different line ending values, but |
| 1675 if text is not None: | 1722 if text is not None: |
| 1676 text = text.replace('\r\n', '\n') | 1723 text = text.replace('\r\n', '\n') |
| 1677 return text.replace('\r', '\n') | 1724 return text.replace('\r', '\n') |
| 1678 return text | 1725 return text |
| 1679 | 1726 |
| 1727 | |
| 1680 def rawToHyperdb(db, klass, itemid, propname, value, **kw): | 1728 def rawToHyperdb(db, klass, itemid, propname, value, **kw): |
| 1681 """ Convert the raw (user-input) value to a hyperdb-storable value. The | 1729 """ Convert the raw (user-input) value to a hyperdb-storable value. The |
| 1682 value is for the "propname" property on itemid (may be None for a | 1730 value is for the "propname" property on itemid (may be None for a |
| 1683 new item) of "klass" in "db". | 1731 new item) of "klass" in "db". |
| 1684 | 1732 |
| 1689 properties = klass.getprops() | 1737 properties = klass.getprops() |
| 1690 | 1738 |
| 1691 # ensure it's a valid property name | 1739 # ensure it's a valid property name |
| 1692 propname = propname.strip() | 1740 propname = propname.strip() |
| 1693 try: | 1741 try: |
| 1694 proptype = properties[propname] | 1742 proptype = properties[propname] |
| 1695 except KeyError: | 1743 except KeyError: |
| 1696 raise HyperdbValueError(_('%r is not a property of %s')%(propname, | 1744 raise HyperdbValueError(_('%r is not a property of %s') % ( |
| 1697 klass.classname)) | 1745 propname, klass.classname)) |
| 1698 | 1746 |
| 1699 # if we got a string, strip it now | 1747 # if we got a string, strip it now |
| 1700 if isinstance(value, type('')): | 1748 if isinstance(value, type('')): |
| 1701 value = value.strip() | 1749 value = value.strip() |
| 1702 | 1750 |
| 1703 # convert the input value to a real property value | 1751 # convert the input value to a real property value |
| 1704 value = proptype.from_raw(value, db=db, klass=klass, | 1752 value = proptype.from_raw(value, db=db, klass=klass, |
| 1705 propname=propname, itemid=itemid, **kw) | 1753 propname=propname, itemid=itemid, **kw) |
| 1706 | 1754 |
| 1707 return value | 1755 return value |
| 1756 | |
| 1708 | 1757 |
| 1709 class FileClass: | 1758 class FileClass: |
| 1710 """ A class that requires the "content" property and stores it on | 1759 """ A class that requires the "content" property and stores it on |
| 1711 disk. | 1760 disk. |
| 1712 """ | 1761 """ |
| 1766 index_content = index_content.decode('utf-8', errors='ignore') | 1815 index_content = index_content.decode('utf-8', errors='ignore') |
| 1767 # indexer will only index text mime type. It will skip | 1816 # indexer will only index text mime type. It will skip |
| 1768 # other types. So if mime type of file is correct, we | 1817 # other types. So if mime type of file is correct, we |
| 1769 # call add_text on content. | 1818 # call add_text on content. |
| 1770 self.db.indexer.add_text((self.classname, nodeid, 'content'), | 1819 self.db.indexer.add_text((self.classname, nodeid, 'content'), |
| 1771 index_content, mime_type) | 1820 index_content, mime_type) |
| 1821 | |
| 1772 | 1822 |
| 1773 class Node: | 1823 class Node: |
| 1774 """ A convenience wrapper for the given node | 1824 """ A convenience wrapper for the given node |
| 1775 """ | 1825 """ |
| 1776 def __init__(self, cl, nodeid, cache=1): | 1826 def __init__(self, cl, nodeid, cache=1): |
| 1777 self.__dict__['cl'] = cl | 1827 self.__dict__['cl'] = cl |
| 1778 self.__dict__['nodeid'] = nodeid | 1828 self.__dict__['nodeid'] = nodeid |
| 1829 | |
| 1779 def keys(self, protected=1): | 1830 def keys(self, protected=1): |
| 1780 return list(self.cl.getprops(protected=protected).keys()) | 1831 return list(self.cl.getprops(protected=protected).keys()) |
| 1832 | |
| 1781 def values(self, protected=1): | 1833 def values(self, protected=1): |
| 1782 l = [] | 1834 l = [] |
| 1783 for name in self.cl.getprops(protected=protected).keys(): | 1835 for name in self.cl.getprops(protected=protected).keys(): |
| 1784 l.append(self.cl.get(self.nodeid, name)) | 1836 l.append(self.cl.get(self.nodeid, name)) |
| 1785 return l | 1837 return l |
| 1838 | |
| 1786 def items(self, protected=1): | 1839 def items(self, protected=1): |
| 1787 l = [] | 1840 l = [] |
| 1788 for name in self.cl.getprops(protected=protected).keys(): | 1841 for name in self.cl.getprops(protected=protected).keys(): |
| 1789 l.append((name, self.cl.get(self.nodeid, name))) | 1842 l.append((name, self.cl.get(self.nodeid, name))) |
| 1790 return l | 1843 return l |
| 1844 | |
| 1791 def has_key(self, name): | 1845 def has_key(self, name): |
| 1792 return name in self.cl.getprops() | 1846 return name in self.cl.getprops() |
| 1847 | |
| 1793 def get(self, name, default=None): | 1848 def get(self, name, default=None): |
| 1794 if name in self: | 1849 if name in self: |
| 1795 return self[name] | 1850 return self[name] |
| 1796 else: | 1851 else: |
| 1797 return default | 1852 return default |
| 1853 | |
| 1798 def __getattr__(self, name): | 1854 def __getattr__(self, name): |
| 1799 if name in self.__dict__: | 1855 if name in self.__dict__: |
| 1800 return self.__dict__[name] | 1856 return self.__dict__[name] |
| 1801 try: | 1857 try: |
| 1802 return self.cl.get(self.nodeid, name) | 1858 return self.cl.get(self.nodeid, name) |
| 1803 except KeyError as value: | 1859 except KeyError as value: |
| 1804 # we trap this but re-raise it as AttributeError - all other | 1860 # we trap this but re-raise it as AttributeError - all other |
| 1805 # exceptions should pass through untrapped | 1861 # exceptions should pass through untrapped |
| 1806 raise AttributeError(str(value)) | 1862 raise AttributeError(str(value)) |
| 1863 | |
| 1807 def __getitem__(self, name): | 1864 def __getitem__(self, name): |
| 1808 return self.cl.get(self.nodeid, name) | 1865 return self.cl.get(self.nodeid, name) |
| 1866 | |
| 1809 def __setattr__(self, name, value): | 1867 def __setattr__(self, name, value): |
| 1810 try: | 1868 try: |
| 1811 return self.cl.set(self.nodeid, **{name: value}) | 1869 return self.cl.set(self.nodeid, **{name: value}) |
| 1812 except KeyError as value: | 1870 except KeyError as value: |
| 1813 # we trap this but re-raise it as AttributeError - all other | 1871 # we trap this but re-raise it as AttributeError - all other |
| 1814 # exceptions should pass through untrapped | 1872 # exceptions should pass through untrapped |
| 1815 raise AttributeError(str(value)) | 1873 raise AttributeError(str(value)) |
| 1874 | |
| 1816 def __setitem__(self, name, value): | 1875 def __setitem__(self, name, value): |
| 1817 self.cl.set(self.nodeid, **{name: value}) | 1876 self.cl.set(self.nodeid, **{name: value}) |
| 1877 | |
| 1818 def history(self, enforceperm=True, skipquiet=True): | 1878 def history(self, enforceperm=True, skipquiet=True): |
| 1819 return self.cl.history(self.nodeid, | 1879 return self.cl.history(self.nodeid, |
| 1820 enforceperm=enforceperm, | 1880 enforceperm=enforceperm, |
| 1821 skipquiet=skipquiet ) | 1881 skipquiet=skipquiet) |
| 1882 | |
| 1822 def retire(self): | 1883 def retire(self): |
| 1823 return self.cl.retire(self.nodeid) | 1884 return self.cl.retire(self.nodeid) |
| 1824 | 1885 |
| 1825 | 1886 |
| 1826 def Choice(name, db, *options): | 1887 def Choice(name, db, *options): |
