Zope/ZODB应用程序的内联引用。
项目描述
版权(c)2007-2022 gocept gmbh & co. kg和贡献者。
版权所有。
本软件受Zope公共许可证第2.1版(ZPL)的约束。ZPL的一个副本应附在本分发中。本软件“按原样”提供,任何明示或暗示的保证(包括但不限于标题保证、适销性保证、不侵权保证和针对特定目的的适用性保证)均被拒绝。
简介
本软件包提供了一个参考实现。
本实现的具体属性是
旨在用于内在引用
提供完整性执行
部分基于关系数据库的外键
动机
在开发应用程序时,我们经常发现需要引用作为应用程序数据存储的对象。此类对象的例子包括集中管理的“主数据”。
对这些对象的引用通常是我们开发的应用程序内的内在引用,因此它们应像正常的Python对象引用一样行为,同时处于我们应用程序的控制之下。
在Zope和ZODB的世界中,有不同方法实现这一点。各种方法有不同的语义和副作用。我们的目标是统一内在引用对象的方式,并提供在需要时切换不同语义的能力,而无需重写应用程序代码,也无需迁移持久数据结构(至少从应用程序的角度来看)。
模型比较
我们的目标是确定不同现有方法的优缺点。我们包括了来自Python/Zope/ZODB世界的三种通用方法,以及用于规范化表的常规关系方法。
我们使用四个标准来描述每个解决方案
- 引用数据
存储什么数据来描述引用?
- 引用语义
引用有什么含义?其含义如何变化?
- 完整性
如果涉及引用的数据更改或被删除,应用程序可能会发生什么?
- 集/查找
应用程序开发者需要做什么来设置引用或查找引用的对象?
属性 |
Python引用 |
弱引用 |
键引用 |
关系数据库 |
---|---|---|---|---|
引用数据 |
OID |
OID |
应用程序特定的键 |
应用程序特定的(主键+表名) |
引用语义 |
引用特定的Python对象 |
引用特定的Python对象 |
引用在查找时与保存的键关联的对象 |
引用在查找时与主键关联的对象(行) |
完整性 |
引用仍然有效,然而,目标对象可能已经失去了对应用程序的意义。 |
引用可能已过时,并使引用对象处于无效状态。 |
引用可能已过时。 |
依赖于对外键的使用和数据库对约束的实现。通常可以强制保持有效。 |
集/查找 |
常规Python属性访问。 |
使用WeakRef包装器存储和__call__查找。可能使用属性以方便起见。 |
取决于实现。可能使用属性以方便起见。 |
显式存储主键。使用JOIN查找。 |
观察
关系型:每个对象(行)都有一个定义主键的规范位置。
ZODB(就像文件系统一样)可以有一个对象的多重硬链接。当删除对对象的最后一个硬链接时,对象被删除。这使得无法使用硬链接来引用对象,因为对象删除不会被注意到,并且对象将继续存在。ZODB本身没有对象定义的规范位置的概念。
关系型:在引用对象时,可以通过声明外键来强制执行完整性。这与存储的数据是正交的。
关系型:由于应用级键用于标识引用的目标,因此应用可以选择删除一行,稍后重新添加一行,具有相同的键。如果强制执行完整性,则需要在数据库级别提供对暂时忽略损坏的外键的支持。
正常的Python引用自然地嵌入到应用中。属性允许隐藏查找和存储引用的实现。
结论与参考实现的要求
允许配置外键约束(无,总是,事务结束时)。此配置必须在任何时间点可更改,并提供自动迁移路径。
使用应用级键来引用对象。
使用规范位置和主键来存储对象,以及确定对象是否被删除。
在修改对象键时区分两种用例
应用引用了正确的对象,但键不正确(因为键本身可能对应用有含义)。在这种情况下,必须更新对象以接收新的、正确键,并且必须更新引用以指向此新键。
应用使用正确的键引用了错误的对象。在这种情况下,必须用不同的对象替换键引用的对象。
实现说明
规范位置由位置/包含关系确定。引用的主键是引用对象的定位。
通过监控包含事件来强制约束。
通过枚举所有键并在引用对象上存储一个引用ID(而不是位置)的间接方式,支持更新/更改键意义的不同方式。更改意义的两个用例通过以下方式实现:
将新路径与现有引用ID关联
将新引用ID与现有路径关联
引用对象
简单引用
为了处理引用,你必须设置一个定位站点
>>> import zope.component.hooks >>> root = getRootFolder() >>> zope.component.hooks.setSite(root)
为了演示目的,我们定义了两个类,一个用于引用对象,另一个定义了引用。使用引用的类必须实现IAttributeAnnotatable,因为引用作为注释存储
>>> from zope.container.contained import Contained >>> import gocept.reference >>> import zope.interface >>> from zope.annotation.interfaces import IAttributeAnnotatable
>>> @zope.interface.implementer(IAttributeAnnotatable) ... class Address(Contained): ... city = gocept.reference.Reference()
>>> class City(Contained): ... pass
由于doctest中定义的类的实例不能持久化,我们从一个真实的Python模块中导入类的实现
>>> from gocept.reference.testing import Address, City
引用对象必须存储在ZODB中,并且必须定位
>>> root['dessau'] = City() >>> root['halle'] = City() >>> root['jena'] = City()
为了引用一个对象,只需将该对象分配给实现为引用描述符的属性即可
>>> theuni = Address() >>> theuni.city = root['dessau'] >>> theuni.city <gocept.reference.testing.City object at 0x...>
也可以分配 None 以使引用指向没有对象
>>> theuni.city = None >>> print(theuni.city) None
值可以被删除,描述符会引发AttributeError
>>> del theuni.city >>> theuni.city Traceback (most recent call last): AttributeError: city
只有包含对象可以分配给启用了完整性保证的引用
>>> theuni.city = 12 Traceback (most recent call last): TypeError: ...
完整性确保的引用
>>> @zope.interface.implementer(IAttributeAnnotatable) ... class Monument(Contained): ... city = gocept.reference.Reference(ensure_integrity=True) >>> from gocept.reference.testing import Monument
定位源
如果引用的来源已定位,则可以保证引用完整性
>>> root['fuchsturm'] = Monument() >>> root['fuchsturm'].city = root['dessau'] >>> root['fuchsturm'].city is root['dessau'] True
>>> import transaction >>> transaction.commit()
>>> del root['dessau'] Traceback (most recent call last): gocept.reference.interfaces.IntegrityError: Can't delete or move <gocept.reference.testing.City object at 0x...>. The (sub-)object <gocept.reference.testing.City object at 0x...> is still being referenced.
>>> transaction.commit() Traceback (most recent call last): transaction.interfaces.DoomedTransaction: transaction doomed, cannot commit
>>> transaction.abort() >>> 'dessau' in root True
要检查一个对象是否被引用,可以将其适配到IReferenceTarget
>>> from gocept.reference.interfaces import IReferenceTarget >>> IReferenceTarget(root['dessau']).is_referenced() True
>>> root['fuchsturm'].city = None >>> IReferenceTarget(root['dessau']).is_referenced() False
>>> del root['dessau'] >>> 'dessau' in root False
XXX 当属性或来源被删除时,引用也将被正确取消。
>>> del root['fuchsturm']
非定位源
如果引用的来源没有定位,我们可以对引用做任何我们想做的事情,包括断开它们
>>> fuchsturm = Monument() >>> fuchsturm.city = root['jena'] >>> fuchsturm.city is root['jena'] True
>>> del fuchsturm.city >>> fuchsturm.city Traceback (most recent call last): AttributeError: city
>>> fuchsturm.city = root['jena'] >>> fuchsturm.city is root['jena'] True
>>> del root['jena'] >>> fuchsturm.city Traceback (most recent call last): gocept.reference.interfaces.LookupError: Reference target '/jena' no longer exists.
更改源的位置状态
由于引用完整性没有给出,我们不能将具有损坏引用的对象放回包含关系中
>>> transaction.commit()
>>> root['fuchsturm'] = fuchsturm Traceback (most recent call last): gocept.reference.interfaces.LookupError: Reference target '/jena' no longer exists.
事务注定要失败,让我们恢复最后的工作状态
>>> transaction.commit() Traceback (most recent call last): transaction.interfaces.DoomedTransaction: transaction doomed, cannot commit
>>> transaction.abort()
我们必须手动修复fuchsturm对象,因为它不是事务的一部分
>>> fuchsturm.__parent__ = fuchsturm.__name__ = None
>>> from gocept.reference.interfaces import IReferenceSource >>> IReferenceSource(fuchsturm).verify_integrity() False
>>> IReferenceTarget(root['halle']).is_referenced() False >>> fuchsturm.city = root['halle'] >>> IReferenceSource(fuchsturm).verify_integrity() True >>> IReferenceTarget(root['halle']).is_referenced() False
>>> root['fuchsturm'] = fuchsturm >>> IReferenceTarget(root['halle']).is_referenced() True
>>> fuchsturm = root['fuchsturm'] >>> del root['fuchsturm'] >>> fuchsturm.city is root['halle'] True
>>> del root['halle'] >>> 'halle' in root False
层次结构
尝试删除包含具有确保完整性的引用对象的对象也是禁止的
>>> import zope.container.sample >>> root['folder'] = zope.container.sample.SampleContainer() >>> root['folder']['frankfurt'] = City() >>> messeturm = Monument() >>> messeturm.city = root['folder']['frankfurt'] >>> root['messeturm'] = messeturm
现在删除 文件夹 将失败,因为子对象正在被引用。引用目标API(IReferenceTarget)允许我们事先检查它
>>> from gocept.reference.interfaces import IReferenceTarget >>> folder_target = IReferenceTarget(root['folder']) >>> folder_target.is_referenced() True >>> folder_target.is_referenced(recursive=False) False
>>> del root['folder'] Traceback (most recent call last): gocept.reference.interfaces.IntegrityError: Can't delete or move <zope.container.sample.SampleContainer object at 0x...>. The (sub-)object <gocept.reference.testing.City object at 0x...> is still being referenced.
从非约束引用升级到约束引用
XXX
从完整性确保的引用降级到非确保
XXX
引用集合
要使用集合通过引用多个其他对象来拥有一个对象的属性,可以使用ReferenceCollection属性。
集合表现得像集合一样,在对象被添加或从集合中删除时管理引用
>>> import zope.component.hooks >>> root = getRootFolder() >>> zope.component.hooks.setSite(root)
我们需要一个定义引用集合的类。(从测试模块导入类是必要的,以便持久化类的实例)
>>> from zope.container.contained import Contained >>> import gocept.reference >>> import zope.interface >>> from zope.annotation.interfaces import IAttributeAnnotatable >>> from gocept.reference.testing import City
最初,集合没有设置,访问它会导致 AttributeError
>>> halle = City() >>> halle.cultural_institutions Traceback (most recent call last): AttributeError: cultural_institutions
因此我们定义了一些文化机构
>>> class CulturalInstitution(Contained): ... title = None >>> from gocept.reference.testing import CulturalInstitution
>>> root['theatre'] = CulturalInstitution() >>> root['cinema'] = CulturalInstitution() >>> root['park'] = CulturalInstitution() >>> import transaction >>> transaction.commit()
尝试设置单个值而不是集合,会引发 TypeError
>>> halle.cultural_institutions = root['park'] Traceback (most recent call last): TypeError: <gocept.reference.testing.CulturalInstitution object at 0x...> can't be assigned as a reference collection: only sets are allowed.
管理整个集合
赋值集合是有效的
>>> halle.cultural_institutions = set([root['park'], root['cinema']]) >>> len(halle.cultural_institutions) 2 >>> list(halle.cultural_institutions) [<gocept.reference.testing.CulturalInstitution object at 0x...>, <gocept.reference.testing.CulturalInstitution object at 0x...>]
由于 halle 尚未定位,完整性保证没有注意到被删除的引用对象
>>> del root['cinema']
结果是断开的引用
>>> list(halle.cultural_institutions) Traceback (most recent call last): gocept.reference.interfaces.LookupError: Reference target '/cinema' no longer exists.
此外,我们无法现在定位 halle,只要引用是断开的
>>> root['halle'] = halle Traceback (most recent call last): gocept.reference.interfaces.LookupError: Reference target '/cinema' no longer exists.
事务注定失败,所以我们取消
>>> transaction.abort()
不幸的是,abort 并不会回滚 Halle 的属性,因为它还不是事务的一部分(因为它不能添加到数据库中)。我们需要手动清理,否则下一次赋值不会引发任何事件
>>> halle.__name__ = None >>> halle.__parent__ = None
电影院现在回来了,Halle 再次处于操作状态
>>> list(halle.cultural_institutions) [<gocept.reference.testing.CulturalInstitution object at 0x...>, <gocept.reference.testing.CulturalInstitution object at 0x...>]
现在我们可以将其添加到数据库中
>>> root['halle'] = halle >>> transaction.commit()
删除引用对象将导致错误
>>> del root['cinema'] Traceback (most recent call last): gocept.reference.interfaces.IntegrityError: Can't delete or move <gocept.reference.testing.CulturalInstitution object at 0x...>. The (sub-)object <gocept.reference.testing.CulturalInstitution object at 0x...> is still being referenced. >>> transaction.abort()
当我们删除引用集合时,目标可以再次被删除
>>> halle.cultural_institutions = None >>> del root['cinema']
管理集合的单个项目
注意:我们没有完全实现集合 API 100%。我们需要时将添加方法。
除了通过赋值整个新集合来更改集合之外,我们还可以像正常 set API 允许的那样,使用单个项目修改集合。
我们将从一个空集合开始
>>> root['jena'] = City() >>> root['jena'].cultural_institutions = set()
我们的引用引擎将这个集合转换为一个管理引用的不同对象
>>> ci = root['jena'].cultural_institutions >>> ci InstrumentedSet([])
我们可以通过向这个集合添加对象来添加新的引用,并且会确保引用的完整性
>>> ci.add(root['park']) >>> transaction.commit() >>> del root['park'] Traceback (most recent call last): gocept.reference.interfaces.IntegrityError: Can't delete or move <gocept.reference.testing.CulturalInstitution object at 0x...>. The (sub-)object <gocept.reference.testing.CulturalInstitution object at 0x...> is still being referenced. >>> transaction.abort()
删除和丢弃是有效的
>>> ci.remove(root['park']) >>> del root['park'] >>> root['park'] = CulturalInstitution() >>> ci.add(root['park']) >>> transaction.commit() >>> del root['park'] Traceback (most recent call last): gocept.reference.interfaces.IntegrityError: Can't delete or move <gocept.reference.testing.CulturalInstitution object at 0x...>. The (sub-)object <gocept.reference.testing.CulturalInstitution object at 0x...> is still being referenced. >>> transaction.abort() >>> ci.discard(root['park']) >>> del root['park'] >>> ci.discard(root['halle'])
清除是有效的
>>> ci.add(root['theatre']) >>> transaction.commit() >>> del root['theatre'] Traceback (most recent call last): gocept.reference.interfaces.IntegrityError: Can't delete or move <gocept.reference.testing.CulturalInstitution object at 0x...>. The (sub-)object <gocept.reference.testing.CulturalInstitution object at 0x...> is still being referenced. >>> transaction.abort() >>> ci.clear() >>> len(ci) 0 >>> del root['theatre']
>>> root['cinema'] = CulturalInstitution() >>> root['cinema'].title = 'Cinema' >>> ci.add(root['cinema']) >>> transaction.commit() >>> del root['cinema'] Traceback (most recent call last): gocept.reference.interfaces.IntegrityError: Can't delete or move <gocept.reference.testing.CulturalInstitution object at 0x...>. The (sub-)object <gocept.reference.testing.CulturalInstitution object at 0x...> is still being referenced. >>> transaction.abort() >>> ci.pop().title 'Cinema' >>> del root['cinema']
更新是有效的
>>> root['cinema'] = CulturalInstitution() >>> root['theatre'] = CulturalInstitution() >>> ci.update([root['cinema'], root['theatre']]) >>> len(ci) 2 >>> del root['cinema'] Traceback (most recent call last): gocept.reference.interfaces.IntegrityError: Can't delete or move <gocept.reference.testing.CulturalInstitution object at 0x...>. The (sub-)object <gocept.reference.testing.CulturalInstitution object at 0x...> is still being referenced. >>> del root['theatre'] Traceback (most recent call last): gocept.reference.interfaces.IntegrityError: Can't delete or move <gocept.reference.testing.CulturalInstitution object at 0x...>. The (sub-)object <gocept.reference.testing.CulturalInstitution object at 0x...> is still being referenced.
验证引用存在
验证一个类是否将一个属性实现为引用并不容易,因为它们的用法是透明的。
引用
让我们构建一个使用引用的示例接口和类
>>> import zope.interface >>> import gocept.reference >>> import zope.annotation.interfaces >>> class IAddress(zope.interface.Interface): ... city = zope.interface.Attribute("City the address belonges to.") >>> @zope.interface.implementer( ... zope.annotation.interfaces.IAttributeAnnotatable, IAddress) ... class Address(object): ... city = gocept.reference.Reference()
verifyClass 不检查属性
>>> import zope.interface.verify >>> zope.interface.verify.verifyClass(IAddress, Address) True
verifyObject 表明对象没有完全满足接口
>>> zope.interface.verify.verifyObject(IAddress, Address()) Traceback (most recent call last): zope.interface.exceptions.BrokenImplementation: The object <...Address object at 0x...> has failed to implement interface builtins.IAddress: The builtins.IAddress.city attribute was not provided.
在引用属性上设置值没有帮助,因为之后无法检查是否存在引用,因为引用是透明的。更糟糕的是,一个没有定义所需属性且实例具有该属性的类,使得测试通过而根本未定义引用
>>> @zope.interface.implementer(IAddress) ... class AddressWithoutReference(object): ... pass >>> address_without_ref = AddressWithoutReference() >>> address_without_ref.city = None >>> zope.interface.verify.verifyObject(IAddress, address_without_ref) True
因此,我们需要一个特殊的 verifyObject 函数,该函数在类中检查是否存在缺少的属性
>>> import gocept.reference.verify >>> gocept.reference.verify.verifyObject(IAddress, Address()) True
这个函数不是完全无懈可击的,因为它也适用于具有属性的实例。这种行为的理由是接口没有说明该属性必须作为引用实现
>>> gocept.reference.verify.verifyObject(IAddress, address_without_ref) True
但如果没有属性的实例在类上没有引用描述符,gocept.reference 的 verifyObject 可以检测到这一点
>>> @zope.interface.implementer(IAddress) ... class StrangeAddress(object): ... @property ... def city(self): ... raise AttributeError >>> strange_address = StrangeAddress() >>> gocept.reference.verify.verifyObject(IAddress, strange_address) Traceback (most recent call last): zope.interface.exceptions.BrokenImplementation: An object has failed to implement interface builtins.IAddress: The 'city' attribute was not provided.
就像 zope.interface.verify.verifyObject 检测到的一样
>>> zope.interface.verify.verifyObject(IAddress, strange_address) Traceback (most recent call last): zope.interface.exceptions.BrokenImplementation: The object <...StrangeAddress object at 0x...> has failed to implement interface builtins.IAddress: The builtins.IAddress.city attribute was not provided.
引用集合
当使用 zope.inferface.verify.verfyObject 检查时,引用集合也会遇到相同的问题
>>> class ICity(zope.interface.Interface): ... cultural_institutions = zope.interface.Attribute( ... "Cultural institutions the city has.") >>> @zope.interface.implementer( ... zope.annotation.interfaces.IAttributeAnnotatable, ICity) ... class City(object): ... cultural_institutions = gocept.reference.ReferenceCollection()
>>> zope.interface.verify.verifyObject(ICity, City()) Traceback (most recent call last): zope.interface.exceptions.BrokenImplementation: The object <...City object at 0x...> has failed to implement interface builtins.ICity: The builtins.ICity.cultural_institutions attribute was not provided.
但 gocept.reference 的特殊变体也适用于集合
>>> gocept.reference.verify.verifyObject(ICity, City()) True
zope.schema字段
为了符合 zope.schema,gocept.reference 有一个自己的 set 字段,该字段具有内部使用的 InstrumentedSet 类作为 type。
为了演示目的,我们创建了一个接口,该接口使用 gocept.reference.field.Set 和 zope.schema.Set。
>>> import gocept.reference >>> import gocept.reference.field >>> import zope.annotation.interfaces >>> import zope.interface >>> import zope.schema >>> import zope.schema.vocabulary >>> dumb_vocab = zope.schema.vocabulary.SimpleVocabulary.fromItems(()) >>> class ICollector(zope.interface.Interface): ... gr_items = gocept.reference.field.Set( ... title=u'collected items using gocept.reference.field', ... value_type=zope.schema.Choice(title=u'items', vocabulary=dumb_vocab) ... ) ... zs_items = zope.schema.Set( ... title=u'collected items using zope.schema', ... value_type=zope.schema.Choice(title=u'items', vocabulary=dumb_vocab) ... )
>>> @zope.interface.implementer( ... ICollector, zope.annotation.interfaces.IAttributeAnnotatable) ... class Collector(object): ... gr_items = gocept.reference.ReferenceCollection() ... zs_items = gocept.reference.ReferenceCollection()
>>> collector = Collector() >>> collector.gr_items = set() >>> collector.gr_items InstrumentedSet([]) >>> collector.zs_items = set() >>> collector.zs_items InstrumentedSet([])
gocept.reference.field.Set 验证了 set 和 InstrumentedSet,并且正确无误,但如果验证其他内容则会抛出异常
>>> ICollector['gr_items'].bind(collector).validate(collector.gr_items) is None True >>> ICollector['gr_items'].bind(collector).validate(set([])) is None True >>> ICollector['gr_items'].bind(collector).validate([]) Traceback (most recent call last): zope.schema._bootstrapinterfaces.WrongType: ([], (<class 'set'>, <class 'gocept.reference.collection.InstrumentedSet'>), None)
正如预期的那样,zope.schema.Set 在 InstrumentedSet 处失败
>>> ICollector['zs_items'].bind(collector).validate(collector.zs_items) Traceback (most recent call last): zope.schema._bootstrapinterfaces.WrongType: (InstrumentedSet([]), <class 'set'>, 'zs_items')
>>> ICollector['zs_items'].bind(collector).validate(set([])) is None True >>> ICollector['zs_items'].bind(collector).validate([]) Traceback (most recent call last): zope.schema._bootstrapinterfaces.WrongType: ([], <class 'set'>, 'zs_items')
变更
1.0 (2023-07-20)
放弃对 Python 2.7 的支持。
添加对 Python 3.7、3.8、3.9、3.10、3.11 的支持。
修复了弃用警告。
0.11 (2020-09-10)
为 Fixer 添加了提交保存点和输出进度的可能性。
0.10 (2020-04-01)
支持 Python 3.8。
使用 tox 作为唯一的测试设置。
将依赖从 ZODB3 切换到 ZODB。
迁移到 Github。
0.9.4 (2017-05-15)
版本 0.9.3 没有包含所有文件。通过添加 MANIFEST.in 来修复这个问题。
0.9.3 (2017-05-14)
使用 pytest 作为测试运行器。
0.9.2 (2015-08-05)
0.9.1 (2011-02-02)
修复了错误:从类中读取时,引用描述符无法找到它们的属性名称。
修复了错误:在类上挖掘引用描述符的属性名称的算法无法处理继承的引用。
0.9.0 (2010-09-18)
依赖 zope.generations 而不是 zope.app.generations。
0.8.0 (2010-08-20)
更新了测试以与 zope.schema 3.6 兼容。
移除了 InstrumentedSet.__init__ 的未使用参数。
避免使用 sets 模块,因为它在 Python 2.6 中已弃用。
0.7.2 (2009-06-30)
修复了前一个版本中添加的生成问题。
0.7.1 (2009-04-28)
通过为 InstrumentedSet 维护使用计数器来修复引用集合的引用计数。
添加了一个重建所有引用计数的工具。添加了一个使用此工具来设置 InstrumentedSet 的新使用计数的数据库生成。
0.7.0 (2009-04-06)
需要较新的 zope.app.generations 版本来消除对 zope.app.zopeappgenerations 的依赖。
0.6.2 (2009-03-27)
gocept.reference.field.Set 的验证现在允许在字段验证中同时使用 InstrumentedSet 和 set,因为这两种变体都会发生。
0.6.1 (2009-03-27)
zope.app.form 使用 _type 属性将表单值转换为字段值,破坏了字段的封装。将 InstrumentedSet 用作 _type 是一个坏主意,因为只有引用集合知道如何实例化 InstrumentedSet。现在在验证过程中临时将 _type 设置为 InstrumentedSet。
0.6 (2009-03-26)
利用 2009 年 1 月 Grok 洞穴冲刺中实现的更简单的 zope 包依赖关系。
添加了 zope.schema 字段 gocept.reference.field.Set,它使用内部使用的 InstrumentedSet 作为字段类型,因此验证不会失败。
gocept.reference 0.5.2 有一个一致性错误:尝试将非集合分配给 ReferenceCollection 属性会导致 TypeError,同时保留其先前分配的值,破坏了对该属性的完整性执行。
0.5.2 (2008-10-16)
修复了问题:当将 gocept.reference 升级到版本 0.5.1 时,引发了重复错误。
0.5.1 (2008-10-10)
确保在依赖于 gocept.reference 的其他包之前使用 zope.app.generations 安装引用管理器。
0.5 (2008-09-11)
添加了一个专门变体的 zope.interface.verify.verifyObject,可以正确处理引用和引用集合。
0.4 (2008-09-08)
将 InstrumentedSet 移动到使用 BTree 数据结构以提高性能。
为 InstrumentedSet 添加了 update 方法。
更新了文档。
0.3 (2008-04-22)
为引用对象集合添加了 set 实现。
0.2 (2007-12-21)
扩展了 IReferenceTarget.is_referenced 的 API,以允许指定是否递归查询引用或仅在特定对象上查询。默认情况下,查询是递归的。
修复了强制执行确保约束的事件处理程序的错误:如果与父位置一起删除,则可能删除引用的对象。
0.1 (2007-12-20)
初始版本。
项目详情
gocept.reference-1.0.tar.gz的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 1ebaabf88cec59358891f2dd2299b81a0663a7124e72674360e71c79ae7e3f76 |
|
MD5 | d61ae3e1fd4fe62c2b5b3a88466b8b2e |
|
BLAKE2b-256 | e3d82cd22d7bdd33d4b150755a19c54bdcfd17124c8c8b44b35de5f2fc210b00 |
gocept.reference-1.0-py2.py3-none-any.whl的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 8f73c8e2019edf7e6d2faefac19546f431381af12f2e325f9926ea44ca9bd1ad |
|
MD5 | 41b6502aba8428a4c675aeede2d24105 |
|
BLAKE2b-256 | 5bc18c0b2ac4652a3eadf8147826fc879c448759b0df8c838376ceaa843efae2 |