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

Source Code for Module cells.cell

   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  Cell, and subclasses of Cell. You will almost certainly never use 
  21  these directly, instead relying on C{L{Model}} and its subclasses to 
  22  instantiate these into objects for you. 
  23   
  24  You should know what these do, though, as you can specify 
  25  C{L{cells.makecell}} or C{L{cells.fun2cell}} instantiate a specific 
  26  type of cell for you: 
  27   
  28      >>> class A(cells.Model): 
  29      ...     l = cells.makecell(value=[1,3,5], celltype=cells.ListCell) 
  30      ...     @cells.fun2cell(celltype=cells.RuleThenInputCell) 
  31      ...     def from_a_global(self, prev): 
  32      ...         global e 
  33      ...         return e["something"] 
  34   
  35   
  36  @var DEBUG: Turns on debugging messages for the cell module. 
  37   
  38  @group Only Inherited: Cell, LazyCell 
  39   
  40  @group Cell Types: RuleCell, InputCell, RuleThenInputCell, 
  41      AlwaysLazyCell, UntilAskedLazyCell, DictCell, ListCell 
  42   
  43  @group Exceptions: EphemeralCellUnboundError, InputCellRunError, 
  44      RuleAndValueInitError, RuleCellSetError, SetDuringNotificationError 
  45  """ 
  46   
  47  DEBUG = False 
  48   
  49  import cells 
  50  import weakref 
  51  import copy 
  52  import UserDict 
  53   
54 -def _debug(*msgs):
55 """ 56 debug() -> None 57 58 Prints debug messages. 59 """ 60 msgs = [ str(_) for _ in msgs ] 61 msgs.insert(0, "cell".rjust(cells._DECO_OFFSET) + " > ") 62 if DEBUG or cells.DEBUG: 63 print " ".join(msgs)
64 65
66 -class Cell(object):
67 """ 68 The base Cell class. Does everything interesting. 69 """
70 - def __init__(self, owner, **kwargs):
71 """ 72 __init__(self, owner, name=None, rule=None, value=None, 73 unchanged_if=None) -> None 74 75 Initializes a Cell object. You must not specify both C{rule} 76 and C{value}. 77 78 @param name: This cell's name. When using a C{Cell} with 79 C{L{Model}}s, this parameter is assigned automatically. 80 81 @param rule: Define a rule which backs this cell. You must 82 only define one of C{rule} or C{value}. Lacking C{celltype}, 83 this creates a L{RuleCell}. This must be passed a 84 callable with the signature C{f(self, prev) -> value}, 85 where C{self} is the model instance the cell is in and 86 C{prev} is the cell's out-of-date value. 87 88 @param value: Define a value for this cell. You must only 89 define one of C{rule} or C{value}. Lacking C{celltype}, this 90 creates an L{InputCell}. 91 92 @param unchanged_if: Sets a function to determine if a cell's 93 value has changed. For example, 94 95 >>> class A(cells.Model): 96 ... x = cells.makecell(value=1, 97 ... unchanged_if=lambda n,o:abs(n-o)>5) 98 ... y = cells.makecell(rule=lambda s,p: s.x * 2) 99 ... 100 >>> a = A() 101 >>> a.x 102 1 103 >>> a.y 104 2 105 >>> a.x = 3 106 >>> a.x 107 3 108 >>> a.y 109 6 110 >>> a.x = 90 111 >>> a.x 112 3 113 >>> a.y 114 6 115 116 The signature for the passed function is C{f(old, new) -> 117 bool}. 118 119 @param ephemeral: Causes the cell to reset its value to None 120 after propogating when it is set. Only makes sense when 121 applied to InputCells 122 123 @raise RuleAndValueInitError: If both C{rule} and C{value} are 124 passed, raise an exception 125 126 """ 127 _debug("running cell init for", kwargs.get("name") or 'anonymous') 128 129 if kwargs.get("value", None) and kwargs.get("rule", None): 130 raise RuleAndValueInitError( 131 "Cell.__init__ was passed both rule and value parameters") 132 133 self.owner = owner 134 self.name = kwargs.get("name", None) 135 self.rule = kwargs.get("rule", lambda s,p: None) 136 self.value = kwargs.get("value", None) 137 self.ephemeral = kwargs.get("ephemeral", False) 138 self.unchanged_if = kwargs.get("unchanged_if", lambda o,n: o == n) 139 140 self.called_by = set([]) #: the cells whose rules call this cell 141 self.calls = set([]) #: the cells which this cell's rule calls 142 143 self.dp = 0 144 self.changed_dp = 0 145 self.bound = False 146 147 self.constant = False 148 self.notifying = False 149 150 self.propogate_to = None 151 self.lazy = False 152 self.last_value = None 153 154 #: storage for synapses used in this cell's (possible) rule 155 self.synapse_space = {} 156 157 if kwargs.has_key("value"): 158 self.bound = True 159 self.changed_dp = cells.cellenv.dp 160 self.dp = cells.cellenv.dp
161
162 - def getvalue(self):
163 """ 164 getvalue(self, init=False) -> value 165 166 Returns this cell's up-to-date value. 167 """ 168 # if there's a cell on the call stack, this get is part of a rule 169 # run. so, make the appropriate changes to the cells' deps 170 if cells.cellenv.curr: # (curr == None when not propogating) 171 cells.cellenv.curr.add_calls(self) 172 self.add_called_by(cells.cellenv.curr) 173 174 self.updatecell() 175 return self.value
176
177 - def set(self, value):
178 """ 179 set(self, value) -> None 180 181 Sets this cell's value and begins propogation of the change, 182 if neccessary. 183 184 @param value: The value to set this cell's value to. 185 """ 186 if cells.cellenv.curr_propogator: # if a propogation is happening 187 _debug(self.name, "sees in-progress propogation; deferring set.") 188 # ... defer the set 189 cells.cellenv.deferred_sets.append((self, ("set", 190 ((value,), {})))) 191 else: 192 _debug(self.name, "setting") 193 if not self.unchanged_if(self.value, value): 194 _debug(self.name, "new value is different; propogating change") 195 self.last_value = self.value 196 self.value = value 197 198 cells.cellenv.dp += 1 199 self.dp = cells.cellenv.dp 200 201 self.propogate() 202 203 if self.ephemeral: 204 self.value = None
205
206 - def updatecell(self, queryer=None):
207 """ 208 updatecell(self, queryer=None) -> bool 209 210 Updates this cell to the current global DP (datapulse), 211 returning True if it changed, False otherwise. 212 213 @param queryer: The cell to L{propogate} to first, if 214 neccessary 215 """ 216 _debug(self.name, "updating") 217 if queryer: 218 self.propogate_to = queryer 219 220 if not self.bound: # if this cell has never been calc'd: 221 _debug(self.name, "unbound, rerunning") 222 self.run() # it's never current 223 return True 224 if self.changed(): # if this cell was changed in this DP 225 _debug(self.name, "changed, telling queryer to recalc") 226 return True # the asking cell must recalculate. 227 if self.dp == cells.cellenv.dp: # if this cell is current, 228 _debug(self.name, "is current.") 229 return False # it's current. 230 if not cells.cellenv.curr_propogator: # if the system isn't propogating, 231 if not self.lazy: # and we're not lazy, 232 _debug(self.name, "sees system is not propogating; is current.") 233 self.dp = cells.cellenv.dp 234 return False # this cell is current. 235 236 # otherwise, verify we're current: (by the above ifs, the 237 # system is propogating and this cell is not current) 238 for cell in self.calls_list(): 239 _debug(self.name, "asking", cell.name, "to update") 240 if cell.updatecell(self): # if any called cell requires us, 241 _debug(self.name, "got recalc command from", cell.name) 242 if not self.dp == cells.cellenv.dp: 243 if self.run(): # we need to re-run 244 # the run changed the value of this cell, so 245 # propogate the change, starting at the cell which 246 # requested this cell update 247 pt = queryer 248 if self.propogate_to: 249 pt = self.propogate_to 250 self.propogate(pt) 251 self.propogate_to = None 252 253 # after that run(), self.calls is out of date, and 254 # all the cells the rule calls are neccessarily 255 # up-to-date, so bomb out of this loop right 256 # now. This cell's up-to-date. 257 return False 258 259 # if we get here, no cell called by this cell required this cell to 260 # update, so update DP and return False (since this cell didn't change) 261 _debug(self.name, 262 "finished asking called cells to update without getting recalc;", 263 "is current.") 264 self.dp = cells.cellenv.dp 265 return False
266
267 - def propogate(self, propogate_first=None):
268 """ 269 propogate(self, propogate_first=None) -> None 270 271 Propogates an updatecell command to the set of cells which call 272 this cell. 273 274 @param propogate_first: If cell C{A} asks cell C{B} to update, 275 and cell C{B}'s value changes (causing a C{propogate} 276 call), it must propogate to C{A} first, before any of the 277 other cells which call C{B}. 278 """ 279 if cells.cellenv.curr_propogator: 280 _debug(self.name, "propogating. Old propogator was", 281 cells.cellenv.curr_propogator.name) 282 else: 283 _debug(self.name, "propogating. No old propogator") 284 285 prev_propogator = cells.cellenv.curr_propogator 286 cells.cellenv.curr_propogator = self 287 self.changed_dp = cells.cellenv.dp 288 self.notifying = True 289 290 if self.owner: 291 self.owner._run_observers(self) 292 293 # first, notify the 'propogate_first' cell 294 if propogate_first: 295 # append everything but the propogate_first cell onto the deferred 296 # propogation FIFO 297 cells.cellenv.queued_updates.extend( 298 Cell.propogation_list(self, propogate_first)) 299 300 _debug(self.name, "first propogating to", propogate_first.name, 301 "then adding", 302 [ cell.name for cell in 303 Cell.propogation_list(self, propogate_first) ], 304 "to deferred") 305 306 _debug(self.name, "asking", propogate_first.name, "to update first") 307 propogate_first.updatecell() 308 _debug(self.name, "finished propogating to first update", 309 propogate_first.name) 310 311 else: 312 _debug(self.name, "propogating to", 313 [ cell.name for cell in 314 Cell.propogation_list(self, propogate_first) ]) 315 316 # weird for testing 317 for cell in Cell.propogation_list(self, propogate_first): 318 if cell.lazy: 319 _debug(self.name, "saw", cell.name, 320 ", but it's lazy -- not updating") 321 else: 322 _debug(self.name, "asking", cell.name, "to update") 323 cell.updatecell(self) 324 325 self.notifying = False 326 cells.cellenv.curr_propogator = prev_propogator 327 328 if cells.cellenv.curr_propogator: 329 _debug(self.name, "finished propogating; switching to propogating", 330 str(cells.cellenv.curr_propogator.name)) 331 else: 332 _debug(self.name, "finished propogating. No old propogator") 333 334 # run deferred stuff if no cell is currently propogating 335 if not cells.cellenv.curr_propogator: 336 # first, updates: 337 # okay, this is a little hacky: 338 cells.cellenv.curr_propogator = cells.Cell(None, 339 name="queued propogation dummy") 340 341 _debug("no cell propogating! running deferred updates.") 342 to_update = cells.cellenv.queued_updates 343 cells.cellenv.queued_updates = [] 344 for cell in to_update: 345 if cell.lazy: 346 _debug(self.name, "saw", cell.name, 347 ", but it's lazy -- not running deferred updated") 348 else: 349 _debug("Running deferred update on", cell.name) 350 cell.updatecell(self) 351 352 cells.cellenv.curr_propogator = None 353 354 # next, deferred set-ish commands: 355 _debug("running deferred sets") 356 to_set = cells.cellenv.deferred_sets 357 cells.cellenv.deferred_sets = [] 358 for cell, cmdargtuple in to_set: 359 cmd, argtuple = cmdargtuple 360 args, kwargs = argtuple 361 _debug("running deferred", cmd, "on", cell.name) 362 args, kwargs = argtuple 363 getattr(cell, cmd)(*args, **kwargs)
364 365
366 - def run(self):
367 """ 368 run(self) -> bool 369 370 Runs the backing function (rule) for this cell. The sequence is: 371 372 1. Remove this cell from all other cell's called-by sets 373 374 2. Empty this cell's calls set. 375 376 3. Run the function, which as a side effect may add this 377 cell to other cells' called-by sets and add links in this 378 cell's calls set 379 380 4. If this cell's value changes, 381 382 4.1 Run any L{observer}s in this cell's Model 383 384 4.2 Return C{True} 385 """ 386 _debug(self.name, "running") 387 # call stack manipulation 388 oldcurr = cells.cellenv.curr 389 cells.cellenv.curr = self 390 391 # the rule run may rewrite the dep graph; prepare for that by nuking 392 # c-b links to this cell and calls links from this cell: 393 self.remove_called_bys() 394 self.reset_calls() 395 396 self.dp = cells.cellenv.dp # we're up-to-date 397 newvalue = self.rule(self.owner, self.value) # run the rule 398 self.bound = True 399 400 # restore old running cell 401 cells.cellenv.curr = oldcurr 402 403 # return changed status 404 if self.unchanged_if(self.value, newvalue): 405 _debug(self.name, "unchanged.") 406 return False 407 else: 408 _debug(self.name, "changed.") 409 self.last_value = self.value 410 self.value = newvalue 411 412 # run any observers on this cell 413 if self.owner: 414 self.owner._run_observers(attribute=self) 415 416 return True
417
418 - def remove_called_bys(self):
419 """ 420 remove_called_bys(self) -> None 421 422 Remove this cell from the called_by lists of all cells this cell calls 423 """ 424 for cell in self.calls_list(): 425 _debug(self.name, "removing c-b link from", cell.name) 426 cell.remove_cb(self)
427
428 - def changed(self):
429 """ 430 changed(self) -> bool 431 432 Did this cell's value change in this DP (datapulse)? 433 """ 434 return cells.cellenv.dp == self.changed_dp
435
436 - def calls_list(self):
437 """ 438 calls_list(self) -> generator 439 440 Returns a generator of cell objects whose rules call this cell 441 """ 442 return (r() for r in self.calls)
443
444 - def called_by_list(self):
445 """ 446 called_by_list(self) -> generator 447 448 Returns a generator of cell objects which this cell's rule calls 449 """ 450 return (r() for r in self.calls)
451
452 - def propogation_list(self, elide=None):
453 """ 454 propogation_list(self, elide=None) -> generator 455 456 Returns a generator of cell objects which this cell should 457 propogate to, minus any cell passed in C{elide}. 458 459 @param elide: Remove a cell from the list of cells to 460 propogate a change to. Used by L{propogate} to remove a 461 cell it had to propogate to first. 462 """ 463 return (r() for r in self.called_by - set([elide]))
464
465 - def add_calls(self, *calls_cells):
466 """Appends the passed list of cells to this cell's calls list""" 467 self.calls.update(set([ weakref.ref(cell) for cell in calls_cells ]))
468
469 - def add_called_by(self, *cb_cells):
470 """Appends the passed list of cells to this cell's called-by list""" 471 self.called_by.update(set([ weakref.ref(cell) for cell in cb_cells ]))
472
473 - def remove_cb(self, *cb_cells):
474 """Removes the passed list of cells from this cell's called-by list""" 475 self.called_by.difference_update(set( 476 [ weakref.ref(cell) for cell in cb_cells ]))
477
478 - def reset_calls(self):
479 """Resets the calls list to empty""" 480 self.calls = set([])
481 482 483 # epydoc can't handle lambdas in a param list, apparently 484 _nonerule = lambda s,p: None 485
486 -class RuleCell(Cell):
487 """A cell whose value is determined by a function (a rule)."""
488 - def __init__(self, owner, rule=_nonerule, *args, **kwargs):
489 """ 490 __init__(self, owner, name=None, rule=lambda s,p: None, 491 unchanged_if=None) -> None 492 493 Initializes a RuleCell object, which may not be C{set} and 494 whose value is determined by a rule. 495 496 @param name: This cell's name. When using a C{Cell} with 497 C{L{Model}}s, this parameter is assigned automatically. 498 499 @param rule: Define a rule which backs this cell. You must 500 only define one of C{rule} or C{value}. Lacking C{celltype}, 501 this creates a L{RuleCell}. This must be passed a 502 callable with the signature C{f(self, prev) -> value}, 503 where C{self} is the model instance the cell is in and 504 C{prev} is the cell's out-of-date value. 505 506 @param unchanged_if: Sets a function to determine if a cell's 507 value has changed. The signature for the passed function 508 is C{f(old, new) -> bool}. 509 510 @raise RuleCellSetError: If C{value} is passed as a parameter 511 """ 512 if kwargs.get("value", None): 513 raise RuleCellSetError("cannot define a RuleCell's value") 514 Cell.__init__(self, owner, rule=rule, *args, **kwargs)
515
516 - def set(self, value):
517 """ 518 set(self, value) -> None 519 520 You may not C{set} a L{RuleCell} 521 522 @raise RuleCellSetError: Always raises this exception. 523 """ 524 raise RuleCellSetError("cannot set() a rule cell")
525 526
527 -class InputCell(Cell):
528 """A cell whose value can be set"""
529 - def __init__(self, owner, value=None, *args, **kwargs):
530 """ 531 __init__(self, owner, name=None, rule=None, value=None, 532 unchanged_if=None) -> None 533 534 Initializes an InputCell object. You may not pass a C{rule}. 535 536 @param name: This cell's name. When using a C{Cell} with 537 C{L{Model}}s, this parameter is assigned automatically. 538 539 @param value: Define a value for this cell. You must only 540 define one of C{rule} or C{value}. Lacking C{celltype}, this 541 creates an L{InputCell}. 542 543 @param unchanged_if: Sets a function to determine if a cell's 544 value has changed. The signature for the passed function 545 is C{f(old, new) -> bool}. 546 547 @raise InputCellRunError: If C{rule} is passed as a parameter 548 """ 549 if kwargs.get("rule", None): 550 raise InputCellRunError("You may not give an InputCell a rule") 551 Cell.__init__(self, owner, value=value, *args, **kwargs)
552
553 - def run(self):
554 """ 555 run(self) -> None 556 557 You may not C{run()} an L{InputCell} 558 559 @raise InputCellRunError: Always raises 560 """ 561 raise InputCellRunError("attempt to run InputCell '" + self.name + "'")
562 563
564 -class RuleThenInputCell(Cell):
565 """Runs the rule to determine initial value, then acts like a InputCell"""
566 - def __init__(self, *args, **kwargs):
567 """ 568 __init__(self, owner, name=None, rule=lambda s,p: None, 569 unchanged_if=None) -> None 570 571 Initializes a RuleThenInputCell, whose initial value is 572 determined by the passed C{rule} but then acts like an L{InputCell} 573 574 @param name: This cell's name. When using a C{Cell} with 575 C{L{Model}}s, this parameter is assigned automatically. 576 577 @param rule: Define a rule which backs this cell. You must 578 only define one of C{rule} or C{value}. Lacking C{celltype}, 579 this creates a L{RuleCell}. This must be passed a 580 callable with the signature C{f(self, prev) -> value}, 581 where C{self} is the model instance the cell is in and 582 C{prev} is the cell's out-of-date value. 583 584 @param unchanged_if: Sets a function to determine if a cell's 585 value has changed. The signature for the passed function 586 is C{f(old, new) -> bool}. 587 588 @raise RuleCellSetError: If C{value} is passed as a parameter 589 """ 590 if kwargs.get("value", None): 591 raise RuleCellSetError("cannot define a RuleCell's value") 592 593 Cell.__init__(self, *args, **kwargs) 594 self.run() 595 self.remove_called_bys() 596 self.reset_calls() 597 self.rule = None 598 self.bound = True
599
600 - def run(self):
601 """ 602 run(self) -> None 603 604 You may not C{run()} an L{RuleThenInputCell} after the initial 605 C{run()} 606 607 @raise InputCellRunError: Raises if instance has been bound 608 """ 609 if self.bound: 610 raise InputCellRunError("attempt to run RuleThenInputCell '" + 611 self.name) 612 else: 613 Cell.run(self)
614 615
616 -class LazyCell(RuleCell):
617 """ 618 A RuleCell which does not update upon propogation. When it has 619 C{L{get}()} run, it updates as other Cells do. 620 """
621 - def __init__(self, *args, **kwargs):
622 """ 623 __init__(self, owner, name=None, rule=lambda s,p: None, 624 unchanged_if=None) -> None 625 626 Initializes a LazyCell object, which may not be C{set} and 627 whose value is determined by a rule. 628 629 @param name: This cell's name. When using a C{Cell} with 630 C{L{Model}}s, this parameter is assigned automatically. 631 632 @param rule: Define a rule which backs this cell. You must 633 only define one of C{rule} or C{value}. Lacking C{celltype}, 634 this creates a L{RuleCell}. This must be passed a 635 callable with the signature C{f(self, prev) -> value}, 636 where C{self} is the model instance the cell is in and 637 C{prev} is the cell's out-of-date value. 638 639 @param unchanged_if: Sets a function to determine if a cell's 640 value has changed. The signature for the passed function 641 is C{f(old, new) -> bool}. 642 643 @raise RuleCellSetError: If C{value} is passed as a parameter 644 """ 645 646 RuleCell.__init__(self, *args, **kwargs) 647 self.lazy = True
648 649 # class OnceAskedLazyCell(LazyCell): 650 # pass 651
652 -class AlwaysLazyCell(LazyCell):
653 """ 654 A LazyCell variant that is *always* Lazy 655 """ 656 pass
657
658 -class UntilAskedLazyCell(LazyCell):
659 """ 660 A LazyCell who converts to a normal RuleCell after its first 661 post-init C{L{get}()} 662 """
663 - def getvalue(self, init=False, *args, **kwargs):
664 v = LazyCell.getvalue(self, *args, **kwargs) 665 if not init: 666 self.lazy = False 667 668 return v
669
670 -class DictCell(InputCell, UserDict.DictMixin):
671 """ 672 A input cell whose value is initialized to {}. An ordinary 673 InputCell doesn't act like we'd like it to in this case: 674 675 >>> class A(cells.Model): 676 ... x = cells.makecell(value={}) 677 ... @cells.fun2cell() 678 ... def xkeys(self, prev): 679 ... return self.x.keys() 680 ... 681 >>> a = A() 682 >>> a.x 683 {} 684 >>> a.xkeys 685 [] 686 >>> a.x['foo'] = 'bar' 687 >>> a.x 688 {'foo': 'bar'} 689 >>> a.xkeys 690 [] 691 692 But if we use a DictCell, this will act like we'd like it to: 693 694 >>> class A(cells.Model): 695 ... x = cells.makecell(value={}, type=DictCell) 696 ... @cells.fun2cell() 697 ... def xkeys(self, prev): 698 ... return self.x.keys() 699 ... 700 >>> a = A() 701 >>> a.x 702 {} 703 >>> a.xkeys 704 [] 705 >>> a.x['foo'] = 'bar' 706 >>> a.x 707 {'foo': 'bar'} 708 >>> a.xkeys 709 ['foo'] 710 711 Note that C{unchanged_if} now operates on dictionary values, 712 rather than the dictionary itself. 713 """
714 - def __init__(self, owner, *args, **kwargs):
715 """ 716 __init__(self, owner, name=None, rule=None, value=None, 717 unchanged_if=None) -> None 718 719 Initializes an DictCell object. You may not pass a C{rule}. 720 721 @param name: This cell's name. When using a C{Cell} with 722 C{L{Model}}s, this parameter is assigned automatically. 723 724 @param value: Define a value for this cell. This must be a 725 dictionary. 726 727 @param unchanged_if: Sets a function to determine if the 728 dictionary's value has changed. The signature for the 729 passed function is C{f(old, new) -> bool}. 730 731 @raise InputCellRunError: If C{rule} is passed as a parameter 732 """ 733 if kwargs.get("rule", None): 734 raise InputCellRunError("You may not give an InputCell a rule") 735 Cell.__init__(self, owner, value=kwargs.pop("value", {}), 736 *args, **kwargs)
737
738 - def setdefault(self, key, value):
739 _debug(self.name, "got setdefault") 740 self.value.setdefault(key, value)
741
742 - def __setitem__(self, key, value):
743 """ 744 __setitem__(self, key, value) -> None 745 746 Sets this cell's value's key's value and begins propogation of 747 the change to the dict, if neccessary. 748 749 @param key: The value to get out of the cell.value dict 750 751 @param value: The value to set this cell's value's key's value to. 752 """ 753 _debug(self.name, "setting setitem as dictcell") 754 if cells.cellenv.curr_propogator: # if a propogation is happening 755 _debug(self.name, "sees in-progress propogation; deferring set.") 756 # defer the set 757 cells.cellenv.deferred_sets.append((self, ("__setitem__", 758 ((key, value), {})))) 759 else: 760 _debug(self.name, "setting") 761 if not self.value.has_key(key) or \ 762 not self.unchanged_if(self.value[key], value): 763 _debug(self.name, "new value is different; propogating change") 764 self.last_value = copy.copy(self.value) 765 self.value[key] = value 766 767 cells.cellenv.dp += 1 768 self.dp = cells.cellenv.dp 769 770 if self.owner: 771 self.owner._run_observers(self) 772 773 self.propogate()
774
775 - def __delitem__(self, key):
776 del(self.value[key]) 777 cells.cellenv.dp += 1 778 self.dp = cells.cellenv.dp 779 780 if self.owner: 781 self.owner._run_observers(self) 782 783 self.propogate()
784
785 - def __repr__(self):
786 return repr(self.value)
787
788 - def get(self, key, default=None):
789 # if there's a cell on the call stack, this get is part of a rule 790 # run. so, make the appropriate changes to the cells' deps 791 _debug(self.name, "getting", repr(key)) 792 if cells.cellenv.curr: # (curr == None when not propogating) 793 cells.cellenv.curr.add_calls(self) 794 self.add_called_by(cells.cellenv.curr) 795 796 self.updatecell() 797 798 return self.value.get(key, default)
799
800 - def __getitem__(self, key):
801 """ 802 __getitem__(self, key) -> value 803 804 Gets the value in self.value[key] 805 806 @param key: lookup 807 """ 808 if cells.cellenv.curr: # (curr == None when not propogating) 809 cells.cellenv.curr.add_calls(self) 810 self.add_called_by(cells.cellenv.curr) 811 812 self.updatecell() 813 return self.value[key]
814
815 - def keys(self):
816 """ 817 keys(self) -> list 818 819 Gets self.value.keys() 820 """ 821 if cells.cellenv.curr: # (curr == None when not propogating) 822 cells.cellenv.curr.add_calls(self) 823 self.add_called_by(cells.cellenv.curr) 824 825 self.updatecell() 826 return self.value.keys()
827
828 - def __contains__(self, key):
829 if cells.cellenv.curr: # (curr == None when not propogating) 830 cells.cellenv.curr.add_calls(self) 831 self.add_called_by(cells.cellenv.curr) 832 833 self.updatecell() 834 return self.value.__contains__(key)
835
836 - def __iter__(self):
837 if cells.cellenv.curr: # (curr == None when not propogating) 838 cells.cellenv.curr.add_calls(self) 839 self.add_called_by(cells.cellenv.curr) 840 841 self.updatecell() 842 return self.value.__iter__()
843
844 - def iteritems(self):
845 if cells.cellenv.curr: # (curr == None when not propogating) 846 cells.cellenv.curr.add_calls(self) 847 self.add_called_by(cells.cellenv.curr) 848 849 self.updatecell() 850 return self.value.iteritems()
851
852 -class ListCell(InputCell):
853 """ 854 A input cell whose value is initialized to []. An ordinary 855 InputCell doesn't act like we'd like it to in this case: 856 857 >>> import cells 858 >>> class A(cells.Model): 859 ... x = cells.makecell(value=[]) 860 ... @cells.fun2cell() 861 ... def xlen(self, prev): 862 ... return len(self.x) 863 ... 864 >>> a = A() 865 >>> a.x 866 [] 867 >>> a.xlen 868 0 869 >>> a.x.append("foo") 870 >>> a.x 871 ['foo'] 872 >>> a.xlen 873 0 874 875 But if we specify a ListCell, it should work as we expect it: 876 877 >>> import cells 878 >>> class A(cells.Model): 879 ... x = cells.makecell(value=[]) 880 ... @cells.fun2cell() 881 ... def xlen(self, prev): 882 ... return len(self.x) 883 ... 884 >>> a = A() 885 >>> a.x 886 [] 887 >>> a.xlen 888 0 889 >>> a.x.append("foo") 890 >>> a.x 891 ['foo'] 892 >>> a.xlen 893 1 894 895 Note that C{unchanged_if} acts on list elements rather than the 896 entire value. 897 """ 898
899 - def __init__(self, owner, *args, **kwargs):
900 """ 901 __init__(self, owner, name=None, rule=None, value=None, 902 unchanged_if=None) -> None 903 904 Initializes an DictCell object. You may not pass a C{rule}. 905 906 @param name: This cell's name. When using a C{Cell} with 907 C{L{Model}}s, this parameter is assigned automatically. 908 909 @param value: Define a value for this cell. This must be a 910 dictionary. 911 912 @param unchanged_if: Sets a function to determine if the 913 dictionary's value has changed. The signature for the 914 passed function is C{f(old, new) -> bool}. 915 916 @raise InputCellRunError: If C{rule} is passed as a parameter 917 """ 918 if kwargs.get("rule", None): 919 raise InputCellRunError("You may not give an InputCell a rule") 920 921 Cell.__init__(self, owner, value=kwargs.pop("value", []), 922 *args, **kwargs)
923
924 - def _onchanges(self):
925 if self.owner: self.owner._run_observers(self) 926 self.propogate()
927
928 - def _pregets(self):
929 if cells.cellenv.curr: # (curr == None when not propogating) 930 cells.cellenv.curr.add_calls(self) 931 self.add_called_by(cells.cellenv.curr) 932 933 self.updatecell()
934
935 - def _should_defer(self, name, argtuple):
936 _debug(self.name, "wonders if it should defer a", name) 937 if cells.cellenv.curr_propogator: # if a propogation is happening 938 _debug(self.name, "sees in-progress propogation; deferring set.") 939 # defer the set 940 cells.cellenv.deferred_sets.append((self, (name, argtuple))) 941 return True 942 943 return False
944 945 # "get"-ish calls
946 - def count(self, v):
947 self._pregets() 948 return self.value.count(v)
949
950 - def index(self, v, start=None, stop=None):
951 self._pregets() 952 if start is None: start = 0 953 if stop is None: stop = len(self.value) 954 955 return self.value.index(v, start, stop)
956
957 - def __getitem__(self, k):
958 self._pregets() 959 return self.value.__getitem__(k)
960
961 - def __iter__(self):
962 self._pregets() 963 return self.value.__iter__()
964
965 - def __len__(self):
966 self._pregets() 967 return self.value.__len__()
968 969 # "set"-ish calls
970 - def pop(self, index=None):
971 """ 972 Warning: ListCell.pop() does not act quite like you may expect 973 it in certain circumstances. If a pop occurs during a 974 propogation, the value will be returned, but the list will not 975 be altered until the end of the propogation (thus ensuring all 976 cells "see" the same value of this cell during the DP). 977 """ 978 if not self._should_defer("pop", (index,)): 979 # not deferred, so do a real pop 980 r = self.value.pop(index) 981 cells.cellenv.dp += 1 982 self.dp = cells.cellenv.dp 983 self._onchanges() 984 else: 985 # deferred. grab the asked-for element of the list 986 if index is None: 987 index = -1 988 r = self.value[index] 989 990 return r
991
992 - def _make_listfun(name):
993 - def fn(self, *args, **kwargs):
994 if not self._should_defer(name, (args, kwargs)): 995 getattr(self.value, name)(*args, **kwargs) 996 cells.cellenv.dp += 1 997 self.dp = cells.cellenv.dp 998 self._onchanges()
999 fn.__name__ = name 1000 return fn
1001 1002 append = _make_listfun("append") 1003 extend = _make_listfun("extend") 1004 insert = _make_listfun("insert") 1005 remove = _make_listfun("remove") 1006 reverse = _make_listfun("reverse") 1007 sort = _make_listfun("sort") 1008 __add__ = _make_listfun("__add__") 1009 __iadd__ = _make_listfun("__iadd__") 1010 __mul__ = _make_listfun("__mul__") 1011 __rmul__ = _make_listfun("__rmul__") 1012 __imul__ = _make_listfun("__imul__") 1013 __setitem__ = _make_listfun("__setitem__") 1014 __delitem__ = _make_listfun("__delitem__")
1015 1016
1017 -class _CellException(Exception):
1018 - def __init__(self, value):
1019 self.value = value
1020 - def __str__(self):
1021 return repr(self.value)
1022
1023 -class RuleCellSetError(_CellException):
1024 """ 1025 RuleCells may not be set. 1026 """ 1027 pass
1028
1029 -class EphemeralCellUnboundError(_CellException):
1030 """ 1031 An EphemeralCell was C{L{get}}ted without being bound. 1032 """ 1033 pass
1034
1035 -class InputCellRunError(_CellException):
1036 """ 1037 An attempt to C{L{run}()} an InputCell was made. 1038 """ 1039 pass
1040
1041 -class SetDuringNotificationError(_CellException):
1042 """ 1043 An attempt at a non-deferred C{L{set}()} happened during 1044 propogation. 1045 """ 1046 pass
1047
1048 -class RuleAndValueInitError(_CellException):
1049 """ 1050 Both C{rule} and C{value} were passed to C{L{__init__}}. 1051 """ 1052 pass
1053