PostgreSQL/JSONB持久性后端

pjpersist的Python项目详细描述


PostgreSQL/JSONB数据持久性

本文概述了^{tt1}的一般功能$ 包裹。pjpersist是的postgresql/jsonb存储实现 持久的python对象。它不是zodb的存储空间。

pjpersist的目标是提供序列化的数据管理器 对象到事务边界处的jsonb blob。PJ数据管理器是 持久数据管理器,它处理事务边界上的事件(请参见 transaction.interfaces.IDataManager)以及来自 持久性框架(请参见persistent.interfaces.IPersistentDataManager)。

数据管理器的实例应该与 事务,这意味着假设您创建了一个新的数据管理器 创建新事务时:

>>> import transaction

注意:conn对象是一个psycopg.Connection实例。在这种情况下 我们的测试使用pjpersist_test数据库。

现在我们定义一个简单的持久对象:

>>> import datetime
>>> import persistent
>>> class Person(persistent.Persistent):
...
...     def __init__(self, name, phone=None, address=None, friends=None,
...                  visited=(), birthday=None):
...         self.name = name
...         self.address = address
...         self.friends = friends or {}
...         self.visited = visited
...         self.phone = phone
...         self.birthday = birthday
...         self.today = datetime.datetime(2014, 5, 14, 12, 30)
...
...     def __str__(self):
...         return self.name
...
...     def __repr__(self):
...         return '<%s %s>' %(self.__class__.__name__, self)

稍后我们将填写其他对象。但现在,让我们创建一个新的 将其放入PJ:

>>> stephan = Person(u'Stephan')
>>> stephan
<Person Stephan>

datamanager提供了一个root属性,对象树在其中生根 可以储存。它的特殊之处在于它可以立即写入数据 到数据库:

>>> dm.root['stephan'] = stephan
>>> dm.root['stephan']
<Person Stephan>

自定义持久性表

默认情况下,持久对象存储在具有转义的 类的python路径:

>>> from pjpersist import serialize
>>> person_cn = serialize.get_dotted_name(Person, True)
>>> person_cn
'u__main___dot_Person'
>>> transaction.commit()
>>> dumpTable(person_cn)
[{'data': {u'_py_persistent_type': u'__main__.Person',
           u'address': None,
           u'birthday': None,
           u'friends': {},
           u'name': u'Stephan',
           u'phone': None,
           u'today': {u'_py_type': u'datetime.datetime',
                      u'value': u'2014-05-14T12:30:00.000000'},
           u'visited': []},
  'id': u'0001020304050607080a0b0c0'}]

如您所见,为此人存储的文档看起来非常像 自然json文档。但是哦不,我忘了指定 斯蒂芬。让我们这样做:

>>> dm.root['stephan'].name = u'Stephan Richter'
>>> dm.root['stephan']._p_changed
True

这一次,数据不会自动保存:

>>> fetchone(person_cn)['data']['name']
u'Stephan'

所以我们必须先提交事务:

>>> dm.root['stephan']._p_changed
True
>>> transaction.commit()
>>> dm.root['stephan']._p_changed
>>> fetchone(person_cn)['data']['name']
u'Stephan Richter'

现在我们为斯蒂芬添加一个地址。地址也是持久对象:

>>> class Address(persistent.Persistent):
...     _p_pj_table = 'address'
...
...     def __init__(self, city, zip):
...         self.city = city
...         self.zip = zip
...
...     def __str__(self):
...         return '%s (%s)' %(self.city, self.zip)
...
...     def __repr__(self):
...         return '<%s %s>' %(self.__class__.__name__, self)

pjpersist支持名为_p_pj_table的特殊属性, 它允许您指定要使用的自定义表。

>>> stephan = dm.root['stephan']
>>> stephan.address = Address('Maynard', '01754')
>>> stephan.address
<Address Maynard (01754)>

请注意,地址不会立即保存在数据库中:

>>> dumpTable('address', isolate=True)
relation "address" does not exist
...

但一旦我们提交了事务,一切都可用:

>>> transaction.commit()
>>> dumpTable('address')
[{'data': {u'_py_persistent_type': u'__main__.Address',
           u'city': u'Maynard',
           u'zip': u'01754'},
  'id': u'0001020304050607080a0b0c0'}]
