├── demos ├── web │ ├── store │ │ ├── __footer__ │ │ ├── __header__ │ │ └── weblog_entry │ ├── static │ │ └── example.txt │ ├── templates │ │ ├── static.tmpl │ │ ├── directory.tmpl │ │ ├── just_edit.tmpl │ │ └── full.tmpl │ ├── cms.cfg │ ├── default.tmpl │ └── README ├── gui │ ├── calc.py │ └── celltk.py └── sql │ ├── cellql-demo.py │ └── cellql-tests.py ├── epydoc.cfg ├── setup.py ├── docs ├── index.html ├── frames.html ├── toc-cells.cellattr-module.html ├── toc-cells.synapse-module.html ├── toc-cells.family-module.html ├── toc-cells.observer-module.html ├── toc-cells.model-module.html ├── toc-cells-module.html ├── toc.html ├── toc-cells.cell-module.html ├── cells.cell.RuleCellSetError-class.html ├── cells.cell.InputCellRunError-class.html ├── cells.cell.EphemeralCellUnboundError-class.html ├── cells.cell.SetDuringNotificationError-class.html ├── cells.cell.RuleAndValueInitError-class.html ├── toc-everything.html ├── epydoc.css ├── cells.cell.CellException-class.html ├── epydoc.js ├── cells.family.FamilyTraversalError-class.html ├── cells.cell._CellException-class.html ├── cells.family.FamilyMeta-class.html └── cells.cell.OnceAskedLazyCell-class.html ├── tutorials ├── README ├── basic.py ├── observers.py ├── family.py └── cell_varieties.py ├── tests ├── synapses.py ├── family.py └── cell_types.py ├── README └── cells ├── synapse.py ├── __init__.py ├── family.py ├── cellattr.py └── observer.py /demos/web/store/__footer__: -------------------------------------------------------------------------------- 1 | _footer_ 2 | -------------------------------------------------------------------------------- /demos/web/store/__header__: -------------------------------------------------------------------------------- 1 | head. 2 | =========== -------------------------------------------------------------------------------- /demos/web/static/example.txt: -------------------------------------------------------------------------------- 1 | foo! 2 | bar! 3 | baz! 4 | woo! 5 | -------------------------------------------------------------------------------- /demos/web/templates/static.tmpl: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /epydoc.cfg: -------------------------------------------------------------------------------- 1 | [epydoc] 2 | 3 | modules: cells 4 | target: docs/ 5 | 6 | 7 | name: PyCells 8 | url: http://pycells.pdxcb.net/ 9 | -------------------------------------------------------------------------------- /demos/web/cms.cfg: -------------------------------------------------------------------------------- 1 | [directories] 2 | static = ./static 3 | templates = ./templates 4 | storage = ./store 5 | 6 | [server] 7 | address = localhost 8 | port = 8088 9 | -------------------------------------------------------------------------------- /demos/web/store/weblog_entry: -------------------------------------------------------------------------------- 1 | This is an example of a page in this system. You can edit it. You can go to `/__header__` and `/__footer__` if you want to change the header and footer. Go to any page and edit it to add it to the system. -------------------------------------------------------------------------------- /demos/web/templates/directory.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Index of <include_resource name="__path__" /> 5 | 6 | 7 | 8 |

Index of

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /demos/web/templates/just_edit.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | A page in the Cells CMS 4 | 5 | 6 | 7 | 8 |
"> 9 | Edit this page
10 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /demos/web/default.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | A page in the Cells CMS 4 | 5 | 6 | 7 | 8 | 9 | 10 | "> 11 | Edit this page
12 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /demos/web/templates/full.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | A page in the Cells CMS 4 | 5 | 6 | 7 | 8 | 9 | 10 | "> 11 | Edit this page
12 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # PyCells: Automatic dataflow management for Python 4 | # Copyright (C) 2006, Ryan Forsythe 5 | 6 | import ez_setup 7 | ez_setup.use_setuptools() 8 | from setuptools import setup, find_packages 9 | 10 | setup(name='PyCells', 11 | version='0.1b1', 12 | packages=find_packages(), 13 | extras_require = { 'Epydoc': ['epydoc>=2.1'], }, 14 | 15 | description='Automatic dataflow management', 16 | author='Ryan Forsythe', 17 | author_email='ryan@pdxcb.net', 18 | url='http://pycells.pdxcb.net/', 19 | license="LGPL", 20 | ) 21 | 22 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | PyCells 7 | 8 | 9 | 10 | 12 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs/frames.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | PyCells 7 | 8 | 9 | 10 | 12 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /tutorials/README: -------------------------------------------------------------------------------- 1 | These tutorials are meant to to step you through some of the basics of 2 | using cells. I'd strongly suggest looking at basic.py first, but 3 | the rest can be done in whatever order interests you the most. 4 | 5 | basic.py: Cells 101. Input cells and Rule cells are covered, 6 | along with using the Model class 7 | 8 | observers.py: In general, one wants to keep rules free of side 9 | effects. Observers allow you to hold to this and get some 10 | actual work done. 11 | 12 | cell_varieties.py: There's more to choose from in the cell 13 | menagerie than just input and rules. This tutorial 14 | introduces you to some of what's there. 15 | 16 | family.py: Some applications require an large number of 17 | similar Models. The Family class can ease some pains with 18 | these apps. 19 | -------------------------------------------------------------------------------- /demos/web/README: -------------------------------------------------------------------------------- 1 | Cells CMS: A wiki-type web thing demonstrating the use of cells in web 2 | applications. 3 | 4 | It's pretty simple. Modify cms.cfg to suit your environment -- the 5 | defaults should work fine unless you have server running on port 8088 6 | -- then run cms.py. It will begin serving static documents out of the 7 | configured static directory (./static, by default). Navigating to 8 | non-static files will either load out of the on-disk store (in 9 | ./store) or offer to create the file. All files are cached in memory 10 | (which could present a problem if you were to use this for anything serious). 11 | 12 | Editing documents -- which are cells -- causes the changes to cascade 13 | throughout the cached documents. For instance, if you modify the 14 | special element __header__, which is used in the default template, it 15 | will cascade that change automatically to all cached pages which use 16 | the template. 17 | -------------------------------------------------------------------------------- /demos/gui/calc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import celltk 4 | import cells 5 | import re 6 | 7 | class CalcInput(celltk.Entry): 8 | unacceptable = re.compile(r"[^0-9 \+-\.\*/\(\)]") 9 | 10 | @cells.fun2cell() 11 | def cleaned_input(self, prev): 12 | if self.text and not self.unacceptable.search(self.text): 13 | return self.text 14 | 15 | return "" 16 | 17 | @cells.fun2cell() 18 | def value(self, prev): 19 | if self.cleaned_input: 20 | try: 21 | v = eval(self.cleaned_input) 22 | except Exception: 23 | print "exception caught" 24 | return "error" 25 | print "value is", repr(v) 26 | return v 27 | else: 28 | return "No input or error" 29 | 30 | @CalcInput.observer(attrib="value") 31 | def outputter(model): 32 | if model.parent and model.parent.output and model.parent.output.stringvar: 33 | model.parent.output.stringvar.set(model.value) 34 | 35 | class CalculatorWindow(celltk.Window): 36 | def __init__(self, *args, **kwargs): 37 | celltk.Window.__init__(self, *args, **kwargs) 38 | self.input = CalcInput(parent=self, update_on="write") 39 | self.output = celltk.Label(parent=self) 40 | 41 | title = cells.makecell(value="Calculator Demo") 42 | input = cells.makecell(value=None) 43 | output = cells.makecell(value=None) 44 | 45 | @cells.fun2cell() 46 | def kids(self, prev): 47 | return [ self.input, self.output ] 48 | 49 | if __name__ == "__main__": 50 | c = CalculatorWindow() 51 | c.widget.pack() 52 | c.widget.mainloop() 53 | -------------------------------------------------------------------------------- /docs/toc-cells.cellattr-module.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | cellattr 7 | 8 | 9 | 10 | 11 | 13 |

Module cellattr

14 |
15 |

Classes

16 |

17 | CellAttr

Functions

19 |

20 | debug

Variables

22 |

23 | DEBUG


25 | [hide private] 27 | 28 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /docs/toc-cells.synapse-module.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | synapse 7 | 8 | 9 | 10 | 11 | 13 |

Module synapse

14 |
15 |

Classes

16 |

17 | ChangeSynapse

19 | Synapse

Functions

21 |

22 | debug

Variables

24 |

25 | DEBUG


27 | [hide private] 29 | 30 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /docs/toc-cells.family-module.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | family 7 | 8 | 9 | 10 | 11 | 13 |

Module family

14 |
15 |

Classes

16 |

17 | Family

19 | FamilyTraversalError

Functions

21 |
22 |

23 | _debug

25 |

Variables

26 |

27 | DEBUG


29 | [hide private] 31 | 32 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /docs/toc-cells.observer-module.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | observer 7 | 8 | 9 | 10 | 11 | 13 |

Module observer

14 |
15 |

Classes

16 |

17 | Observer

19 | ObserverAttr

Functions

21 |
22 |

23 | _debug

25 |

Variables

26 |

27 | DEBUG


29 | [hide private] 31 | 32 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /docs/toc-cells.model-module.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | model 7 | 8 | 9 | 10 | 11 | 13 |

Module model

14 |
15 |

Classes

16 |

17 | BadInitError

19 | Model

21 | ModelMetatype

23 | NonCellSetError

Functions

25 |

26 | debug

Variables

28 |

29 | DEBUG


31 | [hide private] 33 | 34 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /docs/toc-cells-module.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | cells 7 | 8 | 9 | 10 | 11 | 13 |

Module cells

14 |
15 |

Functions

16 |
17 |

18 | _debug

20 |

21 | fun2cell

23 | makecell

25 | reset

Variables

27 |

28 | DEBUG

30 |

31 | _DECO_OFFSET

33 |

34 | cellenv


36 | [hide private] 38 | 39 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /demos/gui/celltk.py: -------------------------------------------------------------------------------- 1 | import cells 2 | import Tkinter 3 | 4 | class BasicTkinterObject(cells.Family): 5 | container = cells.makecell(value=None) 6 | widget = cells.makecell(value=None) 7 | 8 | class Container(BasicTkinterObject): 9 | pass 10 | 11 | class Row(Container): 12 | @cells.fun2cell() 13 | def container(self, prev): 14 | print "Row.container" 15 | if not self.parent: 16 | return None 17 | widget = self.parent.widget 18 | if isinstance(widget, Tkinter.Frame): 19 | return widget 20 | return self.parent.container 21 | 22 | class Label(BasicTkinterObject): 23 | def __init__(self, *args, **kwargs): 24 | BasicTkinterObject.__init__(self, *args, **kwargs) 25 | self.stringvar = Tkinter.StringVar(self.container) 26 | self.widget = Tkinter.Label(self.container, textvariable=self.stringvar) 27 | self.widget.pack() 28 | 29 | stringvar = cells.makecell(value=None) 30 | update_on = cells.makecell(value="none") 31 | 32 | @cells.fun2cell() 33 | def container(self, prev): 34 | print "Entry.container" 35 | return self.parent.widget 36 | 37 | 38 | class Entry(BasicTkinterObject): 39 | def __init__(self, *args, **kwargs): 40 | BasicTkinterObject.__init__(self, *args, **kwargs) 41 | self.stringvar = Tkinter.StringVar(self.container) 42 | self.stringvar.trace("u", self.text_copier) 43 | self.widget = Tkinter.Entry(self.container, textvariable=self.stringvar) 44 | self.widget.pack() 45 | 46 | text = cells.makecell(value=None) 47 | stringvar = cells.makecell(value=None) 48 | update_on = cells.makecell(value="none") 49 | 50 | def text_copier(self, *args): 51 | print "updating text", repr(args), "field is", self.stringvar.get() 52 | self.text = self.stringvar.get() 53 | 54 | @cells.fun2cell() 55 | def container(self, prev): 56 | print "Entry.container" 57 | return self.parent.widget 58 | 59 | @Entry.observer(attrib="stringvar") 60 | def sv_tracer(model): 61 | if model.stringvar and model.update_on == "write": 62 | model.stringvar.trace("w", model.text_copier) 63 | 64 | class Window(Container): 65 | @cells.fun2cell(celltype=cells.RuleThenInputCell) 66 | def widget(self, prev): 67 | print "Window.widget" 68 | f = Tkinter.Frame(self.container) 69 | return f 70 | -------------------------------------------------------------------------------- /tests/synapses.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import unittest, sys 4 | sys.path += "../" 5 | import cells 6 | 7 | """ 8 | Synapses are filters for Cells. They can be applied to any type of 9 | Cell, and they simply process the cell to build a value which is 10 | provided to dependent cells as the filtered cell's value. 11 | 12 | For instance, we want to write to a log file every hundred times a 13 | resource is accessed. So, we build a cell 'log_text' which calls the 14 | resource counter 'hits', with an observer 'log_text_obs' which does 15 | the actual writing. We don't want to store any state with the 16 | 'log_text' cell's rule -- we just want the rule to be called when 17 | there's a hundred hits. So, we define a filter on hits in log_text 18 | that makes hits only propogate to log_text when the delta between its 19 | last-propogated and new values is 100. 20 | 21 | The following synapses are provided by cells.synapse: 22 | (deferred) 23 | """ 24 | 25 | class SynapseTests(unittest.TestCase): 26 | def test_BasicSynapse(self): 27 | # so, we define two cells, one with the filter 28 | self.a = cells.InputCell(None, 1, name="a") 29 | 30 | def b_rule(model, prev): 31 | if cells.ChangeSynapse(owner=self.b, name="filter1", read=self.a, 32 | delta=10)() > prev: 33 | return "no change" 34 | else: 35 | return "changed!" 36 | 37 | self.b = cells.RuleCell(None, b_rule, name="b") 38 | 39 | # and then we build the deps 40 | curr_b = self.b.getvalue() 41 | 42 | # and we increment the filtered cell a bit 43 | self.a.set(self.a.getvalue() + 3) # not enough 44 | self.failUnless(self.b.getvalue() == curr_b) 45 | self.a.set(self.a.getvalue() + 3) # closer.... 46 | self.failUnless(self.b.getvalue() == curr_b) 47 | self.a.set(self.a.getvalue() + 3) # almost there... 48 | self.failUnless(self.b.getvalue() == curr_b) 49 | 50 | # then we go over the threshold: 51 | self.a.set(self.a.getvalue() + 3) 52 | # and we should see b change 53 | self.failIf(self.b.getvalue() == curr_b) 54 | 55 | if __name__ == "__main__": unittest.main() 56 | -------------------------------------------------------------------------------- /docs/toc.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | Table of Contents 7 | 8 | 9 | 10 | 11 | 13 |

Table of Contents

14 |
15 |

16 | Everything 17 |

18 |

Modules

19 |

20 | cells

22 | cells.cell

24 | cells.cellattr

26 | cells.family

28 | cells.model

30 | cells.observer

32 | cells.synapse


