Skip to content. | Skip to navigation

Personal tools

>>> ''.join(word[:3].lower() for word in 'David Isaac Glick'.split())

‘davisagli’

Navigation

You are here: Home / Blog / Registering Add-on-specific components using z3c.baseregistry

Registering Add-on-specific components using z3c.baseregistry

by David Glick posted Oct 16, 2010 03:45 PM
z3c.baseregistry provides a safer way to register Zope components that should only be available within sites that have a particular add-on installed.

(Updated 10/17/2010 to reflect some minor corrections from Martin.)

At Groundwire it is very common for us to run a number of Plone sites within the same Zope instance. This introduces some unique requirements for add-on products to follow when they are registering Zope components:

  1. If the add-on registers some new component, it should do it in such a way that it is only available within sites that have the add-on installed.
  2. If the add-on overrides some default component from Zope or Plone, it should do it in such a way that it can be further overridden for one particular site.

There are several common approaches that can help with these requirements, but each has downsides. Overriding components using overrides.zcml is global (i.e., affects all Plone sites in an instance) and prevents further customization. Registering components for a browser layer only works for components that adapt the request (such as browser views). Registering persistent local utilities or adapters in a site's local component registry keeps things isolated, but can be a headache when it's time to uninstall the add-on or remove the implementation of a component.

There is a lesser-known fourth option: using z3c.baseregistry to create a registry specific to one add-on.

Component registries in Zope 2

In Zope 2 we typically deal with 2 types of component registries (also called site managers historically):

  1. The global registry, which is populated with components at startup by processing ZCML.
  2. A local registry associated with each Plone site (implemented in five.localsitemanager). These store components persistently in the ZODB, and can be populated via the zope.component.interfaces.IComponentRegistration API in Python, or via GenericSetup (componentregistry.xml)

When Zope traverses over a Plone site, its local registry is set as the active registry (via zope.site.hooks.setSite—or zope.app.component.hooks.setSite in older Zopes—which sets a thread local). After that, this registry is the one that can be obtained by zope.component.getSiteManager, and the one that will be implicitly used by the functions that do component lookups. If a component is looked up but not found in the local registry, it will fall back to checking in that registry's base registries. By default in Plone, there is just one base registry, which is the global registry.

Introducing z3c.baseregistry

However, there's no requirement that the global registry be the only base registry. z3c.baseregistry makes it possible to define additional, named registries which can be installed as additional base registries for a particular site. Then when a component lookup occurs, it will be looked for first in the local registry, then in the custom base registry, and then in the global registry.

The cool thing is that while the installation of a z3c.baseregistry is persistent, the components one contains are not. Instead, the components are populated at Zope startup via ZCML, very much like the global registry. The registerIn grouping directive lets us specify which registry components should be registered in:

<registerIn registry=".packageComponents">
  <!-- component directives here -->
</registerIn>

This means that when you uninstall an add-on that has its own base registry, you just need to remove the registry from the site manager's bases, rather than figuring out how to unregister each individual component as would be necessary for persistent components in the local registry. It also means that you can safely remove a component's class when you remove its registration without worrying about breaking legacy persistent registrations of that component.

Step by step

I've used this approach in a few projects lately. Here's what it looks like:

  1. Add z3c.baseregistry to the add-on's install_requires in setup.py, and re-run buildout to make sure it is installed.
  2. Create a new registry instance.

    In __init__.py (or could be elsewhere):

    from zope.component import getGlobalSiteManager
    from z3c.baseregistry.baseregistry import BaseComponents
    packageComponents = BaseComponents(getGlobalSiteManager(), 'foo.bar')
    

    Here, we made sure that the new registry has the global registry as its base, and is named after our add-on package (foo.bar).

  3. Register a local utility for looking up the new registry by name (this is used by z3c.baseregistry internally).

    In configure.zcml:

    <!-- registry for package-specific components -->
    <utility
     component=".packageComponents"
     provides="zope.component.interfaces.IComponents"
     name="foo.bar"
     />
    
  4. Install the new registry in the bases for a particular site. z3c.baseregistry includes a form for doing this through the web, but it doesn't seem to work in Zope 2. Oh well, we can do it with a GenericSetup import handler instead.

    In setuphandlers.py:

    from zope.component import getSiteManager
    from zope.component.interfaces import IComponents
    
    def install_base_registry(site):
        sm = getSiteManager(context=site)
        reg = sm.getUtility(IComponents, name=u'foo.bar')
        sm.__bases__ = tuple([reg] + [r for r in sm.__bases__ if r is not reg])
    

    You would then call this from the add-on's custom "import various" GenericSetup handler.

  5. Now components can be registered for the new add-on specific registry, using the registerIn grouping directive.

    In configure.zcml:

    <!-- make sure we can use registerIn -->
    <include package="z3c.baseregistry" file="meta.zcml"/>
    
    <registerIn registry=".packageComponents">
    
      <browser:page
        for="*"
        name="foobar"
        template="foobar.pt"
        permission="zope.Public"
        />
    
    </registerIn>
    

    These components will be found within sites that have the product installed, but not within sites that don't!

Caveats

  • It should be obvious, but this only localizes the effects of ZCML directives whose effect is to register something in the component registry (e.g. utility, adapter, subscriber, browser:page). Directives that mutate other things, such as <class> which directly modifies a class, will still have a global effect.
  • Don't forget to make sure that the base registry gets removed from the local registry's bases when the add-on is uninstalled. Otherwise removing the product will break the site when it tries to unpickle the base registry.
Martin Aspeli says:
Oct 16, 2010 08:26 PM
Hi David,

Nice to get a writeup on this! :-)

A couple of corrections and suggestions:

1) "Registering components for a browser view only works for components that adapt the request (such as browser views)." - you probably mean a browser *layer* only.

2) "zope.component.hooks.setSite" - should be zope.app.component.hooks.setSite

3) In the first code example:

from zope.component import globalSiteManager
from z3c.baseregistry.baseregistry import BaseComponents
packageComponents = BaseComponents(globalSiteManager, 'foo.bar')

It would be better to do:

from zope.component import getGlobalSiteManager
from z3c.baseregistry.baseregistry import BaseComponents
packageComponents = BaseComponents(getGlobalSiteManager(), 'foo.bar')

I also fear this approach could break plone.testing's component architecture acrobatics, but I'm not sure. That's not a problem with z3c.baseregistry, though.

4) We should make a GenericSetup import handler so we can install base registries with an XML syntax

5) It'd be nice to have an example of how the uninstall should work

Cheers,
Martin
Dylan Jay says:
Oct 17, 2010 03:30 PM
Thanks for this. We also run a lot of sites on single zope instances and we weren't aware of this trick. Very nice. Thanks.
toutpt says:
Oct 19, 2010 06:19 PM
"it will be looked for first in the local registry, then in the custom base registry, and then in the global registry."

I'm afraid this change in the ZCA behaviour take effect on the global performance of Plone.

Do you have made some performance tests around this ? I would like to know how much time it can take to lookup a component in the global site manager with & without z3c.baseregistry.

 
CheliOS says:
Aug 23, 2013 08:51 PM
Hi! #ajgoreigFARE
Navigation