Metadata-Version: 1.0
Name: limone
Version: 0.1a5
Summary: Content type system based on colander schemas.
Home-page: http://pylonsproject.org
Author: Chris Rossi, Archimedean Company
Author-email: pylons-devel@googlegroups.com
License: BSD-derived (http://www.repoze.org/LICENSE.txt)
Description: ======
        Limone
        ======
        
        Limone is a library for generating content types from a Colander_ schema. 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.
        
        .. _Colander: http://docs.pylonsproject.org/projects/colander/dev/
        
        
        Creating Content Types Declaratively
        ------------------------------------
        
        Content 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 a `Person` content type is to add the
        `limone.content_schema` decorator::
        
            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 of `300` is assigned to `age`::
        
            jack.age = 300
        
        A `colander.Invalid` exception 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 Schema
        --------------------------------
        
        In some cases you might want to define a class separately from its schema. For
        this you can use the `limone.content_type` decorator. Let's say that instead
        of turning the `Person` schema into a content type directly, we have an
        `HRPerson` class which extends a hypothetical `HRRecord` class that we want to
        use for our content type::
        
            @limone.content_type(Person)
            class HRPerson(HRRecord):
                pass
        
            fred = HRPerson(name='Fred', age=54)
        
        **NOTE** The decorated class must have a no-arg constructor.
        
        
        Creating a Content Type Imperatively
        ------------------------------------
        
        The above examples use a declarative style for creating content types. Using
        the `make_content_type` function, we can also generate new content types
        imperatively. Assuming `HRPerson` has 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 the `make_content_type` function is::
        
            make_content_type(schema, name, module=None, bases=(object,))
        
        + `schema` is the Colander schema to use to generate the class.
        
        + The value of the `name` parameter 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. (See `Using the
          Limone Registry`_.)
        
        + `module`, if specified, will be used to set the `__module__` attribute of
          the generated class.
        
        + `bases` can be specified as a tuple of types that are the superclasses for
          the generated classes.  **NOTE** The first base class must have a no-arg
          constructor.
        
        
        Using the Limone Registry
        -------------------------
        
        Instances `limone.Registry` can be used to keep track of available content
        types.  An instance of `limone.Registry` is required to make content types
        available via an import hook.  (See `Using the Import Hook`_.)
        
        
        Basic Registration and Retrieval of Content Types
        +++++++++++++++++++++++++++++++++++++++++++++++++
        
        Content types are added to the registry using the `register_content_type`
        method::
        
            registry = limone.Registry()
            registry.regsister_content_type(Person)
        
        The `get_content_type` method 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 the
        `get_content_types` method::
        
            for content_type in registry.get_content_types():
                print content_type.__name__, content_type
        
        Prints::
        
            Person <class 'Person'>
        
        
        Scanning for Content Types
        ++++++++++++++++++++++++++
        
        A 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 the `content_type` or `content_schema` decorator somewhere in your
        package.  The `scan` method 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 Hook
        +++++++++++++++++++++
        
        In 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 the
        `Person` content 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__.Person
        
        What 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 of `limone.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 for `hook_import` is::
        
            hook_import(module='__limone__')
        
        The `hook_import` method inserts an object into `sys.meta_path` that can look
        up content types in the registry. The `module` parameter 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 for `module`, 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 for `module` should not be used if you expect that an
        application will use more than one `limone.Registry` instance inside of a
        single process. In this case, a different value of `module` should be used for
        each instance so that each instance only tries to find its own content types.
        
        The `unhook_import` method cleans up a previously made import hook, returning
        `sys.meta_path` to its previous state.
        
        Using the Colander Appstruct
        ----------------------------
        
        Instances 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/Deserialization
        ----------------------------------------------
        
        Instances 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 Limone
        --------------------
        
        0.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 as `limone_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.  See `limone_zodb` as an example.
        
        0.1a1 (2011-07-07)
        ++++++++++++++++++
        
        - First alpha release.
        
Platform: UNKNOWN
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python
Classifier: Framework :: Pylons
Classifier: License :: Repoze Public License
