跳转到主要内容

使用Python代码和ZCML管理IComponents实例。

项目描述

此包的目的是定义、填充和使用多个IComponents实例,即使用基于文件系统的开发,换句话说,Python代码和ZCML。

详细文档

基本组件

此包的目的是定义、填充和使用多个IComponents实例,使用基于文件系统的开发——换句话说,Python代码和ZCML。

动机

当前组件架构的状态使我们能够

  1. 创建一个全局组件注册表,使用ZCML填充它,并通过zope.component API函数使用它。

  2. 定义本地站点(本地组件注册表),用本地(持久)组件填充它们,并根据位置选择性地使用它们——通常由URL路径定义。

然而,不幸的是,使用ZCML填充本地站点是不可能的。主要原因是在初始启动过程中无法对本地站点进行寻址。

然而,另一方面,我们有一个非常先进的UI配置系统,包括视图、资源、层和皮肤。让我们比较一下这两者。

  1. UI中的视图/资源类似于组件架构中注册的组件。

  2. UI中的皮肤层行为与注册表非常相似。默认皮肤就像全局基础注册表。皮肤,就像本地站点一样,在遍历期间被激活,但可以使用ZCML进行填充。

  3. 层实际上是皮肤层的基层。在组件架构中,这相当于为组件注册表指定基础,这是由于2006年Zope 3.3的大组件架构重构所实现的。

但层可以通过ZCML进行定义和配置。此包的目的是能够创建基础组件注册表,然后使用ZCML填充它们。(顺便说一句:由于皮肤层和层实际上是相同的组件,所以全局、本地和基础组件注册表的概念之间没有区别。)

第二个功能是特定于Zope应用程序服务器的。它提供了一个UI来设置本地站点管理器上的基础。用户可以从所有已注册为IComponents实用工具的注册表中进行选择。

还有一些未来可能考虑的选项。例如,将zope:registerIn指令(请参阅下面的文档)集成到zope:configure指令中很简单。

如果上述文本对您来说太枯燥和理论化了,这里有一个总结。这个包

  1. 实现了Steve Alexander(至少3年)通过ZCML定义本地站点的长期梦想。

  2. 解决了我在复杂的应用程序服务提供商(ASP)设置中遇到的所有问题。

  3. 实现了一个您和所有人真正想要的缺失功能,即使您现在还不知道。

感谢Jim Fulton,他杰出的zope.configurationzope.component包的设计使得功能的实现变得如此简单。我还要感谢Fred Drake在初始设计思想上的帮助。

“基础组件”注册表

基础注册表是实现IComponents接口的全局组件注册表。与基础全局注册表(也称为globalSiteManager)相比,这些注册表不一定可以通过模块全局变量访问,必须与父注册表注册,最常见的是基础全局注册表

>>> from z3c.baseregistry import baseregistry
>>> import zope.component
>>> myRegistry = baseregistry.BaseComponents(
...     zope.component.globalSiteManager, 'myRegistry')
>>> myRegistry
<BaseComponents myRegistry>

另一个非常重要的要求是必须安装zope.component钩子。

>>> import zope.component.hooks
>>> zope.component.hooks.setHooks()

由于此注册表本身不实现任何IComponents API,因此在此处不需要展示这些功能。请参阅zope.component包中的相应文档。

全局注册表的一个特性必须是它们能够有效地pickle,因为它们可以在持久化对象中被引用。正如您所看到的,基础注册表pickle得相当好

>>> import pickle
>>> jar = pickle.dumps(myRegistry, 2)
>>> len(jar) <= 100
True

然而,当我们读取jar时,我们会得到一个错误

>>> pickle.loads(jar)
Traceback (most recent call last):
...
zope.interface.interfaces.ComponentLookupError: (<InterfaceClass zope.interface.interfaces.IComponents>, 'myRegistry')

这是因为我们还没有将其注册为其父级作为IComponents实用工具

>>> from zope.interface.interfaces import IComponents
>>> zope.component.provideUtility(myRegistry, IComponents, 'myRegistry')
>>> pickle.loads(jar)
<BaseComponents myRegistry>

