Package: limone
Limone is a library for generating content types from aColanderschema. A
content type is, in this context, a class that implements the structure and
constraints specified by the schema. This allows a developer to easily
generate model objects which enforce the constraints of the schema, performing
validation during initialization and attribute assignment. Objects are
serializable and deserializable via Colander’s serialization.  Because types
are generated at runtime, Limone also suggests the development of applications
where the structure of the objects used to store your application’s data can
be derived from configuration or user input.Creating Content Types DeclarativelyContent types can be generated declaratively from schema definitions using
decorators. Let’s take a look at the following Colander schema as an example,
taken from the Colander documentation:import colander

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()

class Person(colander.MappingSchema):
    name = colander.SchemaNode(colander.String())
    age = colander.SchemaNode(colander.Int(),
                             validator=colander.Range(0, 200))
    friends = Friends()
    phones = Phones()The simplest way to generate aPersoncontent type is to add thelimone.content_schemadecorator:import colander
import limone

... <elided for brevity>

@limone.content_schema
class Person(colander.MappingSchema):
    name = etc...Instances of Person can then be created in the usual way:jack = Person(
    name='Jack',
    age=52,
    friends=[
        (1, 'Fred'),
        (2, 'Barney')
    ],
    phones=[
        {'location': 'home',
         'number': '555-1212'},
    ])Assigning a value to an attribute triggers Colander schema validation.  For
example, when a value of300is assigned toage:jack.age = 300Acolander.Invalidexception is raised:colander.Invalid: {'age': u'300 is greater than maximum value 200'}When instantiating a content type, values for all required attributes must be
provided:fred = Person()Raises:colander.Invalid: {'age': u'Required', 'name': u'Required'}Decorating a Class With a SchemaIn some cases you might want to define a class separately from its schema. For
this you can use thelimone.content_typedecorator. Let’s say that instead
of turning thePersonschema into a content type directly, we have anHRPersonclass which extends a hypotheticalHRRecordclass that we want to
use for our content type:@limone.content_type(Person)
class HRPerson(HRRecord):
    pass

fred = HRPerson(name='Fred', age=54)NOTEThe decorated class must have a no-arg constructor.Creating a Content Type ImperativelyThe above examples use a declarative style for creating content types. Using
themake_content_typefunction, we can also generate new content types
imperatively. AssumingHRPersonhas been defined as a class, the example
above could have been written:content_type = limone.make_content_type(Person, 'Person', bases=(HRPerson,))
fred = content_type(name='Joe', age=54)The full signature for themake_content_typefunction is:make_content_type(schema, name, module=None, bases=(object,))schemais the Colander schema to use to generate the class.The value of thenameparameter will be assigned to the__name__attribute of the generated class. If added to a registry, the name will also
be used as the key for looking up the content type later. (SeeUsing the
Limone Registry.)module, if specified, will be used to set the__module__attribute of
the generated class.basescan be specified as a tuple of types that are the superclasses for
the generated classes.NOTEThe first base class must have a no-arg
constructor.Using the Limone RegistryInstanceslimone.Registrycan be used to keep track of available content
types.  An instance oflimone.Registryis required to make content types
available via an import hook.  (SeeUsing the Import Hook.)Basic Registration and Retrieval of Content TypesContent types are added to the registry using theregister_content_typemethod:registry = limone.Registry()
registry.regsister_content_type(Person)Theget_content_typemethod is used to retrieve a content type by name:content_type = registry.get_content_type('Person')
joe = content_type(name='Joe', age=54)A tuple of all of the registered content types can be retrieved using theget_content_typesmethod:for content_type in registry.get_content_types():
    print content_type.__name__, content_typePrints:Person <class 'Person'>Scanning for Content TypesA registry instance can also find content types by scanning a package looking
for content types to add to the registry.  This is possible if you have used
either thecontent_typeorcontent_schemadecorator somewhere in your
package.  Thescanmethod is used to search for content types defined with
those decorators and add them to the registry:import limone
import myapp.models

registry = limone.Registry()
registry.scan(myapp.models)Using the Import HookIn the above two declarative examples, because types were being generated at
module scope, they can be imported using the standard Python import mechanism.
For content types that are generated imperatively, however, there may not be a
global name that can be used to import the type.  This would definitely be the
case in an application that generated content types from schemas that were
generated at runtime through configuration or user input.  This can lead to
difficulties–pickling, for example, does not work if the class can’t be found
by Python’s import mechanism.  Using the imperative example from earlier, let’s
see what happens when we try to pickle and then unpickle an instance of thePersoncontent type:import pickle

