跳转到主要内容

对zope.intid的Zope支持

项目描述

简介

使Zope环境中使用zope.intid成为可能。这包括依赖于它的其他包,如zope.keyreference

源代码

贡献者请参阅文档 Plone核心开发流程

源代码位于托管在Github的Plone代码存储库

用法

首先,确保ofs实用程序提供了接口

>>> from Products.Five.tests.testing.simplecontent import (
...   manage_addSimpleContent)

>>> from zope.intid.interfaces import IIntIds
>>> from five.intid import site
>>> import five.intid.tests as tests
>>> from zope.interface.verify import verifyObject
>>> from zope.component import getAllUtilitiesRegisteredFor
>>> from zope.component.hooks import setSite
>>> tests.setUp(self.app)

在实用程序之前添加的内容将不会被注册(除非明确调用)。我们现在将设置一些供以后使用

>>> manage_addSimpleContent(self.folder, 'mycont1', "My Content")
>>> content1 = self.folder.mycont1

five.intid.site为添加、获取和删除IntId实用程序提供了便利函数:add_intidget_intiddel_intid

您可以在特定位置安装实用程序

>>> site.add_intids(self.folder)
>>> folder_intids = site.get_intids(self.folder)
>>> verifyObject(IIntIds, folder_intids)
True

您可以让add_intids找到站点根目录,并在那里安装。它将在任何地方都可用

>>> site.add_intids(self.folder, findroot=True)
>>> root_intids = site.get_intids(self.app)
>>> root_intids
<...IntIds ...>
>>> folder_intids is root_intids
False

最后,执行删除操作

>>> site.del_intids(self.folder, findroot=True)
>>> site.get_intids(self.app)
Traceback (most recent call last):
...
ComponentLookupError: (<InterfaceClass ....IIntIds>, '')

在我们查看intid事件之前,我们需要设置遍历钩子。一旦完成,当我们请求所有注册的Intids时,我们将从测试文件夹中获取实用程序

>>> setSite(self.folder)
>>> tuple(getAllUtilitiesRegisteredFor(IIntIds))
(<...IntIds ...>,)

当我们添加内容时,将触发事件以添加对象的键引用(目前,我们的内容和实用程序都已注册)

>>> manage_addSimpleContent(self.folder, 'mycont2', "My Content")
>>> content2 = self.folder.mycont2
>>> intid = site.get_intids(self.folder)
>>> len(intid.items()) == 1
True

如果将现有内容传递给intid实用程序,则会引发keyerror

>>> intid.getId(content1)
Traceback (most recent call last):
...
IntIdMissingError: <SimpleContent at /test_folder_1_/mycont1>

我们可以调用键引用并获取对象

>>> intid.items()[0][1]()
<SimpleContent at /test_folder_1_/mycont2>

我们可以像这样从实用程序中获取对象的intid

>>> ob_id = intid.getId(content2)

然后可以像这样获取对象

>>> intid.getObject(ob_id)
<SimpleContent at /test_folder_1_/mycont2>

这些对象在检索时被获取封装

>>> from Acquisition import IAcquirer
>>> IAcquirer.providedBy(intid.getObject(ob_id))
True

我们甚至可以通过解析它的intid将未封装的对象转换为封装对象,即使它是未封装的,intid工具也应该可以正常工作

>>> from Acquisition import aq_base
>>> resolved = intid.getObject(intid.getId(aq_base(content2)))
>>> IAcquirer.providedBy(resolved)
True
>>> unwrapped = aq_base(intid)
>>> unwrapped.getObject(ob_id) == resolved
True
>>> unwrapped.getId(content2) == ob_id
True

当对象被添加或删除时,订阅者将其添加到intid工具,并触发一个事件(zope.intid.interfaces.IIntIdAddedEvent,zope.intid.interfaces.IIntIdRemovedEvent分别)。

five.intid将这些事件连接起来作为对象事件重新分发。测试通过连接一个简单的订阅者来验证intid对象事件是否被触发(这些事件对于目录任务很有用)。

>>> tests.NOTIFIED[0]
'<SimpleContent at mycont2> <...IntIdAddedEvent object at ...'

注册和注销对象不会触发这些事件

>>> tests.NOTIFIED[0] = "No change"
>>> uid = intid.register(content1)
>>> intid.getObject(uid)
<SimpleContent at /test_folder_1_/mycont1>

>>> tests.NOTIFIED[0]
'No change'