因此,您始终必须将其基础注册表与其父级注册!

像任何其他组件注册表一样,基础注册表也可以有基础

>>> myOtherRegistry = baseregistry.BaseComponents(
...     zope.component.globalSiteManager, 'myRegistry', (myRegistry,))
>>> myOtherRegistry.__bases__
(<BaseComponents myRegistry>,)

现在让我们看看如何通过ZCML定义和使用基础注册表,这是通常的操作模式。

定义基础注册表

上述任务通常在ZCML中完成。基础组件注册表——或者任何IComponents实现——可以看作是提供上述接口的实用工具,并且可以通过名称区分。让我们定义一个“自定义”注册表

>>> custom = baseregistry.BaseComponents(
...     zope.component.globalSiteManager, 'custom')

让我们确保自定义注册表的父级是基础注册表

>>> custom.__parent__
<BaseGlobalComponents base>

然后使用标准实用程序指令进行注册。在加载此包的元指令后,

>>> from zope.configuration import xmlconfig
>>> from zope.configuration.config import ConfigurationConflictError
>>> context = xmlconfig.string('''
... <configure i18n_domain="zope">
...   <include package="z3c.baseregistry" file="meta.zcml" />
...   <include package="zope.component" file="meta.zcml" />
... </configure>
... ''')

我们可以注册注册表

>>> context = xmlconfig.string('''
... <configure xmlns="http://namespaces.zope.org/zope" i18n_domain="zope">
...
...   <utility
...       component="README.custom"
...       provides="zope.interface.interfaces.IComponents"
...       name="custom" />
...
... </configure>
... ''', context=context)

现在可以通过以下方式访问新的注册表

>>> custom = zope.component.getUtility(IComponents, name='custom')
>>> custom
<BaseComponents custom>

填充不同的注册表

现在来到有趣的部分。让我们为全局基本注册表和“自定义”注册表注册组件。首先,让我们创建一些可以注册的实用程序

>>> import zope.interface
>>> class IExample(zope.interface.Interface):
...     name = zope.interface.Attribute('Name of Example')
>>> @zope.interface.implementer(IExample)
... class Example(object):
...     def __init__(self, name):
...         self.name = name
...     def __repr__(self):
...         return '<%s %r>' %(self.__class__.__name__, self.name)
>>> example1 = Example('example1')
>>> example2 = Example('example2')

创建一些可以注册的适配器

>>> class IToAdapt1(zope.interface.Interface):
...     pass
>>> class IToAdapt2(zope.interface.Interface):
...     pass
>>> class IAdapted(zope.interface.Interface):
...     pass
>>> @zope.component.adapter(IToAdapt1)
... @zope.interface.implementer(IAdapted)
... def adapter1(context):
...     return "adapted1"
>>> @zope.component.adapter(IToAdapt2)
... @zope.interface.implementer(IAdapted)
... def adapter2(context):
...     return "adapted2"
>>> @zope.interface.implementer(IToAdapt1)
... class ToAdapt1(object):
...     def __init__(self, name):
...         self.name = name
...     def __repr__(self):
...         return '<%s %r>' %(self.__class__.__name__, self.name)
>>> toAdapt1 = ToAdapt1('toAdapt1')
>>> @zope.interface.implementer(IToAdapt2)
... class ToAdapt2(object):
...     def __init__(self, name):
...         self.name = name
...     def __repr__(self):
...         return '<%s %r>' %(self.__class__.__name__, self.name)
>>> toAdapt2 = ToAdapt2('toAdapt2')

现在先在全局注册表中注册“example1”,适配器1,在我们自定义注册表中注册“example2”,“adapter2”

>>> context = xmlconfig.string('''
... <configure xmlns="http://namespaces.zope.org/zope" i18n_domain="zope">
...
...   <utility component="README.example1"
...            name="example1" />
...   <adapter
...         factory="README.adapter1"
...         name="adapter1"/>
...
...   <registerIn registry="README.custom">
...     <utility component="README.example2"
...              name="example2" />
...     <adapter
...         factory="README.adapter2"
...         name="adapter2"/>
...   </registerIn>
...
... </configure>
... ''', context=context)