34 | [hide private] 36 | 37 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /tutorials/basic.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | PyCells Tutorials, part one: The Basics 5 | 6 | I'm just going to do a really basic cells application here, 7 | introducing the syntax of PyCells and some of the concepts you'll need 8 | to keep in mind while using cells. 9 | 10 | I'll build a model of a rectangle which will take a width and an 11 | aspect ratio, and make available the calculated length and area. 12 | """ 13 | 14 | # First, we'll import cells: 15 | import cells 16 | 17 | GOLDEN = 1.618 18 | 19 | # The next step is building the model. It's a normal class, but 20 | # extends cells.Model. cells.Model.__init__ will take care of building 21 | # all the support structures within the model for the model's cells to 22 | # work properly: 23 | class Rectangle(cells.Model): 24 | # Now let's add a cell to our model. This will be an Input cell, 25 | # which can be set from outside the Model: 26 | width = cells.makecell(value=1) 27 | # I didn't specify an InputCell (though I could have); 28 | # cells.makecell will create an InputCell if it gets a value 29 | # argument. 30 | 31 | # Similarly, we can make the aspect ratio cell: 32 | ratio = cells.makecell(value=GOLDEN) 33 | 34 | # Now, I make the cell which gives the calculated length. It's 35 | # called a Rule cell, and I can make it like this: 36 | @cells.fun2cell() 37 | def length(self, prev): 38 | # The signature of the rule which defines a RuleCell's value 39 | # must be f(self, prev); self will be passed an instance of 40 | # this Model (just like you'd expect), and prev will be passed 41 | # the cell's previous (aka out-of-date) value. 42 | 43 | # For this Rule I'm going to be ignoring the previous value, 44 | # and just using this instance's ratio and width to get the 45 | # new length: 46 | return float(self.width) * float(self.ratio) 47 | 48 | # There's another way to make RuleCells. We can pass anonymous 49 | # functions to the cells.makecell function: 50 | area = cells.makecell(rule=lambda self, p: 51 | float(self.length) * float(self.width)) 52 | 53 | # So now we've got our Model. Let's test it out: 54 | if __name__ == "__main__": 55 | r = Rectangle() 56 | # Note that we use cells, no matter what type, by retrieving them 57 | # like attributes: 58 | print "By default, the rectangle's width is", r.width 59 | print "And its length is", r.length 60 | print "And its area is", r.area 61 | 62 | v = 2.5 63 | print "Now I'll change the width to", v 64 | # Also note that to set an InputCell's value, we simply set the 65 | # attribute: 66 | r.width = v 67 | print "The length is now", r.length 68 | print "And the area has changed to", r.area 69 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | PyCells: Automatic dataflow management for Python 2 | http://pycells.pdxcb.net 3 | --------------------------------------------------------------------------- 4 | This library, its documentation, tutorials, and all example code 5 | copyright 2006 Ryan Forsythe. It is free software: you may modify and 6 | redistribute it under the terms of the LGPL. You may find the full 7 | license terms in the LICENSE file in this directory. 8 | --------------------------------------------------------------------------- 9 | 10 | Introduction: 11 | ------------- 12 | PyCells is a port of Ken Tilton's Cells extension to Common 13 | Lisp. Cells are objects which detect, notify, and propogate 14 | changes to each other. An example: 15 | 16 | >>> import cells 17 | >>> class Rectangle(cells.Model): 18 | ... width = cells.makecell(value=1) 19 | ... ratio = cells.makecell(value=1.618) 20 | ... @cells.fun2cell() 21 | ... def length(self, prev): 22 | ... print "Length updating..." 23 | ... return float(self.width) * float(self.ratio) 24 | ... 25 | >>> r = Rectangle() 26 | Length updating... 27 | >>> r.length 28 | 1.6180000000000001 29 | >>> r.width = 5 30 | Length updating... 31 | >>> r.length 32 | 8.0899999999999999 33 | 34 | Care is taken to verify that changes are propogated correctly, and 35 | completely, so the user of cells may ignore the underlying 36 | implementation details and trust the system to do the right thing 37 | in all cases. 38 | 39 | Documentation: 40 | -------------- 41 | There are several options for documentation 42 | 43 | /docs: The API documentation, generated from the source by epydoc, 44 | can be found in the docs directory 45 | 46 | /tutorials: I've written up a short set of tutorials to step you 47 | through some of the basics of using cells: 48 | 49 | basic.py: Cells 101. Input cells and Rule cells are covered, 50 | along with using the Model class 51 | 52 | observers.py: In general, one wants to keep rules free of side 53 | effects. Observers allow you to hold to this and get some 54 | actual work done. 55 | 56 | cell_varieties.py: There's more to choose from in the cell 57 | menagerie than just input and rules. This tutorial 58 | introduces you to some of what's there. 59 | 60 | family.py: Some applications require an large number of 61 | similar Models. The Family class can ease some pains with 62 | these apps. 63 | 64 | /demos: In addition to the tutorials, there's several larger demo 65 | applications available. I've written a wiki sort of web server 66 | thing, the start to a SQL library I'm calling CellQL, and a 67 | GUI application and library using Tkinter. 68 | 69 | Installation: 70 | ------------- 71 | Use the setup.py script in the normal way: 72 | 73 | user@host $ sudo python setup.py install 74 | 75 | This is an easy_install script, so you can also use it to build an 76 | egg of PyCells. Run 77 | 78 | user@host $ python setup.py --help-commands 79 | 80 | to see all of your options. 81 | -------------------------------------------------------------------------------- /docs/toc-cells.cell-module.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | cell 7 | 8 | 9 | 10 | 11 | 13 |

Module cell

14 |
15 |

Classes

16 |

17 | AlwaysLazyCell

19 | DictCell

21 | EphemeralCellUnboundError

23 | InputCell

25 | InputCellRunError

27 | LazyCell

29 | ListCell

31 | RuleAndValueInitError

33 | RuleCell

35 | RuleCellSetError

37 | RuleThenInputCell

39 | SetDuringNotificationError

41 | UntilAskedLazyCell

43 |

44 | _CellException

46 |

Functions

47 |
48 |

49 | _debug

51 |
52 |

53 | _nonerule

55 |

Variables

56 |

57 | DEBUG


