跳转到主要内容

Google Protocol Buffer中的ZODB持久化

项目描述

概述

keas.pbpersist 包提供了一种使用 Google Protocol Buffer 编码来存储 ZODB 对象的方法,而不是使用 pickling。它需要 keas.pbstate 包。Protocol Buffer 格式比 pickles 更有限,但它也更简单、文档更好,并且不依赖于任何特定的编程语言。

只有使用 ProtobufState 元类(来自 keas.pbstate.meta 模块)的 Persistent 对象才有资格进行此类序列化,因此想要利用 keas.pbpersist 的应用程序需要在模型级别进行重构。然而,应用程序可以逐步重构,因为 protobuf 编码的对象和 pickled 对象可以在单个数据库中共存,并且可以互相引用。

该包预计将与大多数 ZODB 存储兼容,包括 FileStorage、ZEO、RelStorage 以及任何将对象记录视为不可见二进制流的存储。

注意,此包需要 ZODB 的补丁;请参阅以下网站上的“-polling-serial”补丁版本

http://packages.willowrise.org

如何使用此包

以下文档采用 doctest 格式,这意味着它既是文档也是测试。您可以在 Python 解释器会话中尝试代码。

将 protobuf 序列化程序注册到 ZODB。这只需要在每次应用程序运行时发生一次,但多次发生也无妨。

>>> from keas.pbpersist.pbformat import register
>>> register()
>>> register()

PContact 是一个简单的 Persistent 类,它将状态存储在 protobuf 消息中。请参阅源代码;它相当简单。为此测试,创建一个实例并填写所需的字段。

>>> from keas.pbpersist.tests import PContact
>>> bob = PContact()
>>> bob.name = u'Bob'

设置一个内存中的对象数据库并将 PContact 放入其中。当然,如果您已经有了数据库,您可以将对象附加到您自己的数据库中的某个对象上。

>>> from ZODB.DemoStorage import DemoStorage
>>> from ZODB.DB import DB
>>> import transaction
>>> storage = DemoStorage()
>>> db = DB(storage, database_name='main')
>>> conn1 = db.open()
>>> conn1.root()['bob'] = bob
>>> transaction.commit()

让我们看看数据库中存储了什么。看吧,没有 pickles!{protobuf} 前缀告诉 ZODB 使用 keas.pbpersist 包中的代码来反序列化此对象。

>>> data, serial = storage.load(bob._p_oid, '')
>>> data
'{protobuf}\n \n\x14keas.pbpersist.tests\x12\x08PContact\x12\x07\x08\x01\x12\x03Bob'

以下是使用仅依赖语言无关操作的演示。ObjectRecord 消息包含对象的类、状态和引用;状态字段包含特定于类的 protobuf 消息。

>>> from keas.pbpersist.persistent_pb2 import ObjectRecord
>>> rec = ObjectRecord()
>>> rec.MergeFromString(data[10:])  # skip the {protobuf} prefix
43
>>> rec.class_meta.module_name
u'keas.pbpersist.tests'
>>> rec.class_meta.class_name
u'PContact'
>>> len(rec.references)
0
>>> pbuf = PContact.protobuf_type()
>>> pbuf.MergeFromString(rec.state)
7
>>> pbuf.name
u'Bob'

您可以在 C++ 或 Java 中轻松地模拟此代码,这是 Protocol Buffers 在当前支持的其他两种语言。但是,这是一个嘈杂、复杂的操作。ZODB 使得读取对象变得简单得多。

>>> conn2 = db.open()
>>> bob2 = conn2.root()['bob']
>>> bob2.name
u'Bob'
>>> conn2.close()

参考

创建另一个 PContact,并将该联系人指定为 Bob 的监护人。下面的 add() 方法是 Google 的 protobuf API 的一部分。protobuf_refs 属性由 keas.pbstate 中的 ProtobufState 元类提供。

>>> alice = PContact()
>>> alice.name = u'Alice'
>>> ref = bob.guardians.add()
>>> bob.protobuf_refs.set(ref, alice)
>>> transaction.commit()

[编辑者:我真的不喜欢我们现在管理引用的方式。我更愿意看到“bob.guardians.append(alice)”,但那目前还不可能。]

现在让我们看看如果我们通过 Bob 在不同的 ZODB 连接中获取 Alice 会发生什么。

>>> conn2 = db.open()
>>> bob2 = conn2.root()['bob']
>>> alice2 = bob2.protobuf_refs.get(bob2.guardians[0])
>>> alice2.name
u'Alice'

请注意,alice2 是 alice 的副本,而不是同一个对象,因为这两个来自不同的 ZODB 连接。

>>> alice is alice2
False

我们还有选择通过 OID 来引用对象的选项,但这很少是必要的。

>>> conn3 = db.open()
>>> bob3 = conn3[bob._p_oid]

ZODB 懒惰地加载所有对象,包括使用此包序列化的对象。当持久对象的 _p_changed 属性为 None 时,对象处于“幽灵”状态;它尚未包含数据。访问属性可以透明地加载对象状态。

>>> bob3._p_changed is None
True
>>> bob3.name
u'Bob'
>>> bob3._p_changed
False

清理多余的连接。

>>> conn3.close()
>>> conn2.close()

打包

存储需要能够跟踪引用链以进行打包,因此让我们确保 referencesf() 函数仍然按预期工作。