现在确保实用程序已正确注册到相应的注册表

>>> zope.component.getUtility(IExample, name="example1")
<Example 'example1'>
>>> zope.component.getUtility(IExample, name="example2")
Traceback (most recent call last):
...
zope.interface.interfaces.ComponentLookupError: (<InterfaceClass README.IExample>, 'example2')

现在确保适配器已正确注册到相应的注册表

>>> zope.component.getAdapter(toAdapt1, IAdapted, name="adapter1")
'adapted1'
>>> zope.component.getAdapter(toAdapt2, IAdapted, name="adapter2")
Traceback (most recent call last):
...
zope.interface.interfaces.ComponentLookupError: (<ToAdapt2 'toAdapt2'>, <InterfaceClass README.IAdapted>, 'adapter2')
>>> custom = zope.component.getUtility(IComponents, name='custom')
>>> custom.getUtility(IExample, name="example1")
Traceback (most recent call last):
...
zope.interface.interfaces.ComponentLookupError: (<InterfaceClass README.IExample>, 'example1')
>>> custom.getUtility(IExample, name="example2")
<Example 'example2'>
>>> custom.getAdapter(toAdapt1, IAdapted, name="adapter1")
Traceback (most recent call last):
...
zope.interface.interfaces.ComponentLookupError: (<ToAdapt1 'toAdapt1'>, <InterfaceClass README.IAdapted>, 'adapter1')
>>> custom.getAdapter(toAdapt2, IAdapted, name="adapter2")
'adapted2'

现在注册没有名称的Example类的其他实例。这不应该导致冲突错误

>>> example3 = Example('example3')
>>> example4 = Example('example4')
>>> context = xmlconfig.string('''
... <configure xmlns="http://namespaces.zope.org/zope" i18n_domain="zope">
...
...   <utility component="README.example3" />
...
...   <registerIn registry="README.custom">
...     <utility component="README.example4" />
...   </registerIn>
...
... </configure>
... ''', context=context)
>>> zope.component.getUtility(IExample)
<Example 'example3'>
>>> custom.getUtility(IExample)
<Example 'example4'>

使用基本注册表

通常基本注册表将用于本地站点管理器。让我们创建一个本地站点

>>> from zope.site.folder import Folder
>>> site = Folder()
>>> from zope.site.site import LocalSiteManager
>>> site.setSiteManager(LocalSiteManager(site))
>>> sm = site.getSiteManager()

最初,只有基本全局注册表是本地站点管理器的基

>>> sm.__bases__
(<BaseGlobalComponents base>,)

现在只有来自基本站点的注册表可用

>>> sm.getUtility(IExample)
<Example 'example3'>
>>> sm.getUtility(IExample, name="example1")
<Example 'example1'>
>>> sm.getUtility(IExample, name="example2")
Traceback (most recent call last):
...
zope.interface.interfaces.ComponentLookupError: (<InterfaceClass README.IExample>, 'example2')
>>> sm.getAdapter(toAdapt1, IAdapted, name="adapter1")
'adapted1'
>>> sm.getAdapter(toAdapt2, IAdapted, name="adapter2")
Traceback (most recent call last):
...
zope.interface.interfaces.ComponentLookupError: (<ToAdapt2 'toAdapt2'>, <InterfaceClass README.IAdapted>, 'adapter2')

但如果我们添加“自定义”注册表,那么事情看起来更有趣

>>> sm.__bases__ += (custom,)
>>> sm.__bases__
(<BaseGlobalComponents base>, <BaseComponents custom>)
>>> sm.getUtility(IExample)
<Example 'example3'>
>>> sm.getUtility(IExample, name="example1")
<Example 'example1'>
>>> sm.getUtility(IExample, name="example2")
<Example 'example2'>
>>> sm.getAdapter(toAdapt1, IAdapted, name="adapter1")
'adapted1'
>>> sm.getAdapter(toAdapt2, IAdapted, name="adapter2")
'adapted2'

