import weakref import exceptions import threading # for lock class RecursionError(exceptions.RuntimeError): pass class _subscription(object): """A subscription is returned as a result of subscribing to an observable. When the subscription object is finalized, the subscription is cancelled. This class is used to facilitate subscription cancellation.""" def __init__(self, subscriber, observed): self.subscriber = subscriber self.observed = weakref.ref(observed) def __del__(self): obsrvd = self.observed() if (self.subscriber and obsrvd): obsrvd._cancel(self.subscriber) class _obwan(object): '''Half-hidden class. Only 'observable should construct these. Calls to subscribe, cancel get invoked through the observable. _obwan objects reside in class instances containing observables.''' def __init__(self): self.subscribers = [] self._value = None self._changeLock = threading.Lock() def __call__(self): """returns the current value, the one last set""" return self._value def _notifySubscribers(self, value): for (f,exceptionHdlr) in self._callbacks(): try: f(value) except Exception, ex: if exceptionHdlr and not exceptionHdlr(ex): raise # reraise if not handled def setvalu(self, value): """Notify the subcribers only when the value changes.""" if self._value != value: if self._changeLock.acquire(0): # non-blocking self._value = value try: self._notifySubscribers(value) finally: self._changeLock.release() else: raise RecursionError("Attempted recursion into observable's set method.") def subscribe(self, obsv, exceptionInfo = None): observer = obsv.setvalu if isinstance(obsv, _obwan) else obsv ob_info =(observer, exceptionInfo) self.subscribers.append(ob_info) return _subscription(ob_info, self) def _callbacks(self): scribers = [] scribers.extend(self.subscribers) return scribers def _cancel(self, wref): self.subscribers.remove(wref) class Observable(object): """An observable implemented as a descriptor. Subscribe to an observable via calling xxx.observable.subscribe(callback)""" def __init__(self, nam): self.xname = "__"+nam self.obwan = _obwan def __set__(self,inst, value ): """set gets the instances associated variable and calls its setvalu method, which notifies subribers""" if inst and not hasattr(inst, self.xname): setattr(inst, self.xname, self.obwan()) ow = getattr(inst, self.xname) ow.setvalu(value) def __get__(self, inst, klass): """get gets the instances associated variable returns it""" if inst and not hasattr(inst, self.xname): setattr(inst, self.xname, self.obwan()) return getattr(inst, self.xname) #----------------------------------------------------------------------- # Example & Simple Test #----------------------------------------------------------------------- if __name__ == '__main__': class MyClass(object): """ A Class containing the observables length and width""" length = Observable('length') width = Observable('width') def __init__(self): self.length.setvalu(0) self.width.setvalu(0) class MyObserver(object): """An observer class. The initializer is passed an instance of 'myClass' and subscribes to length and width changes. This observer also itself contains an observable, l2""" l2 = Observable('l2') def __init__(self, name, observedObj): = name self.subs1 = observedObj.length.subscribe(self.print_l) self.subs2 = observedObj.width.subscribe(self.print_w) """An observable can subscribe to an observable, in which case a change will chain through both subscription lists. Here l2's subscribers will be notified whenever observedObj.length changes""" self.subs3 = observedObj.length.subscribe(self.l2) def print_w(self, value): print "%s Observed Width", value def print_l(self, value): print "%s Observed Length", value def cancel(self): """Cancels the instances current subscriptions. Setting self.subs1 to None removes the reference to the subscription object, causing it's finalizer (__del__) method to be called.""" self.subs1 = None self.subs2 = None self.subs3 = None def pl2(value): print "PL2 reports ", value if type(value) == type(3): raise ValueError("pl2 doesn't want ints.") def handlePl2Exceptions( ex ): print 'Handling pl2 exception:', ex, type(ex) return True # true if handled, false if not area = MyClass() kent = MyObserver("Kent", area) billy = MyObserver("Billy", area) subscription = billy.l2.subscribe(pl2, handlePl2Exceptions) area.length = 6 area.width = 4 area.length = "Reddish" billy.subs1 = None print "Billy shouldn't report a length change to 5.15." area.length = 5.15 billy.cancel() print "Billy should no longer report" area.length = 7 area.width = 3 print "Areas values are ", area.length(), area.width() print "Deleting an object with observables having subscribers is ok" area = None area = MyClass() print "replaced area - no subscribers to this new instance" area.length = 5 area.width ="Bluish" c = raw_input("ok? ")