跳转到主要内容

基于colander架构的内容类型系统。

项目描述

Limone是一个从Colander架构生成内容类型的库。在这个上下文中,内容类型是一个实现了架构中指定结构和约束的类。这允许开发人员轻松生成强制执行架构约束的模型对象,在初始化和属性赋值期间执行验证。对象可以通过Colander的序列化进行序列化和反序列化。由于类型是在运行时生成的,因此Limone还建议开发应用程序,其中用于存储应用程序数据的对象结构可以从配置或用户输入中导出。

声明性创建内容类型

可以使用装饰器从架构定义中声明性生成内容类型。以下是一个示例,摘自Colander文档

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

创建Person内容类型的最简单方法是添加limone.content_schema装饰器

import colander
import limone

... <elided for brevity>

@limone.content_schema
class Person(colander.MappingSchema):
    name = etc...

然后可以像往常一样创建Person实例

jack = Person(
    name='Jack',
    age=52,
    friends=[
        (1, 'Fred'),
        (2, 'Barney')
    ],
    phones=[
        {'location': 'home',
         'number': '555-1212'},
    ])

将值赋给属性会触发Colander架构验证。例如,当将值300赋给age时

jack.age = 300

会引发一个colander.Invalid异常

colander.Invalid: {'age': u'300 is greater than maximum value 200'}

在实例化内容类型时,必须提供所有必需属性的值

fred = Person()

引发

colander.Invalid: {'age': u'Required', 'name': u'Required'}

用架构装饰类

在某些情况下,您可能希望将类与其模式分开定义。为此,您可以使用 limone.content_type 装饰器。假设我们不是直接将 Person 模式转换为内容类型,而是有一个 HRPerson 类,它扩展了我们想用作内容类型的假设的 HRRecord

@limone.content_type(Person)
class HRPerson(HRRecord):
    pass

fred = HRPerson(name='Fred', age=54)

注意 被装饰的类必须有一个无参构造函数。

命令式创建内容类型

上面的例子使用声明式风格创建内容类型。使用 make_content_type 函数,我们也可以命令式地生成新内容类型。假设 HRPerson 已被定义为一个类,上面的例子可以写成

content_type = limone.make_content_type(Person, 'Person', bases=(HRPerson,))
fred = content_type(name='Joe', age=54)

make_content_type 函数的完整签名是

make_content_type(schema, name, module=None, bases=(object,))
  • schema 是用于生成类的 Colander 模式。

  • name 参数的值将被分配给生成的类的 __name__ 属性。如果添加到注册表中,该名称也将用作稍后查找内容类型的键。(参见 使用 Limone 注册表。)

  • 如果指定,module 将用于设置生成的类的 __module__ 属性。

  • bases 可以指定为元组,表示生成的类的超类。 注意 第一个基类必须有一个无参构造函数。

使用 Limone 注册表

可以使用 limone.Registry 实例来跟踪可用的内容类型。要使内容类型可通过导入钩子提供,需要一个 limone.Registry 实例。(参见 使用导入钩子。)

内容类型的基本注册和检索

使用 register_content_type 方法将内容类型添加到注册表中

registry = limone.Registry()
registry.regsister_content_type(Person)

使用 get_content_type 方法通过名称检索内容类型

content_type = registry.get_content_type('Person')
joe = content_type(name='Joe', age=54)

可以使用 get_content_types 方法检索所有已注册内容类型的元组

for content_type in registry.get_content_types():
    print content_type.__name__, content_type

打印

Person <class 'Person'>

扫描内容类型

注册表实例还可以通过扫描包来查找内容类型,并将其添加到注册表中。如果您在包中的某个位置使用了 content_typecontent_schema 装饰器,则这是可能的。使用 scan 方法可以搜索使用这些装饰器定义的内容类型,并将它们添加到注册表中

import limone
import myapp.models

registry = limone.Registry()
registry.scan(myapp.models)

使用导入钩子

在上面的两个声明式示例中,由于类型是在模块范围内生成的,因此可以使用标准 Python 导入机制进行导入。然而,对于命令式生成的内容类型,可能没有全局名称可以用来导入类型。如果应用程序从在运行时通过配置或用户输入生成模式的内容类型生成内容类型,则这绝对是这样。这可能会导致困难 - 例如,如果无法通过 Python 的导入机制找到类,则无法进行 pickling。让我们看看当我们尝试 pickle 然后 unpickle Person 内容类型的一个实例时会发生什么

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