59 | [hide private] 61 | 62 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /tests/family.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import unittest, sys 4 | sys.path += "../" 5 | import cells 6 | 7 | """ 8 | A Family object is a type of Model. It provides everything a Model 9 | provides, plus the following: 10 | 11 | 1. A kids attribute, which holds an ordered sequence of Models. 12 | 13 | 2. A kid_slots attribute, which is akin to a blueprint for the Models 14 | in the kids list. 15 | 16 | 3. If a Model in a kids list defines an attribute which is also 17 | defined in the kid_slots blueprint, the attribute in kid_slots 18 | overrides the Model's definition. 19 | 20 | 4. Provides the following convenience methods: 21 | * grandparent: the parent's parent, or None 22 | * kid_number: this Model's position in the parent's kids list 23 | * previous_sib: the sibiling Model in parent.kids[kid_number - 1] 24 | * next_sib: the sibiling Model in parent.kids[kid_number + 1] 25 | 26 | """ 27 | 28 | class FamilyTest(unittest.TestCase): 29 | def setUp(self): 30 | cells.reset() 31 | 32 | class K(cells.Family): 33 | x = cells.makecell(value=1) 34 | model_value = cells.makecell(rule=lambda s,p: s.x * 3) 35 | 36 | class F(cells.Family): 37 | kid_slots = cells.makecell(value=K) 38 | 39 | self.F = F 40 | self.K = K 41 | 42 | def tearDown(self): 43 | del(self.F) 44 | del(self.K) 45 | 46 | def test_KidsAttrib(self): 47 | f = self.F() 48 | 49 | for word in ("Eyy", "Bee", "See"): 50 | class A(cells.Model): 51 | model_name = cells.makecell(value=word) 52 | 53 | f.make_kid(A) 54 | 55 | self.failUnless(3 == len(f.kids)) 56 | self.failUnless(f.kids[0].model_name == "Eyy") 57 | self.failUnless(f.kids[1].model_name == "Bee") 58 | self.failUnless(f.kids[2].model_name == "See") 59 | 60 | def test_KidslotsAttrib(self): 61 | f = self.F() 62 | 63 | # this class will get all of its attribs from the kid slot 64 | class A(cells.Model): 65 | pass 66 | 67 | # model_name will not be overridden 68 | class B(cells.Model): 69 | model_name = cells.makecell(value="Bee") 70 | 71 | # this class will have its defined attrib overridden 72 | class C(cells.Model): 73 | model_value = cells.makecell(rule=lambda s,p: s.x * 10) 74 | 75 | f.make_kid(A) 76 | f.make_kid(B) 77 | f.make_kid(C) 78 | 79 | self.failUnless(f.kids[0].x == 1) 80 | self.failUnless(f.kids[1].model_name == "Bee") 81 | self.failUnless(f.kids[2].model_value == 3) 82 | 83 | def test_GrandparentMethod(self): 84 | f = self.F() 85 | f.make_kid(self.F) 86 | f.kids[0].make_kid(self.K) 87 | self.failUnless(f.kids[0].kids[0].grandparent() is f) 88 | 89 | def test_SiblingMethods(self): 90 | f = self.F() 91 | f.make_kid(self.K) 92 | f.make_kid(self.K) 93 | 94 | kidA = f.kids[0] 95 | kidB = f.kids[1] 96 | 97 | self.failUnless(kidA.next_sib() is kidB) 98 | self.failUnlessRaises(cells.FamilyTraversalError, kidB.next_sib) 99 | 100 | self.failUnlessRaises(cells.FamilyTraversalError, kidA.previous_sib) 101 | self.failUnless(kidB.previous_sib() is kidA) 102 | 103 | def test_PositionMethod(self): 104 | f = self.F() 105 | f.make_kid(self.K) 106 | f.make_kid(self.K) 107 | 108 | kidA = f.kids[0] 109 | kidB = f.kids[1] 110 | 111 | self.failUnless(kidA.position() == 0) 112 | self.failUnless(kidB.position() == 1) 113 | 114 | if __name__ == "__main__": unittest.main() 115 | 116 | -------------------------------------------------------------------------------- /demos/sql/cellql-demo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | CellQL Demo 5 | =========== 6 | 7 | CellQL (pronounced "sell-kwull") is (well, will be) an interface to 8 | several popular RDBMSs including SQLite, MySQL, PostgreSQL, and 9 | Oracle. (Currently it supports SQLite, and only somewhat.) 10 | 11 | This file is meant as a demo of the current state of the CellQL 12 | library and something of a tutorial of its use, as it's currently 13 | poorly documented. 14 | 15 | Notice all those qualifications and parenthetical statements? 16 | Good. CellQL is just a silly little diversion right now. I'm *really* 17 | interested in making it something worth using, but for now it's just a 18 | silly little thing to stick in the demos directory. 19 | """ 20 | 21 | import cellql 22 | import copy 23 | 24 | # I'm going to start with constructing the tables for the DB. Well, 25 | # the table, as this demo's just going to deal with one. We use 26 | # cellql.Table as a base class for tables: 27 | class Contacts(cellql.Table): 28 | # Now let's add columns. First, the ever-popular name fields: 29 | fname = cellql.string() 30 | lname = cellql.string() 31 | 32 | # And, heck, an age might be good: 33 | age = cellql.integer() 34 | 35 | # And a free-form dictionary of phone numbers 36 | phone = cellql.blob() 37 | 38 | print "Connecting to database..." 39 | 40 | # We connect to the DB through a cellql.Database object. Handing it a 41 | # connection string (which is, of course, a Cell) connects it to the 42 | # RDBMS & database of our choice. We're going to use sqlite, as it's 43 | # the only RDBMS supported by CellQL, and connect to the demo.sqlite 44 | # database file in the local directory. You could connect to an 45 | # arbitrary file on your filesystem with sqlite:///path/to/sqlite/file 46 | db = cellql.Database(connection="sqlite://demo.sqlite") 47 | 48 | # Now we'll tell the db to use the contacts table we defined above... 49 | db.addtable(Contacts) 50 | 51 | # When this connects, it runs some logic. If there's currently a table 52 | # in the database named the same as the class just added, CellQL 53 | # verifies the table's structure matches the classes. If it does, it 54 | # makes the contents of that table available; if it doesn't -- or if 55 | # the table doesn't exist -- it (re)creates the table in the database. 56 | 57 | # So now we've connected to the db, and there's a table matching the 58 | # Contacts class in the db. (You can do all this in an ipython session, 59 | # then go in behind it at this point to verify the database is being 60 | # built correctly if you don't believe me.) 61 | 62 | # First, let's see how many rows are currently in the db: 63 | rowcount = len(db.tables["Contacts"].rows) 64 | 65 | # We add rows by creating a copy of the table class we want to add, 66 | # modifying that instance, then pushing it into the database. Like so: 67 | newrow = Contacts() 68 | newrow.fname.value = "Guybrush" 69 | newrow.lname.value = "Threepwood" 70 | newrow.age.value = 17 71 | newrow.phone.value = { "home": "(206) 555-1212", 72 | "work": "(206) 543-2109" } 73 | db.tables["Contacts"].rows.append(newrow) 74 | 75 | # This will push a row into the database, automatically assigning it 76 | # an index which we can use to retrieve it. Hey, that rowcount 77 | # variable will come in handy here: 78 | retrievedrow = db.tables["Contacts"].rows[rowcount] 79 | 80 | # Now we can modify it... 81 | retrievedrow.age.value = 21 82 | phones = copy.copy(retrievedrow.phone.value) 83 | phones["cell"] = "(206) 987-6543" 84 | retrievedrow.phone.value = phones 85 | 86 | # and push it back into the database, which will update the row in 87 | # question: 88 | db.tables["Contacts"].rows[rowcount] = retrievedrow 89 | 90 | # This has a long ways to go, especially in regards to fitting with 91 | # cells, but the framework is there and cellularity should be a 92 | # relatively easy thing to add in the future. 93 | 94 | -------------------------------------------------------------------------------- /tutorials/observers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | PyCells Tutorials, part two: Observers 5 | 6 | In this tutorial I'll extend the Model with Observers, which are bits 7 | of code that get called when the Model is updated. Observers can be 8 | set to run when the whole Model updates, when a specific Cell updates, 9 | or when a value matches a given condition. 10 | """ 11 | 12 | import cells 13 | # I'm just going to grab the Rectangle from part one: 14 | from basic import Rectangle, GOLDEN 15 | 16 | # Now, we can use one of cells.Model's class methods, observer, to add 17 | # an observer to the Rectangle: 18 | @Rectangle.observer() # Without arguments, this Observer attaches to 19 | # the whole Model. It will be run whenever any 20 | # Cell in the Model updates 21 | def rectangle_observer(self): # 'self' here refers to the Rectangle instance 22 | print "The rectangle updated!" 23 | 24 | # As I said, Observers may be attached to specific Cell attributes: 25 | @Rectangle.observer(attrib="ratio") 26 | def ratio_obs(self): 27 | # Note, here, that observers have access to the Model instance 28 | # just like a Rule Cell does 29 | print "The ratio of the rectangle is now", self.ratio 30 | 31 | # One more like the ratio observer, but with the area: 32 | @Rectangle.observer(attrib="area") 33 | def area_obs(self): 34 | print "The area of the rectangle is now", self.area 35 | 36 | # We can also act on the model with Observers, with some caveats. 37 | # Let's say we want to reset the ratio to the default (the golden 38 | # ratio) if it's set to a value below 0. We can do this with an 39 | # observer. First, I'll make a function to test for below-zeroness: 40 | def subzero(value): # The signature for value tests is 41 | # f(value) (depending on what the test 42 | # is attached to, this could be the 43 | # out-of-date or up-to-date value for the cell. 44 | return value < 0 # It returns a boolean to say whether the 45 | # observer should run on this cell 46 | 47 | # Now I'll add another observer, looking for an attribute named 48 | # "ratio" and an up-to-date value which passes the subzero test: 49 | @Rectangle.observer(attrib="ratio", newvalue=subzero) 50 | def zeroratio_observer(self): # and if it's run 51 | print "Woah, that's not a legal ratio! Resetting to default." 52 | self.ratio = GOLDEN # reset the ratio to the golden ratio 53 | 54 | # So, in order to show the problems with the observer method, I'll 55 | # need to run an instance of the Rectangle: 56 | if __name__ == "__main__": 57 | print "Making a new Rectangle" 58 | r = Rectangle() # makes a new Rectangle instance. 59 | # This will trigger the whole-model observer and the ratio & area observers: 60 | # >>> from observers import Rectangle 61 | # >>> r = Rectangle() 62 | # The rectangle updated! 63 | # The area of the rectangle is now 1.618 64 | # The ratio of the rectangle is now 1.618 65 | 66 | print "Setting ratio to 4" 67 | # Now, let's test the default-ratio observer: 68 | r.ratio = 4 69 | # This will not pass the subzero test, so that observer won't fire: 70 | # >>> r.ratio = 4 71 | # The rectangle updated! 72 | # The area of the rectangle is now 4.0 73 | 74 | print "Setting ratio to -3" 75 | r.ratio = -3 76 | # This will pass the subzero test, firing the observer: 77 | # >>> r.ratio = -3 78 | # The rectangle updated! 79 | # Woah, that's not a legal ratio! Resetting to default. 80 | # The area of the rectangle is now -3.0 81 | # The rectangle updated! 82 | # The area of the rectangle is now 1.618 83 | 84 | # It worked ... but the area changed to -3.0 before the ratio 85 | # reset! What the heck? Well, in order to preserve the integrity 86 | # of an update, they happen atomically -- so, each assignment to 87 | # an InputCell matches up with a single update to the cells 88 | # environment. When the observer sets the rectangle's ratio to the 89 | # golden ratio, that set is deferred until the current update to 90 | # -3 is finished. The -3 propgation makes the area change to -3, 91 | # firing area's observer. After that propgation finishes, the set 92 | # back to the golden ratio happens and the model propgates that 93 | # change through the system. 94 | 95 | # So observers changing cells may lead to somewhat unexpected 96 | # behavior. Make sure you know what's going on in your system 97 | # before you start adding observers that change the Model they 98 | # observe. 99 | -------------------------------------------------------------------------------- /tutorials/family.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | PyCells Tutorials, part four: The Family 5 | 6 | Some applications depend on large, or indeterminant, numbers of 7 | similar objects coordinated by another, enclosing object. To ease the 8 | creation of Models in this structure, there exists the Family 9 | object. It's a Model subclass with a number of conveniences which I'll 10 | describe in this tutorial. """ 11 | 12 | import cells 13 | 14 | # Instead of just one address book, I want to be able to have as many 15 | # as I want. (This example is pretty contrived. Sorry.) An 16 | # AddressBooks class, which inherits from Family, will coordinate 17 | # access to the address books, which will be put in Adressbooks' kids 18 | # list. 19 | 20 | # First, let's get an idea of what each adress book needs to look like: 21 | class NamedAddressBook(cells.Family): 22 | name = cells.makecell(value="Anonymous") 23 | entries = cells.makecell(value={}, celltype=cells.DictCell) 24 | names = cells.makecell(rule=lambda s,p: s.entries.keys()) 25 | 26 | @NamedAddressBook.observer(attrib="entries") 27 | def entry_obs(model): 28 | if model.entries: 29 | print "Added a new name to the '" + model.name + "' address book" 30 | 31 | # Now we'll specify that class in the AdressBooks class as the 32 | # kid_slots attribute. The class in this slot acts as a mixin to any 33 | # class passed to the make_kid method. Confusing? Yeah. Pretty useful 34 | # in some situations, though, so watch it in action: 35 | class AddressBooks(cells.Family): 36 | # Note that kid_slots is just a cell. It could be any type of cell 37 | # so long as the value it produced was a class. 38 | kid_slots = cells.makecell(value=NamedAddressBook) 39 | 40 | # I'd like to be able to get all of the names and emails from all 41 | # of the address books: 42 | @cells.fun2cell() 43 | def entries(self, prev): 44 | d = {} 45 | for model in self.kids: 46 | d.update(model.entries) 47 | 48 | return d 49 | 50 | # And also get all the names: 51 | names = cells.makecell(rule=lambda s,p: s.entries.keys()) 52 | 53 | # Let's also get all the address book names & indexes 54 | @cells.fun2cell() 55 | def booknames(self, prev): 56 | d = {} 57 | for model in self.kids: 58 | # I'll use one of the Family convenience methods, position 59 | d[model.name] = model.position() 60 | 61 | return d 62 | 63 | # And, so we can easily see what's happening I'll add some observers 64 | # to the AddressBooks model: 65 | @AddressBooks.observer(attrib="entries") 66 | def entries_obs(model): 67 | print "AddressBooks' entries changed" 68 | 69 | @AddressBooks.observer(attrib="names") 70 | def names_obs(model): 71 | if model.names: 72 | print "All names, in all address books:", repr(model.names) 73 | 74 | @AddressBooks.observer(attrib="booknames") 75 | def booknames_obs(model): 76 | if model.booknames: 77 | print "Your address books are:", repr(model.booknames) 78 | 79 | # That should do it. Let's see it in action: 80 | if __name__ == "__main__": 81 | print "Creating a set of address books" 82 | a = AddressBooks() 83 | print "Adding an address book to the set" 84 | a.make_kid(NamedAddressBook) 85 | print "Naming that new address book 'Work'" 86 | a.kids[0].name = "Work" 87 | print "Now doing the same for 'Friends'" 88 | a.make_kid(NamedAddressBook) 89 | a.kids[1].name = "Friends" 90 | 91 | print 92 | print "Now I'll add some people to my address books..." 93 | a.kids[0].entries["Big Boss"] = "big.boss@example.net" 94 | a.kids[0].entries["Cow Orker"] = "cow.orker@example.net" 95 | a.kids[1].entries["Dr. Inkin G. Buddy"] = "inkin@example.net" 96 | 97 | print 98 | # We can add any Model to the kids list, and PyCells will ensure 99 | # that the enclosing Family class will override the desired 100 | # methods, as defined in kid_slots. For instance: 101 | 102 | class SomeContainer(cells.Family): 103 | entries = cells.makecell(value=[], celltype=cells.ListCell) 104 | size = cells.makecell(rule=lambda s,p: len(s.entries)) 105 | 106 | # SomeContainer has one cell which is named the same as a cell in 107 | # AddressBooks' kid_slots Model, NamedAddressBook: 108 | # entries. However, since it's incompatible with the rest of 109 | # NamedAddressBook's cells -- it's a list rather than a dictionary 110 | # -- AddressBooks will override SomeContainer.entries with the 111 | # cell definition from NamedAddressBook. However, SomeContainer 112 | # has another cell which doesn't appear in NamedAddressBook: 113 | # size. It will survive being pushed into the kids list, and 114 | # continue to work properly. Watch: 115 | 116 | a.make_kid(SomeContainer) 117 | a.kids[2].name = "Family" # It was given the 'name' attribute, 118 | a.kids[2].entries["Dad"] = "dad@example.net" # and 'entries' was overridden, 119 | # but unique attribs are kept. 120 | print "Size of new address book:", a.kids[2].size 121 | 122 | -------------------------------------------------------------------------------- /cells/synapse.py: -------------------------------------------------------------------------------- 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 | Synapses, cell variants which exist only within C{L{RuleCell}}s and 21 | which mediate the propogation of another cell's value. For instance, a 22 | cell C{a} could use cell C{b}'s value, but only if it had changed more 23 | than 5% since the last time C{a} used it. 24 | 25 | @var DEBUG: Turns on debugging messages for the synapse module. 26 | """ 27 | 28 | import cells 29 | import cells.cell as cell 30 | 31 | DEBUG = False 32 | 33 | def debug(*msgs): 34 | msgs = list(msgs) 35 | msgs.insert(0, "synapse".rjust(cells._DECO_OFFSET) + " > ") 36 | if DEBUG or cells.DEBUG: 37 | print(" ".join(msgs)) 38 | 39 | class Synapse(cell.Cell): 40 | """ 41 | A very specialized Cell variant. Synapses are filters for 42 | Cells. They can be applied to any type of Cell, and they simply 43 | process the cell to build a value which is provided to dependent 44 | cells as the filtered cell's value. They live within the cell 45 | which defines them, and as such are only referenceable in that 46 | rule. 47 | """ 48 | initialized = False 49 | 50 | def __new__(cls, owner, name=None, **kwargs): 51 | """ 52 | Create a new synapse instance within the calling Cell, if 53 | neccessary. 54 | 55 | @param cls: Class this synapse is being called from. 56 | 57 | @param owner: Model instance this synapse is being created in 58 | / retrieved from 59 | 60 | @param name: Name of the synapse to retrieve; used as a lookup 61 | on the enclosing Cell's synapse space. 62 | 63 | @param kwargs: standard C{L{Cell}} keyword arguments. 64 | """ 65 | # first, check to see if there's already a synapse with this name in 66 | # the owner Cell 67 | if name not in owner.synapse_space: # and if there isn't 68 | # make one in the owner 69 | debug("building new synapse '" + name + "' in", str(owner)) 70 | owner.synapse_space[name] = cell.Cell.__new__(cls, owner, name=name, **kwargs) 71 | 72 | # finally, return the owner's synapse 73 | return owner.synapse_space[name] 74 | 75 | def __init__(self, owner, name=None, **kwargs): 76 | """ 77 | Initialize the synapse Cell, if neccessary. 78 | 79 | @param owner: The model instance to pass to this synapse's rule 80 | 81 | @param name: This synapse's name 82 | 83 | @param kwargs: Standard C{L{Cell}} keyword arguments 84 | """ 85 | # at this point we're guaranteed to have a Synapse in the 86 | # owner, and self points at that Synapse. We don't know if 87 | # it's been initialized, though. so: 88 | if not self.initialized: 89 | debug("(re)initializing", name) 90 | cell.Cell.__init__(self, owner, name=name, **kwargs) 91 | self.initialized = True 92 | 93 | def __call__(self): 94 | """ 95 | Run C{L{Cell.get}(self)} when a synapse is called as a function. 96 | """ 97 | return self.getvalue() 98 | 99 | def run(self): 100 | """ 101 | Slightly modified version of C{L{Cell.run}()}. 102 | """ 103 | debug(self.name, "running") 104 | # call stack manipulation 105 | oldcurr = cells.cellenv.curr 106 | cells.cellenv.curr = self 107 | 108 | # the rule run may rewrite the dep graph; prepare for that by nuking 109 | # c-b links to this cell and calls links from this cell: 110 | for cell in self.calls_list(): 111 | debug(self.name, "removing c-b link from", cell.name) 112 | cell.remove_cb(self) 113 | self.reset_calls() 114 | 115 | self.dp = cells.cellenv.dp # we're up-to-date 116 | newvalue = self.rule(self.owner, self.value) # run the rule 117 | self.bound = True 118 | 119 | # restore old running cell 120 | cells.cellenv.curr = oldcurr 121 | 122 | # return changed status 123 | if self.unchanged_if(self.value, newvalue): 124 | debug(self.name, "unchanged.") 125 | return False 126 | else: 127 | debug(self.name, "changed.") 128 | self.last_value = self.value 129 | self.value = newvalue 130 | 131 | return True 132 | 133 | def rule(self, owner, oldvalue): 134 | return None 135 | 136 | class ChangeSynapse(Synapse): 137 | """A very simple filter. Only returns the new value when it's 138 | changed by the passed delta 139 | """ 140 | def __init__(self, owner, name=None, read=None, delta=None, **kwargs): 141 | debug("init'ing ChangeSynapse") 142 | if not self.initialized: 143 | self.readvar, self.delta = read, delta 144 | Synapse.__init__(self, owner, name=name, **kwargs) 145 | self.rule = self.synapse_rule 146 | 147 | def synapse_rule(self, owner, oldvalue): 148 | debug("running ChangeSynapse rule") 149 | newval = self.readvar.getvalue() 150 | if not oldvalue or abs(newval - oldvalue) > self.delta: 151 | debug("returning new value", str(newval)) 152 | return newval 153 | else: 154 | debug("returning old value", str(oldvalue)) 155 | return oldvalue 156 | -------------------------------------------------------------------------------- /docs/cells.cell.RuleCellSetError-class.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | cells.cell.RuleCellSetError 7 | 8 | 9 | 10 | 11 | 13 | 14 | 16 | 17 | 18 | 20 | 21 | 22 | 24 | 25 | 26 | 28 | 29 | 30 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 51 | 62 | 63 |
45 | 46 | Package cells :: 47 | Module cell :: 48 | Class RuleCellSetError 49 | 50 | 52 | 53 | 54 | 56 | 60 |
[hide private]
[frames] | no frames]
61 |
64 | 65 |

Class RuleCellSetError 66 |
source code

67 |
 68 | exceptions.Exception --+    
 69 |                        |    
 70 |           _CellException --+
 71 |                            |
 72 |                           RuleCellSetError
 73 | 
74 | 75 |
76 | RuleCells may not be set.

77 | 78 | 79 | 80 | 82 | 83 | 94 | 95 | 96 | 105 | 106 |
84 | 85 | 86 | 87 | 91 | 92 |
Instance Methods[hide private]
93 |
97 |

Inherited from _CellException: 98 | __init__, 99 | __str__ 100 |

101 |

Inherited from exceptions.Exception: 102 | __getitem__ 103 |

104 |
107 | 108 |
109 | 110 | 112 | 113 | 114 | 116 | 117 | 118 | 120 | 121 | 122 | 124 | 125 | 126 | 128 | 129 | 130 | 136 | 137 | 138 | 139 | 140 | 142 | 145 | 146 |
147 | 148 | 157 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /docs/cells.cell.InputCellRunError-class.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | cells.cell.InputCellRunError 7 | 8 | 9 | 10 | 11 | 13 | 14 | 16 | 17 | 18 | 20 | 21 | 22 | 24 | 25 | 26 | 28 | 29 | 30 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 51 | 62 | 63 |
45 | 46 | Package cells :: 47 | Module cell :: 48 | Class InputCellRunError 49 | 50 | 52 | 53 | 54 | 56 | 60 |
[hide private]
[frames] | no frames]
61 |
64 | 65 |

Class InputCellRunError 66 |
source code

67 |
 68 | exceptions.Exception --+    
 69 |                        |    
 70 |           _CellException --+
 71 |                            |
 72 |                           InputCellRunError
 73 | 
74 | 75 |
76 | An attempt to run() an 77 | InputCell was made.

78 | 79 | 80 | 81 | 83 | 84 | 95 | 96 | 97 | 106 | 107 |
85 | 86 | 87 | 88 | 92 | 93 |
Instance Methods[hide private]
94 |
98 |

Inherited from _CellException: 99 | __init__, 100 | __str__ 101 |

102 |

Inherited from exceptions.Exception: 103 | __getitem__ 104 |

105 |
108 | 109 |
110 | 111 | 113 | 114 | 115 | 117 | 118 | 119 | 121 | 122 | 123 | 125 | 126 | 127 | 129 | 130 | 131 | 137 | 138 | 139 | 140 | 141 | 143 | 146 | 147 |
148 | 149 | 158 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /docs/cells.cell.EphemeralCellUnboundError-class.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | cells.cell.EphemeralCellUnboundError 7 | 8 | 9 | 10 | 11 | 13 | 14 | 16 | 17 | 18 | 20 | 21 | 22 | 24 | 25 | 26 | 28 | 29 | 30 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 51 | 62 | 63 |
45 | 46 | Package cells :: 47 | Module cell :: 48 | Class EphemeralCellUnboundError 49 | 50 | 52 | 53 | 54 | 56 | 60 |
[hide private]
[frames] | no frames]
61 |
64 | 65 |

Class EphemeralCellUnboundError 66 |
source code

67 |
 68 | exceptions.Exception --+    
 69 |                        |    
 70 |           _CellException --+
 71 |                            |
 72 |                           EphemeralCellUnboundError
 73 | 
74 | 75 |
76 | An EphemeralCell was getted 77 | without being bound.

78 | 79 | 80 | 81 | 83 | 84 | 95 | 96 | 97 | 106 | 107 |
85 | 86 | 87 | 88 | 92 | 93 |
Instance Methods[hide private]
94 |
98 |

Inherited from _CellException: 99 | __init__, 100 | __str__ 101 |

102 |

Inherited from exceptions.Exception: 103 | __getitem__ 104 |

