The purpose of this package is to define, populate and use multiple IComponents
instances using filesystem-based development in other words, Python code and
ZCML.
Detailed Documentation
======================
===============
Base Components
===============
The purpose of this package is to define, populate and use multiple
``IComponents`` instances using filesystem-based development -- in other
words, Python code and ZCML.
Motivation
----------
The current state of the component architecture allows us to
1. create a global components registry, populate it using ZCML, and use it
via the ``zope.component`` API functions.
2. define local sites (local components registries), populate them with local
(persistent) components, and use them selectively based on location --
commonly defined by the path of the URL.
Unfortunately, it is impossible to populate local sites with ZCML. The main
reason is the lack of addressability of local sites during the initial startup
process.
However, on the other hand we have a very advanced UI configuration system
that involves views, resources, layers and skins. So let's compare the two.
1. Views/Resources in the UI are like registered components in the component
architecture.
2. Skin Layers in the UI behave very much like registries. The default skin
is like the global base registry. Skins, like local sites, are activated
during traversal, but can be populated using ZCML.
3. Layers are really base layers to the skin layer. The equivalent in the
component architecture is to specify bases for a components registry,
which is possible since the Great Component Architecture refactoring for
Zope 3.3 in 2006.
But layers can be defined and configured via ZCML. The purpose of this package
is to be able to create base components registries and then populate them
using ZCML. (As a side note: As skin layers and layers are practically the
same components, there is no difference between the concept of global, local
and base components registries.)
The second feature is specific to the Zope application server. It provides an
UI to set the bases on a local site manager. The user can select among all
registries that have been registered as ``IComponents`` utilities.
There are also a few options that could be considered in the future. For
example, it would be simple to integrate the ``zope:registerIn`` directive
(see below for documentation) into the ``zope:configure`` directive.
If the above text is too dry and theoretical for you, here is the
summary. This package
1. implements Steve Alexander's long dream (at least 3 years) of defining
local sites via ZCML.
2. solves all of my (Stephan Richter) problems I am having with a complex
Application Service Provider (ASP) setup.
3. implements a missing feature that you and everyone else really wanted,
even if you did not know it yet.
Thanks goes to Jim Fulton, whose outstanding design of the
``zope.configuration`` and ``zope.component`` packages made the implementation
of the feature such a breeze. I also want to thank Fred Drake for helping with
the initial design ideas.
"Base Components" Registries
----------------------------
Base registries are global component registries implementing the
``IComponents`` interface. In comparison to the base global registry (also
known as ``globalSiteManager``), these registries are not necessarily
available via module globals and *must* be registered with a parent registry,
most commonly the base global registry:
>>> from z3c.baseregistry import baseregistry
>>> import zope.component
>>> myRegistry = baseregistry.BaseComponents(
... zope.component.globalSiteManager, 'myRegistry')
>>> myRegistry
<BaseComponents myRegistry>
Another *VERY IMPORTANT* requirement is that ``zope.component`` hooks are in
place. Install the hooks now:
>>> import zope.component.hooks
>>> zope.component.hooks.setHooks()
Since this registry does not implement any of the ``IComponents`` API itself,
it is not necessary to demonstrate those features here. Please see the
corresponding documentation in the ``zope.component`` package.
One feature of global registries must be that they pickle efficiently, since
they can be referenced in persisted objects. As you can see, the base registry
pickles quite well:
>>> import pickle
>>> jar = pickle.dumps(myRegistry, 2)
>>> len(jar) <= 100
True
However, when reading the jar, we get an error:
>>> pickle.loads(jar)
Traceback (most recent call last):
...
zope.interface.interfaces.ComponentLookupError: (<InterfaceClass zope.interface.interfaces.IComponents>, 'myRegistry')
This is because we have not registered the registry in its parent as an
``IComponents`` utility, yet:
>>> from zope.interface.interfaces import IComponents
>>> zope.component.provideUtility(myRegistry, IComponents, 'myRegistry')
>>> pickle.loads(jar)
<BaseComponents myRegistry>
Thus it is very important that you *always* register your base registry with
its parent!
Like any other components registry, a base registry can also have bases:
>>> myOtherRegistry = baseregistry.BaseComponents(
... zope.component.globalSiteManager, 'myRegistry', (myRegistry,))
>>> myOtherRegistry.__bases__
(<BaseComponents myRegistry>,)
Let's now have a look at how base registries can be defined and used
via ZCML, which is the usual mode of operation.
Defining Base Registries
------------------------
The above tasks are more commonly done in ZCML. Base components registries --
or any ``IComponents`` implementation for that matter -- can be seen as
utilities providing the aforementioned interface and are distinguishable by
name. So let's define a "custom" registry:
>>> custom = baseregistry.BaseComponents(
... zope.component.globalSiteManager, 'custom')
Let's make sure that the parent of the custom registry is the base registry:
>>> custom.__parent__
<BaseGlobalComponents base>
The registry is then registered using the standard utility directive. After
loading the meta directives for this package,
>>> 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>
... ''')
we can register the registry:
>>> 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)
The new registry can now be accessed as follows:
>>> custom = zope.component.getUtility(IComponents, name='custom')
>>> custom
<BaseComponents custom>
Populating Different Registries
-------------------------------
Now to the interesting part. Let's register components for both the global
base and the "custom" registry. Let's first create some utilities we can
register:
>>> 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')
Create some adapters we can register:
>>> 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')
Let' now register "example1", adapter1 in the global registry
and "example2", "adapter2" in our custom registry:
>>> 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)
Let's now make sure that the utilities have been registered in the right
registry:
>>> 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')
Let's now make sure that the adapters have been registered in the right
registry:
>>> 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'
Let's now register other instances of the ``Example`` class without a
name. This should *not* cause a conflict error:
>>> 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'>
Using Base Registries
---------------------
Most commonly base registries will be used in local site managers. So let's
create a local site:
>>> from zope.site.folder import Folder
>>> site = Folder()
>>> from zope.site.site import LocalSiteManager
>>> site.setSiteManager(LocalSiteManager(site))
>>> sm = site.getSiteManager()
Initially only the base global registry is a base of the local site manager:
>>> sm.__bases__
(<BaseGlobalComponents base>,)
Now only registrations from the base site are available:
>>> 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')
But if we add the "custom" registry, then things look more interesting:
>>> 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'
But where is the registration for example 4? Well, the order of the bases
matters, like the order of base classes in Python matters. The bases run from
must specific to most generic. Thus, if we reverse the order,
>>> bases = list(sm.__bases__)
>>> bases.reverse()
>>> sm.__bases__ = bases
>>> sm.__bases__
(<BaseComponents custom>, <BaseGlobalComponents base>)
then our "custom" registry effectively overrides the global one:
>>> sm.getUtility(IExample)
<Example 'example4'>
>>> sm.getUtility(IExample, name="example1")
<Example 'example1'>
>>> sm.getUtility(IExample, name="example2")
<Example 'example2'>
Edge Cases and Food for Thought
-------------------------------
Duplicate Registrations
~~~~~~~~~~~~~~~~~~~~~~~
Like before, duplicate registrations are detected and reported:
>>> 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'))
...
But as we have seen before, no duplication error is raised, if the same
registration is made for different sites:
>>> 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)
Overriding ZCML
~~~~~~~~~~~~~~~
Overriding should behave as usual. If I define something within a particular
site, then it should be only overridable in that site.
In the following example, ``base-overrides.zcml`` overrides only the global
registration of the following snippet to "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'>
In the next example, ``custom-overrides.zcml`` overrides only the custom
registration of the following snippet to "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'>
Note: Sorry for the convoluted test sequence; this is just how it works. :-(
Nested Registry Usage
~~~~~~~~~~~~~~~~~~~~~
I thought about this one for a long time, but I think it is better not
allowing to nest ``zope:registerIn`` directives, because the logic of
manipulating the discriminator would be very complex for very little added
benefit.
>>> 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...
Cleanup
~~~~~~~
Just unregister the ``zope.component`` hooks:
>>> zope.component.hooks.resetHooks()
Global Non-Component-Registration Actions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ZCML is not only responsible for populating the components registries, but also
to do other global configuration, such as defining security and assigning
interfaces to classes. On the other hand, the ``registerIn`` directive works
by manipulating the discriminator by prefixing it with the current
registry. While I assert that this is the right approach for component
registrations, it does not work for those other global configurations.
In order to address the issue, I need somehow more information. A balance must
be struck between the need to change existing directives and making the
solution non-monolithic. Here are some design ideas:
1. A Special Discriminator Prefix
All directives that globally manipulate the state of the system and do not
register a component have as their first discriminator entry a special
string, like "StateChange". The directive can then look for those entries and
not change the discriminator at this point.
Advantages include the ability to use those directives inside the
``registerIn`` directive and allow gradual upgrading. In the other hand, util
directives are adjusted, conflict resolution will not be available for those
scenarios.
2. A Registry of Global Action Callables
Here this package provides a registry of callables that change the state of
the system. Directive authors can then subscribe their callables to this
registry.
The big advantage of this approach is that you can make it work now for all
built-in directives without changing any implementation. The disadvantage is
that the solution hides the problem to directive authors, so that detailed
documentation must be provided to ensure integrity and avoid
surprises. Another disadvantage is the complexity of yet another registry.
3. Autodetection with False-Positives
As far as I can tell, all actions that manipulate the components registries
use the ``zope.component.zcml.handler`` function. Okay, so that allows me to
detect those. Unfortunately, there might be directives that do *not*
manipulate the state, for example ensuring the existence of something. There
are a bunch of those directives in the core.
The advantage here is that for the core it should just work. However, 3rd
party directive developers might be tripped by this feature. Also, we could
only issue warnings with this solution and probably need to be able to turn
them off.
I have not implemented any of those suggestions, waiting for input from the
community.
=========
CHANGES
=========
3.0 (2023-02-09)
================
- Drop support for Python 2.7, 3.4, 3.5, 3.6.
- Add support for Python 3.8, 3.9, 3.10, 3.11.
- Make tests compatible with ``zope.component >= 5``.
2.2.0 (2018-10-19)
==================
- Add support for Python 3.7.
- Drop support for Python 3.3.
2.1.0 (2017-05-03)
==================
- Add support for Python 3.4, 3.5, 3.6 and PyPy.
- Remove test dependency on ``zope.app.testing`` and
``zope.app.zcmlfiles``, among others.
2.0.0 (2012-11-17)
==================
- zope.configuration changed action tuples to action dicts. This version works
with the new action dict given from zope.configuration since version 3.8.0.
This version is not compatible with zope.configuration version less then
3.8.0
1.3.0 (2010-10-28)
==================
- Fundamental change in the way how baseregistry hooks into ZCA.
Now it uses hooks.setSite, which requires that zope.component hooks
are in place. Usually they are installed by zope.app.appsetup.
Unless you use zope.app.appsetup, install the hooks with
zope.component.hooks.setHooks().
This applies to zope.component versions >= 3.9.4.
1.2.0 (2009-12-27)
==================
- Moved browser dependencies to zmi extras
1.1.0 (2009-03-19)
==================
- Fix base registry management form failure in case, when a site has its
parent's local site manager (that isn't registered as utility) in its
__bases__.
- Use zope.site instead of zope.app.component.
- Drop unused dependencies on zope.app.i18n and zope.app.pagetemplate.
1.0.0 (2008-01-24)
==================
- Initial Release
Raw data
{
"_id": null,
"home_page": "https://github.com/zopefoundation/z3c.baseregistry",
"name": "z3c.baseregistry",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.7",
"maintainer_email": "",
"keywords": "zope3 z3c component global registry baseregistry",
"author": "Stephan Richter, Roger Ineichen and the Zope Community",
"author_email": "zope-dev@zope.dev",
"download_url": "https://files.pythonhosted.org/packages/eb/8d/17d3169c9eec5d82130a26c87cc7a5c228ae1357589c8e2a69ae359e776f/z3c.baseregistry-3.0.tar.gz",
"platform": null,
"description": "The purpose of this package is to define, populate and use multiple IComponents\ninstances using filesystem-based development in other words, Python code and\nZCML.\n\n\nDetailed Documentation\n======================\n\n===============\nBase Components\n===============\n\nThe purpose of this package is to define, populate and use multiple\n``IComponents`` instances using filesystem-based development -- in other\nwords, Python code and ZCML.\n\n\nMotivation\n----------\n\nThe current state of the component architecture allows us to\n\n1. create a global components registry, populate it using ZCML, and use it\n via the ``zope.component`` API functions.\n\n2. define local sites (local components registries), populate them with local\n (persistent) components, and use them selectively based on location --\n commonly defined by the path of the URL.\n\nUnfortunately, it is impossible to populate local sites with ZCML. The main\nreason is the lack of addressability of local sites during the initial startup\nprocess.\n\nHowever, on the other hand we have a very advanced UI configuration system\nthat involves views, resources, layers and skins. So let's compare the two.\n\n1. Views/Resources in the UI are like registered components in the component\n architecture.\n\n2. Skin Layers in the UI behave very much like registries. The default skin\n is like the global base registry. Skins, like local sites, are activated\n during traversal, but can be populated using ZCML.\n\n3. Layers are really base layers to the skin layer. The equivalent in the\n component architecture is to specify bases for a components registry,\n which is possible since the Great Component Architecture refactoring for\n Zope 3.3 in 2006.\n\nBut layers can be defined and configured via ZCML. The purpose of this package\nis to be able to create base components registries and then populate them\nusing ZCML. (As a side note: As skin layers and layers are practically the\nsame components, there is no difference between the concept of global, local\nand base components registries.)\n\nThe second feature is specific to the Zope application server. It provides an\nUI to set the bases on a local site manager. The user can select among all\nregistries that have been registered as ``IComponents`` utilities.\n\nThere are also a few options that could be considered in the future. For\nexample, it would be simple to integrate the ``zope:registerIn`` directive\n(see below for documentation) into the ``zope:configure`` directive.\n\nIf the above text is too dry and theoretical for you, here is the\nsummary. This package\n\n1. implements Steve Alexander's long dream (at least 3 years) of defining\n local sites via ZCML.\n\n2. solves all of my (Stephan Richter) problems I am having with a complex\n Application Service Provider (ASP) setup.\n\n3. implements a missing feature that you and everyone else really wanted,\n even if you did not know it yet.\n\nThanks goes to Jim Fulton, whose outstanding design of the\n``zope.configuration`` and ``zope.component`` packages made the implementation\nof the feature such a breeze. I also want to thank Fred Drake for helping with\nthe initial design ideas.\n\n\n\"Base Components\" Registries\n----------------------------\n\nBase registries are global component registries implementing the\n``IComponents`` interface. In comparison to the base global registry (also\nknown as ``globalSiteManager``), these registries are not necessarily\navailable via module globals and *must* be registered with a parent registry,\nmost commonly the base global registry:\n\n >>> from z3c.baseregistry import baseregistry\n >>> import zope.component\n >>> myRegistry = baseregistry.BaseComponents(\n ... zope.component.globalSiteManager, 'myRegistry')\n\n >>> myRegistry\n <BaseComponents myRegistry>\n\nAnother *VERY IMPORTANT* requirement is that ``zope.component`` hooks are in\nplace. Install the hooks now:\n\n >>> import zope.component.hooks\n >>> zope.component.hooks.setHooks()\n\n\nSince this registry does not implement any of the ``IComponents`` API itself,\nit is not necessary to demonstrate those features here. Please see the\ncorresponding documentation in the ``zope.component`` package.\n\nOne feature of global registries must be that they pickle efficiently, since\nthey can be referenced in persisted objects. As you can see, the base registry\npickles quite well:\n\n >>> import pickle\n >>> jar = pickle.dumps(myRegistry, 2)\n >>> len(jar) <= 100\n True\n\nHowever, when reading the jar, we get an error:\n\n >>> pickle.loads(jar)\n Traceback (most recent call last):\n ...\n zope.interface.interfaces.ComponentLookupError: (<InterfaceClass zope.interface.interfaces.IComponents>, 'myRegistry')\n\nThis is because we have not registered the registry in its parent as an\n``IComponents`` utility, yet:\n\n >>> from zope.interface.interfaces import IComponents\n >>> zope.component.provideUtility(myRegistry, IComponents, 'myRegistry')\n\n >>> pickle.loads(jar)\n <BaseComponents myRegistry>\n\nThus it is very important that you *always* register your base registry with\nits parent!\n\nLike any other components registry, a base registry can also have bases:\n\n >>> myOtherRegistry = baseregistry.BaseComponents(\n ... zope.component.globalSiteManager, 'myRegistry', (myRegistry,))\n >>> myOtherRegistry.__bases__\n (<BaseComponents myRegistry>,)\n\nLet's now have a look at how base registries can be defined and used\nvia ZCML, which is the usual mode of operation.\n\n\nDefining Base Registries\n------------------------\n\nThe above tasks are more commonly done in ZCML. Base components registries --\nor any ``IComponents`` implementation for that matter -- can be seen as\nutilities providing the aforementioned interface and are distinguishable by\nname. So let's define a \"custom\" registry:\n\n >>> custom = baseregistry.BaseComponents(\n ... zope.component.globalSiteManager, 'custom')\n\nLet's make sure that the parent of the custom registry is the base registry:\n\n >>> custom.__parent__\n <BaseGlobalComponents base>\n\nThe registry is then registered using the standard utility directive. After\nloading the meta directives for this package,\n\n >>> from zope.configuration import xmlconfig\n >>> from zope.configuration.config import ConfigurationConflictError\n >>> context = xmlconfig.string('''\n ... <configure i18n_domain=\"zope\">\n ... <include package=\"z3c.baseregistry\" file=\"meta.zcml\" />\n ... <include package=\"zope.component\" file=\"meta.zcml\" />\n ... </configure>\n ... ''')\n\nwe can register the registry:\n\n >>> context = xmlconfig.string('''\n ... <configure xmlns=\"http://namespaces.zope.org/zope\" i18n_domain=\"zope\">\n ...\n ... <utility\n ... component=\"README.custom\"\n ... provides=\"zope.interface.interfaces.IComponents\"\n ... name=\"custom\" />\n ...\n ... </configure>\n ... ''', context=context)\n\nThe new registry can now be accessed as follows:\n\n >>> custom = zope.component.getUtility(IComponents, name='custom')\n >>> custom\n <BaseComponents custom>\n\n\nPopulating Different Registries\n-------------------------------\n\nNow to the interesting part. Let's register components for both the global\nbase and the \"custom\" registry. Let's first create some utilities we can\nregister:\n\n >>> import zope.interface\n\n >>> class IExample(zope.interface.Interface):\n ... name = zope.interface.Attribute('Name of Example')\n\n >>> @zope.interface.implementer(IExample)\n ... class Example(object):\n ... def __init__(self, name):\n ... self.name = name\n ... def __repr__(self):\n ... return '<%s %r>' %(self.__class__.__name__, self.name)\n\n >>> example1 = Example('example1')\n >>> example2 = Example('example2')\n\nCreate some adapters we can register:\n\n >>> class IToAdapt1(zope.interface.Interface):\n ... pass\n\n >>> class IToAdapt2(zope.interface.Interface):\n ... pass\n\n >>> class IAdapted(zope.interface.Interface):\n ... pass\n\n >>> @zope.component.adapter(IToAdapt1)\n ... @zope.interface.implementer(IAdapted)\n ... def adapter1(context):\n ... return \"adapted1\"\n\n >>> @zope.component.adapter(IToAdapt2)\n ... @zope.interface.implementer(IAdapted)\n ... def adapter2(context):\n ... return \"adapted2\"\n\n >>> @zope.interface.implementer(IToAdapt1)\n ... class ToAdapt1(object):\n ... def __init__(self, name):\n ... self.name = name\n ... def __repr__(self):\n ... return '<%s %r>' %(self.__class__.__name__, self.name)\n >>> toAdapt1 = ToAdapt1('toAdapt1')\n\n >>> @zope.interface.implementer(IToAdapt2)\n ... class ToAdapt2(object):\n ... def __init__(self, name):\n ... self.name = name\n ... def __repr__(self):\n ... return '<%s %r>' %(self.__class__.__name__, self.name)\n >>> toAdapt2 = ToAdapt2('toAdapt2')\n\nLet' now register \"example1\", adapter1 in the global registry\nand \"example2\", \"adapter2\" in our custom registry:\n\n >>> context = xmlconfig.string('''\n ... <configure xmlns=\"http://namespaces.zope.org/zope\" i18n_domain=\"zope\">\n ...\n ... <utility component=\"README.example1\"\n ... name=\"example1\" />\n ... <adapter\n ... factory=\"README.adapter1\"\n ... name=\"adapter1\"/>\n ...\n ... <registerIn registry=\"README.custom\">\n ... <utility component=\"README.example2\"\n ... name=\"example2\" />\n ... <adapter\n ... factory=\"README.adapter2\"\n ... name=\"adapter2\"/>\n ... </registerIn>\n ...\n ... </configure>\n ... ''', context=context)\n\nLet's now make sure that the utilities have been registered in the right\nregistry:\n\n >>> zope.component.getUtility(IExample, name=\"example1\")\n <Example 'example1'>\n\n >>> zope.component.getUtility(IExample, name=\"example2\")\n Traceback (most recent call last):\n ...\n zope.interface.interfaces.ComponentLookupError: (<InterfaceClass README.IExample>, 'example2')\n\nLet's now make sure that the adapters have been registered in the right\nregistry:\n\n >>> zope.component.getAdapter(toAdapt1, IAdapted, name=\"adapter1\")\n 'adapted1'\n\n >>> zope.component.getAdapter(toAdapt2, IAdapted, name=\"adapter2\")\n Traceback (most recent call last):\n ...\n zope.interface.interfaces.ComponentLookupError: (<ToAdapt2 'toAdapt2'>, <InterfaceClass README.IAdapted>, 'adapter2')\n\n\n >>> custom = zope.component.getUtility(IComponents, name='custom')\n\n >>> custom.getUtility(IExample, name=\"example1\")\n Traceback (most recent call last):\n ...\n zope.interface.interfaces.ComponentLookupError: (<InterfaceClass README.IExample>, 'example1')\n\n >>> custom.getUtility(IExample, name=\"example2\")\n <Example 'example2'>\n\n\n >>> custom.getAdapter(toAdapt1, IAdapted, name=\"adapter1\")\n Traceback (most recent call last):\n ...\n zope.interface.interfaces.ComponentLookupError: (<ToAdapt1 'toAdapt1'>, <InterfaceClass README.IAdapted>, 'adapter1')\n\n >>> custom.getAdapter(toAdapt2, IAdapted, name=\"adapter2\")\n 'adapted2'\n\n\nLet's now register other instances of the ``Example`` class without a\nname. This should *not* cause a conflict error:\n\n >>> example3 = Example('example3')\n >>> example4 = Example('example4')\n\n >>> context = xmlconfig.string('''\n ... <configure xmlns=\"http://namespaces.zope.org/zope\" i18n_domain=\"zope\">\n ...\n ... <utility component=\"README.example3\" />\n ...\n ... <registerIn registry=\"README.custom\">\n ... <utility component=\"README.example4\" />\n ... </registerIn>\n ...\n ... </configure>\n ... ''', context=context)\n\n >>> zope.component.getUtility(IExample)\n <Example 'example3'>\n\n >>> custom.getUtility(IExample)\n <Example 'example4'>\n\n\nUsing Base Registries\n---------------------\n\nMost commonly base registries will be used in local site managers. So let's\ncreate a local site:\n\n >>> from zope.site.folder import Folder\n >>> site = Folder()\n\n >>> from zope.site.site import LocalSiteManager\n >>> site.setSiteManager(LocalSiteManager(site))\n >>> sm = site.getSiteManager()\n\nInitially only the base global registry is a base of the local site manager:\n\n >>> sm.__bases__\n (<BaseGlobalComponents base>,)\n\nNow only registrations from the base site are available:\n\n >>> sm.getUtility(IExample)\n <Example 'example3'>\n\n >>> sm.getUtility(IExample, name=\"example1\")\n <Example 'example1'>\n\n >>> sm.getUtility(IExample, name=\"example2\")\n Traceback (most recent call last):\n ...\n zope.interface.interfaces.ComponentLookupError: (<InterfaceClass README.IExample>, 'example2')\n\n >>> sm.getAdapter(toAdapt1, IAdapted, name=\"adapter1\")\n 'adapted1'\n\n >>> sm.getAdapter(toAdapt2, IAdapted, name=\"adapter2\")\n Traceback (most recent call last):\n ...\n zope.interface.interfaces.ComponentLookupError: (<ToAdapt2 'toAdapt2'>, <InterfaceClass README.IAdapted>, 'adapter2')\n\nBut if we add the \"custom\" registry, then things look more interesting:\n\n >>> sm.__bases__ += (custom,)\n >>> sm.__bases__\n (<BaseGlobalComponents base>, <BaseComponents custom>)\n\n >>> sm.getUtility(IExample)\n <Example 'example3'>\n\n >>> sm.getUtility(IExample, name=\"example1\")\n <Example 'example1'>\n\n >>> sm.getUtility(IExample, name=\"example2\")\n <Example 'example2'>\n\n >>> sm.getAdapter(toAdapt1, IAdapted, name=\"adapter1\")\n 'adapted1'\n\n >>> sm.getAdapter(toAdapt2, IAdapted, name=\"adapter2\")\n 'adapted2'\n\nBut where is the registration for example 4? Well, the order of the bases\nmatters, like the order of base classes in Python matters. The bases run from\nmust specific to most generic. Thus, if we reverse the order,\n\n >>> bases = list(sm.__bases__)\n >>> bases.reverse()\n >>> sm.__bases__ = bases\n >>> sm.__bases__\n (<BaseComponents custom>, <BaseGlobalComponents base>)\n\nthen our \"custom\" registry effectively overrides the global one:\n\n >>> sm.getUtility(IExample)\n <Example 'example4'>\n\n >>> sm.getUtility(IExample, name=\"example1\")\n <Example 'example1'>\n\n >>> sm.getUtility(IExample, name=\"example2\")\n <Example 'example2'>\n\n\nEdge Cases and Food for Thought\n-------------------------------\n\nDuplicate Registrations\n~~~~~~~~~~~~~~~~~~~~~~~\n\nLike before, duplicate registrations are detected and reported:\n\n >>> try:\n ... xmlconfig.string('''\n ... <configure xmlns=\"http://namespaces.zope.org/zope\" i18n_domain=\"zope\">\n ...\n ... <registerIn registry=\"README.custom\">\n ... <utility component=\"README.example3\" name=\"default\" />\n ... <utility component=\"README.example4\" name=\"default\" />\n ... </registerIn>\n ...\n ... </configure>\n ... ''', context=context)\n ... except ConfigurationConflictError as e:\n ... print(e)\n Conflicting configuration actions\n For: (<BaseComponents custom>, ('utility', <InterfaceClass README.IExample>, ...'default'))\n ...\n\nBut as we have seen before, no duplication error is raised, if the same\nregistration is made for different sites:\n\n >>> context = xmlconfig.string('''\n ... <configure xmlns=\"http://namespaces.zope.org/zope\" i18n_domain=\"zope\">\n ...\n ... <utility component=\"README.example3\" name=\"default\" />\n ...\n ... <registerIn registry=\"README.custom\">\n ... <utility component=\"README.example4\" name=\"default\" />\n ... </registerIn>\n ...\n ... </configure>\n ... ''', context=context)\n\n\nOverriding ZCML\n~~~~~~~~~~~~~~~\n\nOverriding should behave as usual. If I define something within a particular\nsite, then it should be only overridable in that site.\n\nIn the following example, ``base-overrides.zcml`` overrides only the global\nregistration of the following snippet to \"example3\":\n\n >>> context.includepath = ('base.zcml', 'original.zcml')\n >>> context = xmlconfig.string('''\n ... <configure xmlns=\"http://namespaces.zope.org/zope\" i18n_domain=\"zope\">\n ...\n ... <utility component=\"README.example1\" />\n ...\n ... <registerIn registry=\"README.custom\">\n ... <utility component=\"README.example2\" />\n ... </registerIn>\n ...\n ... </configure>\n ... ''', context=context, execute=False)\n\n >>> context.includepath = ('base.zcml',)\n >>> context = xmlconfig.string('''\n ... <includeOverrides package=\"z3c.baseregistry.tests\"\n ... file=\"base-overrides.zcml\" />\n ... ''', context=context)\n\n >>> zope.component.getUtility(IExample)\n <Example 'example3'>\n\n >>> custom.getUtility(IExample)\n <Example 'example2'>\n\nIn the next example, ``custom-overrides.zcml`` overrides only the custom\nregistration of the following snippet to \"example3\":\n\n >>> context.includepath = ('base.zcml', 'original.zcml')\n >>> context = xmlconfig.string('''\n ... <configure xmlns=\"http://namespaces.zope.org/zope\" i18n_domain=\"zope\">\n ...\n ... <utility component=\"README.example1\" />\n ...\n ... <registerIn registry=\"README.custom\">\n ... <utility component=\"README.example4\" />\n ... </registerIn>\n ...\n ... </configure>\n ... ''', context=context, execute=False)\n\n >>> context.includepath = ('base.zcml',)\n >>> context = xmlconfig.string('''\n ... <configure xmlns=\"http://namespaces.zope.org/zope\" i18n_domain=\"zope\">\n ...\n ... <includeOverrides package=\"z3c.baseregistry.tests\"\n ... file=\"custom-overrides.zcml\" />\n ...\n ... </configure>\n ... ''', context=context)\n\n >>> zope.component.getUtility(IExample)\n <Example 'example1'>\n\n >>> custom.getUtility(IExample)\n <Example 'example3'>\n\nNote: Sorry for the convoluted test sequence; this is just how it works. :-(\n\n\nNested Registry Usage\n~~~~~~~~~~~~~~~~~~~~~\n\nI thought about this one for a long time, but I think it is better not\nallowing to nest ``zope:registerIn`` directives, because the logic of\nmanipulating the discriminator would be very complex for very little added\nbenefit.\n\n >>> try:\n ... xmlconfig.string('''\n ... <configure xmlns=\"http://namespaces.zope.org/zope\" i18n_domain=\"zope\">\n ...\n ... <registerIn registry=\"README.custom\">\n ... <registerIn registry=\"zope.component.globalregistry.base\">\n ... <utility component=\"README.example4\" />\n ... </registerIn>\n ... </registerIn>\n ...\n ... </configure>\n ... ''', context=context)\n ... except Exception as e:\n ... print(e)\n Nested ``registerIn`` directives are not permitted.\n File...\n\nCleanup\n~~~~~~~\n\nJust unregister the ``zope.component`` hooks:\n\n >>> zope.component.hooks.resetHooks()\n\n\nGlobal Non-Component-Registration Actions\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nZCML is not only responsible for populating the components registries, but also\nto do other global configuration, such as defining security and assigning\ninterfaces to classes. On the other hand, the ``registerIn`` directive works\nby manipulating the discriminator by prefixing it with the current\nregistry. While I assert that this is the right approach for component\nregistrations, it does not work for those other global configurations.\n\nIn order to address the issue, I need somehow more information. A balance must\nbe struck between the need to change existing directives and making the\nsolution non-monolithic. Here are some design ideas:\n\n1. A Special Discriminator Prefix\n\n All directives that globally manipulate the state of the system and do not\n register a component have as their first discriminator entry a special\n string, like \"StateChange\". The directive can then look for those entries and\n not change the discriminator at this point.\n\n Advantages include the ability to use those directives inside the\n ``registerIn`` directive and allow gradual upgrading. In the other hand, util\n directives are adjusted, conflict resolution will not be available for those\n scenarios.\n\n2. A Registry of Global Action Callables\n\n Here this package provides a registry of callables that change the state of\n the system. Directive authors can then subscribe their callables to this\n registry.\n\n The big advantage of this approach is that you can make it work now for all\n built-in directives without changing any implementation. The disadvantage is\n that the solution hides the problem to directive authors, so that detailed\n documentation must be provided to ensure integrity and avoid\n surprises. Another disadvantage is the complexity of yet another registry.\n\n3. Autodetection with False-Positives\n\n As far as I can tell, all actions that manipulate the components registries\n use the ``zope.component.zcml.handler`` function. Okay, so that allows me to\n detect those. Unfortunately, there might be directives that do *not*\n manipulate the state, for example ensuring the existence of something. There\n are a bunch of those directives in the core.\n\n The advantage here is that for the core it should just work. However, 3rd\n party directive developers might be tripped by this feature. Also, we could\n only issue warnings with this solution and probably need to be able to turn\n them off.\n\nI have not implemented any of those suggestions, waiting for input from the\ncommunity.\n\n\n=========\n CHANGES\n=========\n\n3.0 (2023-02-09)\n================\n\n- Drop support for Python 2.7, 3.4, 3.5, 3.6.\n\n- Add support for Python 3.8, 3.9, 3.10, 3.11.\n\n- Make tests compatible with ``zope.component >= 5``.\n\n\n2.2.0 (2018-10-19)\n==================\n\n- Add support for Python 3.7.\n\n- Drop support for Python 3.3.\n\n\n2.1.0 (2017-05-03)\n==================\n\n- Add support for Python 3.4, 3.5, 3.6 and PyPy.\n\n- Remove test dependency on ``zope.app.testing`` and\n ``zope.app.zcmlfiles``, among others.\n\n\n2.0.0 (2012-11-17)\n==================\n\n- zope.configuration changed action tuples to action dicts. This version works\n with the new action dict given from zope.configuration since version 3.8.0.\n This version is not compatible with zope.configuration version less then\n 3.8.0\n\n\n1.3.0 (2010-10-28)\n==================\n\n- Fundamental change in the way how baseregistry hooks into ZCA.\n Now it uses hooks.setSite, which requires that zope.component hooks\n are in place. Usually they are installed by zope.app.appsetup.\n Unless you use zope.app.appsetup, install the hooks with\n zope.component.hooks.setHooks().\n This applies to zope.component versions >= 3.9.4.\n\n\n1.2.0 (2009-12-27)\n==================\n\n- Moved browser dependencies to zmi extras\n\n\n1.1.0 (2009-03-19)\n==================\n\n- Fix base registry management form failure in case, when a site has its\n parent's local site manager (that isn't registered as utility) in its\n __bases__.\n\n- Use zope.site instead of zope.app.component.\n\n- Drop unused dependencies on zope.app.i18n and zope.app.pagetemplate.\n\n\n1.0.0 (2008-01-24)\n==================\n\n- Initial Release\n",
"bugtrack_url": null,
"license": "ZPL 2.1",
"summary": "Manage IComponents instances using Python code and ZCML.",
"version": "3.0",
"split_keywords": [
"zope3",
"z3c",
"component",
"global",
"registry",
"baseregistry"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "ee4b9cbf7e47a3f5c27d1c9aca20893c597920976261eb191978889f7e9db292",
"md5": "2620639846b94aa0a041a0ddc54d47cc",
"sha256": "1223bb656c7798670d83ef5004396b0996637c89db7058e1f23b2aaf00995ff9"
},
"downloads": -1,
"filename": "z3c.baseregistry-3.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "2620639846b94aa0a041a0ddc54d47cc",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.7",
"size": 28058,
"upload_time": "2023-02-09T10:54:12",
"upload_time_iso_8601": "2023-02-09T10:54:12.386899Z",
"url": "https://files.pythonhosted.org/packages/ee/4b/9cbf7e47a3f5c27d1c9aca20893c597920976261eb191978889f7e9db292/z3c.baseregistry-3.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "eb8d17d3169c9eec5d82130a26c87cc7a5c228ae1357589c8e2a69ae359e776f",
"md5": "c39e60ec945fa7460467f6487b6f3ef1",
"sha256": "4ce608c049e38b466c3e646ea18c8a79186728fcb784dc2bf3f0c24a0e23786e"
},
"downloads": -1,
"filename": "z3c.baseregistry-3.0.tar.gz",
"has_sig": false,
"md5_digest": "c39e60ec945fa7460467f6487b6f3ef1",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.7",
"size": 30457,
"upload_time": "2023-02-09T10:54:14",
"upload_time_iso_8601": "2023-02-09T10:54:14.091996Z",
"url": "https://files.pythonhosted.org/packages/eb/8d/17d3169c9eec5d82130a26c87cc7a5c228ae1357589c8e2a69ae359e776f/z3c.baseregistry-3.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-02-09 10:54:14",
"github": true,
"gitlab": false,
"bitbucket": false,
"github_user": "zopefoundation",
"github_project": "z3c.baseregistry",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"tox": true,
"lcname": "z3c.baseregistry"
}