from .observable import ObservableBase from util.introspect import decormethod import sys from logging import getLogger; log = getLogger('observablelist') try: sentinel except NameError: class Sentinel(object): def __repr__(self): return "<Sentinel (%r backup) %#x>" % (__file__, id(self)) sentinel = Sentinel() def list_notify(f): def wrapper1(self, *args, **kw): old_list = self[:] val = f(self, *args,**kw) self.notify('list', old_list, self) return val return wrapper1 class ObservableList(list, ObservableBase): "A list you can register observers on." def __init__(self, *args): ObservableBase.__init__(self) list.__init__(self, *args) self._observable_frozen = False self._freezecount = 0 @decormethod def notify_wrap(self, func, *args, **kw): """ Methods decorated with notify_wrap make a copy of the list before the operation, then notify observers of the change after. The list itself, the old list, and the new list are sent as arguments. """ val = func(self, *args,**kw) if not self._observable_frozen: self.notify('list', None, self) return val def add_list_observer(self, list_changed, child_changed, *attrs, **kwargs): ch = kwargs.get('childfunc', lambda child: child) # Sanity check -- this list must be holding Observable children if getattr(sys, 'DEV', False): for child in [ch(c) for c in self]: if not hasattr(child, 'add_observer'): raise TypeError("This list has non observable children") class ListWatcher(object): def __init__(self, srclist, list_callback, child_callback, *attrs, **kwargs): self.srclist = srclist self.listcopy = set(srclist) srclist.add_observer(self.on_list_changed, **kwargs) self.list_callback = list_callback self.child_args = [child_callback] + list(attrs) self.child_kwargs = dict(kwargs) # Initial observations. for child in [ch(c) for c in srclist]: child.add_observer(*self.child_args, **self.child_kwargs) def on_list_changed(self, src, attr, old, new): # if not hasattr(src, 'srclist'): # print >> sys.stderr, 'WARNING: observablelist.on_list_change not unlinked' # return assert src is self.srclist new = set(src) old = self.listcopy chargs = self.child_args kwargs = self.child_kwargs for newchild in (new - old): ch(newchild).add_observer(*chargs, **kwargs) for oldchild in (old - new): ch(oldchild).remove_observer(*chargs, **kwargs) self.listcopy = new def disconnect(self): chargs, kwargs = self.child_args, self.child_kwargs for child in self.srclist: child.remove_observer(*chargs) self.srclist.remove_observer(self.on_list_changed) self.srclist.remove_observer(self.list_callback) self.__dict__.clear() self.disconnect = self.disconnect_warning def disconnect_warning(self): log.critical('calling ListWatcher.disconnect more than once') # Inform of list structure changes if list_changed is not None: self.add_observer(list_changed, **kwargs) # Inform of list children changes # call .disconnect on this object when finished. return ListWatcher(self, list_changed, child_changed, *attrs, **kwargs) def remove_list_observer(self, list_changed, child_changed, *attrs): if list_changed is not None: self.remove_observer(list_changed) #TODO: uncruft this with some metaclass magic @notify_wrap def __setitem__(self, i, v): return list.__setitem__(self, i, v) @notify_wrap def __delitem__ (self,key): return list.__delitem__(self, key) @notify_wrap def __setslice__ (self, i, j, seq): return list.__setslice__(self, i, j, seq) @notify_wrap def __delslice__(self, i, j): return list.__delslice__(self, i, j) @notify_wrap def append(self, elem): return list.append(self, elem) @notify_wrap def pop(self, i = sentinel): if i is not sentinel: return list.pop(self, i) else: return list.pop(self) @notify_wrap def extend(self, seq): return list.extend(self, seq) @notify_wrap def insert(self, i, elem): return list.insert(self, i, elem) @notify_wrap def remove(self, elem): return list.remove(self, elem) @notify_wrap def reverse(self): return list.reverse(self) @notify_wrap def sort(self, *a, **k): return list.sort(self, *a, **k) @notify_wrap def __iadd__(self, item): return list.__iadd__(self, item) observable_list = ObservableList if __name__ == '__main__': from util.observe.observable import Observable class Buddy(Observable): def __init__(self, name, status = 'online'): Observable.__init__(self) self.name = name; self.status = status def __repr__(self): return '<Buddy %s (%s)>' % (self.name, self.status) buddies = ObservableList([Buddy(n) for n in 'Bobby Barbera Betty Ben'.split()]) def buddy_list_changed(src, attr, old, new): print 'The buddylist changed. New list is %r' % new def buddy_attr_changed(src, attr, old, new): print 'Buddy %r changed %s from %s to %s' % (src, attr, old, new) buddies.add_list_observer(buddy_list_changed, buddy_attr_changed) print 'Original list', buddies b = buddies[2] b.setnotify('status', 'away') del buddies[2] b.setnotify('status', 'online') # should not notify