├── 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
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 |
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 |
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 |
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 |
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