>>> from ZODB.serialize import referencesf
>>> data, serial = storage.load(conn1.root()._p_oid, '')
>>> referencesf(data) == [bob._p_oid]
True
>>> data, serial = storage.load(bob._p_oid, '')
>>> referencesf(data) == [alice._p_oid]
True
>>> data, serial = storage.load(alice._p_oid, '')
>>> referencesf(data)
[]

引用功能与限制

使用此包序列化的对象可以引用任何其他持久对象。(注意,我们在将“bob”添加到数据库的根对象时已经证明,冻结对象可以引用 Protobuf 序列化对象。)

>>> from persistent.mapping import PersistentMapping
>>> len(bob.guardians)
1
>>> ref = bob.guardians.add()
>>> bob.protobuf_refs.set(ref, PersistentMapping())
>>> transaction.commit()
>>> len(bob.guardians)
2

然而,使用此包序列化的对象不能持有非持久对象的引用。(这是 keas.pbpersist 的限制,但不是 keas.pbstate 的限制,因为 keas.pbstate 不依赖于 ZODB。)我们直到事务提交的第一阶段才会检测到不良引用。

>>> len(bob.guardians)
2
>>> ref = bob.guardians.add()
>>> bob.protobuf_refs.set(ref, {})
>>> len(bob.guardians)
3
>>> transaction.commit()
Traceback (most recent call last):
...
POSError: Protobuf reference target is not a Persistent object: {}

回滚事务可以撤销损害。

>>> transaction.abort()
>>> len(bob.guardians)
2

尽管如此,我们可以创建弱引用!

>>> from persistent.wref import WeakRef
>>> ref = bob.guardians.add()
>>> bob.protobuf_refs.set(ref, WeakRef(PersistentMapping()))
>>> transaction.commit()
>>> len(bob.guardians)
3

我们可以创建跨数据库的引用。

>>> storage_beta = DemoStorage()
>>> db_beta = DB(storage_beta, database_name='beta', databases=db.databases)
>>> root_beta = conn1.get_connection('beta').root()
>>> root_beta['obj'] = PersistentMapping()
>>> transaction.commit()
>>> ref = bob.guardians.add()
>>> bob.protobuf_refs.set(ref, root_beta['obj'])
>>> transaction.commit()

我们可以创建对具有 __getnewargs__ 方法的类的实例的引用。ZODB 不在这样类的实例的引用中存储类元数据。

>>> from keas.pbpersist.tests import Adder
>>> adder = Adder(7)
>>> conn1.root()['adder'] = adder
>>> ref = bob.guardians.add()
>>> bob.protobuf_refs.set(ref, adder)
>>> transaction.commit()

即使该实例位于另一个数据库中,我们也可以创建对具有 __getnewargs__ 方法的类的实例的引用。

>>> adder = Adder(6)
>>> root_beta['adder'] = adder
>>> transaction.commit()
>>> ref = bob.guardians.add()
>>> bob.protobuf_refs.set(ref, adder)
>>> transaction.commit()

为了确保所有这些引用都正常工作,请使用另一个连接访问它们。

>>> conn2 = db.open()
>>> bob2 = conn2.root()['bob']
>>> len(bob2.guardians)
6
>>> bob2.protobuf_refs.get(bob2.guardians[0])
<keas.pbpersist.tests.PContact object at ...>
>>> bob2.protobuf_refs.get(bob2.guardians[1])
{}
>>> bob2.protobuf_refs.get(bob2.guardians[1])._p_jar is conn2
True
>>> bob2.protobuf_refs.get(bob2.guardians[2])
<persistent.wref.WeakRef object at ...>
>>> bob2.protobuf_refs.get(bob2.guardians[2])()
{}
>>> bob2.protobuf_refs.get(bob2.guardians[3])
{}
>>> bob2.protobuf_refs.get(bob2.guardians[3])._p_jar is conn2
False
>>> bob2.protobuf_refs.get(bob2.guardians[4])
<keas.pbpersist.tests.Adder object at ...>
>>> bob2.protobuf_refs.get(bob2.guardians[4]).add(3)
10
>>> bob2.protobuf_refs.get(bob2.guardians[5])
<keas.pbpersist.tests.Adder object at ...>
>>> bob2.protobuf_refs.get(bob2.guardians[5]).add(3)
9
>>> conn2.close()

边缘情况

我们目前无法存储实现 __getnewargs__() 的 ProtobufState 持久类实例。

>>> from keas.pbpersist.tests import PContactWithGetNewArgs
>>> fail_contact = PContactWithGetNewArgs()
>>> fail_contact.name = u'Loser'
>>> conn1.root()['fail_contact'] = fail_contact
>>> transaction.commit()
Traceback (most recent call last):
...
POSError: ProtobufSerializer can not serialize classes using __getnewargs__ or __getinitargs__

清理

测试已完成。关闭对象数据库。

>>> transaction.abort()
>>> conn1.close()
>>> db.close()

项目详情


下载文件

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

源分发

keas.pbpersist-0.1.tar.gz (10.6 kB 查看哈希值

上传时间

由支持

AWSAWS云计算和安全赞助商DatadogDatadog监控FastlyFastlyCDNGoogleGoogle下载分析MicrosoftMicrosoftPSF赞助商PingdomPingdom监控SentrySentry错误记录StatusPageStatusPage状态页面