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):
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