但是示例4的注册在哪里?嗯,基的顺序很重要,就像Python中基类的顺序一样。基从最具体到最通用运行。因此,如果我们颠倒顺序,

>>> bases = list(sm.__bases__)
>>> bases.reverse()
>>> sm.__bases__ = bases
>>> sm.__bases__
(<BaseComponents custom>, <BaseGlobalComponents base>)

那么我们的“自定义”注册表实际上覆盖了全局注册表

>>> sm.getUtility(IExample)
<Example 'example4'>
>>> sm.getUtility(IExample, name="example1")
<Example 'example1'>
>>> sm.getUtility(IExample, name="example2")
<Example 'example2'>

边缘情况和思考的食物

重复注册

像之前一样,检测并报告重复注册

>>> try:
...    xmlconfig.string('''
... <configure xmlns="http://namespaces.zope.org/zope" i18n_domain="zope">
...
...   <registerIn registry="README.custom">
...     <utility component="README.example3" name="default" />
...     <utility component="README.example4" name="default" />
...   </registerIn>
...
... </configure>
... ''', context=context)
... except ConfigurationConflictError as e:
...    print(e)
Conflicting configuration actions
  For: (<BaseComponents custom>, ('utility', <InterfaceClass README.IExample>, ...'default'))
...

但正如我们之前看到的,如果在不同站点进行相同的注册,则不会引发重复错误

>>> context = xmlconfig.string('''
... <configure xmlns="http://namespaces.zope.org/zope" i18n_domain="zope">
...
...   <utility component="README.example3" name="default" />
...
...   <registerIn registry="README.custom">
...     <utility component="README.example4" name="default" />
...   </registerIn>
...
... </configure>
... ''', context=context)
覆盖ZCML

覆盖应该像往常一样表现。如果我在特定站点内定义了某些内容,那么它应该只能在该站点中被覆盖。

在以下示例中,base-overrides.zcml仅覆盖以下片段的全局注册到“example3”

>>> context.includepath = ('base.zcml', 'original.zcml')
>>> context = xmlconfig.string('''
... <configure xmlns="http://namespaces.zope.org/zope" i18n_domain="zope">
...
...   <utility component="README.example1" />
...
...   <registerIn registry="README.custom">
...     <utility component="README.example2" />
...   </registerIn>
...
... </configure>
... ''', context=context, execute=False)
>>> context.includepath = ('base.zcml',)
>>> context = xmlconfig.string('''
...   <includeOverrides package="z3c.baseregistry.tests"
...                     file="base-overrides.zcml" />
... ''', context=context)
>>> zope.component.getUtility(IExample)
<Example 'example3'>
>>> custom.getUtility(IExample)
<Example 'example2'>

在下一个示例中,custom-overrides.zcml仅覆盖以下片段的自定义注册到“example3”

>>> context.includepath = ('base.zcml', 'original.zcml')
>>> context = xmlconfig.string('''
... <configure xmlns="http://namespaces.zope.org/zope" i18n_domain="zope">
...
...   <utility component="README.example1" />
...
...   <registerIn registry="README.custom">
...     <utility component="README.example4" />
...   </registerIn>
...
... </configure>
... ''', context=context, execute=False)
>>> context.includepath = ('base.zcml',)
>>> context = xmlconfig.string('''
... <configure xmlns="http://namespaces.zope.org/zope" i18n_domain="zope">
...
...   <includeOverrides package="z3c.baseregistry.tests"
...                     file="custom-overrides.zcml" />
...
... </configure>
... ''', context=context)
>>> zope.component.getUtility(IExample)
<Example 'example1'>
>>> custom.getUtility(IExample)
<Example 'example3'>

注意:很抱歉测试序列很复杂;这正是它的运作方式。:-)

嵌套注册使用

我考虑了很长时间,但我认为最好不允许嵌套zope:registerIn指令,因为操纵判别器的逻辑对于微小的额外好处来说将非常复杂。