105 |
108 | 109 |
110 | 111 | 113 | 114 | 115 | 117 | 118 | 119 | 121 | 122 | 123 | 125 | 126 | 127 | 129 | 130 | 131 | 137 | 138 | 139 | 140 | 141 | 143 | 146 | 147 |
148 | 149 | 158 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /docs/cells.cell.SetDuringNotificationError-class.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | cells.cell.SetDuringNotificationError 7 | 8 | 9 | 10 | 11 | 13 | 14 | 16 | 17 | 18 | 20 | 21 | 22 | 24 | 25 | 26 | 28 | 29 | 30 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 51 | 62 | 63 |
45 | 46 | Package cells :: 47 | Module cell :: 48 | Class SetDuringNotificationError 49 | 50 | 52 | 53 | 54 | 56 | 60 |
[hide private]
[frames] | no frames]
61 |
64 | 65 |

Class SetDuringNotificationError 66 |
source code

67 |
 68 | exceptions.Exception --+    
 69 |                        |    
 70 |           _CellException --+
 71 |                            |
 72 |                           SetDuringNotificationError
 73 | 
74 | 75 |
76 | An attempt at a non-deferred set() happened during propogation.

78 | 79 | 80 | 81 | 83 | 84 | 95 | 96 | 97 | 106 | 107 |
85 | 86 | 87 | 88 | 92 | 93 |
Instance Methods[hide private]
94 |
98 |

Inherited from _CellException: 99 | __init__, 100 | __str__ 101 |

102 |

Inherited from exceptions.Exception: 103 | __getitem__ 104 |

105 |
108 | 109 |
110 | 111 | 113 | 114 | 115 | 117 | 118 | 119 | 121 | 122 | 123 | 125 | 126 | 127 | 129 | 130 | 131 | 137 | 138 | 139 | 140 | 141 | 143 | 146 | 147 |
148 | 149 | 158 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /docs/cells.cell.RuleAndValueInitError-class.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | cells.cell.RuleAndValueInitError 7 | 8 | 9 | 10 | 11 | 13 | 14 | 16 | 17 | 18 | 20 | 21 | 22 | 24 | 25 | 26 | 28 | 29 | 30 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 51 | 62 | 63 |
45 | 46 | Package cells :: 47 | Module cell :: 48 | Class RuleAndValueInitError 49 | 50 | 52 | 53 | 54 | 56 | 60 |
[hide private]
[frames] | no frames]
61 |
64 | 65 |

Class RuleAndValueInitError 66 |
source code

67 |
 68 | exceptions.Exception --+    
 69 |                        |    
 70 |           _CellException --+
 71 |                            |
 72 |                           RuleAndValueInitError
 73 | 
74 | 75 |
76 | Both rule and value were passed to __init__.

79 | 80 | 81 | 82 | 84 | 85 | 96 | 97 | 98 | 107 | 108 |
86 | 87 | 88 | 89 | 93 | 94 |
Instance Methods[hide private]
95 |
99 |

Inherited from _CellException: 100 | __init__, 101 | __str__ 102 |

103 |

Inherited from exceptions.Exception: 104 | __getitem__ 105 |

106 |
109 | 110 |
111 | 112 | 114 | 115 | 116 | 118 | 119 | 120 | 122 | 123 | 124 | 126 | 127 | 128 | 130 | 131 | 132 | 138 | 139 | 140 | 141 | 142 | 144 | 147 | 148 |
149 | 150 | 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /demos/sql/cellql-tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | CellQL Testing 5 | """ 6 | 7 | import cellql, cells 8 | import unittest 9 | import os 10 | from pysqlite2 import dbapi2 as sqlite 11 | 12 | TESTDB = "TestDB" 13 | 14 | class Sqlite_CellQLTests(unittest.TestCase): 15 | def setUp(self): 16 | self.con = sqlite.connect(TESTDB) 17 | self.cur = self.con.cursor() 18 | 19 | def tearDown(self): 20 | self.cur.close() 21 | self.con.close() 22 | cells.cell.DEBUG = False 23 | os.unlink(TESTDB) 24 | 25 | def test_Init(self): 26 | con = cellql.Database(connection="sqlite://" + TESTDB) 27 | self.failIf(con.connected is False) 28 | 29 | def test_SubclassInit(self): 30 | class TestDB(cellql.Database): 31 | pass 32 | 33 | con = TestDB(connection="sqlite://" + TESTDB) 34 | self.failIf(con.connected is False) 35 | 36 | def test_SubclassWithTable(self): 37 | class Test(cellql.Table): 38 | pass 39 | 40 | class TestDB(cellql.Database): 41 | connection = cells.makecell(value="sqlite://" + TESTDB) 42 | 43 | db = TestDB() 44 | db.addtable(Test) 45 | self.failUnless(list(db.tables["Test"].rows) == []) 46 | 47 | def test_TableWithIntegerColumn(self): 48 | class Test(cellql.Table): 49 | i = cellql.integer(value=0) 50 | 51 | class TestDB(cellql.Database): 52 | connection = cells.makecell(value="sqlite://" + TESTDB) 53 | 54 | db = TestDB() 55 | db.make_kid(Test) 56 | self.failUnless("i" in [ _.name for _ in db.tables["Test"].columns ]) 57 | 58 | def test_CellQLTableCreatesDBTable(self): 59 | class Test(cellql.Table): 60 | i = cellql.integer(value=0) 61 | 62 | class TestDB(cellql.Database): 63 | connection = cells.makecell(value="sqlite://" + TESTDB) 64 | 65 | db = TestDB() 66 | db.addtable(Test) 67 | 68 | try: 69 | self.cur.execute("select * from Test") 70 | except sqlite.OperationalError, e: 71 | self.fail("Caught OperationalError: " + str(e)) 72 | 73 | def test_CreatedTableHasCellQLColumn(self): 74 | class Test(cellql.Table): 75 | i = cellql.integer(value=0) 76 | 77 | class TestDB(cellql.Database): 78 | connection = cells.makecell(value="sqlite://" + TESTDB) 79 | 80 | db = TestDB() 81 | db.addtable(Test) 82 | 83 | try: 84 | self.cur.execute("select i from Test") 85 | except sqlite.OperationalError, e: 86 | self.fail("Caught OperationalError: " + str(e)) 87 | 88 | def test_InsertingValueReflectedInDB(self): 89 | class Test(cellql.Table): 90 | i = cellql.integer(value=0) 91 | 92 | class TestDB(cellql.Database): 93 | connection = cells.makecell(value="sqlite://" + TESTDB) 94 | 95 | db = TestDB() 96 | db.addtable(Test) 97 | newrow = Test() 98 | newrow.i.value = 5 99 | db.tables["Test"].rows.append(newrow) 100 | 101 | self.cur.execute("select i from Test") 102 | l = self.cur.fetchall() 103 | self.failUnless(len(l) == 1) 104 | self.failUnless(l[0][0] == 5) 105 | 106 | def test_InsertingMultipleValues(self): 107 | class Test(cellql.Table): 108 | i = cellql.integer(value=0) 109 | j = cellql.integer(value=0) 110 | 111 | class TestDB(cellql.Database): 112 | connection = cells.makecell(value="sqlite://" + TESTDB) 113 | 114 | db = TestDB() 115 | db.addtable(Test) 116 | newrow = Test() 117 | newrow.i.value = 5 118 | newrow.j.value = 42 119 | db.tables["Test"].rows.append(newrow) 120 | 121 | self.cur.execute("select i, j from Test") 122 | l = self.cur.fetchall() 123 | self.failUnless(len(l) == 1) 124 | self.failUnless(l[0][0] == 5) 125 | self.failUnless(l[0][1] == 42) 126 | 127 | def test_RetrievingRowsFromExtantDB(self): 128 | class Test(cellql.Table): 129 | i = cellql.integer(value=0) 130 | 131 | class TestDB(cellql.Database): 132 | connection = cells.makecell(value="sqlite://" + TESTDB) 133 | 134 | self.cur.execute("CREATE TABLE Test (i INTEGER, " + \ 135 | "pk INTEGER PRIMARY KEY)") 136 | self.con.commit() 137 | self.cur.execute("INSERT INTO Test (i, pk) VALUES (5, 0)") 138 | self.cur.execute("INSERT INTO Test (i, pk) VALUES (42, 1)") 139 | self.con.commit() 140 | 141 | db = TestDB() 142 | db.addtable(Test) 143 | self.failUnless(len(db.tables["Test"].rows) == 2) 144 | row1 = db.tables["Test"].rows[0] 145 | self.failUnless(row1.i.value == 5) 146 | row2 = db.tables["Test"].rows[1] 147 | self.failUnless(row2.i.value == 42) 148 | 149 | def test_SetExtantRow(self): 150 | class Test(cellql.Table): 151 | i = cellql.integer(value=0) 152 | 153 | class TestDB(cellql.Database): 154 | connection = cells.makecell(value="sqlite://" + TESTDB) 155 | 156 | # first i'll manually create a db 157 | self.cur.execute("CREATE TABLE Test (i INTEGER, " + \ 158 | "pk INTEGER PRIMARY KEY)") 159 | self.con.commit() 160 | self.cur.execute("INSERT INTO Test (i, pk) VALUES (5, 0)") 161 | self.cur.execute("INSERT INTO Test (i, pk) VALUES (42, 1)") 162 | self.con.commit() 163 | 164 | # then i'll get a cellql db object 165 | db = TestDB() 166 | db.addtable(Test) # and connect it to the premade table 167 | 168 | # and show the connection found the extant rows 169 | self.failIf(db.tables["Test"].rows[0] is None) 170 | 171 | # now i'll make a new row 172 | newrow = Test() 173 | newrow.i.value = 13 174 | 175 | # and insert it into the db at a currently-occupied position 176 | db.tables["Test"].rows[0] = newrow 177 | 178 | # now i'll go in the back door again to see if it was 179 | # reflected in the actual db 180 | self.cur.execute("SELECT i FROM Test WHERE pk=0") 181 | l = self.cur.fetchall() 182 | self.failUnless(len(l) == 1) 183 | self.failUnless(l[0][0] == 13) 184 | # tada! 185 | 186 | def test_RowTypesInsertAndRetrieve(self): 187 | class Test(cellql.Table): 188 | s = cellql.string() 189 | r = cellql.real() 190 | b = cellql.blob() 191 | i = cellql.integer() 192 | 193 | db = cellql.Database(connection="sqlite://" + TESTDB) 194 | db.addtable(Test) 195 | 196 | orig_s = "Foo" 197 | orig_r = 3.14 198 | orig_b = { "complex": "objects", 199 | "can": "be", 200 | "stored in": "blobs!" } 201 | orig_i = 5 202 | 203 | newrow = Test() 204 | newrow.s.value = orig_s 205 | newrow.r.value = orig_r 206 | newrow.b.value = orig_b 207 | newrow.i.value = orig_i 208 | 209 | db.tables["Test"].rows[0] = newrow 210 | 211 | self.cur.execute("SELECT s,r,b,i FROM Test WHERE pk=0") 212 | db_s, db_r, db_b, db_i = self.cur.fetchall()[0] 213 | 214 | retrieved_row = Test() 215 | self.failUnless(orig_s == 216 | retrieved_row.s.translate_from_sql(db_s)) 217 | self.failUnless(round(orig_r, 3) == 218 | round(retrieved_row.r.translate_from_sql(db_r), 3)) 219 | self.failUnless(orig_i == 220 | retrieved_row.i.translate_from_sql(db_i)) 221 | 222 | translated_db_b = retrieved_row.b.translate_from_sql(db_b) 223 | self.failUnless(set(translated_db_b.keys()) == set(orig_b.keys())) 224 | for key, value in orig_b.iteritems(): 225 | self.failUnless(translated_db_b[key] == value) 226 | 227 | 228 | 229 | if __name__ == "__main__": 230 | unittest.main() 231 | -------------------------------------------------------------------------------- /cells/__init__.py: -------------------------------------------------------------------------------- 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 | PyCells 21 | 22 | PyCells is a port of Ken Tilton's Cells extenstion to Common 23 | Lisp. Cells are objects which automatically discover the cells which 24 | call them, and notify those cells of changes. 25 | 26 | A short example: 27 | 28 | >>> import cells 29 | >>> class Rectangle(cells.Model): 30 | ... width = cells.makecell(value=1) 31 | ... ratio = cells.makecell(value=1.618) 32 | ... @cells.fun2cell() 33 | ... def length(self, prev): 34 | ... print "Length updating..." 35 | ... return float(self.width) * float(self.ratio) 36 | ... 37 | >>> r = Rectangle() 38 | Length updating... 39 | >>> r.length 40 | 1.6180000000000001 41 | >>> r.width = 5 42 | Length updating... 43 | >>> r.length 44 | 8.0899999999999999 45 | 46 | 47 | @var DEBUG: Turns on debugging messages for *all* submodules. This is 48 | a whole lot of text, so you'll probably want to use the 49 | submodules' C{DEBUG} flags instead 50 | 51 | @var cellenv: Thread-local cell environment variables. 52 | """ 53 | DEBUG = False 54 | 55 | _DECO_OFFSET = 9 #: for the debug ' module > ' messages 56 | 57 | import threading 58 | 59 | cellenv = threading.local() 60 | cellenv.dp = 0 61 | cellenv.curr = None 62 | cellenv.curr_propogator = None 63 | cellenv.queued_updates = [] 64 | cellenv.deferred_sets = [] 65 | 66 | from .cellattr import CellAttr 67 | 68 | def makecell(*args, **kwargs): 69 | """ 70 | makecell(rule=None, value=None, unchanged_if=None, 71 | celltype=None) -> CellAttr 72 | 73 | Creates a new cell attribute in a L{Model}. This attribute may be 74 | accessed as one would access a non-cell attribute, and 75 | autovivifies itself in the instance upon first access (in 76 | L{Model.__init__}). 77 | 78 | @param rule: Define a rule which backs this cell. You must only 79 | define one of C{rule} or C{value}. Lacking C{celltype}, this 80 | creates a L{RuleCell}. This must be passed a callable with 81 | the signature C{f(self, prev) -> value}, where C{self} is the 82 | model instance the cell is in and C{prev} is the cell's 83 | out-of-date value. 84 | 85 | @param value: Define a value for this cell. You must only define 86 | one of C{rule} or C{value}. Lacking C{celltype}, this creates an 87 | L{InputCell}. 88 | 89 | @param unchanged_if: Sets a function to determine if a cell's 90 | value has changed. For example, 91 | 92 | >>> class A(cells.Model): 93 | ... x = cells.makecell(value=1, 94 | ... unchanged_if=lambda n,o: abs(n - o) > 5) 95 | ... y = cells.makecell(rule=lambda s,p: s.x * 2) 96 | ... 97 | >>> a = A() 98 | >>> a.x 99 | 1 100 | >>> a.y 101 | 2 102 | >>> a.x = 3 103 | >>> a.x 104 | 3 105 | >>> a.y 106 | 6 107 | >>> a.x = 90 108 | >>> a.x 109 | 3 110 | >>> a.y 111 | 6 112 | 113 | The signature for the passed function is C{f(old, new) -> 114 | bool}. 115 | 116 | @param celltype: Set the cell type to generate. You must pass C{rule} 117 | or C{value} correctly. Refer to L{cells.cell} for available 118 | types. 119 | """ 120 | return CellAttr(*args, **kwargs) 121 | 122 | def fun2cell(*args, **kwargs): 123 | """ 124 | fun2cell(unchanged_if=None, celltype=None) -> decorator 125 | 126 | A decorator which creates a new RuleCell using the decorated 127 | function as the C{rule} parameter. 128 | 129 | @param unchanged_if: Sets a function to determine if a cell's 130 | value has changed. For example, 131 | 132 | >>> class A(cells.Model): 133 | ... x = cells.makecell(value=1, 134 | ... unchanged_if=lambda n,o: abs(n - o) > 5) 135 | ... y = cells.makecell(rule=lambda s,p: s.x * 2) 136 | ... 137 | >>> a = A() 138 | >>> a.x 139 | 1 140 | >>> a.y 141 | 2 142 | >>> a.x = 3 143 | >>> a.x 144 | 3 145 | >>> a.y 146 | 6 147 | >>> a.x = 90 148 | >>> a.x 149 | 3 150 | >>> a.y 151 | 6 152 | 153 | The signature for the passed function is C{f(old, new) -> 154 | bool}. 155 | 156 | @param celltype: Set the cell type to generate. You must pass C{rule} 157 | or C{value} correctly. Refer to L{cells.cell} for available 158 | types. 159 | """ 160 | def fun2cell_decorator(func): 161 | return CellAttr(rule=func, *args, **kwargs) 162 | return fun2cell_decorator 163 | 164 | from .cell import Cell, InputCell, RuleCell, RuleThenInputCell, OnceAskedLazyCell 165 | from .cell import UntilAskedLazyCell, AlwaysLazyCell, DictCell, ListCell 166 | from .cell import _CellException, RuleCellSetError 167 | from .cell import InputCellRunError, SetDuringNotificationError 168 | 169 | from .model import Model, NonCellSetError 170 | from .family import Family, FamilyTraversalError 171 | from .synapse import ChangeSynapse 172 | 173 | def _debug(*msgs): 174 | """ 175 | debug() -> None 176 | 177 | Prints debug messages. 178 | """ 179 | if DEBUG: 180 | print(" ".join(msgs)) 181 | 182 | def reset(): 183 | """ 184 | reset() -> None 185 | 186 | Resets all of PyCells' globals back to their on-package-import 187 | values. This is a pretty dangerous thing to do if you care about 188 | the currently-instantiated cells' state, but quite useful while 189 | fooling around in an ipython session. 190 | """ 191 | global cellenv 192 | 193 | cellenv.dp = 1 194 | cellenv.curr = None 195 | cellenv.curr_propogator = None 196 | cellenv.queued_updates = [] 197 | cellenv.deferred_sets = [] 198 | 199 | reset() 200 | -------------------------------------------------------------------------------- /docs/toc-everything.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | Everything 7 | 8 | 9 | 10 | 11 | 13 |

