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

Source Code for Module advene.model.core.element

  1  """ 
  2  I define the common super-class of all package element classes. 
  3  """ 
  4   
  5  import re 
  6  from itertools import islice 
  7   
  8  from advene.model.consts import _RAISE 
  9  from advene.model.core.meta import WithMetaMixin 
 10  from advene.model.events import ElementEventDelegate, WithEventsMixin 
 11  from advene.model.exceptions import ModelError, UnreachableImportError, \ 
 12                                      NoSuchElementError 
 13  from advene.model.tales import tales_property, tales_use_as_context,\ 
 14                                 WithAbsoluteUrlMixin as WithAbsUrlMixin 
 15  from advene.util.alias import alias 
 16  from advene.util.autoproperty import autoproperty 
 17  from advene.util.session import session 
 18   
 19  # the following constants must be used as values of a property ADVENE_TYPE 
 20  # in all subclasses of PackageElement 
 21  MEDIA      = 'm' 
 22  ANNOTATION = 'a' 
 23  RELATION   = 'r' 
 24  TAG        = 't' 
 25  LIST       = 'l' 
 26  IMPORT     = 'i' 
 27  QUERY      = 'q' 
 28  VIEW       = 'v' 
 29  RESOURCE   = 'R' 
 30   
 31  _package_event_template = { 
 32          MEDIA      : 'media::%s', 
 33          ANNOTATION : 'annotation::%s', 
 34          RELATION   : 'relation::%s', 
 35          TAG        : 'tag::%s', 
 36          LIST       : 'list::%s', 
 37          IMPORT     : 'import::%s', 
 38          QUERY      : 'query::%s', 
 39          VIEW       : 'view::%s', 
 40          RESOURCE   : 'resource::%s', 
 41  } 