>>> intid.unregister(content1)
>>> intid.getObject(uid)
Traceback (most recent call last):
...
ObjectMissingError: ...

>>> tests.NOTIFIED[0]
'No change'

重命名对象不应该破坏对象的重新封装

>>> self.setRoles(['Manager'])
>>> folder.mycont2.meta_type = 'Folder' # We need a metatype to move
>>> folder.manage_renameObject('mycont2','mycont_new')
>>> moved = intid.getObject(ob_id)
>>> moved
<SimpleContent at /test_folder_1_/mycont_new>
>>> [x.path for x in intid.ids]
['/test_folder_1_/mycont_new']

移动它也不应该

>>> from OFS.Folder import manage_addFolder
>>> manage_addFolder(self.folder, 'folder2', "folder 2")
>>> cut = folder.manage_cutObjects(['mycont_new'])
>>> ignore = folder.folder2.manage_pasteObjects(cut)
>>> moved = intid.getObject(ob_id)
>>> moved
<SimpleContent at /test_folder_1_/folder2/mycont_new>
>>> moved.aq_parent
<Folder at /test_folder_1_/folder2>

让我们把它移回来

>>> cut = folder.folder2.manage_cutObjects(['mycont_new'])
>>> ignore = folder.manage_pasteObjects(cut)
>>> folder.manage_renameObject('mycont_new','mycont2')

我们可以创建一个没有获取的对象,这样我们就可以将其添加到intid中

>>> from five.intid.tests import DemoPersistent
>>> demo1 = DemoPersistent()
>>> demo1.__parent__ = self.app
>>> from zope.event import notify
>>> from zope.lifecycleevent import ObjectAddedEvent
>>> notify(ObjectAddedEvent(demo1))
>>> nowrappid = intid.getId(demo1)
>>> demo1 == intid.getObject(nowrappid)
True

现在是查看键引用的好时机,这是这个系统的核心部分。

Zope2中的键引用

键引用是IKeyReference返回的可哈希对象。产生的哈希值是对象所引用的内容的唯一标识符(另一个zodb对象,sqlobject的钩子等)。

在intid中进行对象检索是通过调用键引用来实现的。由于获取的原因,这种实现与zope.intid略有不同。

IKeyReference返回的工厂必须持久化,这要求我们特别注意对获取封装对象的引用,以及通常在zope2中期望返回获取封装对象。

>>> ref = intid.refs[ob_id]
>>> ref
<five.intid.keyreference.KeyReferenceToPersistent object at ...>