Everything

14 |
15 |

All Classes

16 |

17 | cells.cell.AlwaysLazyCell

19 | cells.cell.DictCell

21 | cells.cell.EphemeralCellUnboundError

23 | cells.cell.InputCell

25 | cells.cell.InputCellRunError

27 | cells.cell.LazyCell

29 | cells.cell.ListCell

31 | cells.cell.RuleAndValueInitError

33 | cells.cell.RuleCell

35 | cells.cell.RuleCellSetError

37 | cells.cell.RuleThenInputCell

39 | cells.cell.SetDuringNotificationError

41 | cells.cell.UntilAskedLazyCell

46 |

47 | cells.cellattr.CellAttr

49 | cells.family.Family

51 | cells.family.FamilyTraversalError

53 | cells.model.BadInitError

55 | cells.model.Model

57 | cells.model.ModelMetatype

59 | cells.model.NonCellSetError

61 | cells.observer.Observer

63 | cells.observer.ObserverAttr

65 | cells.synapse.ChangeSynapse

67 | cells.synapse.Synapse

All Functions

69 |
70 |

71 | cells._debug

73 |
74 |

75 | cells.cell._debug

77 | 81 |

82 | cells.cellattr.debug

87 |

88 | cells.fun2cell

90 | cells.makecell

92 | cells.model.debug

97 |

98 | cells.reset

100 | cells.synapse.debug

All Variables

102 |

103 | cells.DEBUG

105 |

106 | cells._DECO_OFFSET

108 |

109 | cells.cell.DEBUG

111 | cells.cellattr.DEBUG

113 | cells.cellenv

115 | cells.family.DEBUG

117 | cells.model.DEBUG

119 | cells.observer.DEBUG

121 | cells.synapse.DEBUG


