Package cells :: Module observer
[hide private]
[frames] | no frames]

Source Code for Module cells.observer

  1  # PyCells: Automatic dataflow management for Python 
  2  # Copyright (C) 2006, Ryan Forsythe 
  3   
  4  # This library is free software; you can redistribute it and/or 
  5  # modify it under the terms of the GNU Lesser General Public 
  6  # License as published by the Free Software Foundation; either 
  7  # version 2.1 of the License, or (at your option) any later version. 
  8  # See LICENSE for the full license text. 
  9   
 10  # This library is distributed in the hope that it will be useful, 
 11  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
 13  # Lesser General Public License for more details. 
 14   
 15  # You should have received a copy of the GNU Lesser General Public 
 16  # License along with this library; if not, write to the Free Software 
 17  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA 
 18   
 19  """ 
 20  Classes which deal with observers, bits of code which fire when a 
 21  C{L{Model}} is updated and updated cells match certain conditions of 
 22  the observer, such as cell name or statements about the cell's value. 
 23   
 24  @var DEBUG: Turns on debugging messages for the observer module. 
 25  """ 
 26   
 27  import cells 
 28  from cells import Cell 
 29   
 30  DEBUG = False 
 31   
32 -def _debug(*msgs):
33 msgs = list(msgs) 34 msgs.insert(0, "observer".rjust(cells._DECO_OFFSET) + " > ") 35 if DEBUG or cells.DEBUG: 36 print " ".join(msgs)
37 38 # we want observers to be defined at the class level but have per-instance 39 # information. So, do the same trick as is done with CellAttr/Cells
40 -class ObserverAttr(object):
41 """ 42 Wrapper for Observers within Models. Will auto-vivify an Observer 43 within a Model instance the first time it's called. 44 """
45 - def __init__(self, name, *args, **kwargs):
46 self.name, self.args, self.kwargs = name, args, kwargs
47
48 - def __get__(self, owner, ownertype):
49 if not owner: return self 50 # if there isn't a value in owner.myname, make it an observer 51 _debug("got request for observer", self.name, 52 "args =", str(self.args), 53 "kwargs =", str(self.kwargs)) 54 if self.name not in owner.__dict__.keys(): 55 owner.__dict__[self.name] = Observer(*self.args, 56 **self.kwargs) 57 return owner.__dict__[self.name]
58
59 -class Observer(object):
60 """ 61 Wrapper for a function which fires when a C{L{Model}} updates and 62 certain conditions are met. Observers may be bound to specific 63 attributes or whether a function returns true when handed a cell's 64 old value or new value, or any combination of the above. An 65 observer that has no conditions on its running runs whenever the 66 Model updates. Observers with multiple conditions will only fire 67 when all the conditions pass. Observers run at most once per 68 datapulse. 69 70 You should use the C{L{Model.observer}} decorator to add Observers 71 to Models: 72 73 >>> import cells 74 >>> class A(cells.Model): 75 ... x = cells.makecell(value=4) 76 ... 77 >>> @A.observer(attrib="x", 78 ... newvalue=lambda a: a % 2) 79 ... def odd_x_obs(model): 80 ... print "New value of x is odd!" 81 ... 82 >>> @A.observer(attrib="x") 83 ... def x_obs(model): 84 ... print "x got changed!" 85 ... 86 >>> @A.observer() 87 ... def model_obs(model): 88 ... print "something in the model changed" 89 ... 90 >>> @A.observer(attrib="x", 91 ... newvalue=lambda a: a % 2, 92 ... oldvalue=lambda a: not (a % 2)) 93 ... def was_even_now_odd_x_obs(model): 94 ... print "New value of x is odd, and it was even!" 95 ... 96 >>> a = A() 97 something in the model changed 98 x got changed! 99 >>> a.x = 5 100 something in the model changed 101 x got changed! 102 New value of x is odd! 103 New value of x is odd, and it was even! 104 >>> a.x = 11 105 something in the model changed 106 x got changed! 107 New value of x is odd! 108 >>> a.x = 42 109 something in the model changed 110 x got changed! 111 112 113 @ivar attrib: (optional) The cell name this observer watches. Only 114 when a cell with this name changes will the observer fire. You 115 may also pass a list of cell names to "watch". 116 117 @ivar oldvalue: A function (signature: C{f(val) -> bool}) which, 118 if it returns C{True} when passed a changed cell's out-of-date 119 value, allows the observer to fire. 120 121 @ivar newvalue: A function (signature: C{f(val) -> bool}) which, 122 if it returns C{True} when passed a changed cell's out-of-date 123 value, allows the observer to fire. 124 125 @ivar func: The function to run when the observer 126 fires. Signature: C{f(model_instance) -> (ignored)} 127 128 @ivar last_ran: The DP this observer last ran in. Observers only 129 run once per DP. 130 """ 131
132 - def __init__(self, attrib, oldvalue, newvalue, func):
133 """ 134 __init__(self, attrib, oldvalue, newvalue, func) 135 136 Initializes a new Observer. All arguments are required, but 137 only func is required to be anything but none. 138 139 See attrib, oldvalue, and newvalue instance variable docs for 140 explanation of their utility. 141 """ 142 self.attrib_name = attrib 143 self.oldvalue = oldvalue 144 self.newvalue = newvalue 145 self.func = func 146 self.last_ran = 0
147
148 - def run_if_applicable(self, model, attr):
149 """ 150 Determine whether this observer should fire, and fire if 151 appropriate. 152 153 @param model: the model instance to search for matching cells 154 within. 155 156 @param attr: the attribute which "asked" this observer to run. 157 """ 158 _debug("running observer", self.func.__name__) 159 if self.last_ran == cells.cellenv.dp: # never run twice in one DP 160 _debug(self.func.__name__, "already ran in this dp") 161 return 162 163 if self.attrib_name: 164 if isinstance(self.attrib_name, str): 165 attrs = (self.attrib_name,) 166 else: 167 attrs = self.attrib_name 168 169 for attrib_name in attrs: 170 if isinstance(attr, Cell): 171 if attr.name == attrib_name: 172 _debug("found a cell with matching name!") 173 break 174 elif getattr(model, attrib_name) is attr: 175 _debug(self.func.__name__, "looked in its model for an " + 176 "attrib with its desired name; found one that " + 177 "matched passed attr.") 178 break 179 else: 180 _debug("Attribute name tests failed") 181 return 182 183 if self.newvalue: 184 if isinstance(attr, Cell): 185 if not self.newvalue(attr.value): 186 _debug(self.func.__name__, 187 "function didn't match cell's new value") 188 return 189 else: 190 if not self.newvalue(attr): 191 _debug(self.func.__name__, "function didn't match non-cell") 192 return 193 194 # since this is immediately post-value change, the last_value attr 195 # of the cell is still good. 196 if self.oldvalue: 197 if isinstance(attr, Cell): 198 if not self.oldvalue(attr.last_value): 199 _debug(self.func.__name__, 200 "function didn't match old value") 201 return 202 203 # if we're here, it passed all the tests, so 204 _debug(self.func.__name__, "running") 205 self.func(model) 206 self.last_ran = cells.cellenv.dp
207