Mongo持久化后端
项目描述
Mongo数据持久化
本文档概述了mongopersist包的一般功能。mongopersist是持久化Python对象的Mongo存储实现。它不是ZODB的存储。
mongopersist的目标是提供一个数据管理器,该管理器在事务边界将对象序列化到Mongo。Mongo数据管理器是一个持久化数据管理器,它在事务边界处理事件(参见transaction.interfaces.IDataManager),以及从持久化框架接收事件(参见persistent.interfaces.IPersistentDataManager)。
数据管理器的实例应该与事务的生存期相同,这意味着假设你在创建新事务时创建一个新的数据管理器。
>>> import transaction
注意:conn对象是pymongo.connection.Connection实例。在这种情况下,我们的测试使用的是mongopersist_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.now() ... ... def __str__(self): ... return self.name ... ... def __repr__(self): ... return '<%s %s>' %(self.__class__.__name__, self)
我们稍后将填写其他对象。但现在,让我们创建一个新的个人并将其存储在Mongo中
>>> stephan = Person(u'Stephan') >>> stephan <Person Stephan>
数据管理器提供了一个具有root属性的特例,其中可以存储对象树根。它是特殊的,因为它会立即将数据写入数据库
>>> dm.root['stephan'] = stephan >>> dm.root['stephan'] <Person Stephan>
自定义持久化集合
默认情况下,持久化对象存储在一个具有类Python路径的集合中
>>> from mongopersist import serialize >>> person_cn = serialize.get_dotted_name(Person) >>> person_cn '__main__.Person'>>> import pprint >>> pprint.pprint(list(conn[DBNAME][person_cn].find())) [{u'_id': ObjectId('4e7ddf12e138237403000000'), u'address': None, u'birthday': None, u'friends': {}, u'name': u'Stephan', u'phone': None, u'today': datetime.datetime(2011, 10, 1, 9, 45), u'visited': []}]
如你所见,存储的个人文档看起来非常像Mongo。但哦,我忘记指定Stephan的全名了。让我们来指定它
>>> dm.root['stephan'].name = u'Stephan Richter'
这一次,数据不会自动保存
>>> conn[DBNAME][person_cn].find_one()['name'] u'Stephan'
因此,我们首先需要提交事务
>>> transaction.commit() >>> conn[DBNAME][person_cn].find_one()['name'] u'Stephan Richter'
现在让我们为Stephan添加一个地址。地址也是持久化对象
>>> class Address(persistent.Persistent): ... _p_mongo_collection = '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)
MongoPersist支持一个特殊属性_p_mongo_collection,允许你指定要使用的自定义集合。
>>> stephan = dm.root['stephan'] >>> stephan.address = Address('Maynard', '01754') >>> stephan.address <Address Maynard (01754)>
请注意,地址不会立即保存到数据库中
>>> list(conn[DBNAME]['address'].find()) []
但一旦我们提交事务,所有内容都可用
>>> transaction.commit() >>> pprint.pprint(list(conn[DBNAME]['address'].find())) [{u'_id': ObjectId('4e7de388e1382377f4000003'), u'city': u'Maynard', u'zip': u'01754'}]>>> pprint.pprint(list(conn[DBNAME][person_cn].find())) [{u'_id': ObjectId('4e7ddf12e138237403000000'), u'address': DBRef(u'address', ObjectId('4e7ddf12e138237403000000'), u'mongopersist_test'), u'birthday': None, u'friends': {}, u'name': u'Stephan Richter', u'phone': None, u'today': datetime.datetime(2011, 10, 1, 9, 45) u'visited': []}]>>> dm.root['stephan'].address <Address Maynard (01754)>
非持久化对象
如你所见,即使是引用看起来也很整洁,并使用标准的MongoDB引用构造。但对于任意非持久化但可序列化的对象怎么办?好吧,让我们为这个创建一个电话号码对象
>>> 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>
现在让我们提交事务并再次查看Mongo文档
>>> transaction.commit() >>> dm.root['stephan'].phone <Phone +1-978-394-5124>>>> pprint.pprint(list(conn[DBNAME][person_cn].find())) [{u'_id': ObjectId('4e7ddf12e138237403000000'), u'address': DBRef(u'address', ObjectId('4e7ddf12e138237403000000'), u'mongopersist_test'), 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': datetime.datetime(2011, 10, 1, 9, 45) u'visited': []}]
如你所见,对于任意非持久化对象,我们需要在子文档中添加一个小提示,但它非常小。如果__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,而元组始终作为列表维护,因为BSON没有两种序列类型。
>>> pprint.pprint(conn[DBNAME][person_cn].find_one( ... {'name': 'Stephan Richter'})) {u'_id': ObjectId('4e7df744e138230a3e000000'), u'address': DBRef(u'address', ObjectId('4e7df744e138230a3e000003'), u'mongopersist_test'), u'birthday': {u'_py_factory': u'datetime.date', u'_py_factory_args': [Binary('\x07\xbc\x01\x19', 0)]}, u'friends': {u'roy': DBRef(u'__main__.Person', ObjectId('4e7df745e138230a3e000004'), u'mongopersist_test')}, 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': datetime.datetime(2011, 9, 24, 11, 29, 8, 930000), u'visited': [u'Germany', u'USA']}
自定义序列化器
如您所见,生日的序列化几乎不理想。然而,我们可以提供一个自定义序列化器,该序列化器使用序号来存储数据。
>>> class DateSerializer(serialize.ObjectSerializer): ... ... def can_read(self, state): ... return isinstance(state, dict) and \ ... state.get('_py_type') == 'datetime.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': 'datetime.date', ... 'ordinal': obj.toordinal()}>>> serialize.SERIALIZERS.append(DateSerializer()) >>> dm.root['stephan']._p_changed = True >>> transaction.commit()
让我们再看看
>>> dm.root['stephan'].birthday datetime.date(1980, 1, 25)>>> pprint.pprint(conn[DBNAME][person_cn].find_one( ... {'name': 'Stephan Richter'})) {u'_id': ObjectId('4e7df803e138230aeb000000'), u'address': DBRef(u'address', ObjectId('4e7df803e138230aeb000003'), u'mongopersist_test'), u'birthday': {u'_py_type': u'datetime.date', u'ordinal': 722839}, u'friends': {u'roy': DBRef(u'__main__.Person', ObjectId('4e7df803e138230aeb000004'), u'mongopersist_test')}, 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': datetime.datetime(2011, 9, 24, 11, 32, 19, 640000), u'visited': [u'Germany', u'USA']}
好多了!
持久化对象作为子文档
为了给开发者提供更多控制,让哪些对象拥有自己的集合以及哪些没有,开发者可以提供一个特殊标志来标记持久化类,使其成为父对象文档的一部分
>>> class Car(persistent.Persistent): ... _p_mongo_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_mongo_sub_object用于标记一个对象类型,使其仅是另一个文档的一部分
>>> dm.root['stephan'].car = car = Car('2005', 'Ford', 'Explorer') >>> transaction.commit()>>> dm.root['stephan'].car <Car 2005 Ford Explorer>>>> pprint.pprint(conn[DBNAME][person_cn].find_one( ... {'name': 'Stephan Richter'})) {u'_id': ObjectId('4e7dfac7e138230d3d000000'), u'address': DBRef(u'address', ObjectId('4e7dfac7e138230d3d000003'), u'mongopersist_test'), u'birthday': {u'_py_type': u'datetime.date', u'ordinal': 722839}, 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': DBRef(u'__main__.Person', ObjectId('4e7dfac7e138230d3d000004'), u'mongopersist_test')}, 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': datetime.datetime(2011, 9, 24, 11, 44, 7, 662000), u'visited': [u'Germany', u'USA']}
我们想要对象持久化的原因是为了使它们能够自动获取更改
>>> dm.root['stephan'].car.year = '2004' >>> transaction.commit() >>> dm.root['stephan'].car <Car 2004 Ford Explorer>
集合共享
由于Mongo非常灵活,有时将多种类型(类似)的对象存储在同一个集合中是有意义的。在这种情况下,您需要指示对象类型将它的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_mongo_collection字符串的类。
现在让我们给斯蒂芬一个扩展地址。
>>> dm.root['stephan'].address2 = ExtendedAddress( ... 'Tettau', '01945', 'Germany') >>> dm.root['stephan'].address2 <ExtendedAddress Tettau (01945) in Germany> >>> transaction.commit()
当加载地址时,它们应该是正确的类型
>>> dm.root['stephan'].address <Address Maynard (01754)> >>> dm.root['stephan'].address2 <ExtendedAddress Tettau (01945) in Germany>
棘手的情况
基本可变类型的更改
棘手。我们如何让框架检测到像列表和字典这样的可变对象中的更改?答案是:我们跟踪它们属于哪个持久化对象,并提供持久化实现。
>>> type(dm.root['stephan'].friends) <class 'mongopersist.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 'mongopersist.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: <__main__.Foo object at 0x7fec75731890>
循环持久引用
通常,持久化对象之间的循环引用不是问题,因为我们总是只存储一个指向对象的链接。然而,有一种情况下,循环依赖成为问题。
如果您设置了一个具有循环引用的对象树,并将其一次添加到存储中,则必须在序列化期间插入对象,以便创建引用。但是,需要小心只创建一个最小的引用对象,以便系统不会尝试递归地减少状态。
>>> 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 mongopersist import mapping >>> class People(mapping.MongoCollectionMapping): ... __mongo_collection__ = person_cn ... __mongo_mapping_key__ = 'short_name'
映射接受数据管理器作为参数。可以轻松创建一个子类来自动分配数据管理器。让我们看看
>>> People(dm).keys() []
列表中没有人的原因是因为还没有文档具有该键,或者键为null。让我们改变这一点
>>> People(dm)['stephan'] = dm.root['stephan'] >>> transaction.commit()>>> People(dm).keys() [u'stephan'] >>> People(dm)['stephan'] <Person Stephan Richter>
请注意,将“短名”属性设置在任何其他人的身上都会将其添加到映射中
>>> dm.root['stephan'].friends['roy'].short_name = 'roy' >>> transaction.commit() >>> sorted(People(dm).keys()) [u'roy', u'stephan']
写入冲突检测
由于Mongo不支持MVCC,它不提供写入冲突检测的概念。但是,可以使用文档上的序列号轻松实现简单的写入冲突检测。
让我们重置数据库并创建一个启用了冲突检测的数据管理器
>>> from mongopersist import conflict, datamanager >>> conn.drop_database(DBNAME) >>> dm2 = datamanager.MongoDataManager( ... conn, ... default_database=DBNAME, ... root_database=DBNAME, ... conflict_handler_factory=conflict.SimpleSerialConflictHandler)
现在我们添加一个人,并看到序列号已存储
>>> dm2.root['stephan'] = Person(u'Stephan') >>> dm2.root['stephan']._p_serial '\x00\x00\x00\x00\x00\x00\x00\x01' >>> pprint.pprint(dm2._conn[DBNAME][person_cn].find_one()) {u'_id': ObjectId('4e7fe18de138233a5b000009'), u'_py_serial': 1, u'address': None, u'birthday': None, u'friends': {}, u'name': u'Stephan', u'phone': None, u'today': datetime.datetime(2011, 9, 25, 22, 21, 1, 656000), u'visited': []}
接下来我们更改这个人并再次提交
>>> dm2.root['stephan'].name = u'Stephan <Unknown>' >>> transaction.commit() >>> pprint.pprint(dm2._conn[DBNAME][person_cn].find_one()) {u'_id': ObjectId('4e7fe18de138233a5b000009'), u'_py_serial': 2, u'address': None, u'birthday': None, u'friends': {}, u'name': u'Stephan <Unknown>', u'phone': None, u'today': datetime.datetime(2011, 9, 25, 22, 21, 1, 656000), u'visited': []}
现在我们开始一个新的事务并做一些修改
>>> dm2.root['stephan'].name = u'Stephan Richter'
然而,在此期间,另一笔交易修改了该对象。(为了简化,我们在这里直接通过Mongo执行此操作。)
>>> _ = dm2._conn[DBNAME][person_cn].update( ... {'name': u'Stephan <Unknown>'}, ... {'$set': {'name': u'Stephan R.', '_py_serial': 3}}) >>> pprint.pprint(dm2._conn[DBNAME][person_cn].find_one()) {u'_id': ObjectId('4e7fe1f4e138233ac4000009'), u'_py_serial': 3, u'address': None, u'birthday': None, u'friends': {}, u'name': u'Stephan R.', u'phone': None, u'today': datetime.datetime(2011, 9, 25, 22, 22, 44, 343000), u'visited': []}
现在,我们的更改交易试图提交
>>> transaction.commit() Traceback (most recent call last): ... ConflictError: database conflict error (oid DBRef(u'__main__.Person', ObjectId('4e7ddf12e138237403000000'), u'mongopersist_test'), class Person, orig serial 2, cur serial 3, new serial 3)>>> transaction.abort()
更改
0.8.4 (2013-06-13)
在同一交易中修复插入后删除的问题。文档未被从Mongo中删除。
修复了复杂对象的transaction.abort()行为。在交易中止后,_py_type信息不会丢失。
0.8.3 (2013-04-09)
修复了MongoContainer与IdNamesMongoContainer在None键上的add和__setitem__行为。由于zope.container.contained.setitem得到了刚刚插入的对象,因此未触发ObjectAddedEvent或ObjectMovedEvent。
MongoContainer始终需要_m_mapping_key并使用该对象的该属性来确定新键。
IdNamesMongoContainer需要_m_mapping_keyNone并使用_id来确定新键。
0.8.2 (2013-04-03)
修复了check_conflict:确保我们使用与对象相同的数据库和集合
0.8.1 (2013-03-19)
修复了对象加载时由于直接分配给__name__而引起的_p_changed设置。这导致从容器中读取的所有对象在加载时都被标记为已更改。这也破坏了缓存性能。
0.8.0 (2013-02-09)
功能:向集合包装器添加了find_objects()和find_one_object(),因此每当您从数据管理器获取集合时,都可以通过find API直接加载对象。
功能:为MongoContained对象添加了完全引用和加载其父对象的能力。这允许直接查询Mongo并从文档中创建对象,而无需通过容器进行,您可能不知道正确的容器。
0.7.7 (2013-02-08)
错误:如果无法删除父属性和名称属性,则不会失败。
0.7.6 (2013-02-08)
功能:切换到pymongo.MongoClient,设置默认的写入关注值,允许覆盖写入关注值。
0.7.5 (2013-02-06)
测试:添加,清理测试
错误:由于缺少文件,重新发布0.7.4版本
0.7.4 (2013-02-05)
错误:由于UserDict实现了dict比较语义,任何空的MongoContainer都会等同于另一个空的。这种行为会导致对象更改无法被Mongo数据管理器正确识别。实现的解决方案是为Mongo容器实现默认的对象比较行为。
0.7.3 (2013-01-29)
功能:更新到最新包版本,特别是pymongo 2.4.x。在这个版本中,pymongo不会重新导出objectid和dbref。
0.7.2 (2012-04-19)
错误:为了避免在多线程环境中在Mongo容器中缓存MongoDataManager实例以避免在单个交易中存在多个MongoDataManagers,改用缓存IMongoDataManagerProvider。
0.7.1 (2012-04-13)
性能:通过允许禁用记录的修改来略微改进了分析器。
性能:在Mongo容器中添加了对_m_jar查找的缓存,因为计算出来相当昂贵。
性能:使用懒散的哈希计算来处理DBRef。同时禁用对任意关键字参数的支持。这在大约2-4%的对象加载时间内产生了差异。
错误:当缺少_py_serial时发生错误。这是由于0.6版本中的一个错误造成的。这还保护了第三方软件,这些软件不了解我们的元数据。
性能:切换到repoze.lru(从lru),这要快得多。
性能:为了避免过多的哈希计算,我们现在使用DBRef引用的哈希值作为缓存键。
错误:ObjectId ids 在跨集合中不能保证唯一性。因此它们不适合作为全局缓存的键。所以我们使用完整的 DBRef 引用。
0.7.0 (2012-04-02)
功能:一个新的 IConflictHandler 接口现在控制所有冲突解决的方面。以下是一些实现:
NoCheckConflictHandler:此处理程序不执行任何操作,当使用时,系统行为与之前将 detect_conflicts 标志设置为 False 时的行为相同。
SimpleSerialConflictHandler:此处理程序使用每个文档的序列号来跟踪版本,然后检测冲突。当检测到冲突时,会引发一个 ConflictError。此处理程序与将 detect_conflicts 设置为 True 相同。
ResolvingSerialConflictHandler:另一个序列处理程序,但它具有解决冲突的能力。为此,持久对象必须实现 _p_resolveConflict(orig_state, cur_state, new_state),它返回新的合并状态。(实验性)
因此,数据管理器的 detect_conflicts 标志已被移除,并由 conflict_handler 属性替换。可以将 conflict_handler_factory 传递给数据管理器构造函数。工厂需要期望一个参数,即数据管理器。
功能:新的 IdNamesMongoContainer 类使用Mongo ObjectId的自然名称作为容器中项的名称/键。不再需要处理生成名称的问题。当然,如果您过去指定了 None 作为键,它已经使用了对象 ID,但它存储在映射键字段中。现在对象 ID 在所有地方都直接使用。
功能:当在持久对象上调用 setattr() 时,即使新值等于旧值,它也会被标记为已更改。为了最小化对 MongoDB 的写入,将最新数据库状态与新的状态进行比较,并且仅在检测到更改时才写入新状态。使用名为 serialize.IGNORE_IDENTICAL_DOCUMENTS(默认:True)的标志来控制此功能。(实验性)
功能:现在 ConflictError 具有一个更有意义的 API。它不再仅引用对象和不同的序列号,而是实际上具有原始、当前和新状态文档。
功能:现在在回滚事务时检测冲突。如果检测到冲突,实现的策略不会重置文档状态。
功能:提供一个标志来打开 MongoDB 访问日志。默认情况下,该标志为 false,因为访问日志非常昂贵。
功能:向 LoggingDecorator 添加了事务 ID。
功能:添加了一个用于测试性能的小脚本。它并不非常复杂,但对于优化的一轮来说已经足够。
功能:在所有级别上大幅提高了性能。这主要是通过删除不必要的数据库访问、更好的缓存和更高效的算法来实现的。这导致了 4-25 倍的加速。
当解析类的路径时,现在会将结果缓存。更重要的是,查找失败也会缓存映射 path -> None。这很重要,因为 resolve() 方法的一个优化导致了很多失败的查找。
在将dbref解析为类型时,如果知道集合中的文档存储其类型路径,我们会尝试使用文档尽早解析dbref。这样可以避免在不需要时频繁查询名称映射集合。
获取对象文档以读取类路径时,现在将读取整个文档并将其存储在_latest_states字典中,以便其他代码可以获取并使用它。这应该可以避免从MongoDB中进行不必要的读取。
大大提高了仅存储一种类型的对象且文档不存储类型的集合(即存储在名称映射集合中)的性能。
Mongo容器通过find()进行快速加载没有正确工作,因为setstate()没有将状态从幽灵状态更改为活动状态,因此状态再次从MongoDB加载并设置在对象上。现在我们使用新的_latest_states缓存,当通过适当渠道调用setstate()时查找文档。现在这种“快速加载”方法真正实现了O(1)数据库查找。
为Mongo容器实现了更多映射方法,因此现在获取完整项目列表的所有方法都很快。
当Mongo对象ID用作哈希键时,使用ID的哈希值。ObjectId类的__cmp__()方法太慢了。
从ObjectWriter类中的对象缓存集合名称查找。
错误:在生产环境中,我们曾多次遇到在文档中突然丢失一些状态的情况,这阻止了对象再次加载。原因是_original_states属性没有存储原始MongoDB文档,而是存储了一个修改过的文档。然而,由于这些状态在回滚时用于重置状态,因此修改过的文档被存储,使受影响的对象无法访问。
错误:当一个事务被终止时,所有已加载对象的状态都会被重置。现在,只有修改过的对象状态被重置。这应该大大降低由于缺少完整MVCC而导致的问题(由于读取和修改对象的比率)。
错误:当通过键/名称(find_*()方法)查找项时,您永远不会得到正确的对象,而是得到数据库中找到的第一个对象。这是由于用更通用的参数覆盖了搜索过滤器。
0.6.1 (2012-03-28)
功能:在集合方法周围添加了相当详细的调试日志
0.6.0 (2012-03-12)
功能:切换到乐观数据转储,这种方法通过早期和随数据到达来处理事务。在事务失败/终止时,所有更改都将取消。有关详细信息,请参阅optimistic-data-dumping.txt。以下是新功能的一些示例
数据管理器跟踪在对象被修改之前的所有原始文档,因此可以进行任何更改。
为数据管理器添加了API(DataManager.insert(obj))以将对象插入数据库。
为数据管理器添加了API(DataManager.remove(obj))以从数据库中删除对象。
可以在事务的任何阶段将数据刷新到Mongo(DataManager.flush()),同时保留完全撤销所有更改的能力。刷新具有以下特性
在给定的交易期间,我们保证用户将始终收到相同的Python对象。这要求flush不会重置对象缓存。
_p_serial增加1。(在对象编写器中自动完成)
对象从注册对象中删除,并将_p_changed标志设置为False。
在刷新之前,检测潜在冲突。
实现了刷新策略:在执行任何查询之前,都会刷新更改。一个简单的包装器 CollectionWrapper 用于确保在正确的方法调用之前调用刷新。两个新的API方法 DataManager.get_collection(db_name, coll_name) 和 DataManager.get_collection_from_object(obj) 允许快速获取包装后的集合。
特性:将 processSpec() 重命名为 process_spec() 以符合包命名约定。
特性:创建了一个 ProcessSpecDecorator,用于在 CollectionWrapper 类中处理 find()、find_one() 和 find_and_modify() 集合方法中的规范。
特性:现在,当从容器中删除对象时,如果 _m_remove_documents 设置为 True,则 MongoContainer 类会从数据库中删除对象。默认设置为 True。
特性:当向 MongoContainer 添加项目时,如果键为 None,则选择 OID 作为键。ID 是完美的键,因为它们在集合内保证唯一。
特性:由于人们不喜欢使用 None 键的 setitem 实现,我还添加了 MongoContainer.add(value, key=None) 方法,这使得指定键是可选的。如果键为 None,则默认使用 OID。
特性:从 MongoContainer.find(...) 和 MongoContainer.find_one(...) 方法中移除了 fields 参数,因为它没有被使用。
特性:如果容器有 N 个项目,则需要 N+1 次查询才能完全加载项目列表。这是由于一次查询返回所有 DBRefs,然后使用一次查询来加载每个的州。现在,第一次查询加载所有完整状态,并使用 DataManager.setstate(obj, doc=None) 的扩展来使用之前查询的数据加载对象的州。
特性:将 MongoContainer.get_collection() 更改为返回 CollectionWrapper 实例。
0.5.5 (2012-03-09)
特性:将 ZODB 依赖项移动到测试依赖项。
错误:当对象有 SimpleContainer 作为属性时,简单地加载此对象会导致它在事务结束时写入。罪魁祸首是包含 SimpleContainer 状态的持久字典。在状态加载期间修改了此字典,导致它被注册为已更改的对象,并标记为 _p_mongo_sub_object,具有原始对象作为 _p_mongo_doc_object。
0.5.4 (2012-03-05)
特性:通过 IMongoSpecProcessor 适配器添加了一个钩子,在每次查找之前调用以处理/记录规范。
0.5.3 (2012/01/16)
错误:MongoContainer 没有发出任何 Zope 容器或生命周期事件。这已通过使用 zope.container.contained 辅助函数来解决。
0.5.2 (2012-01-13)
特性:为 MongoContainer 类添加了一个接口,描述了额外的属性和方法。
0.5.1 (2011-12-22)
错误:MongoContainer 类没有实现 IContainer 接口。
0.5.0 (2011-11-04)
初始发布