123 | [hide private] 125 | 126 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /cells/family.py: -------------------------------------------------------------------------------- 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 | Family, a specialized C{L{Model}} for networks of C{L{Model}}s. 21 | 22 | @var DEBUG: Turns on debugging messages for the cellattr module. 23 | """ 24 | 25 | DEBUG = False 26 | 27 | import cells 28 | from .model import Model, ModelMetatype 29 | from .cellattr import CellAttr 30 | from .cell import ListCell 31 | 32 | 33 | def _debug(*msgs): 34 | """ 35 | debug() -> None 36 | 37 | Prints debug messages. 38 | """ 39 | msgs = list(msgs) 40 | msgs.insert(0, "family".rjust(cells._DECO_OFFSET) + " > ") 41 | if DEBUG or cells.DEBUG: 42 | print(" ".join(msgs)) 43 | 44 | 45 | class Family(Model): 46 | """ 47 | Family 48 | 49 | A specialized C{L{Model}} which has C{kids}, C{kid_slots}, and a 50 | number of convenience functions for traversing the parent/child 51 | graph. 52 | 53 | @ivar kids: A list of Models which are guaranteed to have the 54 | attribute overrides defined in C{L{kid_slots}} 55 | 56 | @ivar kid_slots: An override definition for the Cells inserted 57 | into the C{L{kids}} list. The attributes overridden are every 58 | attribute defined in the class in C{kid_slots} minus the 59 | attributes defined in every Model. 60 | """ 61 | kids = cells.makecell(celltype=ListCell, kid_overrides=False) 62 | kid_slots = cells.makecell(value=Model, kid_overrides=False) 63 | 64 | def __init__(self, *args, **kwargs): 65 | Model.__init__(self, *args, **kwargs) 66 | 67 | def _kid_instance(self, klass=None): 68 | """ 69 | _kid_instance(self, klass) -> Cell 70 | 71 | Creates a new instance of a Cell based on the passed class (in 72 | C{klass}) and the overrides defined in C{kid_slots}. 73 | 74 | @param klass: The base type for the new kid instance 75 | """ 76 | if not klass: 77 | klass = self.kid_slots 78 | _debug("making an instance of", str(klass)) 79 | # first, find the attributes the kid_slots attrib actual wants to 80 | # define: 81 | override_attrnames = [] 82 | for attrname in dir(self.kid_slots): 83 | cvar = getattr(self.kid_slots, attrname) 84 | # if it's a cell attribute, check to see if it's one of 85 | # the "special", non-overriding slots (eg kids) 86 | if isinstance(cvar, CellAttr): 87 | if cvar.kid_overrides: 88 | # and if it isn't, add it to the list of overrides 89 | override_attrnames.append(attrname) 90 | # if it's a normal attribute, override only if it doesn't 91 | # exist in the base class 92 | else: 93 | if attrname not in dir(cells.Family): 94 | override_attrnames.append(attrname) 95 | 96 | # now, do the overrides, bypassing normal getattrs 97 | for attrib_name in override_attrnames: 98 | _debug("overriding", attrib_name, "in", str(klass)) 99 | setattr(klass, attrib_name, self.kid_slots.__dict__[attrib_name]) 100 | 101 | # add any observers the kid_slots class defines: 102 | klass._observernames.update(self.kid_slots._observernames) 103 | 104 | # XXX: should we memoize all that work here? 105 | 106 | # finally, return an instance of that munged class with this obj set 107 | # as its parent: 108 | i = klass(parent=self) 109 | return i 110 | 111 | def make_kid(self, klass): 112 | """ 113 | make_kid(self, klass) -> None 114 | 115 | Adds a new instance of a Cell based on the passed class (in 116 | C{klass}) and the overrides defined in C{kid_slots} into the 117 | C{kids} list 118 | 119 | @param klass: the base type for the new kid instance 120 | """ 121 | _debug("make_kid called with", str(klass)) 122 | self._add_kid(self._kid_instance(klass)) 123 | 124 | def _add_kid(self, kid): 125 | """ 126 | _add_kid(self, kid) -> None 127 | 128 | Inserts the kid into this Family's C{kids} list 129 | 130 | @param kid: the Model instance to insert 131 | """ 132 | kid.parent = self 133 | self.kids.append(kid) 134 | 135 | def position(self): 136 | """ 137 | position(self) -> int 138 | 139 | Returns this instance's position in the enclosing Family's 140 | C{kids} list. Returns -1 if there is no enclosing Family. 141 | 142 | @raise FamilyTraversalError: Raises if there is no enclosing Family 143 | """ 144 | if self.parent: 145 | return self.parent.kids.index(self) 146 | raise FamilyTraversalError("No enclosing Family") 147 | 148 | def previous_sib(self): 149 | """ 150 | previous_sib(self) -> Model 151 | 152 | Returns the Model previous to this Model in the enclosing 153 | Family's C{kids} list 154 | 155 | @raise FamilyTraversalError: Raises if there is no enclosing Family 156 | """ 157 | if self.parent and self.position() > 0: 158 | return self.parent.kids[self.position() - 1] 159 | raise FamilyTraversalError("No enclosing Family") 160 | 161 | def next_sib(self): 162 | """ 163 | previous_sib(self) -> Model 164 | 165 | Returns the Model subsequent to this Model in the enclosing 166 | Family's C{kids} list 167 | 168 | @raise FamilyTraversalError: Raises if there is no enclosing Family 169 | """ 170 | if self.parent and self.position() < len(self.parent.kids) - 1: 171 | return self.parent.kids[self.position() + 1] 172 | raise FamilyTraversalError("No enclosing Family") 173 | 174 | def grandparent(self): 175 | """ 176 | grandparent(self) -> Model 177 | 178 | Returns enclosing Family instance's enclosing Family instance, 179 | or None if no such object exists. 180 | """ 181 | # raise or just return None? 182 | if self.parent: 183 | if self.parent.parent: 184 | return self.parent.parent 185 | return None 186 | 187 | 188 | class FamilyTraversalError(Exception): 189 | """ 190 | Raised when there's some sort of error in C{L{Family}}'s traversal 191 | methods. 192 | """ 193 | 194 | def __init__(self, value): 195 | self.value = value 196 | 197 | def __str__(self): 198 | return repr(self.value) 199 | -------------------------------------------------------------------------------- /tutorials/cell_varieties.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | PyCells Tutorials, part three: Cell Varieties 5 | 6 | There's a number of different types of cells. In this tutorial I'll 7 | talk about them. 8 | """ 9 | 10 | import cells 11 | 12 | # In the basic tutorial, I showed the two most common cell types: 13 | # Input cells and Rule cells. Now let's look at the more exotic Cells. 14 | 15 | # First, the DictCell, which propogates changes to its dictionary. An 16 | # ordinary InputCell won't, since dictionaries are mutable. An example: 17 | 18 | # We'd like an address book. We'd like to set the entries as a 19 | # dictionary of names and email addresses: 20 | class AddressBook(cells.Model): 21 | entries = cells.makecell(value={}) 22 | names = cells.makecell(rule=lambda s,p: s.entries.keys()) 23 | 24 | # and use it like this: 25 | if __name__ == "__main__": 26 | a = AddressBook() 27 | print "Created an AddressBook. I'll add Truman and Bob's addresses to it." 28 | a.entries["Truman Capote"] = "capote@example.net" 29 | a.entries["Robert Sullivan"] = "ratguy@example.net" 30 | 31 | print "Your address book includes the following folks:", repr(a.names) 32 | print 33 | 34 | # Yeah, that doesn't work at all. Why? Because the dictionary doesn't 35 | # propogate the changes. A default Cell simply asks if the old value 36 | # == the new value; when a dictionary has entries altered, it still == 37 | # its pre-change value. But if we specify a DictCell, we get the 38 | # propgation we expect here: 39 | class WorkingAddressBook(AddressBook): 40 | entries = cells.makecell(value={}, celltype=cells.DictCell) 41 | 42 | if __name__ == "__main__": 43 | a = WorkingAddressBook() 44 | print "Created a WorkingAddressBook, now I'll add Truman and Bob to it." 45 | a.entries["Truman Capote"] = "capote@example.net" 46 | a.entries["Robert Sullivan"] = "ratguy@example.net" 47 | 48 | print "Your address book includes the following folks:", repr(a.names) 49 | print 50 | 51 | # Sometimes a Model needs a cell whose value is determined by a 52 | # function, but afterwards acts like an input cell. Let's say we want 53 | # to pull the address book's entries from a pickle, if it exists, on 54 | # cell init, but then allow the entries be modified as before. 55 | import pickle, os.path 56 | 57 | class StorableAddressBook(WorkingAddressBook): 58 | entry_file = cells.makecell(value="./addresses.pickle") 59 | 60 | # We'll build this autoloading entry db with a RuleThenInputCell: 61 | @cells.fun2cell(celltype=cells.RuleThenInputCell) 62 | def entries(self, prev): 63 | # These are written like RuleCells. First we'll look for an 64 | # extant entry file: 65 | if not os.path.isfile(self.entry_file): 66 | # And if it doesn't exist, we'll just return an empty dictionary. 67 | return {} 68 | else: 69 | # If there is a file there, we'll load it into memory 70 | # using the pickle module. 71 | return pickle.load(open(self.entry_file, 'r')) 72 | 73 | # What a RuleThenInputCell does is run the rule, which should generate 74 | # a value for the cell, then transform into an InputCell which may be 75 | # altered. In this case, it loads the previous address book dictionary 76 | # or makes a new, empty one. Let's watch it work: 77 | if __name__ == "__main__": 78 | a = StorableAddressBook() 79 | print "Created a StorableAddressBook. Adding new entries..." 80 | # Now we should have a new, empty address book, providing there's 81 | # no pickle file where StorableAddressBook's init looked. 82 | print "Address book is", repr(a.entries) 83 | print "Altering address book..." 84 | # Let's change the names ... 85 | a.entries = { "Truman Capote": "capote@example.net", 86 | "Robert Sullivan": "ratguy@example.net" } 87 | # to see that it's now acting like an InputCell: 88 | print "Address book is", repr(a.entries) 89 | 90 | # Okay, let's test the unpickling. First, we'll pickle a couple of 91 | # entries: 92 | print "Writing some new entries out to the file" 93 | new_entries = { "David Sedaris": "dave@example.net", 94 | "Michael Chabon": "chabon@example.net" } 95 | pickle.dump(new_entries, open(a.entry_file, 'w')) 96 | 97 | # now we'll make a new StorableAddressBook object, which, upon 98 | # init, will unpickle those entries 99 | b = StorableAddressBook() 100 | print "Created another StorableAddressBook." 101 | print "This new address book is", repr(b.entries) 102 | print 103 | 104 | # Another variety of cell is an ephemeral cell. Ephemerals -- 105 | # InputCells with the ephemeral flag turned "on" -- propgate their 106 | # changes and then return their value to None. For example, suppose we 107 | # want to add entries to the address book by placing a tuple of name & 108 | # email into an input cell: 109 | import copy 110 | 111 | class AlternateInputAddressBook(WorkingAddressBook): 112 | # I'll add an ephemeral input cell to take the tuple 113 | entry = cells.makecell(value=None, ephemeral=True) 114 | 115 | # and a RuleCell to build a dictionary with the entries 116 | @cells.fun2cell() 117 | def entries(self, prev): 118 | d = copy.copy(prev) # prevent the old == new dict issue 119 | if not d: 120 | d = {} 121 | 122 | if self.entry: 123 | d[self.entry[0]] = self.entry[1] 124 | return d 125 | 126 | @AlternateInputAddressBook.observer(attrib="entry") 127 | def entry_obs(self): 128 | if self.entry: 129 | print "New entry:", repr(self.entry) 130 | 131 | @AlternateInputAddressBook.observer(attrib="entries") 132 | def entries_obs(self): 133 | if self.entries: 134 | print "The address book is now:", repr(self.entries) 135 | 136 | if __name__ == "__main__": 137 | print "Creating an AlternateInputAddressBook, then adding an entry." 138 | a = AlternateInputAddressBook() 139 | a.entry = ("Frank Miller", "frank.miller@example.net") 140 | print "The entry cell is now:", repr(a.entry) 141 | print 142 | 143 | # The last class of cell I'll talk about in this tutorial is the lazy 144 | # cell. There are several varieties of lazy cells, but I'll just talk 145 | # about the vanilla lazy cell, an AlwaysLazyCell. Lazy cells are 146 | # variants of RuleCells which only update when they are queried, 147 | # instead of when one of their dependencies updates. An example should 148 | # clear that up: 149 | 150 | # Let's say I want a flag to show if a person is available. It's an 151 | # expensive calculation, requiring a query out to the very slow 152 | # security system to see which offices have had movement in them in 153 | # the last 30 seconds. Because of this, I don't want to make the call 154 | # every time I change the entries in my address book -- I only want to 155 | # make it when I want that bit of info. 156 | import random 157 | 158 | class MonitoringAddressBook(WorkingAddressBook): 159 | # So we'll make a RuleCell-looking thing, but specify it should be 160 | # lazy: 161 | @cells.fun2cell(celltype=cells.AlwaysLazyCell) 162 | def isthere(self, prev): 163 | d = {} 164 | print "Querying security system" 165 | for person in self.names: 166 | # here's where my expensive calculation goes... 167 | d[person] = random.choice([True, False]) 168 | 169 | return d 170 | 171 | # And, again, let's see it in action 172 | if __name__ == "__main__": 173 | print "Creating a monitoring address book..." 174 | c = MonitoringAddressBook() 175 | print "Address book is", repr(dict(c.entries)) 176 | print "Adding a couple of entries..." 177 | c.entries["David Foster Wallace"] = "dfw@example.com" 178 | c.entries["Joyce Carol Oates"] = "jco@example.com" 179 | print "Address book is", repr(c.entries) 180 | 181 | # So now we'll see that the lazy cell's rule only runs when it's queried 182 | print "Let's see who's around..." 183 | result = c.isthere["David Foster Wallace"] 184 | # "Querying security system" will print here 185 | print "DFW is in?", result 186 | # But it's still a cell, and needless calculations won't happen: 187 | result = c.isthere["Joyce Carol Oates"] 188 | # Nothing prints here, as the lazy rule cell doesn't need to update 189 | print "JCO is in?", result 190 | -------------------------------------------------------------------------------- /cells/cellattr.py: -------------------------------------------------------------------------------- 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 | CellAttr, a wrapper for C{L{Cell}} objects within C{L{Models}} 21 | 22 | @var DEBUG: Turns on debugging messages for the cellattr module. 23 | """ 24 | 25 | import cells 26 | from .cell import Cell, RuleCell, InputCell 27 | 28 | DEBUG = False 29 | 30 | 31 | def debug(*msgs): 32 | """ 33 | debug() -> None 34 | 35 | Prints debug messages. 36 | """ 37 | msgs = list(msgs) 38 | msgs.insert(0, "cell attr".rjust(cells._DECO_OFFSET) + " > ") 39 | if DEBUG or cells.DEBUG: 40 | print(" ".join(msgs)) 41 | 42 | 43 | class CellAttr(object): 44 | """ 45 | CellAttr 46 | 47 | A descriptor which auto-vivifies a new Cell in each instance of a 48 | Model, and which does the hiding of C{Cell.L{get}()} and 49 | C{Cell.L{set}()}. 50 | """ 51 | 52 | # kid_overrides is essentially internal, so it's hidden from the 53 | # documentation 54 | def __init__(self, kid_overrides=True, *args, **kwargs): 55 | """ 56 | __init__(self, rule=None, value=None, unchanged_if=None, celltype=None) 57 | 58 | Sets the parameters which will be used as defaults to build a 59 | Cell when the time comes. 60 | 61 | @param rule: Define a rule which backs this cell. You must 62 | only define one of C{rule} or C{value}. Lacking C{celltype}, 63 | this creates a L{RuleCell}. This must be passed a 64 | callable with the signature C{f(self, prev) -> value}, 65 | where C{self} is the model instance the cell is in and 66 | C{prev} is the cell's out-of-date value. 67 | 68 | @param value: Define a value for this cell. You must only 69 | define one of C{rule} or C{value}. Lacking C{celltype}, this 70 | creates an L{InputCell}. 71 | 72 | @param unchanged_if: Sets a function to determine if a cell's 73 | value has changed. The signature for the passed function 74 | is C{f(old, new) -> bool}. 75 | 76 | @param celltype: Set the cell type to generate. You must pass 77 | C{rule} or C{value} correctly. Refer to L{cells.cell} for 78 | available types. 79 | """ 80 | self.kid_overrides = kid_overrides 81 | self.args = args 82 | self.kwargs = kwargs 83 | 84 | if "rule" in kwargs: 85 | self.__doc__ = kwargs['rule'].__doc__ 86 | 87 | def __set__(self, owner, value): 88 | """ 89 | __set__(self, owner, value) -> None 90 | 91 | Runs C{L{set}(value)} on the Cell in C{owner}. 92 | 93 | @param owner: The Model instance in which to look for the Cell 94 | to run C{L{set}()} on. 95 | 96 | @param value: The value to set 97 | """ 98 | self.getcell(owner).set(value) 99 | 100 | def __get__(self, owner, ownertype): 101 | """ 102 | __get__(self, owner, ownertype) -> value 103 | 104 | Runs C{L{get}()} on the Cell in C{owner}, if C{owner} is 105 | passed. Otherwise, return C{self}. 106 | 107 | @param owner: The Model instance in which to look for the Cell 108 | to run C{L{get}()} on . 109 | 110 | @param ownertype: (unused) 111 | """ 112 | if not owner: return self 113 | 114 | cell = self.getcell(owner) 115 | if isinstance(cell, (cells.cell.ListCell, cells.cell.DictCell)): 116 | return cell 117 | else: 118 | # return the value in owner.myname 119 | return cell.getvalue() 120 | 121 | def getkwargs(self, owner): 122 | """ 123 | getkwargs(owner) -> dict 124 | 125 | Returns the keyword arguments for the target cell, taking into 126 | account any overrides which exist in the passed owner model 127 | """ 128 | newkwargs = self.kwargs.copy() 129 | override = owner._initregistry.get(self.name) 130 | if override: 131 | newkwargs.update(override) 132 | 133 | return newkwargs 134 | 135 | def getcell(self, owner): 136 | """ 137 | getcell(owner) -> Cell 138 | 139 | Return the instance of this CellAttr's Cell in C{owner}. If an 140 | instance of the CellAttr's Cell doesn't exist in C{owner}, 141 | first insert an instance into C{owner}, then return it. To 142 | build it, first examine the C{owner} for runtime overrides of 143 | this cell, and use those (if they exist) as parameters to 144 | C{L{buildcell}}. 145 | 146 | @param owner: The Model instance in which to look for the Cell. 147 | """ 148 | # if there isn't a value in owner.myname, make it a cell 149 | debug("got request for cell in", self.name) 150 | if self.name not in list(owner.__dict__.keys()): 151 | debug(self.name, "not in owner. Building a new cell in it.") 152 | newcell = self.buildcell(owner, *self.args, **self.getkwargs(owner)) 153 | owner.__dict__[self.name] = newcell 154 | 155 | # observers have to be run *after* the cell is embedded in the 156 | # instance! 157 | owner._run_observers(newcell) 158 | 159 | debug("finished getting", self.name) 160 | return owner.__dict__[self.name] 161 | 162 | def buildcell(self, owner, *args, **kwargs): 163 | """ 164 | buildcell(self, owner, rule=None, value=None, celltype=None, 165 | unchanged_if=None) -> Cell 166 | 167 | Builds an instance of a Cell into C{owner}. Will automatically 168 | create the right type of Cell given the keyword arguments if 169 | C{celltype} is not passed 170 | 171 | @param owner: The Model instance in which to build the Cell. 172 | 173 | @param rule: Define a rule which backs this cell. You must 174 | only define one of C{rule} or C{value}. Lacking C{celltype}, 175 | this creates a L{RuleCell}. This must be passed a 176 | callable with the signature C{f(self, prev) -> value}, 177 | where C{self} is the model instance the cell is in and 178 | C{prev} is the cell's out-of-date value. 179 | 180 | @param value: Define a value for this cell. You must only 181 | define one of C{rule} or C{value}. Lacking C{celltype}, this 182 | creates an L{InputCell}. 183 | 184 | @param unchanged_if: Sets a function to determine if a cell's 185 | value has changed. The signature for the passed function 186 | is C{f(old, new) -> bool}. 187 | 188 | @param celltype: Set the cell type to generate. You must pass 189 | C{rule} or C{value} correctly. Refer to L{cells.cell} for 190 | available types. 191 | """ 192 | 193 | """Creates a new cell of the appropriate type""" 194 | debug("Building cell: owner:", str(owner)) 195 | debug(" name:", self.name) 196 | debug(" args:", str(args)) 197 | debug(" kwargs:", str(kwargs)) 198 | # figure out what type the user wants: 199 | if 'celltype' in kwargs: # user-specified cell 200 | celltype = kwargs["celltype"] 201 | elif 'rule' in kwargs: # it's a rule-cell. 202 | celltype = RuleCell 203 | elif 'value' in kwargs: # it's a value-cell 204 | celltype = InputCell 205 | else: 206 | raise Exception("Could not determine target type for cell " + 207 | "given owner: " + str(owner) + 208 | ", name: " + self.name + 209 | ", args:" + str(args) + 210 | ", kwargs:" + str(kwargs)) 211 | 212 | kwargs['name'] = self.name 213 | 214 | return celltype(owner, *args, **kwargs) 215 | -------------------------------------------------------------------------------- /cells/observer.py: -------------------------------------------------------------------------------- 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 list(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 priority: When this observer should be run compared to the 129 | other observers on this model. Larger priorities run first, 130 | None (default priority) is run last. Observers with the same 131 | priority are possible; there are no guarantees as to the run 132 | order in that case. 133 | 134 | @ivar last_ran: The DP this observer last ran in. Observers only 135 | run once per DP. 136 | """ 137 | 138 | def __init__(self, attrib, oldvalue, newvalue, func, priority=None): 139 | """__init__(self, attrib, oldvalue, newvalue, func, priority) 140 | 141 | Initializes a new Observer. All arguments are required, but 142 | only func is required to be anything but none. 143 | 144 | See attrib, oldvalue, and newvalue instance variable docs for 145 | explanation of their utility.""" 146 | self.attrib_name = attrib 147 | self.oldvalue = oldvalue 148 | self.newvalue = newvalue 149 | self.func = func 150 | self.priority = priority 151 | self.last_ran = 0 152 | 153 | def run_if_applicable(self, model, attr): 154 | """ 155 | Determine whether this observer should fire, and fire if 156 | appropriate. 157 | 158 | @param model: the model instance to search for matching cells 159 | within. 160 | 161 | @param attr: the attribute which "asked" this observer to run. 162 | """ 163 | _debug("running observer", self.func.__name__) 164 | if self.last_ran == cells.cellenv.dp: # never run twice in one DP 165 | _debug(self.func.__name__, "already ran in this dp") 166 | return 167 | 168 | if self.attrib_name: 169 | if isinstance(self.attrib_name, str): 170 | attrs = (self.attrib_name,) 171 | else: 172 | attrs = self.attrib_name 173 | 174 | for attrib_name in attrs: 175 | if isinstance(attr, Cell): 176 | if attr.name == attrib_name: 177 | _debug("found a cell with matching name!") 178 | break 179 | elif getattr(model, attrib_name) is attr: 180 | _debug(self.func.__name__, "looked in its model for an " + 181 | "attrib with its desired name; found one that " + 182 | "matched passed attr.") 183 | break 184 | else: 185 | _debug("Attribute name tests failed") 186 | return 187 | 188 | if self.newvalue: 189 | if isinstance(attr, Cell): 190 | if not self.newvalue(attr.value): 191 | _debug(self.func.__name__, 192 | "function didn't match cell's new value") 193 | return 194 | else: 195 | if not self.newvalue(attr): 196 | _debug(self.func.__name__, "function didn't match non-cell") 197 | return 198 | 199 | # since this is immediately post-value change, the last_value attr 200 | # of the cell is still good. 201 | if self.oldvalue: 202 | if isinstance(attr, Cell): 203 | if not self.oldvalue(attr.last_value): 204 | _debug(self.func.__name__, 205 | "function didn't match old value") 206 | return 207 | 208 | # if we're here, it passed all the tests, so 209 | _debug(self.func.__name__, "running") 210 | self.func(model) 211 | self.last_ran = cells.cellenv.dp 212 | -------------------------------------------------------------------------------- /docs/epydoc.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | /* Tables */ 4 | table.help { margin-left: auto; margin-right: auto; } 5 | th.summary, th.details, th.index 6 | { text-align: left; font-size: 120%; } 7 | th.group { text-align: left; font-size: 120%; 8 | font-style: italic; } 9 | 10 | /* Documentation page titles */ 11 | h2.module { margin-top: 0.2em; } 12 | h2.class { margin-top: 0.2em; } 13 | h2.type { margin-top: 0.2em; } 14 | h2.py-src { margin-top: 0.2em; } 15 | 16 | /* Headings */ 17 | h1.help { text-align: center; } 18 | h1.heading { font-size: +140%; font-style: italic; 19 | font-weight: bold; } 20 | h2.heading { font-size: +125%; font-style: italic; 21 | font-weight: bold; } 22 | h3.heading { font-size: +110%; font-style: italic; 23 | font-weight: normal; } 24 | h1.tocheading { text-align: center; font-size: 105%; margin: 0; 25 | font-weight: bold; padding: 0; } 26 | h2.tocheading { font-size: 100%; margin: 0.5em 0 0 -0.3em; 27 | font-weight: bold; } 28 | 29 | /* Table of contents */ 30 | p.toc { margin: 0; padding: 0; } 31 | 32 | /* Base tree */ 33 | pre.base-tree { font-size: 80%; margin: 0; } 34 | 35 | /* Summary Sections */ 36 | p.varlist { padding: 0 0 0 7em; text-indent: -7em; 37 | margin: 0; } 38 | .varlist-header { font-weight: bold; } 39 | p.imports { padding: 0 0 0 7em; text-indent: -7em; } 40 | .imports-header { font-weight: bold; } 41 | 42 | /* Details Sections */ 43 | table.func-details { border-width: 2px; border-style: groove; 44 | padding: 0 1em 0 1em; margin: 0.4em 0 0 0; } 45 | h3.func-detail { margin: 0 0 1em 0; } 46 | table.var-details { border-width: 2px; border-style: groove; 47 | padding: 0 1em 0 1em; margin: 0.4em 0 0 0; } 48 | h3.var-details { margin: 0 0 1em 0; } 49 | table.prop-details { border-width: 2px; border-style: groove; 50 | padding: 0 1em 0 1em; margin: 0.4em 0 0 0; } 51 | h3.prop-details { margin: 0 0 1em 0; } 52 | 53 | /* Function signatures */ 54 | .sig { font-weight: bold; } 55 | 56 | /* Doctest blocks */ 57 | .py-prompt { font-weight: bold;} 58 | pre.doctestblock { padding: .5em; margin: 1em; 59 | border-width: 1px; border-style: solid; } 60 | table pre.doctestblock 61 | { padding: .5em; margin: 1em; 62 | border-width: 1px; border-style: solid; } 63 | 64 | /* Variable values */ 65 | pre.variable { padding: .5em; margin: 0; 66 | border-width: 1px; border-style: solid; } 67 | 68 | /* Navigation bar */ 69 | table.navbar { border-width: 2px; border-style: groove; } 70 | .nomargin { margin: 0; } 71 | 72 | /* This is used in
sections containing tables of private 73 | values, to make them flow more seamlessly with the table that 74 | comes before them. */ 75 | .continue { border-top: 0; } 76 | 77 | /* Links */ 78 | a.navbar { text-decoration: none; } 79 | 80 | /* Source Code Listings */ 81 | pre.py-src { border: 2px solid black; } 82 | div.highlight-hdr { border-top: 2px solid black; 83 | border-bottom: 1px solid black; } 84 | div.highlight { border-bottom: 2px solid black; } 85 | a.pysrc-toggle { text-decoration: none; } 86 | .py-line { border-left: 2px solid black; margin-left: .2em; 87 | padding-left: .4em; } 88 | .lineno { font-style: italic; font-size: 90%; 89 | padding-left: .5em; } 90 | /*a.py-name { text-decoration: none; }*/ 91 | 92 | /* For Graphs */ 93 | .graph-without-title { border: none; } 94 | .graph-with-title { border: 1px solid black; } 95 | .graph-title { font-weight: bold; } 96 | 97 | /* Lists */ 98 | ul { margin-top: 0; } 99 | 100 | /* Misc. */ 101 | .footer { font-size: 85%; } 102 | .header { font-size: 85%; } 103 | .breadcrumbs { font-size: 85%; font-weight: bold; } 104 | .options { font-size: 70%; } 105 | .rtype, .ptype, .vtype 106 | { font-size: 85%; } 107 | dt { font-weight: bold; } 108 | .small { font-size: 85%; } 109 | 110 | h2 span.codelink { font-size: 58%; font-weight: normal; } 111 | span.codelink { font-size: 85%; font-weight; normal; } 112 | 113 | /* Body color */ 114 | body { background: #ffffff; color: #000000; } 115 | 116 | /* Tables */ 117 | table.summary, table.details, table.index 118 | { background: #e8f0f8; color: #000000; } 119 | tr.summary, tr.details, tr.index 120 | { background: #70b0ff; color: #000000; } 121 | th.group { background: #c0e0f8; color: #000000; } 122 | 123 | /* Details Sections */ 124 | table.func-details { background: #e8f0f8; color: #000000; 125 | border-color: #c0d0d0; } 126 | h3.func-detail { background: transparent; color: #000000; } 127 | table.var-details { background: #e8f0f8; color: #000000; 128 | border-color: #c0d0d0; } 129 | h3.var-details { background: transparent; color: #000000; } 130 | table.prop-details { background: #e8f0f8; color: #000000; 131 | border-color: #c0d0d0; } 132 | h3.prop-details { background: transparent; color: #000000; } 133 | 134 | /* Function signatures */ 135 | .sig { background: transparent; color: #000000; } 136 | .sig-name { background: transparent; color: #006080; } 137 | .sig-arg, .sig-kwarg, .sig-vararg 138 | { background: transparent; color: #008060; } 139 | .sig-default { background: transparent; color: #602000; } 140 | .summary-sig { background: transparent; color: #000000; } 141 | .summary-sig-name { background: transparent; color: #204080; } 142 | .summary-sig-arg, .summary-sig-kwarg, .summary-sig-vararg 143 | { background: transparent; color: #008060; } 144 | 145 | /* Souce code listings & doctest blocks */ 146 | .py-src { background: transparent; color: #000000; } 147 | .py-prompt { background: transparent; color: #005050; } 148 | .py-string { background: transparent; color: #006030; } 149 | .py-comment { background: transparent; color: #003060; } 150 | .py-keyword { background: transparent; color: #600000; } 151 | .py-output { background: transparent; color: #404040; } 152 | .py-name { background: transparent; color: #000050; } 153 | .py-name:link { background: transparent; color: #000050; } 154 | .py-name:visited { background: transparent; color: #000050; } 155 | .py-number { background: transparent; color: #005000; } 156 | .py-def-name { background: transparent; color: #000060; 157 | font-weight: bold; } 158 | .py-base-class { background: transparent; color: #000060; } 159 | .py-param { background: transparent; color: #000060; } 160 | .py-docstring { background: transparent; color: #006030; } 161 | .py-decorator { background: transparent; color: #804020; } 162 | 163 | pre.doctestblock { background: #f4faff; color: #000000; 164 | border-color: #708890; } 165 | table pre.doctestblock 166 | { background: #dce4ec; color: #000000; 167 | border-color: #708890; } 168 | div.py-src { background: #f0f0f0; } 169 | div.highlight-hdr { background: #d8e8e8; } 170 | div.highlight { background: #d0e0e0; } 171 | 172 | 173 | /* Variable values */ 174 | pre.variable { background: #dce4ec; color: #000000; 175 | border-color: #708890; } 176 | .variable-linewrap { background: transparent; color: #604000; } 177 | .variable-ellipsis { background: transparent; color: #604000; } 178 | .variable-quote { background: transparent; color: #604000; } 179 | .re { background: transparent; color: #000000; } 180 | .re-char { background: transparent; color: #006030; } 181 | .re-op { background: transparent; color: #600000; } 182 | .re-group { background: transparent; color: #003060; } 183 | .re-ref { background: transparent; color: #404040; } 184 | 185 | /* Navigation bar */ 186 | table.navbar { background: #a0c0ff; color: #0000ff; 187 | border-color: #c0d0d0; } 188 | th.navbar { background: #a0c0ff; color: #0000ff; } 189 | th.navselect { background: #70b0ff; color: #000000; } 190 | 191 | /* Links */ 192 | a:link { background: transparent; color: #0000ff; } 193 | a:visited { background: transparent; color: #204080; } 194 | a.navbar:link { background: transparent; color: #0000ff; } 195 | a.navbar:visited { background: transparent; color: #204080; } 196 | -------------------------------------------------------------------------------- /docs/cells.cell.CellException-class.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | cells.cell.CellException 7 | 8 | 9 | 10 | 11 | 13 | 14 | 16 | 17 | 18 | 20 | 21 | 22 | 24 | 25 | 26 | 28 | 29 | 30 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 45 | 56 | 57 |
39 | 40 | Package cells :: 41 | Module cell :: 42 | Class CellException 43 | 44 | 46 | 47 | 48 | 50 | 54 |
[hide private]
[frames] | no frames]
55 |
58 | 59 |

