Welcome, guest | Sign In | My Account | Store | Cart
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):
            self.name = 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"%self.name, value

        def print_l(self, value):
            print "%s Observed Length"%self.name, 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? ")

History

  • revision 5 (14 years ago)
  • previous revisions are not available