class Rectangle(cells.Model): # Now let's add a cell to our model. This will be an Input cell, # which can be set from outside the Model: width = cells.makecell(value=1) # I didn't specify an InputCell (though I could have); # cells.makecell will create an InputCell if it gets a value # argument. # Similarly, we can make the aspect ratio cell: ratio = cells.makecell(value=GOLDEN) # Now, I make the cell which gives the calculated length. It's # called a Rule cell, and I can make it like this: @cells.fun2cell() def length(self, prev): # The signature of the rule which defines a RuleCell's value # must be f(self, prev); self will be passed an instance of # this Model (just like you'd expect), and prev will be passed # the cell's previous (aka out-of-date) value. # For this Rule I'm going to be ignoring the previous value, # and just using this instance's ratio and width to get the # new length: return float(self.width) * float(self.ratio) # There's another way to make RuleCells. We can pass anonymous # functions to the cells.makecell function: area = cells.makecell( rule=lambda self, p: float(self.length) * float(self.width))
class AddressBooks(cells.Family): # Note that kid_slots is just a cell. It could be any type of cell # so long as the value it produced was a class. kid_slots = cells.makecell(value=NamedAddressBook) # I'd like to be able to get all of the names and emails from all # of the address books: @cells.fun2cell() def entries(self, prev): d = {} for model in self.kids: d.update(model.entries) return d # And also get all the names: names = cells.makecell(rule=lambda s,p: s.entries.keys()) # Let's also get all the address book names & indexes @cells.fun2cell() def booknames(self, prev): d = {} for model in self.kids: # I'll use one of the Family convenience methods, position d[model.name] = model.position() return d
class Real(Column): value = cells.makecell(value=0.0) translate_from_sql = cells.makecell(value=lambda v: float(v)) @cells.fun2cell() def create_column_string(self, prev): return self.name + " REAL" @cells.fun2cell() def sql_value(self, prev): return str(self.value)
class String(Column): value = cells.makecell(value="") translate_from_sql = cells.makecell(value=lambda v: v) @cells.fun2cell() def create_column_string(self, prev): return self.name + " TEXT" @cells.fun2cell() def sql_value(self, prev): return self.value
class Integer(Column): value = cells.makecell(value=0) translate_from_sql = cells.makecell(value=lambda v: int(v)) @cells.fun2cell() def create_column_string(self, prev): return self.name + " INTEGER" @cells.fun2cell() def sql_value(self, prev): return str(self.value)
class Column(cells.Model): value = cells.makecell(value=None) name = cells.makecell(value="anonymous") table = cells.makecell(value=None) translate_from_sql = cells.makecell(value=lambda v: pickle.loads(str(v))) @cells.fun2cell() def create_column_string(self, prev): return self.name + " BLOB" @cells.fun2cell() def sql_value(self, prev): return pickle.dumps(self.value)
class Label(BasicTkinterObject): def __init__(self, *args, **kwargs): BasicTkinterObject.__init__(self, *args, **kwargs) self.stringvar = Tkinter.StringVar(self.container) self.widget = Tkinter.Label(self.container, textvariable=self.stringvar) self.widget.pack() stringvar = cells.makecell(value=None) update_on = cells.makecell(value="none") @cells.fun2cell() def container(self, prev): print "Entry.container" return self.parent.widget
class MyModel(cells.Model): x = cells.makecell(value=5) @cells.fun2cell() def a(modelself, prev): self.a_ran = True return modelself.x + modelself.offset def __init__(self, *args, **kwargs): self.offset = 1 cells.Model.__init__(self, *args, **kwargs)
class Entry(BasicTkinterObject): def __init__(self, *args, **kwargs): BasicTkinterObject.__init__(self, *args, **kwargs) self.stringvar = Tkinter.StringVar(self.container) self.stringvar.trace("u", self.text_copier) self.widget = Tkinter.Entry(self.container, textvariable=self.stringvar) self.widget.pack() text = cells.makecell(value=None) stringvar = cells.makecell(value=None) update_on = cells.makecell(value="none") def text_copier(self, *args): print "updating text", repr(args), "field is", self.stringvar.get() self.text = self.stringvar.get() @cells.fun2cell() def container(self, prev): print "Entry.container" return self.parent.widget
class AlternateInputAddressBook(WorkingAddressBook): # I'll add an ephemeral input cell to take the tuple entry = cells.makecell(value=None, ephemeral=True) # and a RuleCell to build a dictionary with the entries @cells.fun2cell() def entries(self, prev): d = copy.copy(prev) # prevent the old == new dict issue if not d: d = {} if self.entry: d[self.entry[0]] = self.entry[1] return d
class StorableAddressBook(WorkingAddressBook): entry_file = cells.makecell(value="./addresses.pickle") # We'll build this autoloading entry db with a RuleThenInputCell: @cells.fun2cell(celltype=cells.RuleThenInputCell) def entries(self, prev): # These are written like RuleCells. First we'll look for an # extant entry file: if not os.path.isfile(self.entry_file): # And if it doesn't exist, we'll just return an empty dictionary. return {} else: # If there is a file there, we'll load it into memory # using the pickle module. return pickle.load(open(self.entry_file, 'r'))
class C(cells.Model): model_value = cells.makecell(rule=lambda s, p: s.x * 10)
class TestDB(cellql.Database): connection = cells.makecell(value="sqlite://" + TESTDB)
class A(cells.Model): model_name = cells.makecell(value=word)
class B(cells.Model): model_name = cells.makecell(value="Bee")
class NamedAddressBook(cells.Family): name = cells.makecell(value="Anonymous") entries = cells.makecell(value={}, celltype=cells.DictCell) names = cells.makecell(rule=lambda s,p: s.entries.keys())
class F(cells.Family): kid_slots = cells.makecell(value=K)
class WorkingAddressBook(AddressBook): entries = cells.makecell(value={}, celltype=cells.DictCell)
class Family(Model): """ Family A specialized C{L{Model}} which has C{kids}, C{kid_slots}, and a number of convenience functions for traversing the parent/child graph. @ivar kids: A list of Models which are guaranteed to have the attribute overrides defined in C{L{kid_slots}} @ivar kid_slots: An override definition for the Cells inserted into the C{L{kids}} list. The attributes overridden are every attribute defined in the class in C{kid_slots} minus the attributes defined in every Model. """ kids = cells.makecell(celltype=ListCell, kid_overrides=False) kid_slots = cells.makecell(value=Model, kid_overrides=False) def __init__(self, *args, **kwargs): Model.__init__(self, *args, **kwargs) def _kid_instance(self, klass=None): """ _kid_instance(self, klass) -> Cell Creates a new instance of a Cell based on the passed class (in C{klass}) and the overrides defined in C{kid_slots}. @param klass: The base type for the new kid instance """ if not klass: klass = self.kid_slots _debug("making an instance of", str(klass)) # first, find the attributes the kid_slots attrib actual wants to # define: override_attrnames = [] for attrname in dir(self.kid_slots): cvar = getattr(self.kid_slots, attrname) # if it's a cell attribute, check to see if it's one of # the "special", non-overriding slots (eg kids) if isinstance(cvar, CellAttr): if cvar.kid_overrides: # and if it isn't, add it to the list of overrides override_attrnames.append(attrname) # if it's a normal attribute, override only if it doesn't # exist in the base class else: if attrname not in dir(cells.Family): override_attrnames.append(attrname) # now, do the overrides, bypassing normal getattrs for attrib_name in override_attrnames: _debug("overriding", attrib_name, "in", str(klass)) setattr(klass, attrib_name, self.kid_slots.__dict__[attrib_name]) # add any observers the kid_slots class defines: klass._observernames.update(self.kid_slots._observernames) # XXX: should we memoize all that work here? # finally, return an instance of that munged class with this obj set # as its parent: i = klass(parent=self) return i def make_kid(self, klass): """ make_kid(self, klass) -> None Adds a new instance of a Cell based on the passed class (in C{klass}) and the overrides defined in C{kid_slots} into the C{kids} list @param klass: the base type for the new kid instance """ _debug("make_kid called with", str(klass)) self._add_kid(self._kid_instance(klass)) def _add_kid(self, kid): """ _add_kid(self, kid) -> None Inserts the kid into this Family's C{kids} list @param kid: the Model instance to insert """ kid.parent = self self.kids.append(kid) def position(self): """ position(self) -> int Returns this instance's position in the enclosing Family's C{kids} list. Returns -1 if there is no enclosing Family. @raise FamilyTraversalError: Raises if there is no enclosing Family """ if self.parent: return self.parent.kids.index(self) raise FamilyTraversalError("No enclosing Family") def previous_sib(self): """ previous_sib(self) -> Model Returns the Model previous to this Model in the enclosing Family's C{kids} list @raise FamilyTraversalError: Raises if there is no enclosing Family """ if self.parent and self.position() > 0: return self.parent.kids[self.position() - 1] raise FamilyTraversalError("No enclosing Family") def next_sib(self): """ previous_sib(self) -> Model Returns the Model subsequent to this Model in the enclosing Family's C{kids} list @raise FamilyTraversalError: Raises if there is no enclosing Family """ if self.parent and self.position() < len(self.parent.kids) - 1: return self.parent.kids[self.position() + 1] raise FamilyTraversalError("No enclosing Family") def grandparent(self): """ grandparent(self) -> Model Returns enclosing Family instance's enclosing Family instance, or None if no such object exists. """ # raise or just return None? if self.parent: if self.parent.parent: return self.parent.parent return None
class M(cells.Model): x = cells.makecell(value=3) a = cells.makecell(rule=a_rule, celltype=cells.AlwaysLazyCell)
class MyModel(cells.Model): x = cells.makecell(value=5) a = cells.makecell(rule=lambda s, p: int(s.x) + s.offset) offset = 2
class Database(cells.Family): """Class for interaction with a database. (Short example of init usage here) """ # Public Non-Cells supported_rdbms = ("sqlite", ) addtable = cells.Family.make_kid def __del__(self): self._rawcon.close() # Public Cells connection = cells.makecell(value="") @cells.fun2cell() def tables(self, prev): d = {} for kid in self.kids: d[kid.name] = kid return d @cells.fun2cell() def connected(self, prev): if self._rawcon: return True else: return False # Private Cells @cells.fun2cell() def _contuple(self, prev): """ The RDBMS and connection strings. """ try: rdbms, constring = self.connection.split("://", 1) if rdbms not in self.supported_rdbms: raise UnsupportedRDBMSError( "Only the following RDBMSs are supported:", ",".join(self.supported_rdbms)) return (rdbms, constring) except ValueError: return (None, None) @cells.fun2cell() def _rdbmsmodule(self, prev): """ The appropriate RDBMS module. """ if not self._contuple[0]: return None if self._contuple[0] == "sqlite": from pysqlite2 import dbapi2 as sqlite return sqlite @cells.fun2cell() def _rawcon(self, prev): """ The "raw" connection to the database """ if self._rdbmsmodule: return self._rdbmsmodule.connect(self._contuple[1]) else: return None
class Table(cells.Family): def __init__(self, *args, **kwargs): cells.Family.__init__(self, *args, **kwargs) self.rows.db = self.parent self.rows.table = self # build column list for attr in self.__dict__.keys(): val = getattr(self, attr) if isinstance(val, (Column, PrimaryKey)): val.name = attr val.table = self self.columns.append(val) self.ready = True _debug(self.name, "has columns", list(self.columns)) # TODO: Make the primary key not-hardcoded: pk = primary_key() columns = cells.makecell(celltype=cells.ListCell) ready = cells.makecell(value=False) rows = cells.makecell(celltype=RowList) inserted = cells.makecell(value=False) @cells.fun2cell(celltype=cells.RuleThenInputCell) def name(self, prev): return self.__class__.__name__ # Sometimes a Table object acts like a Table, and so we need a # "create table" string to feed to the RDBMS @cells.fun2cell() def create_table_string(self, prev): colnames = [column.create_column_string for column in self.columns] if colnames: return "CREATE TABLE " + self.name + \ "(" + ",\n".join(colnames) + ")" else: return "" # Other times the Table object acts like a row in a table, so we # need an "insert into" string to feed to the RDBMS when this row # is created @cells.fun2cell() def insert_string(self, prev): return " ".join( ("INSERT INTO", self.name, "(", ",".join([_.name for _ in self.columns]), ") VALUES (", ",".join(['?'] * len(self.columns)), ")")) # we'll also need a list of values for the rdbms to use with the # insert string @cells.fun2cell() def insert_values(self, prev): return [_.sql_value for _ in self.columns] # We'll also need an update string when objects change @cells.fun2cell() def update_string(self, prev): return " ".join(("UPDATE", self.name, "SET", ", ".join([_.name + "=?" for _ in self.columns ]), "WHERE pk=" + str(self.pk.sql_value)))
class N(self.M): # modify an attribute the observers are looking for a = cells.makecell(rule=lambda s, p: int(s.x) * s.offset)
class AddressBook(cells.Model): entries = cells.makecell(value={}) names = cells.makecell(rule=lambda s, p: s.entries.keys())
class PrimaryKey(Integer): name = cells.makecell(value="pk") @cells.fun2cell() def create_column_string(self, prev): return self.name + " INTEGER PRIMARY KEY"
class Model(object, metaclass=ModelMetatype): """ A class in which CellAttrs may be used. Models automatically bring their cells up-to-date at C{L{__init__}}-time. Cells may be altered at runtime by passing C{attrname=value}, or C{attrname=hash} to the constructor. @ivar model_name: A cell holding The name of this Model. By default, None. @ivar model_value: A cell holding the value of this Model. By default, None. @ivar parent: A cell for C{L{Family}} graph traversal. By default, None. """ _initialized = False model_name = cells.makecell(value=None, kid_overrides=False) model_value = cells.makecell(value=None, kid_overrides=False) parent = cells.makecell(value=None, kid_overrides=False) def __init__(self, *args, **kwargs): """ __init__(self, [<attrname>=<value, rule or dict>], ...) -> None Initialize a Model with optional overrides. By passing a parameter with the same name as a cell attribute, you may override that cell attribute. For example: >>> class A(cells.Model): ... x = cells.makecell(value=1) ... >>> a1 = A() >>> a1.x 1 >>> a2 = A(x="blah") >>> a2.x 'blah' This override can be arbitrarily complex; for instance, you can make a RuleCell into a ValueCell, change a attribute's celltype ... In short, anything you can do at Model defintion time you can alter at instantiation time: >>> class B(cells.Model): ... x = cells.makecell(rule=lambda s,p: 3 * s.y) ... y = cells.makecell(value=2) ... >>> b = B() >>> b.x 6 >>> b.y = 1 >>> b.x 3 >>> b = B(y=10) >>> b.x 30 >>> b.y 10 >>> b = B(x={'celltype': cells.RuleThenInputCell}) >>> b.x 6 >>> b.y 2 >>> b.x = 5 >>> b.x 5 >>> b.y = 1 >>> b.x 5 @param attrname: The name of the attribute you wish to override. If this is set to a callable, it will override the rule for the cell. If it's set to a dictionary with one or more of 'rule', 'value', or 'celltype', those attributes will be overridden in the cell. Otherwise, it will override the value of the target cell. """ self._initregistry = {} klass = self.__class__ # do automagic overriding: for k, v in kwargs.items(): # for each keyword arg if k in dir(klass): # if there's a match in my class # normalize the input if isinstance(v, collections.Callable): cellinit = {'rule': v} elif 'keys' in dir(v): # kinda ran out of synonyms/shortened versions of # keys, here. I just want to see if any of 'rule', # 'value', or 'celltype' are in the keys of the # dict in v: quays = list(v.keys()) for qui in ('rule', 'value', 'celltype'): if qui in quays: cellinit = v break else: cellinit = {'value': v} if not cellinit: raise BadInitError( "A cell initialization dictionary was not built. Try wrapping your value or rule assignment in a dictionary.") # set the new init in the registry for this cell name; to be # read at cell-build time self._initregistry[k] = cellinit # turn the observer name registry into a list of real Observers self._observers = [] for name in self._observernames: self._observers.append(getattr(self, name)) self._prioritize_observers() # do initial equalizations debug("INITIAL EQUALIZATIONS START") for name in dir(self): try: # if it's not been otherwise initialized debug("examining", name) if name not in self.__dict__: debug(name, "is already in this object") # grab the cell attr and figure out if it's an always-lazy cellattr = getattr(self.__class__, name) if isinstance(cellattr, cells.CellAttr): kwargs = cellattr.getkwargs(self) debug(name, "is a cellattr with kwargs", repr(kwargs)) # if it isn't an always-lazy if issubclass(kwargs.get('celltype', object), cells.AlwaysLazyCell): debug(name, "should be init'd") # get it, initializing it getattr(self, name) # will run observers by itself else: debug(name, "is an always-lazy") except EphemeralCellUnboundError as e: debug(name, "was an unbound ephemeral") debug("INITIAL EQUALIZATIONS END") # run observers on non-cell attributes for key in self._noncells: self._run_observers(getattr(self, key)) # and now we're initialized. lock the object down. self._initialized = True def __setattr__(self, key, value): """ Per KT's spec, Models may not set non-cell attributes after __init__. @raise NonCellSetError: If you try to set a non-cell attribute """ # always set Cells if isinstance(self.__dict__.get(key), Cell): object.__setattr__(self, key, value) # we can set noncells before init elif not self._initialized: if key not in self._noncells: # make sure it's registered, though self._noncells.add(key) object.__setattr__(self, key, value) # and then set it # we can set anything we've not seen, too elif key not in list(self.__dict__.keys()): object.__setattr__(self, key, value) # but the only thing left is non-cells we've seen, which is verboten else: raise NonCellSetError("Setting non-cell attributes of models " + \ "after init is disallowed") def _run_observers(self, attribute): """Runs each observer in turn. There's some optimization that could go on here, if it turns out to be neccessary. """ debug("model running observers -- ", str(len(self._observers)), "to test") for observer in self._observers: observer.run_if_applicable(self, attribute) def _buildcell(self, name, *args, **kwargs): """ """ debug("Building cell: owner:", str(self)) debug(" name:", name) debug(" args:", str(args)) debug(" kwargs:", str(kwargs)) # figure out what type the user wants: if 'celltype' in kwargs: celltype = kwargs["celltype"] elif 'rule' in kwargs: # it's a rule-cell. celltype = cells.RuleCell elif 'value' in kwargs: # it's a value-cell celltype = cells.InputCell else: raise Exception("Could not determine target type for cell " + "given owner: " + str(self) + ", name: " + name + ", args:" + str(args) + ", kwargs:" + str(kwargs)) kwargs['name'] = name return celltype(self, *args, **kwargs) def _prioritize_observers(self): """ _prioritize_observers(self) -> none (re)Sorts the observer list by priorities """ self._observers.sort(key=lambda x: x.priority, reverse=True) @classmethod def observer(klass, attrib=None, oldvalue=None, newvalue=None, priority=None): """ observer(attrib=None, oldvalue=None, newvalue=None, priority=Non) -> decorator A classmethod to add an observer attribute to a Model. The observer may be set to fire on any change in the model, any change in an attribute, or when a function testing the new or old value of a cell returns true. >>> import cells >>> class A(cells.Model): ... x = cells.makecell(value=4) ... >>> @A.observer(attrib="x", newvalue=lambda a: a % 2) ... def odd_x_obs(model): ... print "New value of x is odd!" ... >>> a = A() >>> a.x 4 >>> a.x = 5 New value of x is odd! >>> a.x = 42 >>> a.x = 11 New value of x is odd! @param attrib: An attribute name to attach the observer to @param oldvalue: A function to run on the now-out-of-date value of a cell which changed in this datapulse; if the function returns True, the observer will fire. The signature for the function must be C{f(val) -> bool} @param newvalue: A function to run on up-to-date value; if the function returns True, the observer will fire. The signature for the function must be C{f(val) -> bool} @param priority: When this observer should be run compared to the other observers on this model. Observers have a priority of None by default. Observers with larger priorities are run first, None last. Observers with the same priority are run in arbitrary order. """ def observer_decorator(func): klass._observernames.add(func.__name__) setattr(klass, func.__name__, ObserverAttr(func.__name__, attrib, oldvalue, newvalue, func)) return observer_decorator
class K(cells.Family): x = cells.makecell(value=1) model_value = cells.makecell(rule=lambda s, p: s.x * 3)
class Page(cells.Model): """A number of representations of a given resource (eg, '/foo')""" def __init__(self, cache, *args, **kwargs): self.cache = cache self.config = CellCMSConfig cells.Model.__init__(self, *args, **kwargs) special = re.compile(r"/__.*__") cleanpath = re.compile(r"(/([A-Za-z0-9]+\.)*([A-Za-z0-9]*))*(\?.*)?") # INPUT CELLS request = cells.makecell(value=None) modified = cells.makecell(value=0) built = cells.makecell(value=0) # RULE CELLS @cells.fun2cell() def dirty_path(self, prev): """The not-cleaned path from self.request""" if not self.request: return "/" return self.request.path @cells.fun2cell() def cleaned_path(self, prev): """A safe path which may be used for reading/writing to""" if not self.cleanpath.match(self.dirty_path): print "Illegal path request:", self.dirty_path return "/" return self.dirty_path @cells.fun2cell() def dynamic_path(self, prev): """The location, on-disk, of the requested resource if it's dynamic""" return (self.config.get('directories', 'storage') + self.cleaned_path) @cells.fun2cell() def static_path(self, prev): """The location, on-disk, of the requested resource if it's static""" return (self.config.get('directories', 'static') + self.cleaned_path) # these just figure out if the request is for a dynamic or static # resource is_dynamic = cells.makecell(rule=lambda s, p: glob.glob(s.dynamic_path)) is_static = cells.makecell(rule=lambda s, p: glob.glob(s.static_path)) @cells.fun2cell() def is_directory(self, prev): """Is the request for a directory?""" return os.path.isdir(self.static_path) @cells.fun2cell() def is_special(self, prev): """Is the request for a 'special' resource (eg, '__foo__')""" return self.special.match(self.cleaned_path) @cells.fun2cell() def is_valid(self, prev): """Is the resource available on-disk?""" return self.is_dynamic or self.is_static @cells.fun2cell() def raw_source(self, prev): """Holds the unrendered source data for this Page""" if not self.is_valid: return "Resource not found. You can edit it below." # all we need to do is depend on self.modified and the source # will be updated whenever the modification-time is # changed. I'm pretty sure this test will always fail. if self.modified < self.built: return prev if self.is_directory: # implies is_static currdir = os.getcwd() # save old dir os.chdir(self.static_path) # change to the requested dir out = ["Directory listing:", "------------------------------"] out += glob.glob("*") # get the files here os.chdir(currdir) # change back to the original dir return "\n".join(out) if self.is_static: # because of above test, not a dir. return open(self.static_path).read() if self.is_dynamic: return (open(self.dynamic_path).read()) else: return "Should not have got here!" @cells.fun2cell() def source(self, prev): """Holds the rendered, untemplatized version of this Page""" if self.is_dynamic: return markdown.markdown(self.raw_source) else: return self.raw_source @cells.fun2cell() def template_file(self, prev): """Determines which template to apply to this Page depending on request type""" if self.is_special: return "templates/just_edit.tmpl" if self.is_directory: return "templates/directory.tmpl" if self.is_static: return "templates/static.tmpl" else: return "templates/full.tmpl" @cells.fun2cell() def templatized(self, prev): """Holds the rendered, templatized version of this Page""" if self.is_dynamic or self.is_directory or not self.is_valid: return Template(cache=self.cache, page=self, template=self.template_file).render() else: return self.raw_source
class SomeContainer(cells.Family): entries = cells.makecell(value=[], celltype=cells.ListCell) size = cells.makecell(rule=lambda s,p: len(s.entries))