content_type = make_content_type(PersonSchema, 'Person', bases=(HRPerson,))
fred = content_type(name='Fred', age=54)
fred2 = pickle.loads(pickle.dumps(fred))
assert fred is not fred2
assert fred.serialize() == fred2.serialize()We get this exception:pickle.PicklingError: Can't pickle <class 'Person'>: it's not found as __main__.PersonWhat we can do, though, is hook Python’s import mechanism so that Python can
look up the content type in our Limone instance.  This requires that the
content type be registered with an instance oflimone.Registry:import pickle

registry = limone.Registry()
registry.register_content_type(Person)
registry.hook_import()

content_type = make_content_type(PersonSchema, 'Person', bases=(HRPerson,))
fred = content_type(name='Fred', age=54)
fred2 = pickle.loads(pickle.dumps(fred))
assert fred is not fred2
assert fred.serialize() == fred2.serialize()

registry.unhook_import()The pickle and unpickle operations are now successful because pickle is able
to look up the type using Python’s import mechanism.The signature forhook_importis:hook_import(module='__limone__')Thehook_importmethod inserts an object intosys.meta_paththat can look
up content types in the registry. Themoduleparameter is used to set the__module__attribute on generated content types. This will also be used by
the import hook to identify the types that it is able to import. Using the
default value formodule, with the import hook in place, we see that we can
import imperatively generated content types in the standard Pythonic way:from __limone__ import Person
fred = Person(name='Fred', age=54)The default value formoduleshould not be used if you expect that an
application will use more than onelimone.Registryinstance inside of a
single process. In this case, a different value ofmoduleshould be used for
each instance so that each instance only tries to find its own content types.Theunhook_importmethod cleans up a previously made import hook, returningsys.meta_pathto its previous state.Using the Colander AppstructInstances of a content type can be converted to their Colander appstruct
representations:jack = Person(
    name='Jack',
    age=52,
    friends=[
        (1, 'Fred'),
        (2, 'Barney')
    ],
    phones=[
        {'location': 'home',
         'number': '555-1212'},
    ])

from pprint import pprint
pprint(jack.appstuct())Produces this output:{'age': 52,
 'friends': [(1, u'Fred'), (2, u'Barney')],
 'name': u'Jack',
 'phones': [{'location': u'home', 'number': u'555-1212'}]}A new instance can be created from an appstruct:jack = Person.from_appstruct(
    {'age': 52,
     'friends': [(1, u'Fred'), (2, u'Barney')],
     'name': u'Jack',
     'phones': [{'location': u'home', 'number': u'555-1212'}]})A partial appstruct may be used to update an instance:jack.update_from_appstruct({'age': 53})Using Colander`s Serialization/DeserializationInstances of a content type can be serialized using Colander’s serialization:jack = Person(
    name='Jack',
    age=52,
    friends=[
        (1, 'Fred'),
        (2, 'Barney')
    ],
    phones=[
        {'location': 'home',
         'number': '555-1212'},
    ])

from pprint import pprint
pprint(jack.serialize())Produces this output:{'age': '52',
 'friends': [('1', u'Fred'), ('2', u'Barney')],
 'name': u'Jack',
 'phones': [{'location': u'home', 'number': u'555-1212'}]}Note that Colander’s serialization is a kind of intermediate format.  All
scalar values are serialized to strings, but sequences, tuples and mappings
are returned as lists, tuples and dicts, respectively.  This intermediate form
is easily fed into other serializers, like json, to produce a serialized
byte sequence.Instances can be instantiated via Colander’s deserialization:jack = Person.deserialize(
    {'age': '52',
     'friends': [('1', u'Fred'), ('2', u'Barney')],
     'name': u'Jack',
     'phones': [{'location': u'home', 'number': u'555-1212'}]})Deserialization can also be used to update an existing instance:jack.deserialize_update({'age': '53'})Changelog for Limone0.1a5 (2011-09-01)Added public API for converting instances to and from Colander appstructs.0.1a4 (2011-08-11)Make sure that a property of a _MappingNode is set that __setattr__ is used
to set the underlying attribute.  This is done for the benefit of extensions
that rely on __setattr__ being called, such aslimone_zodb.Use an overridable property factory for generating property descriptors for
attributes of content types.0.1a3 (2011-08-09)Refactor such that every node contained by a content object has a reference
to the content object.0.1a2 (2011-07-16)Refactored some internals to allow extension by packages that might want to
use Limone as a base.  Seelimone_zodbas an example.0.1a1 (2011-07-07)First alpha release.