Cell attributes are descriptors used to implement cell-based attributes. They are created using a name:
>>> from peak.events import trellis, activity >>> from peak.events.trellis import Cell, CellAttribute >>> CellAttribute(__name__='C') CellAttribute('C')
When used as descriptors in a class, they read or write the .value
attribute of the corresponding cell in the object's __cells__
dictionary:
>>> class Converter(object): ... C = CellAttribute(__name__='C') ... F = CellAttribute(__name__='F') ... def __init__(self): ... self.__cells__ = dict( ... F = Cell(lambda: self.C * 1.8 + 32, 32), ... C = Cell(lambda: (self.F - 32)/1.8, 0), ... ) >>> Converter.C CellAttribute('C') >>> tc = Converter() >>> tc.C 0.0 >>> tc.F 32.0 >>> tc.C = 100 >>> tc.F 212.0
Setting a CellAttribute-mediated attribute to a Cell
instance, replaces
that instance in the __cells__
dictionary:
>>> tc.F = Cell(lambda: -tc.C) >>> tc.F -100
Getting or setting a cell attribute that has no cell in the object's
__cells__
invokes its make_cell()
method to create a cell:
>>> def demo_factory(typ, ob, name): ... print "Creating", name, "cell for", typ, "instance" ... return Cell(lambda: ob.C*2, 0) >>> Converter.F.make_cell = demo_factory >>> del tc.__cells__['F'] >>> tc.F Creating F cell for <class 'Converter'> instance 200 >>> tc.F = 42 >>> del tc.__cells__['F'] >>> tc.F = 27 Creating F cell for <class 'Converter'> instance >>> tc.F 27 >>> tc.F = Cell(lambda: -tc.C) >>> tc.F -100
And cell attributes are of course inherited by subclasses:
>>> class Converter2(Converter): ... pass >>> tc2 = Converter2() >>> del tc2.__cells__['F'] >>> tc2.F = -40 Creating F cell for <class 'Converter2'> instance >>> tc2.F -40
TodoProperty objects are like CellAttribute, but offer an extra future
attribute:
>>> from peak.events.trellis import TodoProperty >>> class X(object): ... x = TodoProperty(__name__='x') ... future_x = x.future >>> help(X) Help on class X in module __builtin__: <BLANKLINE> class X(object) | ... | future_x | The future value of the 'x' attribute... | x... | Property representing a ``todo`` attribute <BLANKLINE>
>>> from peak.events.trellis import IsOptional, NO_VALUE, CellFactories >>> from peak.events.trellis import *>>> class Test(trellis.Component): ... x = compute(lambda self: 27) ... attrs(y = 42)>>> Test.x.rule <function ... at ...>>>> print Test.y.rule None
>>> Test.y.value 42>>> Test.x.value NO_VALUE>>> CellFactories(Test) {'y': <bound method CellAttribute.make_cell of CellAttribute('y')>, 'x': <bound method CellAttribute.make_cell of CellAttribute('x')>}>>> Test.x CellAttribute('x')>>> Test.y CellAttribute('y')>>> Test.y.make_cell(Test, Test(), 'y') Value(42)
>>> t = Test() >>> t.__cells__ = {}>>> t.x 27>>> t.__cells__['x'] Constant(27)
IsOptional defaults to false for any attribute that has an explicit setting defined in a given class in any other registry besides themselves:
>>> IsOptional(Test) {'y': False, 'x': True} >>> Test.x.discrete False >>> Test.y.discrete False
The default factory handles sentinel values by not passing them to the Cell constructor:
>>> Test.x.rule = None >>> Test.x.value = NO_VALUE >>> Test.x.factory = Cell >>> Test.x.make_cell(Test, Test(), 'x') Value(None)
And it binds non-None rules to the instance:
>>> Test.x.rule = lambda self: 42 >>> Test.x.initial_value(Test()) >>> Test.x.make_cell(Test, Test(), 'x') Cell(<bound method Test.<lambda> of <Test object at...>>, None [uninitialized])
And uses the .discrete
attribute to determine discreteness:
>>> Test.x.rule = None >>> Test.x.discrete = True >>> Test.x.make_cell(Test, Test(), 'x') Value(None, discrete[None])
TodoProperty
only uses the rule to create the default value, and always
creates a rule-free receiver, regardless of the value or receiver flag
settings:
>>> Test.x = TodoProperty(__name__='x', rule=lambda self: dict(), value=54) >>> Test.to_x = Test.x.future >>> Test.x.make_cell(Test, Test(), 'x') TodoValue(<bound method Test.<lambda> of <Test ...>>, {}, discrete[{}]) >>> CellFactories(Test)['x'] = Test.x.make_cell
>>> t = Test() >>> t.__cells__ = {}>>> def dump(): print "t.x =", t.x>>> watcher = Cell(dump) >>> watcher.value t.x = {}
>>> t.to_x Traceback (most recent call last): ... RuntimeError: future can only be accessed from a @modifier>>> def add(key, val): ... t.to_x[key] = val >>> add = modifier(add)>>> add(1, 2) t.x = {1: 2} t.x = {}>>> def update(**kw): ... for k, v in kw.items(): ... add(k, v)>>> update(x=1, y=2) t.x = {'y': 2} t.x = {} t.x = {'x': 1} t.x = {}>>> update = modifier(update)
>>> update(x=1, y=2) # it all happens in one go t.x = {'y': 2, 'x': 1} t.x = {}
The task_factory creates a TaskCell:
>>> from peak.events.activity import task >>> def f(self): yield 1; print "done" >>> t = task(f).make_cell(Test, Test(), 'x') >>> t TaskCell(None) >>> print t.value None >>> activity.EventLoop.flush() # yield 1 >>> activity.EventLoop.flush() # 'print done' done
>>> from peak.util.decorators import decorate>>> class Test(trellis.Component): ... def aRule(self): ... return 42 ... r = maintain(aRule) # trick to exercise auto-name-finding ... anEvent = attr(resetting_to=-1) ... optRule = compute(lambda:99) ... todo = todo(lambda self:{})>>> Test.r CellAttribute('aRule')>>> Test.todo TodoProperty('todo')>>> print Test.anEvent.rule None
>>> Test.r.rule <function aRule at...>>>> Test.anEvent.value -1>>> Test.r.value NO_VALUE>>> Test.anEvent.discrete True>>> Test.r.discrete False>>> CellFactories(Test) {'anEvent': <bound method CellAttribute.make_cell of CellAttribute('anEvent')>, 'optRule': <bound method CellAttribute.make_cell of CellAttribute('optRule')>, 'r': <bound method CellAttribute.make_cell of CellAttribute('aRule')>, 'todo': <bound method TodoProperty.make_cell of TodoProperty('todo')>}>>> IsOptional(Test) {'anEvent': False, 'optRule': True, 'r': False, 'todo': False}>>> class Test(trellis.Component): ... todos( ... added = lambda self:{}, ... removed = lambda self:set() ... ) ... to_add = added.future ... to_remove = removed.future ... activity.task() ... def task(self): yield None>>> CellFactories(Test) {'removed': <bound method TodoProperty.make_cell of TodoProperty('removed')>, 'task': <bound method CellAttribute.make_cell of CellAttribute('task')>, 'added': <bound method TodoProperty.make_cell of TodoProperty('added')>}>>> class Test(trellis.Component): ... trellis.compute.attrs( ... x = lambda self: self.y ... ) ... y = trellis.make(dict, writable=True) >>> x=Test() >>> x.x {} >>> trellis.Cells(x) {'y': Value({}), 'x': LazyCell(<bound method Test.x of <Test object...>>, {} [inactive])}>>> class Test(trellis.Component): ... trellis.compute.attrs( ... x = lambda self: self.y ... ) ... trellis.make.attrs(y = dict) >>> x=Test() >>> x.x {} >>> trellis.Cells(x) {'y': Constant({}), 'x': Constant({})}
>>> def hello(msg): ... print msg>>> class Test(Component): ... trellis.maintain.attrs( ... X = lambda self: self.Y + 2 ... ) ... trellis.attrs.resetting_to(Y = 0) ... Z = trellis.attr(0) ... def always(self): ... print "always!" ... always = maintain(always) ... def only_on_request(self): ... print "hello!" ... only_on_request = compute(only_on_request) ... A=compute(lambda s:hello("A!")) ... maintain.attrs(B=lambda s:hello("B!"))>>> Test.B.discrete False>>> Test.always.discrete False>>> Test.X.discrete False>>> Test.Y.discrete True>>> Test.Z.discrete False
Non-optional attributes are activated at creation time, as are the appropriate cells:
>>> t = Test() B! always! >>> t.__cells__.keys() ['Y', 'always', 'Z', 'B', 'X'] >>> t.only_on_request hello! >>> t.A A! >>> t.A >>> t.X 2 >>> t.Y = 23 >>> t.X 2 >>> t.Z = 1 >>> t.X 2 >>> def show_X(): ... print "X =", t.X >>> show_X = Cell(show_X) >>> show_X.value X = 2 >>> t.Y = 23 X = 25 X = 2 >>> t.__cells__.keys() ['A', 'B', 'always', 'only_on_request', 'Y', 'X', 'Z'] >>> del show_X
Keyword arguments are accepted by the constructor:
>>> t = Test(always=Constant(False), B=Constant(0), Z=55) >>> t.Z 55 >>> t.B 0 >>> t.always False
But not for undefined attributes:
>>> t = Test(qqqq=42) Traceback (most recent call last): ... TypeError: Test() has no keyword argument 'qqqq'
Creating a component from within a rule should not create a dependency link to the rule:
>>> x = Cell(value=27) >>> class Test(Component): ... trellis.maintain.attrs(x = lambda self: x.value) >>> def _rule(): ... print "creating" ... Test() >>> r = Cell(_rule) >>> r.value creating >>> x.value = 99
And initializing a component cell that would ordinarily be read-only, should replace it with a constant:
>>> class Test(Component): ... trellis.make.attrs(x = lambda self: {}) >>> t = Test(x=()) >>> t.__cells__['x'] Constant(()) >>> t.x = r Traceback (most recent call last): ... AttributeError: Can't change a constant
A component should not create cells for attributes that are not cell properties (e.g., due to override in a subclass):
>>> class Test(Component): ... trellis.maintain() ... def attr(self): ... print "computing" >>> Test() computing <Test object at ...> >>> class Test2(Test): ... attr = None >>> Test2() # attr should not be initialized <Test2 object at ...>
get_value/set_value methods:
>>> c = Cell() >>> print c.get_value() None >>> c.set_value(42) >>> c.value 42 >>> c1 = Constant(42) >>> c1.get_value() 42 >>> c1.set_value(99) Traceback (most recent call last): ... AttributeError: 'Constant' object has no attribute 'set_value' >>> c1 = Cell(lambda: c.value) >>> c1.get_value() 42 >>> c1.set_value(99) Traceback (most recent call last): ... AttributeError: 'ReadOnlyCell' object has no attribute 'set_value' >>> c.set_value(99) >>> c1.get_value() 99
>>> poll() Traceback (most recent call last): ... RuntimeError: poll() must be called from a rule>>> repeat() Traceback (most recent call last): ... RuntimeError: repeat() must be called from a rule
"Poll" re-invokes a rule on the next recalc of anything:
>>> c2 = Cell(value=99) >>> def count(): ... if poll(): print "calculating" ... return c.value+1 >>> c = Cell(count, 0) >>> def hello(): print "c =", c.value >>> c1 = Performer(hello) c = calculating 1 >>> c.value 1 >>> c.value 1 >>> c2.value = 16 calculating c = 2 >>> c2.value = 7 calculating c = 3 >>> c2.value = 66 calculating c = 4
At least, until/unless you get rid of the cell:
>>> c = Cell(value=99) >>> c.value 99
Which of course requires that its listeners drop their references first:
>>> c2.value = 27 calculating c = 99
And now the repeated polling of the now-vanished cell stops:
>>> Cell().value = 66 >>> c.value = 20 c = 20
>>> class LineReceiver(trellis.Component): ... bytes = trellis.attr(resetting_to='') ... delimiter = trellis.attr('\r\n') ... _buffer = '' ... ... trellis.maintain(resetting_to=None) ... def line(self): ... buffer = self._buffer = self._buffer + self.bytes ... lines = buffer.split(self.delimiter, 1) ... if len(lines)>1: ... buffer = self._buffer = lines[1] ... trellis.repeat() ... return lines[0] ... ... trellis.maintain() ... def dump(self): ... if self.line is not None: ... print "Line:", self.line>>> LineReceiver.line.discrete True>>> lp = LineReceiver() >>> lp.bytes = 'xyz' >>> lp.bytes = '\r' >>> lp.bytes = '\n' Line: xyz>>> lp.bytes = "abcdef\r\nghijkl\r\nmnopq" Line: abcdef Line: ghijkl>>> lp.bytes = "FOObarFOObazFOOspam\n" >>> lp.delimiter = "FOO" Line: mnopq Line: bar Line: baz>>> lp.delimiter = "\n" Line: spam>>> lp.bytes = 'abc\nabc\n' Line: abc Line: abc
>>> def raiser(): ... raise Exception("foo") ... yield None # make it a generator>>> raiser().next() Traceback (most recent call last): ... Exception: foo>>> activity.resume() Traceback (most recent call last): ... RuntimeError: resume() must be called from an @activity.task>>> def yielder(ob): ... while not ob.bytes: ... print "pausing" ... yield activity.Pause ... yield ob.bytes>>> def t(): ... yield 1 ... yield 2 ... while 1: ... yield yielder(lp); ... print "Got:", activity.resume() ... yield activity.Pause # nothing changes unless we pause>>> c = activity.TaskCell(t)
>>> d = Dict({1:2}, a="b") >>> d {'a': 'b', 1: 2}>>> hash(d) Traceback (most recent call last): ... TypeError
>>> d.pop(1) Traceback (most recent call last): ... InputConflict: Can't read and write in the same operation
>>> d.popitem() Traceback (most recent call last): ... InputConflict: Can't read and write in the same operation>>> d.setdefault(2, 4) Traceback (most recent call last): ... InputConflict: Can't read and write in the same operation>>> del d['a'] >>> d {1: 2}>>> def dump(): ... for name in 'added', 'changed', 'deleted': ... if getattr(d,name): ... print name, '=', getattr(d,name) >>> dump = Performer(dump) >>> dump.value>>> d[2] = 3 added = {2: 3}>>> del d[1] deleted = {1: 2}>>> del d[42] Traceback (most recent call last): ... KeyError: 42>>> d[2] = "blue" changed = {2: 'blue'}>>> d.clear() deleted = {2: 'blue'}>>> d.update({1:2}, blue=2) added = {'blue': 2, 1: 2}>>> d.update({3:4}) added = {3: 4}>>> d.update(blue='shoe') changed = {'blue': 'shoe'}>>> def go(): d[99] = 42; del d[99] >>> modifier(go)()>>> def go(): d[99] = 42; d[99] = 26 >>> modifier(go)() added = {99: 26}>>> def go(): del d[99]; d[99] = 42 >>> modifier(go)() changed = {99: 42}>>> def go(): d[99] = 71; del d[99] >>> modifier(go)() deleted = {99: 42}>>> def go(): d[99] = 71; d[1] = 23; d.clear(); d.update({1:3}, a='b') >>> modifier(go)() added = {'a': 'b'} changed = {1: 3} deleted = {'blue': 'shoe', 3: 4}
>>> L = List("abc") >>> L ['a', 'b', 'c']>>> def dump(): ... if L.changed: ... print "changed to", L >>> dump = Performer(dump) >>> dump.value>>> hash(L) Traceback (most recent call last): ... TypeError
>>> L.pop() Traceback (most recent call last): ... InputConflict: Can't read and write in the same operation>>> L.pop(0) Traceback (most recent call last): ... InputConflict: Can't read and write in the same operation
>>> L.append(23) changed to ['a', 'b', 'c', 23]
>>> L[1:2] = [3] changed to ['a', 3, 'c', 23]>>> del L[:3] changed to [23]>>> L[0] = 42 changed to [42]>>> del L[0] changed to []>>> L += [1, 2] changed to [1, 2]>>> L *= 3 changed to [1, 2, 1, 2, 1, 2]>>> del L[2:] changed to [1, 2]>>> L.reverse() changed to [2, 1]>>> L.remove(2) changed to [1]
>>> L.insert(0, 88) changed to [88, 1]>>> L.extend( (423, -99) ) changed to [88, 1, 423, -99]>>> L.sort() changed to [-99, 1, 88, 423]
>>> try: ... s = set ... except NameError: ... from sets import Set as set>>> s = Set('abc') >>> s Set(['a', 'c', 'b'])>>> hash(s) Traceback (most recent call last): ... TypeError: Can't hash a Set, only an ImmutableSet.
>>> s.pop() Traceback (most recent call last): ... InputConflict: Can't read and write in the same operation>>> def dump(): ... for name in 'added', 'removed': ... if getattr(s, name): ... print name, '=', list(getattr(s,name)) >>> dump = Performer(dump) >>> dump.value>>> s.clear() removed = ['a', 'c', 'b']>>> s.add(1) added = [1]
>>> s.remove(1) removed = [1]
>>> s.remove(2) Traceback (most recent call last): ... KeyError: 2
>>> s.symmetric_difference_update((1,2)) added = [1, 2]>>> s.difference_update((2,3)) removed = [2]>>> def go(): s.add(3); s.remove(3) >>> modifier(go)()>>> def go(): s.remove(1); s.add(1) >>> modifier(go)()>>> def go(): s.add(3); s.clear() >>> modifier(go)() removed = [1]>>> def go(): ss=s; ss |= set([1,2]); ss &= set([2,3]) >>> modifier(go)() added = [2]>>> def go(): ss=s; s.remove(2); ss |= set([2, 3]) >>> modifier(go)() added = [3]>>> def go(): ss=s; ss |= set([4]); ss -= set([4, 2]) >>> modifier(go)() removed = [2]>>> s Set([3])>>> def go(): ss=s; s.remove(3); s.add(4); ss ^= set([3, 4]) >>> modifier(go)()>>> s Set([3])
This bit tests whether a rule gets to fire when a cell's value is initialized. When you set a ruled cell's value before computing its value, the "old" value should be the passed-in value, and the "new" value should be the assigned value (overriding the rule):
>>> def aRule(): ... print "running", c.value ... return 42 >>> c = Cell(aRule, 96) >>> c.value = 27 running 27 >>> c.value 27
And if the setting occurs in a modifier or action rule, the rule's execution should be deferred until the current calculation/rule is complete, thereby allowing several cells to be simultaneously pre-set and initialized at once:
>>> c = trellis.Cell(aRule, 96) >>> def go(): ... c.value = 27 ... print "finished setting" >>> trellis.modifier(go)() finished setting running 27 >>> c.value 27
Observers:
>>> v = trellis.Value(26) >>> def o(): ... print "value is", v.value >>> o = trellis.Performer(o) value is 26 >>> v.value = 27 value is 27 >>> o.layer Max >>> def fail(): ... v.value = 99 >>> trellis.Performer(fail) Traceback (most recent call last): ... RuntimeError: Can't change objects during @perform or @compute
The infamous "Pentagram of Death" problem is described as follows:
"""If X is an input cell, and A and B and H depend on it, and C depends on B, and A depends on C, and H depends on A and C, then most algorithms will fail to handle a situation where H is recalculated before C knows it's out of date."""
Let's try it. Note that the order of values in the lambda expressions is intended to force the dependencies to be resolved in an order that ensures H gets told that X has changed before C does, and that C has to find out whether B has changed before it is allowed to be recalculated:
>>> def recalc(name): ... print "calculating", name >>> X = trellis.Cell(value=1) >>> A = trellis.Cell(lambda: recalc("A") or (X.value, C.value)) >>> B = trellis.Cell(lambda: recalc("B") or X.value) >>> C = trellis.Cell(lambda: recalc("C") or (B.value, X.value)) >>> H = trellis.Cell(lambda: recalc("H") or (X.value, C.value))
We'll calculate H first, so it will be X's first listener:
>>> H.value calculating H calculating C calculating B (1, (1, 1))
And then A, so it'll be the last listener:
>>> A.value calculating A (1, (1, 1))
At this point, the layers of all the cells are known:
>>> X.layer 0 >>> B.layer 1 >>> C.layer 2 >>> A.layer 3 >>> H.layer 3
X is at layer zero because it doesn't depend on anything. B depends only on X, and C depends on both X and B (so it's a higher layer). A depends on X and C, so it's higher than C, and H is the same.
So now, if we change X, everyone should update in the correct order:
>>> X.value = 2 calculating B calculating C calculating A calculating H >>> H.value (2, (2, 2))
If this had been a shoddy algorithm, then H.value
would have been
(2, (1, 1))
instead, and the update order might have been different. Note
by the way that B is calculated before C, because C depends on B as its first
dependency. So it has to look "up" the dependency graph to see if B or X have
changed before C's rule can be run. Since B is first in C's dependency order,
it gets recalculated first.
(Similarly, H gets recalculated before C, because its first dependency is X, so it immediately realizes it needs to recalculate. X also notifies H first that a recalculation might be necessary.)
Circular calculations:
>>> F = Cell(lambda: C.value*1.8 + 32, 32) >>> C = Cell(lambda: (F.value-32)/1.8, 0) >>> F.value 32.0 >>> C.value 0.0 >>> F.value = 212 >>> C.value 100.0 >>> C.value = 0 >>> F.value 32.0 >>> C.value = -40 >>> F.value -40.0 >>> def temp(): ... if C.value<10: ... print "Brrrrr!" >>> temp = Cell(temp) >>> temp.value Brrrrr! >>> F.value = 212 >>> C.value 100.0 >>> F.value = 0 Brrrrr! >>> C.value = 9 Brrrrr! >>> F.value = 30 Brrrrr! >>> del temp
Spreadsheet simulation:
>>> from UserDict import DictMixin, UserDict >>> import sys >>> class Spreadsheet(DictMixin, UserDict): ... def __init__(self, *args, **kw): ... self.data = {} ... for arg in args+(kw,): self.update(arg) ... ... def __getitem__(self, key): ... return self.data[key][1].value ... ... def __setitem__(self, key, value): ... def rule(): ... value = self.data[key][0].value ... print "computing", value ... if sys.version>="2.4": ... return eval(value, globals(), self) ... code = compile(value, '<string>', 'eval') ... d = dict([(k,self[k]) for k in code.co_names if k in self.data]) ... return eval(code, globals(), d) ... if key in self.data: ... self.data[key][0].value = value ... else: ... self.data[key] = Cell(value=value), Cell(rule, None) >>> ss = Spreadsheet() >>> ss['a1'] ='5' >>> ss['a2']='2*a1' >>> ss['a3']='2*a2' >>> ss['a1'] computing 5 5 >>> ss['a2'] computing 2*a1 10 >>> ss['a1'] = '7' computing 7 computing 2*a1 >>> ss['a1'] 7 >>> ss['a2'] 14 >>> ss['a3'] computing 2*a2 28 >>> ss['a1'] = '3' computing 3 computing 2*a1 computing 2*a2
Events:
>>> def last_ping(): ... if ping.value is not None: ... print "ping", ping.value ... return ping.value ... return last_ping.value >>> last_ping = Cell(last_ping) >>> ping = Cell(discrete=True) >>> print last_ping.value None >>> ping.value = 1 ping 1 >>> last_ping.value 1 >>> F.value = 27 >>> print ping.value # value goes away as soon as something changes None >>> last_ping.value 1 >>> ping.value = 2 ping 2 >>> last_ping.value 2 >>> ping.value = 2 # deps are recalculated even if value is same ping 2 >>> F.value = 99 >>> print ping.value None >>> last_ping.value 2
Dynamic dependencies:
>>> def C_rule(): ... print "computing", ... if A.value<5: ... print A.value, B.value ... else: ... print "...done" >>> A = Cell(value=1) >>> B = Cell(value=2) >>> C = Cell(C_rule) >>> C.value computing 1 2 >>> C.value >>> A.value = 3 computing 3 2 >>> B.value = 4 computing 3 4 >>> A.value = 5 computing ...done >>> B.value = 6 # nothing happens, since C no longer depends on B >>> A.value = 3 computing 3 6 >>> B.value = 7 # but now it's back depending on B again. computing 3 7 >>> B.value = 7 >>> A.value = 1 computing 1 7 >>> A.value = 1
Forcing a rule to repeat itself:
>>> from peak.events.trellis import repeat, ReadOnlyCell >>> def counter(): ... if counter.value == 10: ... return counter.value ... repeat() ... return counter.value + 1 >>> counter = ReadOnlyCell(counter, 1) >>> counter.value 10
>>> class MyConnector(trellis.AbstractConnector): ... def connect(self, sensor): ... print "connecting", sensor ... return sensor ... def disconnect(self, sensor, key): ... print "disconnecting", key>>> c = trellis.Cell(MyConnector()) >>> c Sensor(<bound method MyConnector.read of...>, None [uninitialized])
>>> print c.value None
>>> c2 = trellis.Cell(lambda: c.value)>>> print c2.value connecting Sensor(<bound method MyConnector.read of...>, None) None
>>> c.receive(42) >>> c.value 42 >>> c2.value 42
>>> del c2 disconnecting Sensor(<bound method MyConnector.read of...>, 42)
>>> c.listening NOT_GIVEN>>> class Demo(trellis.Component): ... a = trellis.compute(lambda self:trellis.noop()) ... a.connector() ... def _connect_a(self, sensor): ... print "connecting", sensor ... v = trellis.Value(42) ... # this read should not produce a dependency, because ... # connect/disconnect methods are in an implicit @modifier: ... v.value ... return sensor ... a.disconnector() ... def _disconnect_a(self, sensor, key): ... print "disconnecting", key ... trellis.perform(optional=False) ... def p(self): ... poll() ... print "recalc">>> d = Demo() recalc
>>> print d.a recalc None
>>> c = trellis.Cells(d)['a'] >>> c Sensor(<bound method Demo.a of...>, None)>>> c2 = trellis.Cell(lambda: c.value)>>> print c2.value recalc connecting Sensor(<bound method Demo.a of...>, None) recalc None
>>> c.receive(42) recalc
>>> c.value 42 >>> c2.value 42>>> del c2 recalc disconnecting Sensor(<bound method Demo.a of...>, 42) recalc
>>> c.listening NOT_GIVEN>>> del d.__cells__['p'] # get rid of 'recalc' printing
>>> some_global = 42>>> class GlobalConnector(MyConnector): ... def read(self): ... print "reading" ... return some_global>>> c = trellis.Cell(GlobalConnector(), None) >>> c.value reading 42>>> def writer(): ... if c.was_set: ... print "writing", c.value ... global some_global ... some_global = c.value>>> writer = trellis.Performer(writer) connecting Effector(<bound method GlobalConnector.read of...>, 42)
>>> c2 = trellis.Cell(lambda: c.value)>>> print c2.value 42
>>> c.value = 99 writing 99>>> some_global 99>>> c.value = 99>>> some_global = 77 >>> c.receive(77)>>> c2.value 77>>> del c2, writer disconnecting Effector(<bound method GlobalConnector.read of...>, 77)
>>> class X(trellis.Component): ... g = trellis.maintain(GlobalConnector())>>> x = X() reading
>>> x.g 77>>> c2 = trellis.Cell(lambda: x.g)>>> print c2.value connecting Effector(<bound method GlobalConnector.read of...>, 77) 77
>>> del c2 disconnecting Effector(<bound method GlobalConnector.read of...>, 77)
>>> c1=trellis.Value(66)>>> def cantwrite(): ... c1.value = 23>>> c2=trellis.LazyCell(cantwrite) >>> c2.value Traceback (most recent call last): ... RuntimeError: Can't change objects during @perform or @compute
>>> def calc(): ... return c1.value>>> c2=trellis.LazyCell(calc) >>> c2.value 66
>>> list(c2.iter_listeners()) [] >>> list(c2.iter_subjects()) []>>> c3 = trellis.Cell(c2.get_value) >>> c3.value 66
>>> list(c2.iter_listeners()) [ReadOnlyCell(<...get_value of LazyCell(<function calc...>, 66)>, 66)]
>>> list(c2.iter_subjects()) [Value(66)]
>>> del c3 >>> list(c2.iter_listeners()) [] >>> list(c2.iter_subjects()) []>>> c2=trellis.LazyCell(lambda: {}) >>> c2.value {} >>> c2 Constant({})
>>> x = trellis.CellAttribute(z=22) Traceback (most recent call last): ... TypeError: CellAttribute() has no keyword argument 'z'>>> x = trellis.CellAttribute.mkattr(initially=None, resetting_to=None) Traceback (most recent call last): ... TypeError: Can't specify both 'initially' and 'resetting_to'>>> x = trellis.CellAttribute.mkattr(initially=None, make=list) Traceback (most recent call last): ... TypeError: Can't specify both a value and 'make'>>> x = trellis.CellAttribute.mkattr(resetting_to=None, make=list) Traceback (most recent call last): ... TypeError: Can't specify both a value and 'make'>>> trellis.CellAttribute.mkattr(resetting_to=42).initial_value(None) 42>>> trellis.CellAttribute.mkattr(initially=99).initial_value(None) 99>>> trellis.CellAttribute.mkattr(make=list).initial_value(None) []>>> trellis.CellAttribute.mkattr(make=list).can_connect() True>>> trellis.CellAttribute.mkattr(resetting_to=42).can_connect() True>>> trellis.TodoProperty.mkattr(rule=list).can_connect() False>>> class Test(trellis.Component): ... t = trellis.todo(list) ... t.connector(lambda self: None) Traceback (most recent call last): ... TypeError: TodoProperty('t') cannot have a .connector>>> class Test(trellis.Component): ... t = trellis.todo(list) ... t.disconnector(lambda self: None) Traceback (most recent call last): ... TypeError: TodoProperty('t') cannot have a .disconnector>>> t = trellis.compute(lambda self: 42) >>> t.connect, t.disconnect (None, None)>>> conn = lambda self:23 >>> t.connector(conn) is conn True >>> t.connector(conn) Traceback (most recent call last): ... TypeError: CellAttribute('t') already has a .connector>>> t.connect is conn True
>>> t.make_cell(int, 42, 't') Traceback (most recent call last): ... TypeError: CellAttribute('t') is missing a .disconnector>>> disc = lambda self:99 >>> t.disconnector(disc) is disc True >>> t.disconnector(disc) Traceback (most recent call last): ... TypeError: CellAttribute('t') already has a .disconnector>>> t.disconnect is disc True
>>> t.make_cell(int, 42, 't') Sensor(<bound method int.t of 42>, None [uninitialized])>>> t.connect = None >>> t.make_cell(int, 42, 't') Traceback (most recent call last): ... TypeError: CellAttribute('t') is missing a .connector>>> t.rule = AbstractConnector() >>> t.connect = None >>> t.connector(conn) Traceback (most recent call last): ... TypeError: The rule for CellAttribute('t') is itself a Connector>>> t.disconnect = None >>> t.disconnector(disc) Traceback (most recent call last): ... TypeError: The rule for CellAttribute('t') is itself a Connector
Connect/disconnect of func w/same name should return the cellattr so as not to overwrite it:
>>> def attr(self): return 42 >>> attr = trellis.compute(attr) >>> attr.connector(attr.rule) is attr True >>> attr.disconnector(attr.rule) is attr True >>> class Dummy: ... trellis.compute() ... def attr(self): ... return 42 ... ... _attr = attr ... ... attr.connector() ... def attr(self,*args): ... pass ... ... print attr is _attr ... ... attr.disconnector() ... def attr(self,*args): ... pass ... ... print attr is _attr True True
But only if has the same name:
>>> foo = trellis.compute(attr.rule) >>> foo.connector(foo.rule) is foo.rule True >>> foo.disconnector(foo.rule) is foo.rule True >>> class Dummy: ... trellis.compute() ... def foo(self): ... return 99 ... ... foo.connector() ... def conn(self,*args): ... pass ... ... print conn is foo.connect ... ... foo.disconnector() ... def disconn(self,*args): ... pass ... ... print disconn is foo.disconnect True True
And only if the local variable of the same name is the attribute object:
>>> def attr(self): return 42 >>> _attr = trellis.compute(attr) >>> _attr.connector(attr) is _attr.connect True >>> _attr.disconnector(attr) is _attr.disconnect True >>> class Dummy: ... trellis.compute() ... def attr(self): ... return 42 ... ... _attr = attr; del attr ... ... _attr.connector() ... def attr(self,*args): ... pass ... ... print attr is _attr.connect ... ... _attr.disconnector() ... def attr(self,*args): ... pass ... ... print attr is _attr.disconnect True True
>>> p = trellis.Pipe() >>> def show(): print p >>> show = trellis.Performer(show) [] >>> p.append(1) [1] [] >>> p.extend([2,3]) [2, 3] [] >>> def add_many(*args): ... for arg in args: ... p.append(arg) >>> add_many = trellis.modifier(add_many)>>> add_many(7,8,9) [7, 8, 9] []>>> def show(): print 42 in p >>> show = trellis.Performer(show) False>>> p.append(27) False False
>>> p.append(42) True False
>>> def show(): print list(p) >>> show = trellis.Performer(show) [] >>> p.append(99) [99] []>>> def show(): print len(p) >>> show = trellis.Performer(show) 0 >>> add_many(3,4,5) 3 0
>>> class Demo(trellis.Component): ... cc = trellis.cellcache(lambda self, key: key) ... cc.connector() ... def cc_conn(self, key): ... print "connecting", key ... cc.disconnector() ... def cc_disc(self, key): ... print "disconnecting", key>>> d = Demo() >>> c1 = d.cc[1] >>> c2 = d.cc[2] >>> c1.value 1 >>> c2.value 2>>> c = Cell(lambda: c1.value) >>> c.value connecting 1 1>>> c = Cell(lambda: c2.value) disconnecting 1>>> c.value connecting 2 2>>> del c disconnecting 2