跳转到主要内容

基于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 (11.0 kB 查看哈希值)

上传时间

支持者:

AWS AWS 云计算和安全赞助商 Datadog Datadog 监控 Fastly Fastly CDN Google Google 下载分析 Microsoft Microsoft PSF赞助商 Pingdom Pingdom 监控 Sentry Sentry 错误记录 StatusPage StatusPage 状态页面