>>> dumpTable(person_cn)
[{'data': {u'_py_persistent_type': u'__main__.Person',
           u'address': {u'_py_type': u'DBREF',
                        u'database': u'pjpersist_test',
                        u'id': u'0001020304050607080a0b0c0',
                        u'table': u'address'},
           u'birthday': None,
           u'friends': {},
           u'name': u'Stephan Richter',
           u'phone': None,
           u'today': {u'_py_type': u'datetime.datetime',
                      u'value': u'2014-05-14T12:30:00.000000'},
           u'visited': []},
  'id': u'0001020304050607080a0b0c0'}]
>>> dm.root['stephan'].address
<Address Maynard (01754)>

非持久性对象

如您所见,即使引用看起来很好,而且所有组件都很容易 可见的。但如果任意的,非持久的,可挑剔的, 物体?好吧,让我们为此创建一个phone number对象:

>>> class Phone(object):
...
...     def __init__(self, country, area, number):
...         self.country = country
...         self.area = area
...         self.number = number
...
...     def __str__(self):
...         return '%s-%s-%s' %(self.country, self.area, self.number)
...
...     def __repr__(self):
...         return '<%s %s>' %(self.__class__.__name__, self)
>>> dm.root['stephan'].phone = Phone('+1', '978', '394-5124')
>>> dm.root['stephan'].phone
<Phone +1-978-394-5124>

现在我们提交事务并再次查看jsonb文档:

>>> transaction.commit()
>>> dm.root['stephan'].phone
<Phone +1-978-394-5124>
>>> dumpTable(person_cn)
[{'data': {u'_py_persistent_type': u'__main__.Person',
           u'address': {u'_py_type': u'DBREF',
                        u'database': u'pjpersist_test',
                        u'id': u'0001020304050607080a0b0c0',
                        u'table': u'address'},
           u'birthday': None,
           u'friends': {},
           u'name': u'Stephan Richter',
           u'phone': {u'_py_type': u'__main__.Phone',
                      u'area': u'978',
                      u'country': u'+1',
                      u'number': u'394-5124'},
           u'today': {u'_py_type': u'datetime.datetime',
                      u'value': u'2014-05-14T12:30:00.000000'},
           u'visited': []},
  'id': u'0001020304050607080a0b0c0'}]

如您所见,对于任意的非持久性对象,我们需要在 子文档,但非常少。如果__reduce__方法返回 一个更复杂的结构,写更多的元数据。下一步我们会看到的 存储日期和其他任意数据时:

>>> dm.root['stephan'].friends = {'roy': Person(u'Roy Mathew')}
>>> dm.root['stephan'].visited = (u'Germany', u'USA')
>>> dm.root['stephan'].birthday = datetime.date(1980, 1, 25)
>>> transaction.commit()
>>> dm.root['stephan'].friends
{u'roy': <Person Roy Mathew>}
>>> dm.root['stephan'].visited
[u'Germany', u'USA']
>>> dm.root['stephan'].birthday
datetime.date(1980, 1, 25)

如您所见,字典键总是转换为Unicode,元组是 始终作为列表维护,因为json没有两种序列类型。

>>> import pprint
>>> pprint.pprint(dict(
...     fetchone(person_cn, """data @> '{"name": "Stephan Richter"}'""")))
{'data': {u'_py_persistent_type': u'__main__.Person',
          u'address': {u'_py_type': u'DBREF',
                       u'database': u'pjpersist_test',
                       u'id': u'0001020304050607080a0b0c0',
                       u'table': u'address'},
          u'birthday': {u'_py_type': u'datetime.date',
                        u'value': u'1980-01-25'},
          u'friends': {u'roy': {u'_py_type': u'DBREF',
                                u'database': u'pjpersist_test',
                                u'id': u'0001020304050607080a0b0c0',
                                u'table': u'u__main___dot_Person'}},
          u'name': u'Stephan Richter',
          u'phone': {u'_py_type': u'__main__.Phone',
                     u'area': u'978',
                     u'country': u'+1',
                     u'number': u'394-5124'},
          u'today': {u'_py_type': u'datetime.datetime',
                     u'value': u'2014-05-14T12:30:00.000000'},
          u'visited': [u'Germany', u'USA']},
 'id': u'0001020304050607080a0b0c0'}