Class CellException 60 |
source code

61 |
 62 | exceptions.Exception --+
 63 |                        |
 64 |                       CellException
 65 | 
66 | 67 |
Known Subclasses:
68 |
69 | RuleCellSetError, 70 | EphemeralCellUnboundError, 71 | InputCellRunError, 72 | SetDuringNotificationError 73 |
74 | 75 |
76 | 77 | 78 | 80 | 81 | 92 | 93 | 94 | 97 | 102 | 103 | 104 | 107 | 111 | 112 | 113 | 118 | 119 |
82 | 83 | 84 | 85 | 89 | 90 |
Instance Methods[hide private]
91 |
95 |   96 | 98 | __init__(self, 99 | value) 100 | 101 |
105 |   106 | 108 | __str__(self) 109 | 110 |
114 |

Inherited from exceptions.Exception: 115 | __getitem__ 116 |

117 |
120 | 121 |
122 | 123 | 124 | 126 | 127 | 138 | 139 |
128 | 129 | 130 | 131 | 135 | 136 |
Method Details[hide private]
137 |
140 | 141 |
142 |
143 | 144 | 152 |
145 |

__init__(self, 146 | value) 147 |
(Constructor) 148 |

149 |
source code 
153 | 154 | 155 |
156 |
Overrides: 157 | exceptions.Exception.__init__ 158 |
159 |
160 |
161 |
162 | 163 |
164 |
165 | 166 | 173 |
167 |

__str__(self) 168 |
(Informal representation operator) 169 |

170 |
source code 
174 | 175 | 176 |
177 |
Overrides: 178 | exceptions.Exception.__str__ 179 |
180 |
181 |
182 |
183 |
184 | 185 | 187 | 188 | 189 | 191 | 192 | 193 | 195 | 196 | 197 | 199 | 200 | 201 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 211 | 214 | 215 |
216 | 217 | 226 | 227 | 228 | 229 | -------------------------------------------------------------------------------- /tests/cell_types.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | 4 | import unittest, sys 5 | sys.path += "../" 6 | import cells 7 | 8 | """ 9 | There are several types of cells. These are their rules: 10 | 11 | Input Cells: 12 | 1. Must be 'set'able 13 | 2. A set during propogation must defer until the propogation is complete 14 | 3. Trying to .run() a input cell raises an exception 15 | 16 | DictCells: 17 | 1. Act like InputCells 18 | 2. Also propogate changes if the hash is changed -- ie, if a key is 19 | set to a different value. 20 | 21 | Rule Cells: 22 | 1. Trying to set a rule cell throws an exception 23 | 24 | Rule Then Input Cells: 25 | 1. Evaluates once at init, sets its value, and then acts like an InputCell 26 | 27 | Lazy Rule Cell, Once-asked flavor: 28 | 1. Evaluates during model initialization (if it's housed in a model) 29 | 2. Does not evaluate immediately when changes are propogated to it 30 | 3. Evaluates, if neccessary, when read 31 | 32 | Lazy Rule Cell, Until-asked flavor: 33 | 1. Evaluates only when read 34 | 2. After evaluating, acts like a standard rule cell (ie, responds to propogation 35 | normally). 36 | 37 | Lazy Rule Cell, Always-lazy flavor: 38 | 1. Evaluates only when read, and is unbound or a dependency has changed. 39 | 2. Does not evaluate during model init 40 | 41 | Ephemeral Cells: 42 | 1. After propogation, their value is returned to None 43 | """ 44 | 45 | class CellTypeTests_Input(unittest.TestCase): 46 | def setUp(self): 47 | cells.reset() 48 | self.x = cells.InputCell(None, 5, name="x") 49 | 50 | def test_1_Settability(self): 51 | "Input 1: An input cell must be settable" 52 | try: 53 | self.x.set(4) 54 | except Exception as e: 55 | self.fail() 56 | 57 | def test_2_SetDuringPropogation(self): 58 | "Input 2: A set during propogation must defer until propogation is complete" 59 | a = cells.RuleCell(None, lambda s,p: self.x.getvalue() * 3, name="a") 60 | 61 | self.captured_x = None 62 | def d_rule(s,p): 63 | self.x.set(2) 64 | if not self.captured_x: 65 | self.captured_x = self.x.value 66 | return a.getvalue() 67 | d = cells.RuleCell(None, d_rule, name="d") 68 | 69 | # establish dependencies. 70 | d.getvalue() # x.value = 2, now. 71 | self.captured_x = None # set trap 72 | self.x.set(5) 73 | # that set propogates to a, and a.value = 15. a's change propogates to 74 | # d. The x.set(2) happens in the middle of d's rule, but it defers until 75 | # the propogation is complete. So the captured x value should be 5. 76 | self.failUnless(self.captured_x == 5) 77 | 78 | def test_3_RunRaises(self): 79 | "Input 3: Running a value cell raises an exception." 80 | self.failUnlessRaises(cells.InputCellRunError, self.x.run) 81 | 82 | class CellTypeTests_Rule(unittest.TestCase): 83 | def test_1a_Nonsettability(self): 84 | "Rule 1: Rule cells may not be set" 85 | cells.reset() 86 | x = cells.RuleCell(None, lambda s,p: 0, name="x") 87 | self.failUnlessRaises(cells.RuleCellSetError, x.set, 5) 88 | 89 | class CellTypeTests_RuleThenInput(unittest.TestCase): 90 | def test_1b_RunRaisesAfterEval(self): 91 | """Rule 1: 1. Evaluates once at init, sets its value, and then 92 | acts like an InputCell. 93 | """ 94 | cells.reset() 95 | x = cells.RuleThenInputCell(None, rule=lambda s,p: 0, name="x") 96 | x.getvalue() 97 | self.failUnlessRaises(cells.InputCellRunError, x.run) 98 | 99 | class CellTypeTests_OnceAskedLazy(unittest.TestCase): 100 | def setUp(self): 101 | cells.reset() 102 | self.x = cells.InputCell(None, 4, name="x") 103 | self.a = cells.OnceAskedLazyCell(None, name="a", 104 | rule=lambda s,p: self.x.getvalue()+(p or 1)) 105 | 106 | self.a.getvalue() # establish dependencies 107 | self.x.set(42) # cause propogation 108 | 109 | def test_1_EvaluateDuringMOInit(self): 110 | "Once-asked Lazy 1: Once-asked lazy cells are eval'd during MO init." 111 | #TODO: Make a real test 112 | pass 113 | 114 | def test_2_NoEvalOnPropogation(self): 115 | "Once-asked Lazy 2: Once-asked lazy cells are not evaluated when changes propogate to them" 116 | self.failUnless(self.a.value == 5) # sneaky exam doesn't cause eval 117 | 118 | def test_3_EvalOnRead(self): 119 | "Once-asked Lazy 3: Once-asked lazy cells with changed called cells evaluate when called" 120 | self.failUnless(self.a.getvalue() == 47) # standard exam causes eval 121 | 122 | class CellTypeTests_UntilAskedLazy(unittest.TestCase): 123 | def setUp(self): 124 | cells.reset() 125 | self.x = cells.InputCell(None, 4, name="x") 126 | self.a = cells.UntilAskedLazyCell(None, name="a", 127 | rule=lambda s,p: self.x.getvalue() + 1) 128 | self.a.getvalue(init=True) # establish dependencies, a = 5 129 | self.x.set(42) # cause propogation 130 | 131 | def test_1_EvalOnRead(self): 132 | "Until-asked Lazy 1: Until-asked lazys with changed called cells are evaluated when called" 133 | self.failUnless(self.a.value == 5) # sneaky exam doesn't cause eval 134 | self.failUnless(self.a.getvalue() == 43) # standard exam causes eval 135 | 136 | def test_2_ActsNormalAfterEval(self): 137 | "Until-asked Lazy 2: Until-asked lazys act like normal rule cells after initial evaluation" 138 | self.failUnless(self.a.value == 5) # sneaky exam doesn't cause eval 139 | self.failUnless(self.a.getvalue() == 43) # standard exam causes eval 140 | self.x.set(5) # cause another propogation 141 | self.failUnless(self.a.value == 6) # should have caused a's evaluation 142 | 143 | class CellTypeTests_AlwaysLazy(unittest.TestCase): 144 | def test_1_NoEvalOnPropogation(self): 145 | "Always Lazy 1: Always lazys never evaluate on change propogation" 146 | cells.reset() 147 | x = cells.InputCell(None, 4, name="x") 148 | a = cells.AlwaysLazyCell(None, name="a", 149 | rule=lambda s,p: x.getvalue() + (p or 1)) 150 | 151 | a.getvalue() # establish dependencies 152 | x.set(42) # cause propogation 153 | self.failUnless(a.value == 5) # sneaky exam doesn't cause lazy eval 154 | self.failUnless(a.getvalue() == 47) # standard eval causes lazy eval 155 | x.set(5) # cause another propogation 156 | self.failUnless(a.value == 47) # yadda yadda... 157 | self.failUnless(a.getvalue() == 52) 158 | 159 | def test_2_NoEvalOnInit(self): 160 | "Always Lazy 2: Does not evaluate during model init" 161 | cells.reset() 162 | ran_flag = False 163 | 164 | def a_rule(self, prev): 165 | ran_flag = True 166 | return self.x + 2 167 | 168 | class M(cells.Model): 169 | x = cells.makecell(value=3) 170 | a = cells.makecell(rule=a_rule, celltype=cells.AlwaysLazyCell) 171 | 172 | m = M() 173 | self.failIf(ran_flag) 174 | self.failUnless(m.a == 5) 175 | 176 | class CellTypeTests_DictTests(unittest.TestCase): 177 | def setUp(self): 178 | cells.reset() 179 | self.x = cells.DictCell(None, name="x") 180 | 181 | def test_1_DictCellSettable(self): 182 | try: 183 | self.x.set({'blah': 'blah blippity bloo'}) 184 | except Exception as e: 185 | self.fail() 186 | 187 | def test_2_RunRaises(self): 188 | self.failUnlessRaises(cells.InputCellRunError, self.x.run) 189 | 190 | def test_3_ChangingKeyValuePropogates(self): 191 | def y_rule(model, prev): 192 | return self.x.keys() 193 | 194 | y = cells.Cell(None, name="y", rule=y_rule) 195 | y.getvalue() # establish deps 196 | self.failUnless(y.getvalue() == []) # no keys in x yet 197 | self.x['foo'] = 'blah blah' # cause propogation 198 | self.failUnless(y.getvalue() == ['foo']) 199 | 200 | class CellTypeTests_Ephemerals(unittest.TestCase): 201 | def test_Ephemeral(self): 202 | x = cells.InputCell(None, name="x", value=None, ephemeral=True) 203 | y = cells.RuleCell(None, name="y", rule=lambda s,p: x.getvalue()) 204 | y.getvalue() # establish dep 205 | s = "Foobar" 206 | x.set(s) # propogate change 207 | self.failUnless(y.getvalue() == s) # y should have got the new value 208 | self.failUnless(x.getvalue() is None) # but x should be back to None 209 | 210 | if __name__ == "__main__": 211 | unittest.main() 212 | -------------------------------------------------------------------------------- /docs/epydoc.js: -------------------------------------------------------------------------------- 1 | function toggle_private() { 2 | // Search for any private/public links on this page. Store 3 | // their old text in "cmd," so we will know what action to 4 | // take; and change their text to the opposite action. 5 | var cmd = "?"; 6 | var elts = document.getElementsByTagName("a"); 7 | for(var i=0; i...
"; 113 | elt.innerHTML = s; 114 | } 115 | } 116 | 117 | function toggle(id) { 118 | elt = document.getElementById(id+"-toggle"); 119 | if (elt.innerHTML == "-") 120 | collapse(id); 121 | else 122 | expand(id); 123 | } 124 | function highlight(id) { 125 | var elt = document.getElementById(id+"-def"); 126 | if (elt) elt.className = "highlight-hdr"; 127 | var elt = document.getElementById(id+"-expanded"); 128 | if (elt) elt.className = "highlight"; 129 | var elt = document.getElementById(id+"-collapsed"); 130 | if (elt) elt.className = "highlight"; 131 | } 132 | 133 | function num_lines(s) { 134 | var n = 1; 135 | var pos = s.indexOf("\n"); 136 | while ( pos > 0) { 137 | n += 1; 138 | pos = s.indexOf("\n", pos+1); 139 | } 140 | return n; 141 | } 142 | 143 | // Collapse all blocks that mave more than `min_lines` lines. 144 | function collapse_all(min_lines) { 145 | var elts = document.getElementsByTagName("div"); 146 | for (var i=0; i 0) 150 | if (elt.id.substring(split, elt.id.length) == "-expanded") 151 | if (num_lines(elt.innerHTML) > min_lines) 152 | collapse(elt.id.substring(0, split)); 153 | } 154 | } 155 | 156 | function expandto(href) { 157 | var start = href.indexOf("#")+1; 158 | if (start != 0) { 159 | if (href.substring(start, href.length) != "-") { 160 | collapse_all(4); 161 | pos = href.indexOf(".", start); 162 | while (pos != -1) { 163 | var id = href.substring(start, pos); 164 | expand(id); 165 | pos = href.indexOf(".", pos+1); 166 | } 167 | var id = href.substring(start, href.length); 168 | expand(id); 169 | highlight(id); 170 | } 171 | } 172 | } 173 | 174 | function kill_doclink(id) { 175 | if (id) { 176 | var parent = document.getElementById(id); 177 | parent.removeChild(parent.childNodes.item(0)); 178 | } 179 | else if (!this.contains(event.toElement)) { 180 | var parent = document.getElementById(this.parentID); 181 | parent.removeChild(parent.childNodes.item(0)); 182 | } 183 | } 184 | 185 | function doclink(id, name, targets) { 186 | var elt = document.getElementById(id); 187 | 188 | // If we already opened the box, then destroy it. 189 | // (This case should never occur, but leave it in just in case.) 190 | if (elt.childNodes.length > 1) { 191 | elt.removeChild(elt.childNodes.item(0)); 192 | } 193 | else { 194 | // The outer box: relative + inline positioning. 195 | var box1 = document.createElement("div"); 196 | box1.style.position = "relative"; 197 | box1.style.display = "inline"; 198 | box1.style.top = 0; 199 | box1.style.left = 0; 200 | 201 | // A shadow for fun 202 | var shadow = document.createElement("div"); 203 | shadow.style.position = "absolute"; 204 | shadow.style.left = "-1.3em"; 205 | shadow.style.top = "-1.3em"; 206 | shadow.style.background = "#404040"; 207 | 208 | // The inner box: absolute positioning. 209 | var box2 = document.createElement("div"); 210 | box2.style.position = "relative"; 211 | box2.style.border = "1px solid #a0a0a0"; 212 | box2.style.left = "-.2em"; 213 | box2.style.top = "-.2em"; 214 | box2.style.background = "white"; 215 | box2.style.padding = ".3em .4em .3em .4em"; 216 | box2.style.fontStyle = "normal"; 217 | box2.onmouseout=kill_doclink; 218 | box2.parentID = id; 219 | 220 | var links = ""; 221 | target_list = targets.split(","); 222 | for (var i=0; i" + 226 | target[0] + ""; 227 | } 228 | 229 | // Put it all together. 230 | elt.insertBefore(box1, elt.childNodes.item(0)); 231 | //box1.appendChild(box2); 232 | box1.appendChild(shadow); 233 | shadow.appendChild(box2); 234 | box2.innerHTML = 235 | "Which "+name+" do you want to see documentation for?" + 236 | ""; 241 | } 242 | } 243 | 244 | -------------------------------------------------------------------------------- /docs/cells.family.FamilyTraversalError-class.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | cells.family.FamilyTraversalError 7 | 8 | 9 | 10 | 11 | 13 | 14 | 16 | 17 | 18 | 20 | 21 | 22 | 24 | 25 | 26 | 28 | 29 | 30 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 51 | 62 | 63 |
45 | 46 | Package cells :: 47 | Module family :: 48 | Class FamilyTraversalError 49 | 50 | 52 | 53 | 54 | 56 | 60 |
[hide private]
[frames] | no frames]
61 |
64 | 65 |

