Package: limone_zodb
Limone ZODBis an extension ofLimonewhich generates content types that
are persistable via ZODB.  Usage is the same asLimoneexcept that the
decorators andmake_content_typefunction are imported from thelimone_zodbpackage instead of thelimonepackage:import colander
import limone_zodb
import persistent

class Friend(colander.TupleSchema):
    rank = colander.SchemaNode(colander.Int(),
                              validator=colander.Range(0, 9999))
    name = colander.SchemaNode(colander.String())

class Phone(colander.MappingSchema):
    location = colander.SchemaNode(colander.String(),
                                  validator=colander.OneOf(['home', 'work']))
    number = colander.SchemaNode(colander.String())

class Friends(colander.SequenceSchema):
    friend = Friend()

class Phones(colander.SequenceSchema):
    phone = Phone()

@limone_zodb.content_schema
class Person(colander.MappingSchema):
    name = colander.SchemaNode(colander.String())
    age = colander.SchemaNode(colander.Int(),
                             validator=colander.Range(0, 200))
    friends = Friends()
    phones = Phones()

jake = Person(name='Jake', age=21)
assert isinstance(jake, persistent.Persistent)RationaleNaively, one might presume that usingpersistent.Persistentas a base class
for the content type would be sufficient. What can happen, though, is you can
find that certain changes to a content object fail to persist. For example,
without usinglimone_zodbwe might have just done something more like:import limone
import persistent

@limone.content_type(Person)
class PersistentPerson(persistent.Persistent):
    pass

jake = PersistentPerson(name='Jake', age=21)Than, later, in another transaction:jake.age = 22  # This will persist just fineWhile this will work fine if you only change direct attributes of the content
object, if you change deep attributes, likefriendsorphonesin the
example above, those won’t necessarily persist automatically.  This is not due
to anything particular toLimone.  This behavior can be observed with any
object containing nested data structures.  Let’s take a look at this example
which doesn’t useLimone:import persistent

class C(persistent.Persistent):
    def __init__(self):
        self.foo = 'Hello'
        self.bars = ['tiki', 'biker']

o = C()Assuming we have stored the instanceoin the ZODB and retrieved it another
transaction, we could try to modify it.  If we change the value ofo.fooand
commit our transactoin, a new copy ofowill be written to the ZODB and our
change will have been saved:import transaction

o.foo = 'Howdy'  # This change will persist
transaction.commit()If, on the other hand, we add a new value too.bars, then this change alone
will not be sufficient to cause a new value ofoto be written to the ZODB
and the change will not be persisted when the transaction is committed:o.bars.append('lesbian')  # Change does not persist
transaction.commit()The reason for this lies in howpersistent.Persistentobject work.persistent.Persistentoverrides the__setattr__method ofobjectso that
when an attribute is set on a persistent object, that object is advertised to
the transaction as having been changed, and a new copy is written when the
transaction is committed.bars, though, is just a regular old Python list
that doesn’t know anything about persistence.  Mutating that list does not
advertise the change to the transaction, so the ZODB doesn’t know that there is
anything new to be written to the database.For this exact same reason, changes to aLimonecontent object that only
involve a nested data structure, will fail to advertise to the transaction
that a change has occurred.  If these are the only changes to an object, that
object’s new state will not be written to the database when the transaction is
committed.Limone ZODBgets around this problem by passing a metaclass into the normalLimonecontent type generation process which makes sure that making any
change to aLimone ZODBcontent object, at any level of the content
structure, will cause the change to be persisted automatically, without any
hoops to be jumped through by the developer.Changelog for Limone ZODB0.1a2 (2011-08-09)Changed the method resolution order of _MappingNode to make sure that
limone’s __setattr__ is called on attribute assignment.0.1a1 (2011-07-16)Initial alpha release.