>>> try:
...    xmlconfig.string('''
... <configure xmlns="http://namespaces.zope.org/zope" i18n_domain="zope">
...
...   <registerIn registry="README.custom">
...     <registerIn registry="zope.component.globalregistry.base">
...       <utility component="README.example4" />
...     </registerIn>
...   </registerIn>
...
... </configure>
... ''', context=context)
... except Exception as e:
...     print(e)
Nested ``registerIn`` directives are not permitted.
    File...
清理

只需注销zope.component钩子

>>> zope.component.hooks.resetHooks()
全局非组件注册操作

ZCML不仅负责填充组件注册表,还要执行其他全局配置,例如定义安全和将接口分配给类。另一方面,registerIn指令通过在当前注册表前缀中操纵判别器来工作。虽然我主张这是组件注册的正确方法,但它不适用于其他全局配置。

为了解决这个问题,我需要更多的信息。需要在改变现有指令和使解决方案非单一化之间取得平衡。以下是一些设计想法

  1. 特殊判别器前缀

    所有全局操纵系统状态且不注册组件的指令都将其第一个判别器条目作为特殊字符串,如“StateChange”。然后指令可以查找这些条目,并在此点不更改判别器。

    优点包括能够在registerIn指令内部使用这些指令,并允许逐步升级。另一方面,如果调整util指令,这些场景将无法进行冲突解决。

  2. 全局操作调用者注册表

    在此,此包提供了一个调用者注册表,这些调用者可以更改系统的状态。指令作者可以将他们的调用者订阅到此注册表中。

    这种方法的优点是您可以在不更改任何实现的情况下立即让所有内置指令都生效。缺点是这种解决方案隐藏了指令作者的问题,因此必须提供详细的文档以确保完整性和避免意外。另一个缺点是另一个注册表的复杂性。

  3. 带有误报的自动检测

    据我所知,所有操作组件注册表都使用zope.component.zcml.handler函数。好吧,这让我能够检测到这些。不幸的是,可能存在不操作状态的指令,例如确保某个事物的存在。在核心中有一堆这样的指令。

    这里的优点是对于核心来说应该只是正常工作。然而,第三方指令开发者可能会被这个特性所困扰。此外,我们只能用这个解决方案发出警告,可能还需要能够关闭它们。

我尚未实施任何这些建议,正在等待社区的反馈。

更改

3.0 (2023-02-09)

  • 停止支持Python 2.7、3.4、3.5、3.6。

  • 添加对Python 3.8、3.9、3.10、3.11的支持。

  • 使测试与zope.component >= 5兼容。

2.2.0 (2018-10-19)

  • 添加对Python 3.7的支持。

  • 停止支持Python 3.3。

2.1.0 (2017-05-03)

  • 添加对Python 3.4、3.5、3.6和PyPy的支持。

  • 删除对zope.app.testingzope.app.zcmlfiles等测试的依赖。

2.0.0 (2012-11-17)

  • zope.configuration将操作元组改为操作字典。此版本从3.8.0版本开始与zope.configuration的新操作字典兼容。此版本与3.8.0之前的zope.configuration版本不兼容。

1.3.0 (2010-10-28)

  • baseregistry与ZCA交互方式的基本改变。现在它使用hooks.setSite,这要求zope.component钩子就绪。通常它们由zope.app.appsetup安装。除非您使用zope.app.appsetup,否则请使用zope.component.hooks.setHooks()安装钩子。这适用于zope.component版本>=3.9.4。

1.2.0 (2009-12-27)

  • 将浏览器依赖项移动到zmi额外组件中

1.1.0 (2009-03-19)

  • 修复在站点在其__bases__中具有其父站点的本地站点管理器(未注册为实用程序)时基注册表管理表单失败的问题。

  • 使用zope.site代替zope.app.component。

  • 删除对zope.app.i18n和zope.app.pagetemplate的未使用依赖。

1.0.0 (2008-01-24)

  • 初始发布

项目详情


下载文件

下载适合您平台的文件。如果您不确定选择哪个,请了解更多关于安装包的信息。

源分发

z3c.baseregistry-3.0.tar.gz (30.5 kB 查看哈希值)

上传时间

构建分发

z3c.baseregistry-3.0-py3-none-any.whl (28.1 kB 查看哈希值)

上传于 Python 3

由以下支持