引用对象持有对未封装目标对象的引用以及一个属性来获取应用程序(同样,也不是封装的,即

>>> ref.object
<SimpleContent at mycont2>

>>> type(ref.object)
<class 'Products.Five.tests.testing.simplecontent.SimpleContent'>

>>> ref.root
<Application at >

调用引用对象(或封装的属性wrapped_object)将返回一个获取封装的对象封装对象(按照创建时的方式封装)

>>> ref.wrapped_object == ref()
True

>>> ref()
<SimpleContent at /test_folder_1_/mycont2>

>>> IAcquirer.providedBy(ref())
True

解析机制将尽力在获取链的末尾结束当前请求,就像在正常情况下一样

>>> ref.wrapped_object.aq_chain[-1]
<ZPublisher.BaseRequest.RequestContainer object at ...>

哈希计算是数据库名称和对象的持久对象ID(oid)的组合

>>> ref.dbname
'unnamed'

>>> hash((ref.dbname, ref.object._p_oid)) == hash(ref)
True

>>> tests.tearDown()

获取循环

five.intid在aq_parent和__parent__中检测获取链中的循环。

设置循环

>>> import Acquisition
>>> class Acq(Acquisition.Acquirer): pass
>>> foo = Acq()
>>> foo.bar = Acq()
>>> foo.__parent__ = foo.bar

在一个具有获取循环的对象上查找根将引发错误

>>> from five.intid import site
>>> site.get_root(foo.bar)
Traceback (most recent call last):
...
AttributeError: __parent__ loop found

在一个具有获取循环的对象上查找连接将简单地返回None

>>> from five.intid import keyreference
>>> keyreference.connectionOfPersistent(foo.bar)

不可引用

一些对象实现了IPersistent但从未实际持久化,或包含对这种对象的引用。具体来说,CMFCore目录视图包含从未持久化的FSObjects,以及包含对这种对象引用的DirectoryViewSurrogates。由于FSObjects从未实际持久化,five.intid假设它可以添加

对于这样的对象,不可引用模块提供无操作订阅者和适配器,以从five.intid处理中排除此类对象。

>>> from zope import interface, component
>>> from five.intid import unreferenceable
>>> from Products.CMFCore import FSPythonScript
>>> foo = FSPythonScript.FSPythonScript('foo', __file__)
>>> self.app._setObject('foo', foo)
'foo'
>>> keyref = unreferenceable.KeyReferenceNever(self.app.foo)
Traceback (most recent call last):
...
NotYet
>>> foo in self.app._p_jar._registered_objects
False

没有id的对象

尝试为一个尚未正确添加到容器中的对象获取键引用是可能的,但该对象有一个路径。在这种情况下,我们将引发NotYet异常,让调用代码根据需要延迟,因为键引用否则会解析错误的对象(确切地说,是父对象)从错误的路径。

>>> from zope.keyreference.interfaces import IKeyReference
>>> from five.intid.keyreference import KeyReferenceToPersistent
>>> from zope.component import provideAdapter
>>> provideAdapter(KeyReferenceToPersistent)
>>> from OFS.SimpleItem import SimpleItem
>>> item = SimpleItem('').__of__(self.folder)
>>> '/'.join(item.getPhysicalPath())
'/test_folder_1_/'
>>> IKeyReference(item)
Traceback (most recent call last):
...
NotYet: <SimpleItem at >

如果对象被放置在循环包含中,IKeyReference(object)应该不能适配,让调用代码根据需要延迟。此外,任何对象访问都会被击败并引发RuntimeError。

这种情况发生在具有Plone4网站five.intid启用(five.intid.site.add_intids(site))并尝试通过@@manage-portlets添加小部件时。plone.portlet.static.static.Assignment似乎在某个时间有一个循环路径。

创建具有循环包含的项目
>>> item_b = SimpleItem().__of__(self.folder)
>>> item_b.id = "b"
>>> item_c = SimpleItem().__of__(item_b)
>>> item_c.id = "c"
>>> item_b.__parent__ = item_c
>>> assert item_b.__parent__.__parent__ == item_b
>>> item_b.id
Traceback (most recent call last):
...
RuntimeError: Recursion detected in acquisition wrapper
>>> try:
...     IKeyReference(item_c)
... except RuntimeError as exc:
...     # expected with zope.interface 5.1+:
...     # Recursion detected in acquisition wrapper
...     print("Error")
... except TypeError as exc:
...     # before zope.interface 5.1 it was not able to lets non-AttributeErrors
...     # propagate from descriptors which resultet in a Could Not Adapt TypeError
...     print("Error")
Error

变更日志

2.0.0 (2023-10-07)

破坏性更改

  • 停止支持python 2.7。[gforcada] (#1)

内部

  • 更新配置文件。[plone 开发者] (cfffba8c)

1.2.7 (2023-02-22)

错误修复

  • 通过plone/meta添加配置。[gforcada] (#1)

1.2.6 (2020-05-06)

错误修复

  • 修复弃用警告。根据Python版本,更新setup.py使其依赖于Zope2Zope。[jensens] (#1)

  • 修复测试,使其能够正确与zope.interface >= 5.1一起工作。[jensens] (#17)

1.2.5 (2020-03-13)

错误修复

  • 修复了在Zope 5上出现的ModuleNotFoundError:没有名为‘App.class_init’的模块。[maurits] (#15)

1.2.4 (2019-10-12)

错误修复

  • 在Zope 4中,在遍历时也捕获KeyError以修复复制粘贴时创建关系的问题。修复了https://github.com/plone/Products.CMFPlone/issues/2866。[pbauer] (#12)

  • 当查找回引用时,使用unrestrictedTraverse的查找速度过慢。简化的遍历将查找速度提高了80倍。[jensens, 2silver] (#14)

1.2.3 (2019-06-19)

错误修复

1.2.2 (2019-05-01)

错误修复

  • 修复在将数据库从py2迁移后,oid和root_oid可能意外转换为文本的情况。[pbauer] (#7)

1.2.1 (2019-02-13)

错误修复

  • 避免弃用警告。[gforcada] (#6)

1.2.0 (2018-11-07)

错误修复

  • 修复Python 3中的doctests。[ale-rt]

  • 适应zope.intid的变化,以更新抛出的错误。(这使得测试与Zope 2.13不兼容。)[pbauer]

1.1.2 (2016-09-09)

错误修复

  • IKeyReference适配器抛出NotYet(例如,因为对象没有适当的路径)时,防止在removeIntIdSubscriber上出错。[ale-rt]

1.1.1 (2016-08-19)

修复

  • Acquisition-unwrap aq_iter链中的每个项目,因为getSite().__parent__可能返回从原始上下文获取的对象,这会破坏父级循环检测。[thet]

  • IKeyReference适配器抛出NotYet(例如,因为对象没有适当的路径)时,防止在moveIntIdSubscriber上出错。[ale-rt]

1.1.0 (2016-02-14)

新增

  • 增强:遵循PEP8和Plone代码约定。[jensens]

修复

  • 修复:使其与Acquisition>=4.0.1兼容(并要求该版本)。在上述版本之前,循环获取不会检测到。现在它们会被检测到,并且对于循环,适配会失败并抛出“无法适配”的错误。任何属性访问都会以详细的RuntimeError失败。同时清理循环包含的替代方案。[jensens]

1.0.3 - 2012-10-05

  • 确保IConnection适配器对未包装的持久对象有效。[davisagli]

1.0.2 - 2011-12-02

  • 仅在ObjectAddedEvent事件处理程序中忽略“临时”对象。[mj]

1.0.1 - 2011-11-30

  • 在Plone portal_factory工具中忽略“临时”对象。[mj]

1.0 - 2011-10-10

  • 删除最后一个zope.app依赖项。[hannosch]

  • 删除intid浏览器视图。[hannosch]

  • 使代码现代化,适用于Zope 2.13。[hannosch]

0.5.2 - 2011年1月22日

  • 直接从zope.component导入getAllUtilitiesRegisteredFor,并删除对zope.app.zapi的依赖。[Arfrever]

  • 修复chameleon模板错误。[robgietema]

0.5.1 - 2010年8月4日

  • 修复测试,使其通过修正Acquisition 2.13.1中ImplicitAcquisitionWrapper的tp_name。[davisagli]

0.5.0 - 2010年2月6日

  • 仅使用非弃用的zope导入,five.intid现在仅支持Zope 2.12+。[alecm]

0.4.4 - 2010年2月6日

  • 修复了当根对象不在尝试解析的对象所在的同一个数据库中时出现的POSKeyError。[thefunny42]

  • 修复了所有弃用的导入并更新了setup.py。[thet, wichert]

  • 修复了测试以反映更改的Zope API。[thet]

0.4.3 - 2009年7月19日

  • 当根据路径查找对象时,将AttributeError视为NotFound错误。unrestrictedTraverse()在无法遍历时会抛出AttributeError。[optilude]

0.4.2 - 2009年4月26日

  • 更持久地安装实用程序。[alecm]

  • 删除five:traversable语句。它自Zope 2.10以来已被弃用。[hannosch]

  • 使用来自zope.component.event的objectEventNotify而不是zope.app.event。后者自Zope 2.10以来已被弃用。[hannosch]

  • 指定包依赖项。[hannosch]

0.4.1 - 2009年3月17日

  • 修复了先前版本中缺失的zcml文件。

0.4.0 - 2009年3月17日

  • 在默认的keyreference构造函数中,当对象尚未具有合适的路径时,抛出NotYet异常。这避免了过早创建键引用并指向对象的父级或不存在对象的问题。[optilude]

0.3.0 - 2008年11月7日

  • 添加无法引用的intid事件处理程序和IKeyReference实现,以处理从未真正持久化的IPersistent对象,例如CMFCore目录视图对象。[mj]

  • 移除对CMFCore目录视图对象的显式异常,并使用订阅者和适配器注册来处理无法引用的对象。[mj]

0.2.1 - 2008年11月5日

  • 避免在__cmp__中进行不必要的适配器查找,因为__cmp__被调用的次数很多,且性能敏感。现在累积时间为0.080秒,而之前为1.820秒(在分析6000次比较时)。[tesdal]

  • 避免在BTree遍历中重复调用__cmp__。[tesdal]

0.2.0 - 2008年5月20日

  • 稍微整理文档,以便可用于pypi页面。[wichert]

  • 许多人为此做出了很多改变。[alecm, hannosch, maurits, mborch, reinout, rockt, witsch]

0.1.4 - 2006年11月11日

  • 首次公开发布。[brcwhit]

由以下机构支持

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