自定义序列化程序

(要演示的补丁)

>>> dm.root['stephan'].birthday = datetime.date(1981, 1, 25)
>>> transaction.commit()
>>> pprint.pprint(
...     fetchone(person_cn,
...         """data @> '{"name": "Stephan Richter"}'""")['data']['birthday'])
{u'_py_type': u'datetime.date', u'value': u'1981-01-25'}

如您所见,生日的序列化是一个iso字符串。我们可以, 但是,请提供使用序号存储数据的自定义序列化程序。

>>> class DateSerializer(serialize.ObjectSerializer):
...
...     def can_read(self, state):
...         return isinstance(state, dict) and \
...                state.get('_py_type') == 'custom_date'
...
...     def read(self, state):
...         return datetime.date.fromordinal(state['ordinal'])
...
...     def can_write(self, obj):
...         return isinstance(obj, datetime.date)
...
...     def write(self, obj):
...         return {'_py_type': 'custom_date',
...                 'ordinal': obj.toordinal()}
>>> serialize.SERIALIZERS.append(DateSerializer())
>>> dm.root['stephan']._p_changed = True
>>> transaction.commit()

让我们再来看看:

>>> dm.root['stephan'].birthday
datetime.date(1981, 1, 25)
>>> pprint.pprint(dict(
...     fetchone(person_cn, """data @> '{"name": "Stephan Richter"}'""")))
{'data': {u'_py_persistent_type': u'__main__.Person',
          u'address': {u'_py_type': u'DBREF',
                       u'database': u'pjpersist_test',
                       u'id': u'0001020304050607080a0b0c0',
                       u'table': u'address'},
          u'birthday': {u'_py_type': u'custom_date', u'ordinal': 723205},
          u'friends': {u'roy': {u'_py_type': u'DBREF',
                                u'database': u'pjpersist_test',
                                u'id': u'0001020304050607080a0b0c0',
                                u'table': u'u__main___dot_Person'}},
          u'name': u'Stephan Richter',
          u'phone': {u'_py_type': u'__main__.Phone',
                     u'area': u'978',
                     u'country': u'+1',
                     u'number': u'394-5124'},
          u'today': {u'_py_type': u'custom_date', u'ordinal': 735367},
          u'visited': [u'Germany', u'USA']},
 'id': u'0001020304050607080a0b0c0'}

好多了!

>>> del serialize.SERIALIZERS[:]

作为子文档的持久对象

以便更好地控制哪些对象接收自己的表 如果没有,开发人员可以提供一个特殊的标志来标记 持久类,使其成为其父对象文档的一部分:

>>> class Car(persistent.Persistent):
...     _p_pj_sub_object = True
...
...     def __init__(self, year, make, model):
...         self.year = year
...         self.make = make
...         self.model = model
...
...     def __str__(self):
...         return '%s %s %s' %(self.year, self.make, self.model)
...
...     def __repr__(self):
...         return '<%s %s>' %(self.__class__.__name__, self)

_p_pj_sub_object用于将对象的类型标记为只是部分 另一份文件:

>>> dm.root['stephan'].car = car = Car('2005', 'Ford', 'Explorer')
>>> transaction.commit()
>>> dm.root['stephan'].car
<Car 2005 Ford Explorer>
>>> pprint.pprint(dict(
...     fetchone(person_cn, """data @> '{"name": "Stephan Richter"}'""")))
{'data': {u'_py_persistent_type': u'__main__.Person',
          u'address': {u'_py_type': u'DBREF',
                       u'database': u'pjpersist_test',
                       u'id': u'0001020304050607080a0b0c0',
                       u'table': u'address'},
          u'birthday': {u'_py_type': u'datetime.date',
                        u'value': u'1981-01-25'},
          u'car': {u'_py_persistent_type': u'__main__.Car',
                   u'make': u'Ford',
                   u'model': u'Explorer',
                   u'year': u'2005'},
          u'friends': {u'roy': {u'_py_type': u'DBREF',
                                u'database': u'pjpersist_test',
                                u'id': u'0001020304050607080a0b0c0',
                                u'table': u'u__main___dot_Person'}},
          u'name': u'Stephan Richter',
          u'phone': {u'_py_type': u'__main__.Phone',
                     u'area': u'978',
                     u'country': u'+1',
                     u'number': u'394-5124'},
          u'today': {u'_py_type': u'datetime.date', u'value': u'2014-05-14'},
          u'visited': [u'Germany', u'USA']},
 'id': u'0001020304050607080a0b0c0'}