42 43 -class PackageElement(WithMetaMixin, WithEventsMixin, WithAbsUrlMixin, object):
44 """ 45 I am the common subclass of all package element. 46 47 48 Package elements are unique volatile instances: 49 50 * unique, because it is enforced that the same element will never 51 be represented at a given time by two distinct instances; hence, 52 elements can be compared with the ``is`` operator as well as 53 ``==`` 54 55 * volatile, because it is not guaranteed that, at two instants, the 56 instance representing a given element will be the same; unused instances 57 may be freed at any time, and a new instance will be created on demand. 58 59 This should normally normally be transparent for the user. 60 61 Developper note 62 =============== 63 So that volatility is indeed transparent to users, the `__setattr__` method 64 has been overridden: since custom attributes are not stored in the backend, 65 the instance should be kept in memory as long as it has custom attributes. 66 67 As a consequence, all "non-custom" attributes (i.e. those that will be 68 correctly re-generated when the element is re-instantiated) must be 69 declared as class attribute (usually with None as their default value). 70 71 This must also be true of subclasses of elements (NB: mixin classes should 72 normally already do that). 73 """ 74 75 # the __setattr__ overridding requires that all attributs relying on the 76 # backend are always present: 77 _id = None 78 _owner = None 79 _weight = 0 80
81 - def __init__(self, owner, id):
82 """ 83 Must not be used directly, nor overridden. 84 Use class methods instantiate or create_new instead. 85 """ 86 self._id = id 87 self._owner = owner 88 self._weight = 0 89 owner._elements[id] = self # cache to prevent duplicate instanciation
90 91 @classmethod
92 - def instantiate(cls, owner, id, *args):
93 """ 94 Factory method to create an instance from backend data. 95 96 This method expect the exact data from the backend, so it does not 97 need to be tolerant or to check consistency (the backend is assumed to 98 be sane). 99 """ 100 r = cls(owner, id) 101 return r
102
103 - def __setattr__(self, name, value):
104 """ 105 Make instance heavier when a new custom attribute is created. 106 """ 107 if name not in self.__dict__ and not hasattr(self.__class__, name): 108 #print "=== weightening", self.id, "because of", name 109 self._increase_weight() 110 super(PackageElement, self).__setattr__(name, value)
111
112 - def __delattr__(self, name):
113 """ 114 Make instance lighter when a custom attribute is deleted. 115 """ 116 super(PackageElement, self).__delattr__(name) 117 if not hasattr(self.__class__, name): 118 self._decrease_weight()
119 120 @classmethod
121 - def create_new(cls, owner, id):
122 """ 123 Factory method to create a new instance both in memory and backend. 124 125 This method will usually perform checks and conversions from its actual 126 arguments to the data expected to the backend. It is responsible for 127 1/ storing the data in the backend and 2/ initializing the instance 128 (for which it should reuse instantiate to reduce redundancy). 129 130 Note that this method *should* be tolerant w.r.t. its parameters, 131 especially accepting both element instances or ID-refs. 132 133 NB: this method does nothing and must not be invoked by superclasses 134 (indeed, it raises an exception). 135 """ 136 raise NotImplementedError("must be overridden in subclasses")
137 138 @staticmethod
139 - def _check_reference(pkg, element, type=None, required=False):
140 """ 141 Raise a ModelError if element is not referenceable by pkg, and (if 142 provided) if it has not the given type. Furthermore, if required is set 143 to True, raise a ModelError if element is None (else None is silently 144 ignored). 145 146 Note that element may be a strict ID-ref, in which case this method 147 will do its best to check its type, but will *succeed silently* if the 148 element is unreachable (because parsers need to be able to add 149 unreachable elements). 150 151 Also, return the ID-ref of that element in this element's owner package, 152 for this information is usually useful in the situations where a check 153 is performed. If element is None, return "". 154 """ 155 if element is None or element == "": 156 if required: 157 raise ModelError("required element") 158 else: 159 return "" 160 161 if isinstance(element, basestring): 162 assert element.find(":") > 0 # imported 163 element_id = element 164 element = pkg.get(element_id) 165 if element is not None and element.ADVENE_TYPE != type: 166 raise ModelError("type mismatch", element, type) 167 else: 168 # silently succeed, for the sake of parsers 169 return element_id 170 171 assert isinstance(element, PackageElement) 172 if type is not None: 173 elttype = element.ADVENE_TYPE 174 if elttype != type: 175 raise ModelError("type mismatch", element, type) 176 if not pkg._can_reference(element): 177 raise ModelError("can not reference", pkg, element) 178 return element.make_id_in(pkg)
179
180 - def make_id_in(self, pkg):
181 """Compute an id-ref for this element in the context of the given package. 182 """ 183 if self._owner is pkg: 184 return self._id 185 186 # breadth first search in the import graph 187 queue = pkg._imports_dict.items() 188 current = 0 # use a cursor rather than actual pop 189 visited = {pkg:True} 190 parent = {} 191 found = False 192 while not found and current < len(queue): 193 prefix,p = queue[current] 194 if p is self._owner: 195 found = True 196 else: 197 if p is not None: 198 visited[p] = True 199 for prefix2,p2 in p._imports_dict.iteritems(): 200 if p2 not in visited: 201 queue.append((prefix2,p2)) 202 parent[(prefix2,p2)] = (prefix,p) 203 current += 1 204 if not found: 205 raise ValueError("Element is not reachable from that package") 206 r = self._id 207 c = queue[current] 208 while c is not None: 209 r = "%s:%s" % (c[0], r) 210 c = parent.get(c) 211 return r
212
213 - def iter_references(self, package=None):
214 """ 215 Iter over all references that are made to this element. 216 217 A reference is represented by a tuple of the form 218 * ('item', list) 219 * ('member', relation) 220 * ('meta', package_or_element, key) 221 * ('tagged', package, tag) 222 * ('tagging', package, element) -- for tags only 223 * (attribute_name, other_element) 224 225 References are searched in the given package. If no package is given, 226 references are searched in this element's owner package and in all 227 packages that are currently loaded and directly importing this 228 packages. 229 """ 230 o = self._owner 231 if package is None: 232 referrers = o._get_referrers() 233 else: 234 referrers = {package._backend : {package._id : package}} 235 for be, d in referrers.iteritems(): 236 for pid, eid, rel in be.iter_references(d, self._get_uriref()): 237 yield Reference(self, d[pid], eid, rel)
238
239 - def delete(self):
240 """ 241 Delete this element. 242 243 If the element is known to be referenced by other elements, 244 all remaining references are cut (but the referer elements are 245 not deleted). Note that this does not guarantees that some 246 references the the deleted element will not continue to exist in 247 packages that are not currently loaded. 248 """ 249 self.emit("pre-deleted") 250 for r in self.iter_references(): 251 r.cut() 252 self._owner._backend.delete_element(self._owner._id, self._id, 253 self.ADVENE_TYPE) 254 del self._owner._elements[self._id] 255 self.emit("deleted") 256 self.__class__ = DeletedPackageElement
257 258 @autoproperty
259 - def _get_id(self):
260 """ 261 The identifier of this element in the context of its owner package. 262 """ 263 return self._id
264 265 @autoproperty
266 - def _set_id(self, new_id):
267 """ 268 Rename this element to `new_id`, if it is not already in use in the 269 package, else raises an AssertionError. 270 """ 271 o = self._owner 272 importers = o._importers 273 assert not o.has_element(new_id) 274 old_id = self._id 275 self.emit("pre-renamed") 276 # renaming in the owner package 277 o._backend.rename_element(o._id, old_id, self.ADVENE_TYPE, new_id) 278 # best effort renaming in all (known) importing packages 279 old_uriref = self.uriref 280 ibe_dict = o._get_referrers() 281 for be, d in ibe_dict.iteritems(): 282 be.rename_references(d, old_uriref, new_id) 283 # actually renaming 284 del o._elements[old_id] 285 o._elements[new_id] = self 286 self._id = new_id 287 # updating caches of packages and instantiated elements 288 new_uriref = self._get_uriref() 289 for be, d in ibe_dict.iteritems(): 290 for pid, eid, rel in be.iter_references(d, new_uriref): 291 p = d[pid] 292 prefix = importers.get(p, "") # p may be the owner package 293 old_idref = prefix and "%s:%s" % (prefix, old_id) or old_id 294 new_idref = prefix and "%s:%s" % (prefix, new_id) or new_id 295 if eid == "": 296 p._update_caches(old_idref, new_idref, self, rel) 297 else: 298 e = p._elements.get(eid) # only update *instantiated* elts 299 if e is not None: 300 e._update_caches(old_idref, new_idref, self, rel) 301 # furthermore, if this element is an import, all id-refs using this 302 # import must be updated 303 # NB: since this method is embeded in a property, it can not easily 304 # be overloaded, that is why we implement this here rather than in 305 # import_.py 306 if self.ADVENE_TYPE is IMPORT: 307 del o._imports_dict[old_id] 308 o._imports_dict[new_id] = self._imported 309 self._imported._importers[o] = new_id 310 for eid, rel, ref \ 311 in o._backend.iter_references_with_import(o._id, new_id): 312 old_idref = "%s:%s" % (old_id, ref) 313 new_idref = "%s:%s" % (new_id, ref) 314 if eid == "": 315 o._update_caches(old_idref, new_idref, None, rel) 316 else: 317 e = o._elements.get(eid) # only update *instantiated* elts 318 if e is not None: 319 e._update_caches(old_idref, new_idref, None, rel) 320 self.emit("renamed")
321
322 - def _update_caches(self, old_idref, new_idref, element, relation):
323 """ 324 This cooperative method is used to update all caches when an element 325 in the cache is renamed. The old_idref and new_idref are provided, 326 as well as the relation (as represented by backend methods 327 `iter_references` and `iter_references_with_import`) with this element. 328 The renamed element may be provided or be None, depending on the 329 situation. 330 """ 331 super(PackageElement, self) \ 332 ._update_caches(old_idref, new_idref, element, relation)
333 334 335 @autoproperty
336 - def _get_uriref(self):
337 """ 338 The URI-ref identifying this element. 339 340 It is built from the URI of its owner package, suffixed with the id 341 of the element as a fragment-id (#). 342 """ 343 o = self._owner 344 u = o._uri or o._url 345 return "%s#%s" % (u, self._id)
346 347 @autoproperty
348 - def _get_owner(self):
349 """ 350 The package containing (or owner package) this element. 351 """ 352 return self._owner
353 354 # tag management 355
356 - def iter_my_tags(self, package=None, inherited=True):
357 """Iter over the tags associated with this element in ``package``. 358 359 If ``package`` is not set, the session variable ``package`` is used 360 instead. If the latter is not set, a TypeError is raised. 361 362 If ``inherited`` is set to False, the tags associated by imported 363 packages of ``package`` will not be yielded. 364 365 If a tag is unreachable, None is yielded. 366 367 See also `iter_my_tag_ids`. 368 """ 369 return self._iter_my_tags_or_tag_ids(package, inherited, True)
370
371 - def iter_my_tag_ids(self, package=None, inherited=True, _get=0):
372 """Iter over the id-refs of the tags associated with this element in 373 ``package``. 374 375 If ``package`` is not set, the session variable ``package`` is used 376 instead. If the latter is not set, a TypeError is raised. 377 378 If ``inherited`` is set to False, the tags associated by imported 379 packages of ``package`` will not be yielded. 380 381 See also `iter_my_tags`. 382 """ 383 # this actually also implements iter_my_tags 384 # see _iter_my_tags_or_tag_ids below 385 if package is None: 386 package = session.package 387 if package is None: 388 raise TypeError("no package set in session, must be specified") 389 u = self._get_uriref() 390 if not inherited: 391 pids = (package._id,) 392 get_element = package.get_element 393 for pid, tid in package._backend.iter_tags_with_element(pids, u): 394 if _get: 395 y = package.get_element(tid, None) 396 else: 397 y = tid 398 yield y 399 else: 400 for be, pdict in package._backends_dict.iteritems(): 401 for pid, tid in be.iter_tags_with_element(pdict, u): 402 p = pdict[pid] 403 if _get: 404 y = p.get_element(tid, None) 405 else: 406 y = package.make_id_for(p, tid) 407 yield y
408 409 @alias(iter_my_tag_ids)
410 - def _iter_my_tags_or_tag_ids(self):
411 # iter_my_tag_ids and iter_my_tags have a common implementation. 412 # Normally, it should be located in a "private" method named 413 # _iter_my_tags_or_tag_id. 414 # However, for efficiency reasons, that private method and 415 # iter_my_tag_ids have been merged into one. Both names are necessary 416 # because the "public" iter_my_tag_ids may be overridden while the 417 # "private" method should not. Hence that alias. 418 pass
419
420 - def iter_taggers(self, tag, package=None):
421 """Iter over all the packages associating this element to ``tag``. 422 423 ``package`` is the top-level package. If not provided, the ``package`` 424 session variable is used. If the latter is unset, a TypeError is 425 raised. 426 """ 427 if package is None: 428 package = session.package 429 if package is None: 430 raise TypeError("no package set in session, must be specified") 431 eu = self._get_uriref() 432 tu = tag._get_uriref() 433 for be, pdict in package._backends_dict.iteritems(): 434 for pid in be.iter_taggers(pdict, eu, tu): 435 yield pdict[pid]
436
437 - def has_tag(self, tag, package=None, inherited=True):
438 """Is this element associated to ``tag`` by ``package``. 439 440 If ``package`` is not provided, the ``package`` session variable is 441 used. If the latter is unset, a TypeError is raised. 442 443 If ``inherited`` is set to False, only return True if ``package`` 444 itself associates this element to ``tag``; else return True also if 445 the association is inherited from an imported package. 446 """ 447 if package is None: 448 package = session.package 449 if package is None: 450 raise TypeError("no package set in session, must be specified") 451 if not inherited: 452 eu = self._get_uriref() 453 tu = tag._get_uriref() 454 it = package._backend.iter_taggers((package._id,), eu, tu) 455 return bool(list(it)) 456 else: 457 return list(self.iter_taggers(tag, package))
458 459 # reference management 460
461 - def _increase_weight(self):
462 """ 463 Elements are created with weight 0. Increasing its weight is equivalent 464 to creating a strong reference to it, making it not volatile. Once the 465 reason for keeping the element is gone, the weight should be decreased 466 again with `_decrease_weight`. 467 """ 468 # FIXME: this is not threadsafe ! 469 self._weight += 1 470 if self._weight == 1: 471 self._owner._heavy_elements.add(self)
472
473 - def _decrease_weight(self):
474 """ 475 :see: _increase_weight 476 """ 477 # FIXME: this is not threadsafe ! 478 self._weight -= 1 479 if self._weight == 0: 480 self._owner._heavy_elements.remove(self)
481 482 # events management 483
484 - def _make_event_delegate(self):
485 """ 486 Required by WithEventsMixin 487 """ 488 return ElementEventDelegate(self)
489
490 - def emit(self, detailed_signal, *args):
491 """ 492 Override WithEventsMixin.emit in order to automatically emit the 493 package signal corresponding to each element signal. 494 """ 495 WithEventsMixin.emit(self, detailed_signal, *args) 496 def lazy_params(): 497 colon = detailed_signal.find(":") 498 if colon > 0: s = detailed_signal[:colon] 499 else: s = detailed_signal 500 yield _package_event_template[self.ADVENE_TYPE] % s 501 yield self 502 yield s 503 yield args
504 self._owner.emit_lazy(lazy_params)
505
506 - def connect(self, detailed_signal, handler, *args):
507 """ 508 Connect a handler to a signal. 509 510 Note that an element with connected signals becomes heavier (i.e. less 511 volatile). 512 513 :see: `WithEventsMixin.connect` 514 """ 515 r = super(PackageElement, self).connect(detailed_signal, handler, *args) 516 self._increase_weight() 517 return r
518
519 - def disconnect(self, handler_id):
520 """ 521 Disconnect a handler from a signal. 522 523 :see: `connect` 524 :see: `WithMetaMixin.disconnect` 525 """ 526 r = super(PackageElement, self).disconnect(handler_id) 527 self._decrease_weight() 528 return r
529
530 - def _self_connect(self, detailed_signal, handler, *args):
531 """ 532 This alternative to `connect` can only be used by the element itself. 533 It connects the handler to the signal but *does not* make the element 534 heavier (since if the handler will disappear at the same time as the 535 element...). 536 """ 537 return super(PackageElement, self) \ 538 .connect(detailed_signal, handler, *args)
539 540 541 @tales_property 542 @tales_use_as_context("package")
543 - def _tales_my_tags(self, context_package):
544 class TagCollection(ElementCollection): 545 __iter__ = lambda s: self.iter_my_tags(context_package) 546 __contains__ = lambda s,x: self.has_tag(x, context_package)
547 return TagCollection(self._owner) 548
549 - def _compute_absolute_url(self, aliases):
550 base = self._owner._compute_absolute_url(aliases) 551 if base[-8:] == "/package": 552 # remove '/package' from the end, and add our id 553 return "%s:%s" % (base[:-8], self._id) 554 else: 555 return "%s/%s" % (base, self._id)
556 557 @tales_property
558 - def _tales_representation(self, context):
559 """Return a concise representation for the element. 560 """ 561 c=context.globals['options']['controller'] 562 return c.get_title(self)
563 564 @tales_property
565 - def _tales_color(self, context):
566 """Return the color of the element. 567 """ 568 c=context.globals['options']['controller'] 569 col=c.get_element_color(self) 570 if col is None: 571 return col 572 m=re.search('#(..)..(..)..(..)..', col) 573 if m: 574 # Approximate the color, since CSS specification only 575 # allows 24-bit color definition 576 return '#'+''.join(m.groups()) 577 else: 578 return col
579
580 -class DeletedPackageElement(object):
581 """ 582 I am just a dummy class to which deleted elements are mutated. 583 584 That way, they are no longer usable, preventing their owner from 585 unknowingly handling an element that has actually been deleted. 586 587 Note however that good practices should be to register to the deletion 588 event on the elements you reference, so as to be notified as soon as they 589 are deleted. 590 """ 591 pass
592
593 594 -class ElementCollection(object):
595 """ 596 A base-class for coder-friendly and TAL-friendly element collections. 597 598 Subclasses must override either __iter__ or both __len__ and __getitem__. 599 600 In most cases, it is a good idea to override __contains__, and __len__ 601 (even if the subclass is overriding __iter__). 602 603 The class attribute _allow_filtering can also be overridden to disallow 604 the use of the filter method. 605 """
606 - def __init__(self, owner_package):
607 """ 608 Initialise the element collection. 609 610 `owner_package`is used only in the `get` method, to provide a context 611 to the ID-ref. 612 """ 613 self._owner = owner_package
614
615 - def __eq__(self, other):
616 try: 617 o=tuple(other) 618 except TypeError: 619 return False 620 return tuple(self) == tuple(other)
621
622 - def __iter__(self):
623 """ 624 Default implementation relying on __len__ and __getitem__. 625 """ 626 for i in xrange(len(self)): 627 yield self[i]
628
629 - def __len__(self):
630 """ 631 Default (and inefficient) implementation relying on __iter__. 632 """ 633 return len(list(self))
634
635 - def __getitem__(self, key):
636 """ 637 Default implementation relying on __iter__. 638 """ 639 if isinstance(key, (int, long)): 640 if key >= 0: 641 for i,j in enumerate(self): 642 if i == key: 643 return j 644 raise IndexError, key 645 else: 646 return list(self)[key] 647 elif isinstance(key, slice): 648 if key.step is None or key.step > 0: 649 key = key.indices(self.__len__()) 650 return list(islice(self, *key)) 651 else: 652 return list(self)[key] 653 else: 654 r = self.get(key) 655 if r is None: 656 raise KeyError(key) 657 return r
658
659 - def __repr__(self):
660 return "[" + ",".join(self.keys()) + "]"
661
662 - def get(self, key, default=None):
663 e = self._owner.get(key) 664 if e is None: 665 return default 666 elif e in self: 667 return e 668 else: 669 return default
670
671 - def keys(self):
672 return [ e.make_id_in(self._owner) for e in self ]
673 674 _allow_filter = True 675
676 - def filter(collection, **kw):
677 """ 678 Use underlying iter method with the given keywords to make a filtered 679 version of that collection. 680 """ 681 if not collection._allow_filter: 682 raise TypeError("filtering is not allowed on %r") % collection 683 class FilteredCollection(ElementCollection): 684 def __iter__ (self): 685 return collection.__iter__(**kw)
686 def __len__(self): 687 return collection.__len__(**kw)
688 def filter(self, **kw): 689 raise NotImplementedError("can not filter twice") 690 return FilteredCollection(collection._owner) 691 692 @property
693 - def _tales_size(self):
694 """Return the size of the group. 695 """ 696 return self.__len__()
697 698 @property
699 - def _tales_first(self):
700 try: 701 return self.__iter__().next() 702 except StopIteration: 703 return None
704 705 @property
706 - def _tales_rest(self):
707 class RestCollection(ElementCollection): 708 def __iter__(self): 709 it = self.__iter__() 710 it.next() 711 for i in it: yield i
712 def __len__(self): 713 return self.__len__()-1 714 def filter(self, **kw): 715 raise NotImplementedError("RestCollection can not be filtered") 716 return RestCollection(self) 717
718 -class ElementCollectionWrapper(ElementCollection):
719 """Wrap an ElementCollection around an existing list. 720 """
721 - def __init__(self, l, p):
722 super(ElementCollectionWrapper, self).__init__(p) 723 self._wrapped=l
724
725 - def __len__(self):
726 return len(self._wrapped)
727
728 - def __iter__(self):
729 return self._wrapped.__iter__()
730
731 - def __getitem__(self, i):
732 return self._wrapped.__getitem__(i)
733
734 -class Reference(object):
735 """ 736 An object representing a reference from an element or package to an 737 element. 738 """
739 - def __init__(self, referree, package, element_id, relation):
740 self._f = referree 741 self._p = package 742 self._e = element_id 743 self._r = relation
744 745 @property
746 - def referrer(self):
747 eid = self._e 748 if eid == "": 749 return self._p 750 else: 751 return self._p.get(eid, _RAISE)
752 753 @property
754 - def reference_type(self):
755 return self._r.split(" ")[0]
756 757 @property
758 - def reference_parameter(self):
759 L = self._r.split(" ") 760 if len(L) == 1: 761 return None 762 elif L[0] in (":item", ":member"): 763 return int(L[1]) 764 elif L[0].startswith(":tag"): # :tag or :tagged 765 try: 766 return self._p.get(L[1]) 767 except UnreachableImportError: 768 return L[1] 769 except NoSuchElementError: 770 return L[1] 771 else: 772 return L[1]
773
774 - def cut(self):
775 L = self._r.split(" ") 776 typ = L[0] 777 referrer = self.referrer 778 if typ in (":item", ":member"): 779 del referrer[int(L[1])] 780 elif typ == ":tag": 781 p = self._p 782 tagged = p.get(L[1]) or L[1] 783 p.dissociate_tag(tagged, self._f) 784 elif typ == ":tagged": 785 p = self._p 786 tag = p.get(L[1]) or L[1] 787 p.dissociate_tag(self._f, tag) 788 elif typ == ":meta": 789 referrer.del_meta(L[1]) 790 else: 791 setattr(referrer, typ, None)
792
793 - def replace(self, other):
794 raise NotImplementedError()
795