跳转到主要内容

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引用自然地嵌入到应用中。属性允许隐藏查找和存储引用的实现。

结论与参考实现的要求

  • 允许配置外键约束(无,总是,事务结束时)。此配置必须在任何时间点可更改,并提供自动迁移路径。

  • 使用应用级键来引用对象。

  • 使用规范位置和主键来存储对象,以及确定对象是否被删除。

  • 在修改对象键时区分两种用例

    1. 应用引用了正确的对象,但键不正确(因为键本身可能对应用有含义)。在这种情况下,必须更新对象以接收新的、正确键,并且必须更新引用以指向此新键。

    2. 应用使用正确的键引用了错误的对象。在这种情况下,必须用不同的对象替换键引用的对象。

实现说明

  • 规范位置由位置/包含关系确定。引用的主键是引用对象的定位。

  • 通过监控包含事件来强制约束。

  • 通过枚举所有键并在引用对象上存储一个引用ID(而不是位置)的间接方式,支持更新/更改键意义的不同方式。更改意义的两个用例通过以下方式实现:

    1. 将新路径与现有引用ID关联

    2. 将新引用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.schemagocept.reference 有一个自己的 set 字段,该字段具有内部使用的 InstrumentedSet 类作为 type

为了演示目的,我们创建了一个接口,该接口使用 gocept.reference.field.Setzope.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 验证了 setInstrumentedSet,并且正确无误,但如果验证其他内容则会抛出异常

>>> 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.SetInstrumentedSet 处失败

>>> 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 的验证现在允许在字段验证中同时使用 InstrumentedSetset,因为这两种变体都会发生。

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

上传时间

构建分发

gocept.reference-1.0-py2.py3-none-any.whl (33.5 kB 查看哈希值)

上传时间 Python 2 Python 3

支持

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