def serialize(self, f, full=False, indent=None, level=0): """Serialize the Element and it's sub-Elements.""" if indent is None: f.write("""\ from __future__ import with_statement import Tuke """) indent = [] self.serialize(f,full,indent=[],level=0) else: def out(s): for i in indent: f.write(i) f.write(s) from Tuke.repr_helper import shortest_class_name cname = shortest_class_name(self.__class__) s = '%s = %s(' % (self.__shadowless__.id,cname) out(s) indent.append(' ' * len(s)) kw = self._repr_kwargs() kw['id'] = self.__shadowless__.id kw['transform'] = self.__shadowless__.transform first = True for n,v in sorted(kw.iteritems()): if not first: f.write(',\n') out('%s=%s' % (n,repr(v))) else: first = False f.write('%s=%s' % (n,repr(v))) f.write('); ') if level > 0: f.write('__%d.add(%s)\n' % (level,self.__shadowless__.id)) else: f.write('__%d = %s\n' % (level,self.__shadowless__.id)) indent.pop() if not isinstance(self,ReprableByArgsElement) or full: subs = [] for e in self: if isinstance(e,Element): subs.append(e) subs.sort(key=lambda e: e.id) if subs: out('with %s as __%d:\n' % (self.__shadowless__.id,level + 1)) for e in subs: unwrap(e).serialize(f,full, indent=indent + [' ',], level=level + 1)
def _compute_implicit_callbacks(self): """Recompute the implicit callbacks chain. Starts fresh. The design here is to simply start fresh after each topology change. Given that most connects are going to be one or two levels deep, this is simpler, and probably more efficient, than a more complex "find what level changed" algorithm. """ class Invalidator: def __init__(self,ref): self.ref = weakref.ref(ref) self.valid = True def __call__(self): if self.valid: # One shot self.valid = False # Should only work if the original _elemref is alive. ref = self.ref() if ref is not None: ref._compute_implicit_callbacks() # Old invalidator is deleted, garbage collection will take care of the # rest. self._invalidator_callable = Invalidator(self) # Walk from our parent element, to the destination, setting callbacks # along the way. While we're at it, the reverse path is being # calculated in p, for insertion into the target's implicitly connected # dict. e = unwrap(self.connects._parent) p = Tuke.Id('.') for i in tuple(tuple.__iter__(self.ref)) + (None,): if i is not None: e.topology_notify(Tuke.Id(i),self._invalidator_callable) if i == '..': p = e.__shadowless__.id + p else: p = Tuke.Id('..') + p try: e = unwrap(e[Tuke.Id(i)]) except KeyError: # Didn't reach the last Element referenced in the path; # implicit connection doesn't exist because explicit connection # is dangling. break except TypeError: # Did reach the last Element referenced in the path. The # calculated reverse path is inserted as the key in # _implicitly_connected, the invalidator_callable is used as a # magic cookie. This is a WeakValueDict, so when the cookie # goes away, the entry does too. # # Note that gc-lag could turn this simple construct into a # problem when Element.remove is implemented. e.connects._implicitly_connected[p] = self._invalidator_callable break
def test_unwrap(self): """unwrap()""" def T(got,expected = True): self.assert_(expected == got,'got: %s expected: %s' % (got,expected)) a = Element(id=Id('a')) u = unwrap(a) T(u.id,Id('.')) u = unwrap(u) T(u.id,Id('.'))
def testElement_notify(self): """Element.notify()""" def T(got,expected = True): self.assert_(expected == got,'got: %s expected: %s' % (got,expected)) a = Element(id=Id('a')) class cb: def __init__(self): self.count = 0 def __call__(self): self.count += 1 self.assertRaises(ValueError, lambda:a.topology_notify(Id('a'),lambda:None)) self.assertRaises(ValueError, lambda:a.topology_notify(Id('z/y'),lambda:None)) self.assertRaises(TypeError, lambda:a.topology_notify(Id('.'),None)) # Test callback adding and deletion cb1 = cb() a.topology_notify(Id('a/b'),cb1) T(unwrap(a).__dict_callbacks__['b'],set((weakref.ref(cb1),))) cb2 = cb() a.topology_notify(Id('a/b'),cb2) T(unwrap(a).__dict_callbacks__['b'], set((weakref.ref(cb1), weakref.ref(cb2)))) del cb1 T(unwrap(a).__dict_callbacks__['b'],set((weakref.ref(cb2),))) del cb2 T(not unwrap(a).__dict_callbacks__.has_key('b') or unwrap(a).__dict_callbacks__['b'] == set()) # Test that callbacks are actually called # Called for adding an object cb1 = cb() a.topology_notify(Id('a/b'),cb1) cb2 = cb() a.topology_notify(Id('a/b'),cb2) b = Element(id=Id('b')) cbp = cb() b.topology_notify(Id('.'),cbp) a.add(b) T(cb1.count,1) T(cb2.count,1) T(cbp.count,1) T(not unwrap(a).__dict_callbacks__.has_key('b')) T(not unwrap(b).__dict_callbacks__.has_key('parent')) # Test for removing an object, just a check that there is no .remove # for now. T(not hasattr(a,'remove'))
def _common_parent(self, b): a = unwrap(self) b = unwrap(b) ap = set() bp = set() while ((not ap.intersection(bp))) and \ (a is not None or b is not None): if a is not None: ap.add(a) a = unwrap(a.parent) if b is not None: bp.add(b) b = unwrap(b.parent) p = ap.intersection(bp) if p: assert (len(p) == 1) return p.pop() else: return None
def _common_parent(self,b): a = unwrap(self) b = unwrap(b) ap = set() bp = set() while ((not ap.intersection(bp))) and \ (a is not None or b is not None): if a is not None: ap.add(a) a = unwrap(a.parent) if b is not None: bp.add(b) b = unwrap(b.parent) p = ap.intersection(bp) if p: assert(len(p) == 1) return p.pop() else: return None
def serialize(self, f, full=False, indent=None, level=0): """Serialize the Element and it's sub-Elements.""" if indent is None: f.write("""\ from __future__ import with_statement import Tuke """) indent = [] self.serialize(f, full, indent=[], level=0) else: def out(s): for i in indent: f.write(i) f.write(s) from Tuke.repr_helper import shortest_class_name cname = shortest_class_name(self.__class__) s = '%s = %s(' % (self.__shadowless__.id, cname) out(s) indent.append(' ' * len(s)) kw = self._repr_kwargs() kw['id'] = self.__shadowless__.id kw['transform'] = self.__shadowless__.transform first = True for n, v in sorted(kw.iteritems()): if not first: f.write(',\n') out('%s=%s' % (n, repr(v))) else: first = False f.write('%s=%s' % (n, repr(v))) f.write('); ') if level > 0: f.write('__%d.add(%s)\n' % (level, self.__shadowless__.id)) else: f.write('__%d = %s\n' % (level, self.__shadowless__.id)) indent.pop() if not isinstance(self, ReprableByArgsElement) or full: subs = [] for e in self: if isinstance(e, Element): subs.append(e) subs.sort(key=lambda e: e.id) if subs: out('with %s as __%d:\n' % (self.__shadowless__.id, level + 1)) for e in subs: unwrap(e).serialize(f, full, indent=indent + [ ' ', ], level=level + 1)
def _compute_implicit_callbacks(self): """Recompute the implicit callbacks chain. Starts fresh. The design here is to simply start fresh after each topology change. Given that most connects are going to be one or two levels deep, this is simpler, and probably more efficient, than a more complex "find what level changed" algorithm. """ class Invalidator: def __init__(self, ref): self.ref = weakref.ref(ref) self.valid = True def __call__(self): if self.valid: # One shot self.valid = False # Should only work if the original _elemref is alive. ref = self.ref() if ref is not None: ref._compute_implicit_callbacks() # Old invalidator is deleted, garbage collection will take care of the # rest. self._invalidator_callable = Invalidator(self) # Walk from our parent element, to the destination, setting callbacks # along the way. While we're at it, the reverse path is being # calculated in p, for insertion into the target's implicitly connected # dict. e = unwrap(self.connects._parent) p = Tuke.Id('.') for i in tuple(tuple.__iter__(self.ref)) + (None, ): if i is not None: e.topology_notify(Tuke.Id(i), self._invalidator_callable) if i == '..': p = e.__shadowless__.id + p else: p = Tuke.Id('..') + p try: e = unwrap(e[Tuke.Id(i)]) except KeyError: # Didn't reach the last Element referenced in the path; # implicit connection doesn't exist because explicit connection # is dangling. break except TypeError: # Did reach the last Element referenced in the path. The # calculated reverse path is inserted as the key in # _implicitly_connected, the invalidator_callable is used as a # magic cookie. This is a WeakValueDict, so when the cookie # goes away, the entry does too. # # Note that gc-lag could turn this simple construct into a # problem when Element.remove is implemented. e.connects._implicitly_connected[ p] = self._invalidator_callable break