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

Source Code for Module cells.model

  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  Support for the C{L{Model}} object, an object in which C{L{CellAttr}}s 
 21  may be embedded. 
 22   
 23  @var DEBUG: Turns on debugging messages for the model module. 
 24  """ 
 25   
 26  import cells 
 27  from cell import Cell, EphemeralCellUnboundError 
 28  from cellattr import CellAttr 
 29  from observer import Observer, ObserverAttr 
 30   
 31  DEBUG = False 
 32   
33 -def debug(*msgs):
34 """ 35 debug() -> None 36 37 Prints debug messages. 38 """ 39 msgs = list(msgs) 40 msgs.insert(0, "model".rjust(cells._DECO_OFFSET) + " > ") 41 if DEBUG or cells.DEBUG: 42 print " ".join(( str(msg) for msg in msgs))
43
44 -class ModelMetatype(type):
45 - def __init__(klass, name, bases, dikt):
46 # copy over inherited registries of observers and non-cell attributes 47 klass._observernames = set([]) 48 klass._noncells = set([]) 49 50 for cls in bases: 51 obsnames = getattr(cls, "_observernames", None) 52 noncellnames = getattr(cls, "_noncells", None) 53 if obsnames: 54 klass._observernames.update(obsnames) 55 if noncellnames: 56 klass._noncells.update(noncellnames) 57 58 # do some work on various attributes of this class: 59 for k,v in dikt.iteritems(): 60 debug("metaclass inspecting", k) 61 if isinstance(v, CellAttr): 62 debug("metaclass adding name field to", k) 63 v.name = k 64 65 elif isinstance(v, ObserverAttr): 66 debug("registering observer", k) 67 klass._observernames.add(k) 68 69 else: # non-cell, non-observer attrib 70 debug("registering noncell", k) 71 klass._noncells.add(k)
72 73
74 -class Model(object):
75 """ 76 A class in which CellAttrs may be used. Models automatically bring 77 their cells up-to-date at C{L{__init__}}-time. Cells may be 78 altered at runtime by passing C{attrname=value}, or 79 C{attrname=hash} to the constructor. 80 81 @ivar model_name: A cell holding The name of this Model. By 82 default, None. 83 84 @ivar model_value: A cell holding the value of this Model. By 85 default, None. 86 87 @ivar parent: A cell for C{L{Family}} graph traversal. By default, 88 None. 89 """ 90 __metaclass__ = ModelMetatype 91 92 _initialized = False 93 94 model_name = cells.makecell(value=None, kid_overrides=False) 95 model_value = cells.makecell(value=None, kid_overrides=False) 96 parent = cells.makecell(value=None, kid_overrides=False) 97
98 - def __init__(self, *args, **kwargs):
99 """ 100 __init__(self, [<attrname>=<value, rule or dict>], ...) -> None 101 102 Initialize a Model with optional overrides. By passing a 103 parameter with the same name as a cell attribute, you may 104 override that cell attribute. For example: 105 106 >>> class A(cells.Model): 107 ... x = cells.makecell(value=1) 108 ... 109 >>> a1 = A() 110 >>> a1.x 111 1 112 >>> a2 = A(x="blah") 113 >>> a2.x 114 'blah' 115 116 This override can be arbitrarily complex; for instance, you 117 can make a RuleCell into a ValueCell, change a attribute's 118 celltype ... In short, anything you can do at Model defintion 119 time you can alter at instantiation time: 120 121 >>> class B(cells.Model): 122 ... x = cells.makecell(rule=lambda s,p: 3 * s.y) 123 ... y = cells.makecell(value=2) 124 ... 125 >>> b = B() 126 >>> b.x 127 6 128 >>> b.y = 1 129 >>> b.x 130 3 131 >>> b = B(y=10) 132 >>> b.x 133 30 134 >>> b.y 135 10 136 >>> b = B(x={'celltype': cells.RuleThenInputCell}) 137 >>> b.x 138 6 139 >>> b.y 140 2 141 >>> b.x = 5 142 >>> b.x 143 5 144 >>> b.y = 1 145 >>> b.x 146 5 147 148 @param attrname: The name of the attribute you wish to 149 override. If this is set to a callable, it will override 150 the rule for the cell. If it's set to a dictionary with 151 one or more of 'rule', 'value', or 'celltype', those 152 attributes will be overridden in the cell. Otherwise, it 153 will override the value of the target cell. 154 """ 155 self._initregistry = {} 156 klass = self.__class__ 157 158 # do automagic overriding: 159 for k,v in kwargs.iteritems(): # for each keyword arg 160 if k in dir(klass): # if there's a match in my class 161 # normalize the input 162 if callable(v): 163 cellinit = {'rule': v} 164 elif 'keys' in dir(v): 165 # kinda ran out of synonyms/shortened versions of 166 # keys, here. I just want to see if any of 'rule', 167 # 'value', or 'celltype' are in the keys of the 168 # dict in v: 169 quays = v.keys() 170 for qui in ('rule', 'value', 'celltype'): 171 if qui in quays: 172 cellinit = v 173 break 174 else: 175 cellinit = {'value': v} 176 177 if not cellinit: 178 raise BadInitError("A cell initialization dictionary was not built. Try wrapping your value or rule assignment in a dictionary.") 179 180 # set the new init in the registry for this cell name; to be 181 # read at cell-build time 182 self._initregistry[k] = cellinit 183 184 # turn the observer name registry into a list of real Observers 185 self._observers = [] 186 for name in self._observernames: 187 self._observers.append(getattr(self, name)) 188 189 # do initial equalizations 190 debug("INITIAL EQUALIZATIONS START") 191 for name in dir(self): 192 try: 193 getattr(self, name) # will run observers by itself 194 except EphemeralCellUnboundError, e: 195 debug(name, "was an unbound ephemeral") 196 debug("INITIAL EQUALIZATIONS END") 197 198 # run observers on non-cell attributes 199 for key in self._noncells: 200 self._run_observers(getattr(self, key)) 201 202 # and now we're initialized. lock the object down. 203 self._initialized = True
204
205 - def __setattr__(self, key, value):
206 """ 207 Per KT's spec, Models may not set non-cell attributes after 208 __init__. 209 210 @raise NonCellSetError: If you try to set a non-cell attribute 211 """ 212 # always set Cells 213 if isinstance(self.__dict__.get(key), Cell): 214 object.__setattr__(self, key, value) 215 # we can set noncells before init 216 elif not self._initialized: 217 if key not in self._noncells: # make sure it's registered, though 218 self._noncells.add(key) 219 object.__setattr__(self, key, value) # and then set it 220 # we can set anything we've not seen, too 221 elif key not in self.__dict__.keys(): 222 object.__setattr__(self, key, value) 223 # but the only thing left is non-cells we've seen, which is verboten 224 else: 225 raise NonCellSetError, "Setting non-cell attributes of models " + \ 226 "after init is disallowed"
227
228 - def _run_observers(self, attribute):
229 """Runs each observer in turn. There's some optimization that 230 could go on here, if it turns out to be neccessary. 231 """ 232 debug("model running observers -- ", str(len(self._observers)), 233 "to test") 234 for observer in self._observers: 235 observer.run_if_applicable(self, attribute)
236
237 - def _buildcell(self, name, *args, **kwargs):
238 """ 239 240 """ 241 debug("Building cell: owner:", str(self)) 242 debug(" name:", name) 243 debug(" args:", str(args)) 244 debug(" kwargs:", str(kwargs)) 245 # figure out what type the user wants: 246 if kwargs.has_key('celltype'): 247 celltype = kwargs["celltype"] 248 elif kwargs.has_key('rule'): # it's a rule-cell. 249 celltype = cells.RuleCell 250 elif kwargs.has_key('value'): # it's a value-cell 251 celltype = cells.InputCell 252 else: 253 raise Exception("Could not determine target type for cell " + 254 "given owner: " + str(self) + 255 ", name: " + name + 256 ", args:" + str(args) + 257 ", kwargs:" + str(kwargs)) 258 259 kwargs['name'] = name 260 return celltype(self, *args, **kwargs)
261 262 @classmethod
263 - def observer(klass, attrib=None, oldvalue=None, newvalue=None):
264 """ 265 observer(attrib=None, oldvalue=None, newvalue=None) -> decorator 266 267 A classmethod to add an observer attribute to a Model. The 268 observer may be set to fire on any change in the model, any 269 change in an attribute, or when a function testing the new or 270 old value of a cell returns true. 271 272 >>> import cells 273 >>> class A(cells.Model): 274 ... x = cells.makecell(value=4) 275 ... 276 >>> @A.observer(attrib="x", newvalue=lambda a: a % 2) 277 ... def odd_x_obs(model): 278 ... print "New value of x is odd!" 279 ... 280 >>> a = A() 281 >>> a.x 282 4 283 >>> a.x = 5 284 New value of x is odd! 285 >>> a.x = 42 286 >>> a.x = 11 287 New value of x is odd! 288 289 @param attrib: An attribute name to attach the observer to 290 291 @param oldvalue: A function to run on the now-out-of-date 292 value of a cell which changed in this datapulse; if the 293 function returns True, the observer will fire. The 294 signature for the function must be C{f(val) -> bool} 295 296 @param newvalue: A function to run on up-to-date value; if the 297 function returns True, the observer will fire. The 298 signature for the function must be C{f(val) -> bool} 299 """
300 - def observer_decorator(func):
301 klass._observernames.add(func.__name__) 302 setattr(klass, func.__name__, ObserverAttr(func.__name__, attrib, 303 oldvalue, newvalue, 304 func))
305 return observer_decorator
306 307
308 -class NonCellSetError(Exception):
309 """ 310 You may not set a non-cell Model attribute after initialization. 311 """
312 - def __init__(self, value):
313 self.value = value
314 - def __str__(self):
315 return repr(self.value)
316
317 -class BadInitError(Exception):
318 - def __init__(self, value):
319 self.value = value
320 - def __str__(self):
321 return repr(self.value)
322