Package advene :: Package model :: Package core :: Module meta
[hide private]
[frames] | no frames]

Source Code for Module advene.model.core.meta

  1  from weakref import ref 
  2   
  3  from advene.model.consts import _RAISE, PARSER_META_PREFIX 
  4  from advene.model.exceptions import ModelError 
  5  from advene.model.tales import tales_property 
  6  from advene.util.alias import alias 
  7  from advene.util.sorted_dict import SortedDict 
8 9 -class WithMetaMixin(object):
10 """Metadata access mixin. 11 12 I factorize all metadata-related code for classes Package and 13 PackageElement. 14 15 I also provide an alias mechanism to make frequent metadata easily 16 accessible as python properties. 17 18 FIXME: expand description with usage example 19 """ 20 21 # NB: unlike other collections in the Advene model, metadata no not provide 22 # means to get id-ref *only* for unreachable elements. Since metadata 23 # already require a test (is the value a string value or an element value) 24 # mixing "pure" strings, id-refs and elements would seem too cumbersome... 25 26 __cache = None # SortedDict of all known metadata 27 # values are either string or (weakref, id) 28 __cache_is_complete = False # is self.__cache complete? 29
30 - def _update_caches(self, old_idref, new_idref, element, relation):
31 """ 32 :see-also: `advene.model.core.element.PackageElement._update_caches` 33 """ 34 if relation.startswith(":meta ") and self.__cache is not None: 35 key = relation[6:] 36 if element is not None: 37 wref = ref(element) 38 else: 39 wref = lambda: None 40 self.__cache[key] = (wref, new_idref) 41 else: 42 try: 43 super(WithMetaMixin, self) \ 44 ._update_caches(old_idref, new_idref, element, relation) 45 except AttributeError: 46 pass
47
48 - def iter_meta(self):
49 """Iter over all the metadata of this object. 50 51 Yields (key, value) pairs, where the value is either a string or an 52 element. If the element is unreachable, value is None. 53 54 See also `iter_meta_ids`. 55 """ 56 if hasattr(self, "ADVENE_TYPE"): 57 p = self._owner 58 eid = self._id 59 typ = self.ADVENE_TYPE 60 else: 61 p = self 62 eid = "" 63 typ = "" 64 65 if self.__cache_is_complete: 66 # then rely completely on cache 67 for k, v in self.__cache.iteritems(): 68 if v is KeyError: 69 continue 70 if isinstance(v, tuple): 71 tpl = v 72 v = tpl[0]() 73 if v is None: 74 v = p.get_element(tpl[1], None) 75 yield k, v 76 else: 77 # retrieve data from backend and cache them 78 cache = self.__cache 79 complete = True 80 if cache is None: 81 cache = self.__cache = SortedDict() 82 for k, v, v_is_id in p._backend.iter_meta(p._id, eid, typ): 83 if v_is_id: 84 # it is no use looking in cache: if the element is in it, 85 # then it will also be in the package's cache, and the 86 # retrieval from the package will be as efficient 87 e = p.get_element(v, None) 88 if e is not None: 89 cache[k] = (ref(e), v) 90 else: 91 complete = False 92 v = e 93 else: 94 cache[k] = v 95 yield k, v 96 self.__cache_is_complete = complete
97
98 - def iter_meta_ids(self):
99 """Iter over all the metadata of this object. 100 101 Yields (key, value) pairs, where the value is a string with a special 102 attribute ``is_id`` indicating if it represents the id-ref of an 103 element. 104 105 See also `iter_meta`. 106 """ 107 if hasattr(self, "ADVENE_TYPE"): 108 p = self._owner 109 eid = self._id 110 typ = self.ADVENE_TYPE 111 else: 112 p = self 113 eid = "" 114 typ = "" 115 116 if self.__cache_is_complete: 117 # then rely completely on cache 118 for k, v in self.__cache.iteritems(): 119 if v is KeyError: 120 continue 121 if isinstance(v, tuple): 122 v = metadata_value(v[1], True) 123 else: 124 v = metadata_value(v, False) 125 yield k, v 126 else: 127 # retrieve data from backend 128 cache = self.__cache 129 for k, v, v_is_id in p._backend.iter_meta(p._id, eid, typ): 130 yield k, metadata_value(v, v_is_id)
131
132 - def get_meta(self, key, default=_RAISE):
133 """Return the metadata (string or element) associated to the given key. 134 135 If no metadata is associated to the given key, a KeyError is raised. 136 If the given key references an unreachable element, a 137 `NoSuchElementError` or `UnreachableImportError` is raised. 138 139 All exceptions can be avoided by providing a ``default`` value, that 140 will be returned instead of raising an exception. 141 """ 142 return self._get_meta_id_or_ref(key, default, False)
143
144 - def get_meta_id(self, key, default=_RAISE, _return_id=True):
145 """ 146 Return the metadata id (string or element) associated to the given key. 147 148 The returned value is a string with a special attribute ``is_id`` 149 indicating if it represents the id-ref of an element. 150 151 If no metadata is associated to the given key, a KeyError is raised, 152 unless ``default`` is provideded, in which case its value is returned 153 instead. 154 """ 155 # NB: this method actually implement both get_meta and get_meta_ids, 156 # with the flag _return_id to choose between the two. 157 158 if hasattr(self, "ADVENE_TYPE"): 159 p = self._owner 160 eid = self._id 161 typ = self.ADVENE_TYPE 162 else: 163 p = self 164 eid = "" 165 typ = "" 166 cache = self.__cache 167 if cache is None: 168 cache = self.__cache = SortedDict() 169 170 val = cache.get((key)) 171 if isinstance(val, tuple): 172 if _return_id: 173 val = metadata_value(val[1], True) 174 else: 175 wref, the_id = val 176 val = wref() 177 if val is None: 178 val = p.get_element(the_id, default) 179 if val is not default: 180 cache[key] = (ref(val), the_id) 181 elif isinstance(val, basestring): 182 if _return_id: 183 val = metadata_value(val, False) 184 elif val is None: # could also be KeyError 185 tpl = p._backend.get_meta(p._id, eid, typ, key) 186 if tpl is None: 187 val = cache[key] = KeyError 188 else: 189 if _return_id: 190 val = metadata_value(*tpl) 191 elif tpl[1]: 192 the_id = tpl[0] 193 val = p.get_element(the_id, default) 194 if val is not default: 195 cache[key] = (ref(val), tpl[0]) 196 else: 197 val = cache[key] = tpl[0] 198 199 if val is KeyError: # from cache or set above 200 if default is _RAISE: 201 raise KeyError(key) 202 else: 203 val = default 204 return val
205 206 @alias(get_meta_id)
207 - def _get_meta_id_or_ref(self):
208 # get_meta and get_meta_id have a common implementation. 209 # Normally, it should be located in a "private" method named 210 # _get_meta_id_or_ref. 211 # However, for efficiency reasons, that private method and 212 # get_meta_id have been merged into one. Both names are necessary 213 # because the "public" get_meta_id may be overridden while the 214 # "private" method should not. Hence that alias. 215 pass
216
217 - def set_meta(self, key, val, val_is_idref=False):
218 """Set the metadata. 219 220 ``val`` can either be a PackageElement or a string. If an element, it 221 must be directly imported by the package of self, or a ModelError will 222 be raised. 223 224 Use `val_is_idref` only if you know what you are doing: it forces `val` 225 to be interpreted as an id-ref rather than a plain string; if the 226 id-ref has an import-prefix, only the existence of the import is 227 checked. This is mainly useful for parsers. 228 """ 229 if hasattr(self, "ADVENE_TYPE"): 230 p = self._owner 231 eid = self._id 232 typ = self.ADVENE_TYPE 233 else: 234 p = self 235 eid = "" 236 typ = "" 237 if hasattr(val, "ADVENE_TYPE"): 238 if not p._can_reference(val): 239 raise ModelError, "Element should be directy imported" 240 vstr = val.make_id_in(p) 241 vstr_is_id = True 242 val = (ref(val), vstr) 243 elif val_is_idref: 244 assert val.find(":") > 0, "Expected *strict* id-ref" 245 if not p._can_reference(val): 246 raise ModelError, "Element or import does not exist %s" % val 247 vstr = val 248 vstr_is_id = True 249 val = (None, val) 250 else: 251 vstr = unicode(val) 252 vstr_is_id = False 253 cache = self.__cache 254 if cache is None: 255 cache = self.__cache = SortedDict() 256 257 self.emit("pre-modified-meta::" + key, key, val) 258 cache[key] = val 259 p._backend.set_meta(p._id, eid, typ, key, vstr, vstr_is_id) 260 self.emit("modified-meta::" + key, key, val)
261
262 - def del_meta(self, key):
263 """Delete the metadata. 264 265 Note that if the given key is not in use, this will have no effect. 266 """ 267 if hasattr(self, "ADVENE_TYPE"): 268 p = self._owner 269 eid = self._id 270 typ = self.ADVENE_TYPE 271 else: 272 p = self 273 eid = "" 274 typ = "" 275 cache = self.__cache 276 self.emit("pre-modified-meta::" + key, key, None) 277 if cache is not None and key in cache: 278 del cache[key] 279 p._backend.set_meta(p._id, eid, typ, key, None, False) 280 self.emit("modified-meta::" + key, key, None)
281 282 @property
283 - def meta(self):
284 return _MetaDict(self)
285 286 @classmethod
287 - def make_metadata_property(cls, key, alias=None, default=_RAISE, doc=None):
288 """Attempts to create a python property in cls mapping to metadata key. 289 290 If alias is None, key is considered as a URI, and the last part of 291 that URI (after # or /) is used. 292 293 If default is not provided, an exception is raised whenever the 294 property is accessed when it is not set. Else, the default value will 295 be returned is that case. 296 297 If doc is not None or not provided, a simple docstring will be 298 generated. 299 300 Raises an AttributeError if cls already has a member with that name. 301 302 FIXME: should attach docstring to the property somehow 303 """ 304 if alias is None: 305 cut = max(key.rfind("#"), key.rfind("/")) 306 alias = key[cut+1:] 307 308 if hasattr(cls, alias): 309 raise AttributeError(alias) 310 311 def getter(obj): 312 return obj.get_meta(key, default)
313 314 def setter(obj, val): 315 return obj.set_meta(key, val)
316 317 def deller(obj): 318 return obj.del_meta(key) 319 320 if doc is None: 321 doc = "shortcut for metadata with key\n <%s>" % key 322 323 setattr(cls, alias, property(getter, setter, deller, doc)) 324 325 # @tales_path1_function 326 # def _tales_meta(self, path): 327 # nsd = self._get_ns_dict() 328 # return _PrefixDict(self, nsd[path]) 329 330 @tales_property
331 - def _tales_meta(self, context=None):
332 d=dict( (qname, 333 dict( (k.replace(uri, ''), v) 334 for (k, v) in self.iter_meta() 335 if k.startswith(uri) ) ) 336 for (qname, uri) in self._get_ns_dict().iteritems() ) 337 return d
338
339 - def _get_ns_dict(self):
340 if hasattr(self, "ADVENE_TYPE"): 341 package = self.owner 342 else: 343 package = self 344 namespaces = {} 345 prefixes = package.get_meta(PARSER_META_PREFIX + "namespaces", "") 346 for line in prefixes.split("\n"): 347 if line: 348 prefix, uri = line.split(" ") 349 namespaces[prefix] = uri 350 return namespaces
351
352 353 -class _MetaDict(object):
354 """A dict-like object representing the metadata of an object. 355 356 Note that many methods have an equivalent with suffix ``_id`` or 357 ``_ids`` which use `get_meta_id` instead of `get_meta` and 358 `iter_meta_ids` instead of `iter_meta`, respectively. 359 """ 360 361 __slots__ = ["_owner",] 362
363 - def __init__ (self, owner):
364 self._owner = owner
365
366 - def __contains__(self, k):
367 return self.get_meta(k, None) is not None
368
369 - def __delitem__(self, k):
370 return self._owner.del_meta(k)
371
372 - def __getitem__(self, k):
373 return self._owner.get_meta(k)
374
375 - def __iter__(self):
376 return ( k for k, _ in self._owner.iter_meta_ids() )
377
378 - def __len__(self):
379 return len(list(iter(self)))
380
381 - def __setitem__(self, k, v):
382 return self._owner.set_meta(k, v)
383
384 - def clear(self):
385 for k in self.keys(): 386 self._owner.del_meta(k)
387
388 - def copy(self):
389 return dict(self)
390
391 - def get(self, k, v=None):
392 return self._owner.get_meta(k, v)
393
394 - def get_id(self, k, v=None):
395 return self._owner.get_meta_id(k, v)
396
397 - def has_key(self, k):
398 return self._owner.get_meta(k, None) is not None
399
400 - def items(self):
401 return list(self._owner.iter_meta())
402
403 - def items_ids(self):
404 return list(self._owner.iter_meta_ids())
405
406 - def iteritems(self):
407 return self._owner.iter_meta()
408
409 - def iteritems_ids(self):
410 return self._owner.iter_meta_ids()
411
412 - def iterkeys(self):
413 return ( k for k, _ in self._owner.iter_meta_ids() )
414
415 - def itervalues(self):
416 return ( v for _, v in self._owner.iter_meta() )
417
418 - def itervalues_ids(self):
419 return ( v for _, v in self._owner.iter_meta_ids() )
420
421 - def keys(self):
422 return [ k for k, _ in self._owner.iter_meta_ids() ]
423
424 - def pop(self, k, d=_RAISE):
425 v = self._owner.get_meta(k, None) 426 if v is None: 427 if d is _RAISE: 428 raise KeyError, k 429 else: 430 v = d 431 else: 432 self._owner.del_meta(k) 433 return v
434
435 - def pop_id(self, k, d=_RAISE):
436 v = self._owner.get_meta_id(k, None) 437 if v is None: 438 if d is _RAISE: 439 raise KeyError, k 440 else: 441 v = d 442 else: 443 self._owner.del_meta(k) 444 return v
445
446 - def popitem(self):
447 it = self._owner.iter_meta() 448 try: 449 k, v = it.next() 450 except StopIteration: 451 raise KeyError() 452 else: 453 self._owner.del_meta(k) 454 return v
455
456 - def popitem_id(self):
457 it = self._owner.iter_meta_ids() 458 try: 459 k, v = it.next() 460 except StopIteration: 461 raise KeyError() 462 else: 463 self._owner.del_meta(k) 464 return v
465
466 - def setdefault(self, k, d=""):
467 assert isinstance(d, basestring) or hasattr(d, "ADVENE_TYPE"), "Default value must be a string or an Advene element" 468 v = self._owner.get_meta(k, None) 469 if v is None: 470 self._owner.set_meta(k, d) 471 v = d 472 return v
473
474 - def update(self, e=None, **f):
475 e_keys = getattr(e, "keys", None) 476 if callable(e_keys): 477 for k in e_keys(): 478 self._owner.set_meta(k, e[k]) 479 elif e is not None: 480 for k, v in e: 481 self._owner.set_meta(k, v) 482 for k, v in f.iteritems(): 483 self._owner.set_meta(k, v)
484
485 - def values(self):
486 return [ v for _, v in self._owner.iter_meta() ]
487
488 - def values_ids(self):
489 return [ v for _, v in self._owner.iter_meta_ids() ]
490
491 492 #class _PrefixDict(object): 493 # """ 494 # A dict-like object used as an intermediate object in TALES expression. 495 # """ 496 # __slots__ = ["_obj", "_prefix"] 497 # 498 # def __init__(self, obj, prefix): 499 # self._obj = obj 500 # self._prefix = prefix 501 # 502 # def __getitem__(self, path): 503 # return self._obj.get_meta(self._prefix+path) 504 # 505 # def __call__(self): 506 # # TODO use iter_meta(prefix=X) when implemented 507 # return ( (k[len(self._prefix):], v) 508 # for k,v in self._obj.iter_meta() 509 # if k.startswith(self._prefix)) 510 # 511 -class metadata_value(unicode):
512 - def __new__ (cls, val, is_id):
513 return unicode.__new__(cls, val)
514 - def __init__ (self, val, is_id):
515 self.is_id = is_id
516