Class FamilyTraversalError 66 |
source code

67 |
 68 | exceptions.Exception --+
 69 |                        |
 70 |                       FamilyTraversalError
 71 | 
72 | 73 |
74 | Raised when there's some sort of error in Family's 76 | traversal methods.

77 | 78 | 79 | 80 | 82 | 83 | 94 | 95 | 96 | 99 | 104 | 105 | 106 | 109 | 113 | 114 | 115 | 120 | 121 |
84 | 85 | 86 | 87 | 91 | 92 |
Instance Methods[hide private]
93 |
97 |   98 | 100 | __init__(self, 101 | value) 102 | 103 |
107 |   108 | 110 | __str__(self) 111 | 112 |
116 |

Inherited from exceptions.Exception: 117 | __getitem__ 118 |

119 |
122 | 123 |
124 | 125 | 126 | 128 | 129 | 140 | 141 |
130 | 131 | 132 | 133 | 137 | 138 |
Method Details[hide private]
139 |
142 | 143 |
144 |
145 | 146 | 154 |
147 |

__init__(self, 148 | value) 149 |
(Constructor) 150 |

151 |
source code 
155 | 156 | 157 |
158 |
Overrides: 159 | exceptions.Exception.__init__ 160 |
161 |
162 |
163 |
164 | 165 |
166 |
167 | 168 | 175 |
169 |

__str__(self) 170 |
(Informal representation operator) 171 |

172 |
source code 
176 | 177 | 178 |
179 |
Overrides: 180 | exceptions.Exception.__str__ 181 |
182 |
183 |
184 |
185 |
186 | 187 | 189 | 190 | 191 | 193 | 194 | 195 | 197 | 198 | 199 | 201 | 202 | 203 | 205 | 206 | 207 | 213 | 214 | 215 | 216 | 217 | 219 | 222 | 223 |
224 | 225 | 234 | 235 | 236 | 237 | -------------------------------------------------------------------------------- /docs/cells.cell._CellException-class.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | cells.cell._CellException 7 | 8 | 9 | 10 | 11 | 13 | 14 | 16 | 17 | 18 | 20 | 21 | 22 | 24 | 25 | 26 | 28 | 29 | 30 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 51 | 62 | 63 |
45 | 46 | Package cells :: 47 | Module cell :: 48 | Class _CellException 49 | 50 | 52 | 53 | 54 | 56 | 60 |
[hide private]
[frames] | no frames]
61 |
64 | 65 |

Class _CellException 66 |
source code

67 |
 68 | exceptions.Exception --+
 69 |                        |
 70 |                       _CellException
 71 | 
72 | 73 |
Known Subclasses:
74 |
75 | EphemeralCellUnboundError, 76 | InputCellRunError, 77 | RuleAndValueInitError, 78 | RuleCellSetError, 79 | SetDuringNotificationError 80 |
81 | 82 |
83 | 84 | 85 | 87 | 88 | 99 | 100 | 101 | 104 | 109 | 110 | 111 | 114 | 118 | 119 | 120 | 125 | 126 |
89 | 90 | 91 | 92 | 96 | 97 |
Instance Methods[hide private]
98 |
102 |   103 | 105 | __init__(self, 106 | value) 107 | 108 |
112 |   113 | 115 | __str__(self) 116 | 117 |
121 |

Inherited from exceptions.Exception: 122 | __getitem__ 123 |

124 |
127 | 128 |
129 | 130 | 131 | 133 | 134 | 145 | 146 |
135 | 136 | 137 | 138 | 142 | 143 |
Method Details[hide private]
144 |
147 | 148 |
149 |
150 | 151 | 159 |
152 |

__init__(self, 153 | value) 154 |
(Constructor) 155 |

156 |
source code 
160 | 161 | 162 |
163 |
Overrides: 164 | exceptions.Exception.__init__ 165 |
166 |
167 |
168 |
169 | 170 |
171 |
172 | 173 | 180 |
174 |

__str__(self) 175 |
(Informal representation operator) 176 |

177 |
source code 
181 | 182 | 183 |
184 |
Overrides: 185 | exceptions.Exception.__str__ 186 |
187 |
188 |
189 |
190 |
191 | 192 | 194 | 195 | 196 | 198 | 199 | 200 | 202 | 203 | 204 | 206 | 207 | 208 | 210 | 211 | 212 | 218 | 219 | 220 | 221 | 222 | 224 | 227 | 228 |
229 | 230 | 239 | 240 | 241 | 242 | -------------------------------------------------------------------------------- /docs/cells.family.FamilyMeta-class.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | cells.family.FamilyMeta 7 | 8 | 9 | 10 | 11 | 13 | 14 | 16 | 17 | 18 | 20 | 21 | 22 | 24 | 25 | 26 | 28 | 29 | 30 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 45 | 56 | 57 |
39 | 40 | Package cells :: 41 | Module family :: 42 | Class FamilyMeta 43 | 44 | 46 | 47 | 48 | 50 | 54 |
[hide private]
[frames] | no frames]
55 |
58 | 59 |

Type FamilyMeta 60 |
source code

61 |
 62 |      object --+        
 63 |               |        
 64 |            type --+    
 65 |                   |    
 66 | model.ModelMetatype --+
 67 |                       |
 68 |                      FamilyMeta
 69 | 
70 | 71 |
72 | 73 | 74 | 76 | 77 | 88 | 89 | 90 | 93 | 100 | 101 | 102 | 121 | 122 |
78 | 79 | 80 | 81 | 85 | 86 |
Instance Methods[hide private]
87 |
91 |   92 | 94 | __init__(klass, 95 | name, 96 | bases, 97 | dikt) 98 | 99 |
103 |

Inherited from type: 104 | __call__, 105 | __cmp__, 106 | __delattr__, 107 | __getattribute__, 108 | __hash__, 109 | __new__, 110 | __repr__, 111 | __setattr__, 112 | __subclasses__, 113 | mro 114 |

115 |

Inherited from object: 116 | __reduce__, 117 | __reduce_ex__, 118 | __str__ 119 |

120 |
123 | 124 |
125 | 126 | 127 | 129 | 130 | 141 | 142 | 143 | 159 | 160 |
131 | 132 | 133 | 134 | 138 | 139 |
Class Variables[hide private]
140 |
144 |

Inherited from type: 145 | __base__, 146 | __bases__, 147 | __basicsize__, 148 | __dictoffset__, 149 | __flags__, 150 | __itemsize__, 151 | __mro__, 152 | __name__, 153 | __weakrefoffset__ 154 |

155 |

Inherited from object: 156 | __class__ 157 |

158 |
161 | 162 |
163 | 164 | 165 | 167 | 168 | 179 | 180 |
169 | 170 | 171 | 172 | 176 | 177 |
Method Details[hide private]
178 |
181 | 182 |
183 |
184 | 185 | 195 |
186 |

__init__(klass, 187 | name, 188 | bases, 189 | dikt) 190 |
(Constructor) 191 |

192 |
source code 
196 | 197 | 198 |
199 |
Overrides: 200 | model.ModelMetatype.__init__ 201 |
202 |
203 |
204 |
205 |
206 | 207 | 209 | 210 | 211 | 213 | 214 | 215 | 217 | 218 | 219 | 221 | 222 | 223 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 233 | 236 | 237 |
238 | 239 | 248 | 249 | 250 | 251 | -------------------------------------------------------------------------------- /docs/cells.cell.OnceAskedLazyCell-class.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | cells.cell.OnceAskedLazyCell 7 | 8 | 9 | 10 | 11 | 13 | 14 | 16 | 17 | 18 | 20 | 21 | 22 | 24 | 25 | 26 | 28 | 29 | 30 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 51 | 62 | 63 |
45 | 46 | Package cells :: 47 | Module cell :: 48 | Class OnceAskedLazyCell 49 | 50 | 52 | 53 | 54 | 56 | 60 |
[hide private]
[frames] | no frames]
61 |
64 | 65 |

Class OnceAskedLazyCell 66 |
source code

67 |
 68 | object --+            
 69 |          |            
 70 |       Cell --+        
 71 |              |        
 72 |       RuleCell --+    
 73 |                  |    
 74 |           LazyCell --+
 75 |                      |
 76 |                     OnceAskedLazyCell
 77 | 
78 | 79 |
80 | 81 | 82 | 84 | 85 | 96 | 97 | 98 | 132 | 133 |
86 | 87 | 88 | 89 | 93 | 94 |
Instance Methods[hide private]
95 |
99 |

Inherited from LazyCell: 100 | __init__ 101 |

102 |

Inherited from RuleCell: 103 | set 104 |

105 |

Inherited from Cell: 106 | add_called_by, 107 | add_calls, 108 | called_by_list, 109 | calls_list, 110 | changed, 111 | getvalue, 112 | propogate, 113 | propogation_list, 114 | remove_called_bys, 115 | remove_cb, 116 | reset_calls, 117 | run, 118 | updatecell 119 |

120 |

Inherited from object: 121 | __delattr__, 122 | __getattribute__, 123 | __hash__, 124 | __new__, 125 | __reduce__, 126 | __reduce_ex__, 127 | __repr__, 128 | __setattr__, 129 | __str__ 130 |

131 |
134 | 135 |
136 | 137 | 138 | 140 | 141 | 152 | 153 | 154 | 159 | 160 |
142 | 143 | 144 | 145 | 149 | 150 |
Class Variables[hide private]
151 |
155 |

Inherited from object: 156 | __class__ 157 |

158 |
161 | 162 |
163 | 164 | 165 | 167 | 168 | 179 | 180 | 181 | 188 | 189 |
169 | 170 | 171 | 172 | 176 | 177 |
Instance Variables[hide private]
178 |
182 |

Inherited from Cell: 183 | called_by, 184 | calls, 185 | synapse_space 186 |

187 |
190 | 191 |
192 | 193 | 195 | 196 | 197 | 199 | 200 | 201 | 203 | 204 | 205 | 207 | 208 | 209 | 211 | 212 | 213 | 219 | 220 | 221 | 222 | 223 | 225 | 228 | 229 |
230 | 231 | 240 | 241 | 242 | 243 | --------------------------------------------------------------------------------