基于Google的Protocol Buffers的对象数据库基础
项目描述
概览
Google的Protocol Buffers项目提供了一种有趣的数据序列化方法。Protocol Buffer消息的生成和解析都很高效,足够灵活以应对模式变化,表达力强,且可以在多种编程语言中使用。
如果我们把这些特性与对象数据库结合起来会怎么样?对象数据库通常提供了一个优秀的软件基础。遗憾的是,对象数据库通常只绑定到一种编程语言。这个包提供使用Protocol Buffers进行对象序列化,理论上可以构建一个多语言访问的对象数据库。
使用此包还可以提供模式文档。Protocol Buffers包要求程序员以简洁的形式编写其数据模式,这同时也作为模式文档。虽然通常可以通过查看应用程序代码来猜测模式,但以Protocol Buffer格式编写要直接得多,信息也更丰富。
此包旨在与ZODB等对象数据库结合使用,但此包不要求使用ZODB。
测试
下面的测试描述了如何使用此包。这些测试依赖于名为testclasses_pb2.py的模块,该模块由testclasses.proto生成,使用以下命令生成,一旦安装了Google Protocol Buffers包即可
protoc --python_out . *.proto
创建一个Contact类。注意它的元类。元类向类添加属性,以便您可以使用简单的属性访问来读取和写入协议缓冲区消息字段。'create_time'属性就是这样一个字段。
>>> import time >>> from keas.pbstate.meta import ProtobufState >>> from keas.pbstate.testclasses_pb2 import ContactPB >>> class Contact(object): ... __metaclass__ = ProtobufState ... protobuf_type = ContactPB ... def __init__(self): ... self.create_time = int(time.time()) ...
创建此类的实例并验证实例具有预期的属性。这些属性都在.proto文件中进行了描述。
>>> c = Contact() >>> c.create_time > 0 True >>> c.name u'' >>> c.address.line1 u'' >>> c.address.country u'United States'
该实例还提供了对protobuf消息、其类型(从类继承)以及消息的引用的访问。稍后将讨论引用。
>>> c.protobuf <keas.pbstate.testclasses_pb2.ContactPB object at ...> >>> c.protobuf_type <class 'keas.pbstate.testclasses_pb2.ContactPB'> >>> c.protobuf_refs <keas.pbstate.meta.ProtobufReferences object at ...>
设置和检索一些属性。
>>> c.name = u'John Doe' >>> c.address.line1 = u'100 First Avenue' >>> c.address.country = u'Canada' >>> c.name u'John Doe' >>> c.address.country u'Canada'
尝试将一个属性设置为protobuf消息无法序列化的值。
>>> c.name = 100 Traceback (most recent call last): ... TypeError: 100 has type <type 'int'>, but expected one of: (<type 'str'>, <type 'unicode'>) >>> c.name u'John Doe'
尝试设置一个未在.proto文件中声明的属性。
>>> c.phone = u'555-1234' Traceback (most recent call last): ... AttributeError: 'Contact' object has no attribute 'phone'
混入
一个类可以混入访问子消息的属性。这在子类化时很有用(尽管通常应该避免子类化)。
这是一个在单个类中混入ContactPB属性和AddressPB属性的类。
>>> class MixedContact(object): ... __metaclass__ = ProtobufState ... protobuf_type = ContactPB ... protobuf_mixins = ('address',)>>> mc = MixedContact() >>> mc.line1 = u'180 Market St.' >>> mc.line1 u'180 Market St.' >>> mc.address.line1 u'180 Market St.'
序列化
ProtobufState还提供了__getstate__和__setstate__方法,Python用于序列化目的。
尝试不提供所有必需字段来序列化对象。
>>> c.__getstate__() Traceback (most recent call last): ... EncodeError: Required field AddressPB.city is not set.
填写所有必需字段,然后序列化。
>>> c.address.city = u'Toronto' >>> c.create_time = 1001 >>> c.__getstate__() ('\x08\xe9\x07\x12\x08John Doe\x1a#\n\x10100 First Avenue\x1a\x07Toronto2\x06Canada', {})
创建一个联系人和从c复制其状态。
>>> c_dup = Contact.__new__(Contact) >>> c_dup.__setstate__(c.__getstate__()) >>> c_dup.name u'John Doe' >>> c_dup.address.country u'Canada'
创建另一个联系人,但这次不提供地址信息。
>>> c2 = Contact() >>> c2.create_time = 1002 >>> c2.name = u'Mary Anne' >>> c2.__getstate__() ('\x08\xea\x07\x12\tMary Anne', {})
对象引用
使用ProtobufState元类的类支持通过使用'protobuf_refs'属性对任意对象的引用。
我们的Contact类有一个'guardians'属性,其中包含一个引用列表。ProtobufState元类将任何具有_p_refid字段的消息或子消息视为引用。稍后我们将讨论引用。
向c2添加一个监护人,但暂时不说明是谁。
>>> guardian_ref = c2.guardians.add()
调用protobuf_refs.set()使guardian_ref引用c。
>>> c2.protobuf_refs.set(guardian_ref, c)
让我们回顾一下发生了什么。set方法生成一个引用ID,然后该ID被分配给guardian_ref._p_refid,并将refid和目标对象添加到protobuf_refs实例的内部状态中。任何具有_p_refid字段的消息都是引用。每个_p_refid字段应类型为uint32。
读取引用。
>>> c2.protobuf_refs.get(guardian_ref) is c True
验证引用被正确序列化。
>>> data, targets = c2.__getstate__() >>> targets[c2.guardians[0]._p_refid] is c True
删除引用。
>>> c2.protobuf_refs.delete(guardian_ref) >>> c2.protobuf_refs.get(guardian_ref, 'gone') 'gone'
验证引用不再包含在序列化状态中。
>>> data, targets = c2.__getstate__() >>> len(targets) 0
为ZODB设计的功能
本包为存储 ProtobufState 对象到 ZODB 提供了足够的功能,尽管没有 keas.pbpersist 包,存储的对象仍然会被 Python pickle 包裹,使得它们难以被其他语言访问。请参考 keas.pbpersist 包以获取在不使用 Python pickle 的情况下将 ProtobufState 对象存储到 ZODB 的简单方法。
在 ZODB 中,对象有一个 _p_changed 属性来指示它们何时是脏的。ProtobufState 元类会导致实例在存在 _p_changed 属性时修改它;每次消息发生变化时都会将其设置为 True。
以下是一个 PersistentContact 类,它有一个 _p_changed 属性。(我们还定义了一个 FakePersistent 基类,以避免依赖 ZODB。)
>>> class FakePersistent(object): ... __slots__ = ('_changed',) ... def _get_changed(self): ... return getattr(self, '_changed', False) ... def _set_changed(self, value): ... self._changed = value ... if not value: ... # reset the _cache_byte_size_dirty flags ... self.protobuf.ByteSize() ... _p_changed = property(_get_changed, _set_changed) ... >>> class PersistentContact(FakePersistent): ... __metaclass__ = ProtobufState ... protobuf_type = ContactPB ...>>> c3 = PersistentContact() >>> c3._p_changed False >>> c3.create_time = 1003 >>> c3.name = u'Snoopy' >>> c3._p_changed = False
读取属性不会设置 _p_changed。
>>> c3.name u'Snoopy' >>> c3._p_changed False
写入属性会设置 _p_changed。
>>> c3.name = u'Woodstock' >>> c3._p_changed True
向重复元素添加内容会设置 _p_changed。
>>> c3._p_changed = False >>> c3._p_changed False >>> c3.guardians.add() <keas.pbstate.testclasses_pb2.Ref object at ...> >>> c3._p_changed True >>> del c3.guardians[0]
c3 的副本最初应该有 _p_changed = False;设置一个属性应该将 _p_changed 设置为 true。
>>> c4 = PersistentContact.__new__(PersistentContact) >>> c4.__setstate__(c3.__getstate__()) >>> c4._p_changed False >>> c4.name = u'Linus' >>> c4._p_changed True
__getstate__ 返回的元组实际上是一个元组的子类。StateTuple 告诉 ZODB 序列化器它可以以不同于默认 pickle 格式的格式保存状态。
>>> type(c.__getstate__()) <class 'keas.pbstate.state.StateTuple'> >>> c.__getstate__().serial_format 'protobuf'
边缘情况
合成一个 refid 哈希冲突。这种情况很少发生,但只要没有单个对象持有超过约十亿(2**30)个对其他对象的引用,此包应该可以透明地处理这种情况。
首先创建一个引用
>>> guardian_ref = c2.guardians.add() >>> c2.protobuf_refs.set(guardian_ref, c)
秘密地更改该引用的目标
>>> c2.protobuf_refs._targets[guardian_ref._p_refid] = mc
向原始目标添加一个新的引用。第一个生成的 refid 将会冲突,但 protobuf_refs 应该会自动选择一个不同的 refid。
>>> guardian2_ref = c2.guardians.add() >>> c2.protobuf_refs.set(guardian2_ref, c) >>> guardian_ref._p_refid == guardian2_ref._p_refid False
异常条件
不允许删除消息属性。
>>> del c.name Traceback (most recent call last): ... AttributeError: can't delete attribute >>> del mc.line1 Traceback (most recent call last): ... AttributeError: can't delete attribute
检查混入名称。
>>> class MixedUpContact(object): ... __metaclass__ = ProtobufState ... protobuf_type = ContactPB ... protobuf_mixins = ('bogus',) Traceback (most recent call last): ... AttributeError: Field 'bogus' not defined for protobuf type <...>
通过使用错误的 protobuf_refs 设置引用来创建一个损坏的引用。为了防止这种情况,protobuf_refs 属性和 protobuf_refs.set() 的第一个参数必须来自同一个包含对象。
>>> c.guardians.add() <keas.pbstate.testclasses_pb2.Ref object at ...> >>> c2.protobuf_refs.set(c.guardians[0], c) >>> c.__getstate__() Traceback (most recent call last): ... KeyError: 'Object contains broken references: <Contact object at ...>' >>> del c.guardians[0]
不要省略 protobuf_type 属性。
>>> class FailedContact(object): ... __metaclass__ = ProtobufState Traceback (most recent call last): ... TypeError: Class ...FailedContact needs a protobuf_type attribute
项目详情
keas.pbstate-0.1.1.tar.gz 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | f25d48da36adcf88fc523de7af2e6e1c7acc41150b01fc4e80f61cc8a473e79f |
|
MD5 | a354b21cd1f51bab6f3ac73dc40d8b94 |
|
BLAKE2b-256 | c85878f314b4ca8e5ea906f976fcea039dec159b2b8e3823b4619eca87c2a853 |