我们会得到这个异常

pickle.PicklingError: Can't pickle <class 'Person'>: it's not found as __main__.Person

我们可以做的,尽管如此,是钩子 Python 的导入机制,以便 Python 可以在我们的 Limone 实例中查找内容类型。这要求内容类型必须使用 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()

现在,pickle 和 unpickle 操作现在是成功的,因为 pickle 能够使用 Python 的导入机制查找类型。

hook_import 的签名是

hook_import(module='__limone__')

《hook_import》方法将一个对象插入到《sys.meta_path》中,以便在注册表中查找内容类型。《module》参数用于设置生成内容类型的《__module__》属性。这将也被导入钩子用于识别它可以导入的类型。使用《module》的默认值,在导入钩子设置的情况下,我们可以以标准的Python方式导入命令生成的类型。

from __limone__ import Person
fred = Person(name='Fred', age=54)

如果预期一个应用程序将在单个进程内部使用多个《limone.Registry》实例,则不应使用《module》的默认值。在这种情况下,每个实例应使用不同的《module》值,以便每个实例只尝试找到它自己的内容类型。

《unhook_import》方法清理之前创建的导入钩子,将《sys.meta_path》返回到其原始状态。

使用Colander Appstruct

内容类型的实例可以被转换为它们的Colander appstruct表示。

jack = Person(
    name='Jack',
    age=52,
    friends=[
        (1, 'Fred'),
        (2, 'Barney')
    ],
    phones=[
        {'location': 'home',
         'number': '555-1212'},
    ])

from pprint import pprint
pprint(jack.appstuct())

生成此输出

{'age': 52,
 'friends': [(1, u'Fred'), (2, u'Barney')],
 'name': u'Jack',
 'phones': [{'location': u'home', 'number': u'555-1212'}]}

可以从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'}]})

可以使用部分appstruct来更新实例。

jack.update_from_appstruct({'age': 53})

使用Colander的序列化/反序列化

可以使用Colander的序列化来序列化内容类型的实例。

jack = Person(
    name='Jack',
    age=52,
    friends=[
        (1, 'Fred'),
        (2, 'Barney')
    ],
    phones=[
        {'location': 'home',
         'number': '555-1212'},
    ])

from pprint import pprint
pprint(jack.serialize())

生成此输出

{'age': '52',
 'friends': [('1', u'Fred'), ('2', u'Barney')],
 'name': u'Jack',
 'phones': [{'location': u'home', 'number': u'555-1212'}]}

注意,Colander的序列化是一种中间格式。所有标量值都序列化为字符串,但序列、元组和映射分别作为列表、元组和字典返回。这种中间形式可以很容易地输入到其他序列化器中,如json,以生成序列化的字节序列。

可以通过Colander的反序列化实例化实例。

jack = Person.deserialize(
    {'age': '52',
     'friends': [('1', u'Fred'), ('2', u'Barney')],
     'name': u'Jack',
     'phones': [{'location': u'home', 'number': u'555-1212'}]})

反序列化也可以用于更新现有实例。

jack.deserialize_update({'age': '53'})

Limone的变更日志

0.1a5(2011-09-01)

  • 添加了将实例转换为Colander appstruct的公共API。

0.1a4(2011-08-11)

  • 确保将__setattr__用于设置《_MappingNode》属性。这是为了方便依赖__setattr__被调用的扩展,如《limone_zodb》。

  • 使用可覆盖的属性工厂来生成内容类型属性的属性描述符。

0.1a3(2011-08-09)

  • 重构以确保每个内容对象包含的每个节点都有一个对内容对象的引用。

0.1a2(2011-07-16)

  • 重构了一些内部结构,以便扩展可能希望使用Limone作为基础的包。以《limone_zodb》为例。

0.1a1(2011-07-07)

  • 第一个alpha版本。

项目详情


下载文件

下载适合您平台的文件。如果您不确定选择哪个,请了解更多关于安装包的信息。

源分发

limone-0.1a5.tar.gz (16.8 kB 查看散列)

上传时间:

支持者