Package Products :: Package ZenUtils :: Module observable |
[hide private]
[frames] | no frames]

# Source Code for Module Products.ZenUtils.observable

      1  ##############################################################################
      2  #
      3  # Copyright (C) Zenoss, Inc. 2009, all rights reserved.
      4  #
      5  # This content is made available according to terms specified in
      6  # License.zenoss under the directory where your Zenoss product is installed.
      7  #
      8  ##############################################################################
      9
     10
     11  """
     12  The observable module provides an interface and an optional mixin class that
     13  provide an Observer pattern for attribute based change notifications of an
     14  object.
     15
     16  An object that is Observable (i.e. provides the IObservable interface) will
     17  allow attribute observers, a.k.a. listeners, to be attached or detached for
     18  specific attribute names. These observers will be notified whenever the
     19  specified attribute changes value.
     20
     21  Observers can be any Python object that is callable.
     22
     23  An example of using the observer pattern in your implementation is:
     24
     25
     26  class MyObject(MyParentClass, ObservableMixin):
     27      def __init__(self):
     28          super(MyObject, self).__init__()
     29          self.name = "Super Duper Object"
     30          self.age = 42
     31
     32      def doIt(self):
     33          self.name = "I changed my name!"
     34          self.age = 37
     35
     36  def ageListener(observable, attrName, oldValue, newValue):
     37      print "Age has been changed from %d to %d" % (oldValue, newValue)
     38
     39  foo = MyObject()
     40  foo.attachAttributeObserver("age", ageListener)
     41  foo.doIt()
     42
     43
     44  This implementation will likely only work with new style classes that have been
     45  properly implemented.
     46  """
     47
     48  import Globals
     49  import zope.interface
     50
     51 -class IObservable(zope.interface.Interface):
     52      """
     53      Classes that implement the IObservable interface agree to provide the
     54      Observer pattern for object attribute changes.
     55      """
     56 -    def attachAttributeObserver(self, name, observer):
     57          """
     58          Attaches an observer that will be notified when the specified attribute
     59          changes value.
     60
     61          @param name the attribute name to observer
     62          @param observer the observer/listener to be notified
     63          @type observer callable
     64          """
     65          pass
     66
     67 -    def detachAttributeObserver(self, name, observer):
     68          """
     69          Removes an observer from watching the specified attribute.
     70
     71          @param name the attribute name to remove the observer from
     72          @param observer the observer/listener to be removed
     73          """
     74          pass
     75
     76 -    def notifyAttributeChange(self, name, oldValue, newValue):
     77          """
     78          Notify all registered observers that an attribute has changed value.
     79          Register observers must be a Python callable and will receive the
     80          following keyword arguments:
     81              observable - a reference to the observed object
     82              attrName - the attribute name that has changed
     83              oldValue - the previous value
     84              newValue - the new value
     85
     86          @param name the attribute name that has changed
     87          @param oldValue the old attribute value
     88          @param newValue the new attribute value
     89          """
     90          pass
     91
     92 -class ObservableMixin(object):
     93      """
     94      A mixin class that provides an implementation of the IObservable interface
     95      for any new-style class to use. This implementation will provide
     96      notification for all attribute changes, except for the attributes used
     97      to track the registered observers themselves.
     98      """
     99      zope.interface.implements(IObservable)
    100
    101 -    def __init__(self):
    102          super(ObservableMixin, self).__init__()
    103          self._observers = {}
    104
    105 -    def attachAttributeObserver(self, name, observer):
    106          if not callable(observer):
    107              raise ValueError("observer must be callable")
    108
    109          if not name in self._observers:
    110              self._observers[name] = []
    111
    112          if observer not in self._observers[name]:
    113              self._observers[name].append(observer)
    114
    115 -    def detachAttributeObserver(self, name, observer):
    116          try:
    117              self._observers[name].remove(observer)
    118          except (KeyError, ValueError):
    119              pass
    120
    121 -    def notifyAttributeChange(self, name, oldValue, newValue):
    122          # don't bother notifying if we don't have an _observers attribute
    123          # yet (during construction)
    124          if hasattr(self, '_observers') and name in self._observers:
    125              for observer in self._observers[name]:
    126                  observer(observable=self,
    127                           attrName=name,
    128                           oldValue=oldValue,
    129                           newValue=newValue)
    130
    131 -    def __setattr__(self, name, newValue):
    132          # override the __setattr__ method so that we can grab the previous
    133          # value and then notify all of the observers of the change
    134          oldValue = getattr(self, name, None)
    135          self._doSetattr(name, newValue)
    136          self.notifyAttributeChange(name, oldValue, newValue)
    137
    138 -    def _doSetattr(self, name, value):
    139          super(ObservableMixin, self).__setattr__(name, value)
    140
    141
    142 -class ObservableProxy(ObservableMixin):
    143
    144      reservedNames = ('_observers','_obj','_doSetattr','notifyAttributeChange', 'detachAttributeObserver',
    145                       'attachAttributeObserver')
    146
    147 -    def __init__(self, obj):
    148          super(ObservableProxy, self).__init__()
    149          super(ObservableMixin,self).__setattr__('_obj', obj)
    150
    151 -    def __getattribute__(self, name):
    152          if name in ObservableProxy.reservedNames:
    153              return super(ObservableProxy, self).__getattribute__(name)
    154          else:
    155              return self._obj.__getattribute__(name)
    156
    157 -    def _doSetattr(self, name, value):
    158          if name in ObservableProxy.reservedNames:
    159              return super(ObservableMixin, self).__setattr__(name, value)
    160          else:
    161              self._obj.__setattr__(name, value)
    162