{ "info": { "author": "Chris Rossi, Archimedean Company", "author_email": "pylons-devel@googlegroups.com", "bugtrack_url": null, "classifiers": [ "Framework :: Pylons", "Intended Audience :: Developers", "License :: Repoze Public License", "Programming Language :: Python" ], "description": "======\nLimone\n======\n\nLimone is a library for generating content types from a Colander_ schema. A\ncontent type is, in this context, a class that implements the structure and\nconstraints specified by the schema. This allows a developer to easily\ngenerate model objects which enforce the constraints of the schema, performing\nvalidation during initialization and attribute assignment. Objects are\nserializable and deserializable via Colander's serialization. Because types\nare generated at runtime, Limone also suggests the development of applications\nwhere the structure of the objects used to store your application's data can\nbe derived from configuration or user input.\n\n.. _Colander: http://docs.pylonsproject.org/projects/colander/dev/\n\n\nCreating Content Types Declaratively\n------------------------------------\n\nContent types can be generated declaratively from schema definitions using\ndecorators. Let's take a look at the following Colander schema as an example,\ntaken from the Colander documentation::\n\n import colander\n\n class Friend(colander.TupleSchema):\n rank = colander.SchemaNode(colander.Int(),\n validator=colander.Range(0, 9999))\n name = colander.SchemaNode(colander.String())\n\n class Phone(colander.MappingSchema):\n location = colander.SchemaNode(colander.String(),\n validator=colander.OneOf(['home', 'work']))\n number = colander.SchemaNode(colander.String())\n\n class Friends(colander.SequenceSchema):\n friend = Friend()\n\n class Phones(colander.SequenceSchema):\n phone = Phone()\n\n class Person(colander.MappingSchema):\n name = colander.SchemaNode(colander.String())\n age = colander.SchemaNode(colander.Int(),\n validator=colander.Range(0, 200))\n friends = Friends()\n phones = Phones()\n\nThe simplest way to generate a `Person` content type is to add the\n`limone.content_schema` decorator::\n\n import colander\n import limone\n\n ... \n\n @limone.content_schema\n class Person(colander.MappingSchema):\n name = etc...\n\nInstances of Person can then be created in the usual way::\n\n jack = Person(\n name='Jack',\n age=52,\n friends=[\n (1, 'Fred'),\n (2, 'Barney')\n ],\n phones=[\n {'location': 'home',\n 'number': '555-1212'},\n ])\n\nAssigning a value to an attribute triggers Colander schema validation. For\nexample, when a value of `300` is assigned to `age`::\n\n jack.age = 300\n\nA `colander.Invalid` exception is raised::\n\n colander.Invalid: {'age': u'300 is greater than maximum value 200'}\n\nWhen instantiating a content type, values for all required attributes must be\nprovided::\n\n fred = Person()\n\nRaises::\n\n colander.Invalid: {'age': u'Required', 'name': u'Required'}\n\n\nDecorating a Class With a Schema\n--------------------------------\n\nIn some cases you might want to define a class separately from its schema. For\nthis you can use the `limone.content_type` decorator. Let's say that instead\nof turning the `Person` schema into a content type directly, we have an\n`HRPerson` class which extends a hypothetical `HRRecord` class that we want to\nuse for our content type::\n\n @limone.content_type(Person)\n class HRPerson(HRRecord):\n pass\n\n fred = HRPerson(name='Fred', age=54)\n\n**NOTE** The decorated class must have a no-arg constructor.\n\n\nCreating a Content Type Imperatively\n------------------------------------\n\nThe above examples use a declarative style for creating content types. Using\nthe `make_content_type` function, we can also generate new content types\nimperatively. Assuming `HRPerson` has been defined as a class, the example\nabove could have been written::\n\n content_type = limone.make_content_type(Person, 'Person', bases=(HRPerson,))\n fred = content_type(name='Joe', age=54)\n\nThe full signature for the `make_content_type` function is::\n\n make_content_type(schema, name, module=None, bases=(object,))\n\n+ `schema` is the Colander schema to use to generate the class.\n\n+ The value of the `name` parameter will be assigned to the `__name__`\n attribute of the generated class. If added to a registry, the name will also\n be used as the key for looking up the content type later. (See `Using the\n Limone Registry`_.)\n\n+ `module`, if specified, will be used to set the `__module__` attribute of\n the generated class.\n\n+ `bases` can be specified as a tuple of types that are the superclasses for\n the generated classes. **NOTE** The first base class must have a no-arg\n constructor.\n\n\nUsing the Limone Registry\n-------------------------\n\nInstances `limone.Registry` can be used to keep track of available content\ntypes. An instance of `limone.Registry` is required to make content types\navailable via an import hook. (See `Using the Import Hook`_.)\n\n\nBasic Registration and Retrieval of Content Types\n+++++++++++++++++++++++++++++++++++++++++++++++++\n\nContent types are added to the registry using the `register_content_type`\nmethod::\n\n registry = limone.Registry()\n registry.regsister_content_type(Person)\n\nThe `get_content_type` method is used to retrieve a content type by name::\n\n content_type = registry.get_content_type('Person')\n joe = content_type(name='Joe', age=54)\n\nA tuple of all of the registered content types can be retrieved using the\n`get_content_types` method::\n\n for content_type in registry.get_content_types():\n print content_type.__name__, content_type\n\nPrints::\n\n Person \n\n\nScanning for Content Types\n++++++++++++++++++++++++++\n\nA registry instance can also find content types by scanning a package looking\nfor content types to add to the registry. This is possible if you have used\neither the `content_type` or `content_schema` decorator somewhere in your\npackage. The `scan` method is used to search for content types defined with\nthose decorators and add them to the registry::\n\n import limone\n import myapp.models\n\n registry = limone.Registry()\n registry.scan(myapp.models)\n\n\nUsing the Import Hook\n+++++++++++++++++++++\n\nIn the above two declarative examples, because types were being generated at\nmodule scope, they can be imported using the standard Python import mechanism.\nFor content types that are generated imperatively, however, there may not be a\nglobal name that can be used to import the type. This would definitely be the\ncase in an application that generated content types from schemas that were\ngenerated at runtime through configuration or user input. This can lead to\ndifficulties--pickling, for example, does not work if the class can't be found\nby Python's import mechanism. Using the imperative example from earlier, let's\nsee what happens when we try to pickle and then unpickle an instance of the\n`Person` content type::\n\n import pickle\n\n content_type = make_content_type(PersonSchema, 'Person', bases=(HRPerson,))\n fred = content_type(name='Fred', age=54)\n fred2 = pickle.loads(pickle.dumps(fred))\n assert fred is not fred2\n assert fred.serialize() == fred2.serialize()\n\nWe get this exception::\n\n pickle.PicklingError: Can't pickle : it's not found as __main__.Person\n\nWhat we can do, though, is hook Python's import mechanism so that Python can\nlook up the content type in our Limone instance. This requires that the\ncontent type be registered with an instance of `limone.Registry`::\n\n import pickle\n\n registry = limone.Registry()\n registry.register_content_type(Person)\n registry.hook_import()\n\n content_type = make_content_type(PersonSchema, 'Person', bases=(HRPerson,))\n fred = content_type(name='Fred', age=54)\n fred2 = pickle.loads(pickle.dumps(fred))\n assert fred is not fred2\n assert fred.serialize() == fred2.serialize()\n\n registry.unhook_import()\n\nThe pickle and unpickle operations are now successful because pickle is able\nto look up the type using Python's import mechanism.\n\nThe signature for `hook_import` is::\n\n hook_import(module='__limone__')\n\nThe `hook_import` method inserts an object into `sys.meta_path` that can look\nup content types in the registry. The `module` parameter is used to set the\n`__module__` attribute on generated content types. This will also be used by\nthe import hook to identify the types that it is able to import. Using the\ndefault value for `module`, with the import hook in place, we see that we can\nimport imperatively generated content types in the standard Pythonic way::\n\n from __limone__ import Person\n fred = Person(name='Fred', age=54)\n\nThe default value for `module` should not be used if you expect that an\napplication will use more than one `limone.Registry` instance inside of a\nsingle process. In this case, a different value of `module` should be used for\neach instance so that each instance only tries to find its own content types.\n\nThe `unhook_import` method cleans up a previously made import hook, returning\n`sys.meta_path` to its previous state.\n\nUsing the Colander Appstruct\n----------------------------\n\nInstances of a content type can be converted to their Colander appstruct\nrepresentations::\n\n jack = Person(\n name='Jack',\n age=52,\n friends=[\n (1, 'Fred'),\n (2, 'Barney')\n ],\n phones=[\n {'location': 'home',\n 'number': '555-1212'},\n ])\n\n from pprint import pprint\n pprint(jack.appstuct())\n\nProduces this output::\n\n {'age': 52,\n 'friends': [(1, u'Fred'), (2, u'Barney')],\n 'name': u'Jack',\n 'phones': [{'location': u'home', 'number': u'555-1212'}]}\n\nA new instance can be created from an appstruct::\n\n jack = Person.from_appstruct(\n {'age': 52,\n 'friends': [(1, u'Fred'), (2, u'Barney')],\n 'name': u'Jack',\n 'phones': [{'location': u'home', 'number': u'555-1212'}]})\n\nA partial appstruct may be used to update an instance::\n\n jack.update_from_appstruct({'age': 53})\n\nUsing Colander`s Serialization/Deserialization\n----------------------------------------------\n\nInstances of a content type can be serialized using Colander's serialization::\n\n jack = Person(\n name='Jack',\n age=52,\n friends=[\n (1, 'Fred'),\n (2, 'Barney')\n ],\n phones=[\n {'location': 'home',\n 'number': '555-1212'},\n ])\n\n from pprint import pprint\n pprint(jack.serialize())\n\nProduces this output::\n\n {'age': '52',\n 'friends': [('1', u'Fred'), ('2', u'Barney')],\n 'name': u'Jack',\n 'phones': [{'location': u'home', 'number': u'555-1212'}]}\n\nNote that Colander's serialization is a kind of intermediate format. All\nscalar values are serialized to strings, but sequences, tuples and mappings\nare returned as lists, tuples and dicts, respectively. This intermediate form\nis easily fed into other serializers, like json, to produce a serialized\nbyte sequence.\n\nInstances can be instantiated via Colander's deserialization::\n\n jack = Person.deserialize(\n {'age': '52',\n 'friends': [('1', u'Fred'), ('2', u'Barney')],\n 'name': u'Jack',\n 'phones': [{'location': u'home', 'number': u'555-1212'}]})\n\nDeserialization can also be used to update an existing instance::\n\n jack.deserialize_update({'age': '53'})\n\n\n\nChangelog for Limone\n--------------------\n\n0.1a5 (2011-09-01)\n------------------\n\n- Added public API for converting instances to and from Colander appstructs.\n\n0.1a4 (2011-08-11)\n++++++++++++++++++\n\n- Make sure that a property of a _MappingNode is set that __setattr__ is used\n to set the underlying attribute. This is done for the benefit of extensions\n that rely on __setattr__ being called, such as `limone_zodb`.\n\n- Use an overridable property factory for generating property descriptors for\n attributes of content types.\n\n0.1a3 (2011-08-09)\n++++++++++++++++++\n\n- Refactor such that every node contained by a content object has a reference\n to the content object.\n\n0.1a2 (2011-07-16)\n++++++++++++++++++\n\n- Refactored some internals to allow extension by packages that might want to\n use Limone as a base. See `limone_zodb` as an example.\n\n0.1a1 (2011-07-07)\n++++++++++++++++++\n\n- First alpha release.", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "http://pylonsproject.org", "keywords": "", "license": "BSD-derived (http://www.repoze.org/LICENSE.txt)", "maintainer": null, "maintainer_email": null, "name": "limone", "package_url": "https://pypi.org/project/limone/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/limone/", "project_urls": { "Download": "UNKNOWN", "Homepage": "http://pylonsproject.org" }, "release_url": "https://pypi.org/project/limone/0.1a5/", "requires_dist": null, "requires_python": null, "summary": "Content type system based on colander schemas.", "version": "0.1a5" }, "last_serial": 794202, "releases": { "0.1a1": [ { "comment_text": "", "digests": { "md5": "afec34371d2aa5d4c6600fbbc2b3db03", "sha256": "fb869bca918e0c02bd827082cebf45ee4a83dad02fe1d7ad5f9de7147237c82a" }, "downloads": -1, "filename": "limone-0.1a1.tar.gz", "has_sig": false, "md5_digest": "afec34371d2aa5d4c6600fbbc2b3db03", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 14400, "upload_time": "2011-07-07T17:01:20", "url": "https://files.pythonhosted.org/packages/e2/b4/3fd7d368f50a9fb6d5f72358f4cd4e7cfd8024d5bdf112293738cae1fb7f/limone-0.1a1.tar.gz" } ], "0.1a2": [ { "comment_text": "", "digests": { "md5": "d3d3d62a2097542d42c57f11e62edf7f", "sha256": "279c1088c3e7566cb13846cf55703cff410b7713978e81b0a47fe5af6b55118d" }, "downloads": -1, "filename": "limone-0.1a2.tar.gz", "has_sig": false, "md5_digest": "d3d3d62a2097542d42c57f11e62edf7f", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 14757, "upload_time": "2011-07-16T20:43:06", "url": "https://files.pythonhosted.org/packages/49/83/976cdc96288ba65ffffd5f61b7e1abbec08ef8fee5b5df6dec8c68bf1ee9/limone-0.1a2.tar.gz" } ], "0.1a3": [ { "comment_text": "", "digests": { "md5": "c7bf16ff00d2b7be434c49623ca0d9fb", "sha256": "a7b451bae25e9364710c9bd14c55474bef6c7efaa387dab692208955c5b3aff0" }, "downloads": -1, "filename": "limone-0.1a3.tar.gz", "has_sig": false, "md5_digest": "c7bf16ff00d2b7be434c49623ca0d9fb", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 14958, "upload_time": "2011-08-09T18:18:42", "url": "https://files.pythonhosted.org/packages/4b/ae/199f9d057266629474b6eb9f53dee3367a6e27874551600e61df45c3a16b/limone-0.1a3.tar.gz" } ], "0.1a4": [ { "comment_text": "", "digests": { "md5": "704b623ea47d57cb2150884aad95e63b", "sha256": "924e58330208903de005a5cb53c8d0b5e1586efddeb8919463dea994ad5388b7" }, "downloads": -1, "filename": "limone-0.1a4.tar.gz", "has_sig": false, "md5_digest": "704b623ea47d57cb2150884aad95e63b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 15225, "upload_time": "2011-08-11T21:17:02", "url": "https://files.pythonhosted.org/packages/04/25/3b504c50de077414bc5cbd3a46137d252af21fc2b3a53d708edd08bdd68d/limone-0.1a4.tar.gz" } ], "0.1a5": [ { "comment_text": "", "digests": { "md5": "961a16e1b2b738f7c847c68d84350b7a", "sha256": "c4b81f4dd4b3e5c32034153e724e63088ef4be569d29209095dd56934552e7c2" }, "downloads": -1, "filename": "limone-0.1a5.tar.gz", "has_sig": false, "md5_digest": "961a16e1b2b738f7c847c68d84350b7a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 16762, "upload_time": "2011-09-01T18:12:56", "url": "https://files.pythonhosted.org/packages/de/a8/b5b1f2a19476f634d7ba62b9f529e859673aee43c8868272b19182f8dd27/limone-0.1a5.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "961a16e1b2b738f7c847c68d84350b7a", "sha256": "c4b81f4dd4b3e5c32034153e724e63088ef4be569d29209095dd56934552e7c2" }, "downloads": -1, "filename": "limone-0.1a5.tar.gz", "has_sig": false, "md5_digest": "961a16e1b2b738f7c847c68d84350b7a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 16762, "upload_time": "2011-09-01T18:12:56", "url": "https://files.pythonhosted.org/packages/de/a8/b5b1f2a19476f634d7ba62b9f529e859673aee43c8868272b19182f8dd27/limone-0.1a5.tar.gz" } ] }