我们希望对象持久化的原因是这样它们可以接受更改 自动:

>>> dm.root['stephan'].car.year = '2004'
>>> transaction.commit()
>>> dm.root['stephan'].car
<Car 2004 Ford Explorer>

表格共享

由于PostgreSQL/JSONB非常灵活,所以有时存储是有意义的。 同一表中有多种(类似)对象。在那些情况下你 指示对象键入以将其python路径存储为文档的一部分。

警告:请注意,尽管此方法效率较低,因为 必须加载文档才能创建导致更多数据库的重影 访问。

>>> class ExtendedAddress(Address):
...
...     def __init__(self, city, zip, country):
...         super(ExtendedAddress, self).__init__(city, zip)
...         self.country = country
...
...     def __str__(self):
...         return '%s (%s) in %s' %(self.city, self.zip, self.country)

为了完成表共享,只需创建另一个类 与另一个字符串具有相同的_p_pj_table字符串(子分类将 确保)。

现在让我们给斯蒂芬两个扩展地址。

>>> dm.root['stephan'].address2 = ExtendedAddress(
...     'Tettau', '01945', 'Germany')
>>> dm.root['stephan'].address2
<ExtendedAddress Tettau (01945) in Germany>
>>> dm.root['stephan'].address3 = ExtendedAddress(
...     'Arnsdorf', '01945', 'Germany')
>>> dm.root['stephan'].address3
<ExtendedAddress Arnsdorf (01945) in Germany>
>>> transaction.commit()

加载地址时,地址类型应正确:

>>> dm.root['stephan'].address
<Address Maynard (01754)>
>>> dm.root['stephan'].address2
<ExtendedAddress Tettau (01945) in Germany>
>>> dm.root['stephan'].address3
<ExtendedAddress Arnsdorf (01945) in Germany>

持久序列化挂钩

当持久性组件实现IPersistentSerializationHooks时,它 对象可以执行一些自定义存储功能。

>>> from pjpersist.persistent import PersistentSerializationHooks
>>> class Usernames(PersistentSerializationHooks):
...     _p_pj_table = 'usernames'
...     format = 'email'
...
...     def _pj_after_store_hook(self, conn):
...         print('After Store Hook')
...
...     def _pj_after_load_hook(self, conn):
...         print('After Load Hook')

当我们存储对象时,钩子被称为: (实际上是两次,因为这是一个新对象)

>>> dm.root['stephan'].usernames = Usernames()
>>> transaction.commit()
After Store Hook
After Store Hook

加载时也会发生同样的情况:

>>> dm.root['stephan'].usernames.format
After Load Hook
'email'

如果对象不是新对象,存储挂钩只会触发一次:

>>> dm.root['stephan'].usernames.format = 'snailmail'
>>> transaction.commit()
After Store Hook

列序列化

pjpersist还允许对象指定值,通常是属性或 属性,作为列存储在对象的存储表中。

注意,我们只支持单向转换,因为对象状态 将始终从datajsonb字段反序列化。

>>> import zope.schema
>>> class IPerson(zope.interface.Interface):
...
...     name = zope.schema.TextLine(title=u'Name')
...     address = zope.schema.TextLine(title=u'Address')
...     visited = zope.schema.Datetime(title=u'Visited')
...     phone = zope.schema.TextLine(title=u'Phone')

最初,我们只在列中存储名称:

>>> from pjpersist.persistent import SimpleColumnSerialization, select_fields
>>> @zope.interface.implementer(IPerson)
... class ColumnPerson(SimpleColumnSerialization, Person):
...     _p_pj_table = 'cperson'
...     _pj_column_fields = select_fields(IPerson, 'name')

因此,一旦我创建了这样一个person并提交了事务,person表就是 扩展以存储属性,并将此人添加到表中:

>>> dm.root['anton'] = anton = ColumnPerson(u'Anton')
>>> transaction.commit()
>>> dumpTable('cperson')
[{'data': {u'_py_persistent_type': u'__main__.ColumnPerson',
           u'address': None,
           u'birthday': None,
           u'friends': {},
           u'name': u'Anton',
           u'phone': None,
           u'today': {u'_py_type': u'datetime.datetime',
                      u'value': u'2014-05-14T12:30:00.000000'},
           u'visited': []},
  'id': u'0001020304050607080a0b0c0',
  'name': u'Anton'}]

棘手案例

基本可变类型的更改

棘手,棘手。我们如何使框架检测可变的变化 对象,如列表和字典?答:我们会记录下 它们所属的持久对象并提供持久实现。

>>> type(dm.root['stephan'].friends)
 <class 'pjpersist.serialize.PersistentDict'>
>>> dm.root['stephan'].friends[u'roger'] = Person(u'Roger')
>>> transaction.commit()
>>> sorted(dm.root['stephan'].friends.keys())
[u'roger', u'roy']

列表也是如此:

>>> type(dm.root['stephan'].visited)
 <class 'pjpersist.serialize.PersistentList'>
>>> dm.root['stephan'].visited.append('France')
>>> transaction.commit()
>>> dm.root['stephan'].visited
[u'Germany', u'USA', u'France']

循环非持久引用

存储在子文档中的任何可变对象都不能有多个 对象树中的引用,因为没有全局引用。这些 检测并报告循环引用:

>>> class Top(persistent.Persistent):
...     foo = None
>>> class Foo(object):
...     bar = None
>>> class Bar(object):
...     foo = None
>>> top = Top()
>>> foo = Foo()
>>> bar = Bar()
>>> top.foo = foo
>>> foo.bar = bar
>>> bar.foo = foo
>>> dm.root['top'] = top
Traceback (most recent call last):
...
CircularReferenceError: <...>

循环持久引用

一般来说,持久对象之间的循环引用不是问题, 因为我们总是只存储到对象的链接。然而,有一种情况是 循环依赖成为一个问题。

如果使用循环引用设置对象树,然后将该树添加到 存储必须在序列化期间立即插入对象,以便 可以创建引用。但是,需要注意的是,只需创建 最小的引用对象,以便系统不尝试递归地 减少状态。

>>> class PFoo(persistent.Persistent):
...     bar = None
>>> class PBar(persistent.Persistent):
...     foo = None
>>> top = Top()
>>> foo = PFoo()
>>> bar = PBar()
>>> top.foo = foo
>>> foo.bar = bar
>>> bar.foo = foo
>>> dm.root['ptop'] = top

容器和桌子

既然我们已经谈了很多关于存储一个对象的血腥细节, 那么反映整个表的映射呢,例如 一桌人。

可以采取许多方法。以下实施 将文档中的属性定义为映射键,并将 表格:

>>> from pjpersist import mapping
>>> class People(mapping.PJTableMapping):
...     __pj_table__ = person_cn
...     __pj_mapping_key__ = 'short_name'

映射将数据管理器作为参数。一个人可以很容易地创造一个 自动分配数据管理器的子类。让我们看看:

>>> People(dm).keys()
[]

没有人在列表中的原因是没有文档具有密钥 或者密钥为空。让我们改变一下:

>>> People(dm)['stephan'] = dm.root['stephan']
>>> transaction.commit()
>>> People(dm).keys()
[u'stephan']
>>> People(dm)['stephan']
<Person Stephan Richter>

还要注意,对任何其他人设置“short name”属性将添加 它到映射:

>>> dm.root['stephan'].friends['roy'].short_name = 'roy'
>>> transaction.commit()
>>> sorted(People(dm).keys())
[u'roy', u'stephan']

更改

1.7.1(2019-06-19)

  • 修复了序列化程序使用键dict_data获取映射时的边情况。 读取此类对象失败。
  • 修复了序列化程序的边缘情况,当对象的状态处于活动状态时 在持久化对象中变为“空”。基本上是我只是 {“持久”类型:“someClass”} 未调用某个类的setstate,因此该对象可能会丢失 属性。就像userdict的子类一样,它将丢失数据属性。
  • 删除了对dict键中0x00字符的检查。原来PostgreSQL只是 无法存储0x00。

1.7.0(2019-05-29)

  • 支持序列化期间的次秒日期时间和时间解析。
  • 添加使用缓存参数到pjContainer。\u load_one()以支持忽略 缓存。(如果一个容器跟踪多个 项目的版本,然后尝试加载所有旧版本。)

1.6.0(2019-05-29)

  • 使id数据列名可通过\u pj\u id\u列配置 pjContainer中的pj_data_列属性。
  • 使用pjcontainer时自动为对象分配名称,而不仅仅是 idnamespjcontainer

1.5.0(2018-10-10)

  • 支持Python3.7。将Python3.5测试从Tox中删除。

1.4.1(2018-09-13)

  • 无需登录tpc_完成。

1.4.0(2018-09-13)

  • 实现了在dm没有写入时跳过tpc_prepare。 我们发现aws aurora目前在tpc_prepare上速度很慢。 当数据管理器没有写操作时,不需要调用tpc_prepare。 参阅 CaluaTPCApReaRayOngNoxRead Effice < /CIT>,默认为TRUE TRUE 兼容性。
  • 添加了记录事务是否有写操作的功能。 请参见记录读写事务,默认为false

1.3.2(2018-04-19)

  • 更精确地刷新数据管理器以避免不必要的数据库 写。

1.3.1(2018-04-11)

  • 通过消除查询,启用并发添加到idnamepjcontainer 这导致了事务冲突。

1.3.0(2018-03-22)

  • python 3兼容性修复程序
  • 更高效的pjContainer.values()实现

1.2.2(2017-12-12)

  • 需要保护所有数据库调用不受数据库断开连接的影响

1.2.1(2017-12-12)

  • psycopg2.operational errorpsycopg2.interfaceerror将被捕获 在SQL命令执行时,并将其重新设置为数据库已断开连接

1.2.0(2017-10-24)

  • 添加了一个新的助手函数来将子对象链接到主文档对象。这是 当实现自定义的\u getstate\uu()\u setstate\uu()时需要。一个 提供了详细的例子。
  • 为允许 查询期间仅刷新某些对象。刷新提示是一个列表表名 需要刷新以使查询返回正确结果的。
  • zope特定的容器使用flush_提示只刷新对象 在容器上运行查询时进行管理。
  • 刷新对象时,现在只刷新每个主文档对象 一次。在修复之前,任何子对象都会导致其doc对象被转储 再一次。

注意:这些优化在现实世界中提供了15%的性能改进 应用。

1.1.2(2017-09-14)

  • 确保提交后更改的对象不再被更改。

1.1.1(2017-07-03)

  • 什么都没变。

1.0.0(2017-03-18)

  • 首次公开发行
  • 从Mongopersist到W的分叉项目使用postgresql和jsonb数据 键入。主要动力是利用PostgreSQL的 事务性支持。

欢迎加入QQ群-->: 979659372 Python中文网_新手群

推荐PyPI第三方库


热门话题
Java3D图表JavaGnuplotHybrid   java httpclient异常“org.apache.http.conn.ConnectionPoolTimeoutException:等待连接超时”   java如何使用Drive API连接到Google Drive文件夹   java如何正确组合@PathParams和@RequestBody?   使用JsonParser的java Jackson反序列化正在跳过@context的第一个键值对   java Twitter4j为getCreatedAt和getCountry获取空白或null   java doOnNext()不会被称为Spring Webflux   java Liberty批处理在使用与spring批处理相同的输入参数启动作业时未引发异常(JobInstanceAlreadyExistsException)   api使用WordNet有没有简单的基于Java的词义消歧?   Java和YAML:如何解析多个YAML文档并将它们合并到单个YAML表示?   swing Java:mouseClicked事件不总是触发   从firebase获取Json作为字符串在java中不起作用   java将带有extrastring的意图从活动发送到选项卡片段   Java中的调用方法问题   java是否可以在TestNG dataprovider类中注入变量   java如何在安卓 studio中使列表中的项目可单击并引导到具有特定格式的新页面   java程序在eclipse中运行,但不在终端中运行   java无法让mockito模拟公共类的公共方法