- -
-

Managing ZCA with PyAMS

-

The Zope Component Architecture (aka ZCA) is used by the Pyramid framework “under the hood” to handle interfaces, -adapters and utilities. You don’t have to use it in your own applications. But you can.

-

The ZCA is mainly adding elements like interfaces, adapters and utilities to the Python language. It -allows you to write a framework or an application by using components which can be extended easily.

-

You will find several useful resources about ZCA concepts on the internet.

-
-

Local utilities

-

In ZCA, a utility is a registered component which provides an interface. This interface is the -contract which defines features (list of attributes and methods) provided by the component which implements it.

-

When a Pyramid application starts, a global registry is created to register a whole set of utilities and -adapters; this registration can be done via ZCML directives or via native Python code. -In addition, PyAMS allows you to define local utilities, which are stored and registered in the ZODB via a site -manager.

-
-
-

Defining site root

-

One of PyAMS pre-requisites is to use the ZODB, at least to store the site root application, it’s configuration and a -set of local utilities. PyAMS site management describes application startup and local site manager initialization process.

-

This site can be used to store local utilities whose configuration, which is easily available to site -administrators through management interface, is stored in the ZODB.

-
-
-

Registering global utilities

-

Global utilities are components providing an interface which are registered in the global registry. -PyAMS_utils package provides custom annotations to register global utilities without using ZCML. For example, a skin -is nothing more than a simple utility providing the ISkin interface:

-
from pyams_default_theme.layer import IPyAMSDefaultLayer
-from pyams_skin.interfaces import ISkin
-from pyams_utils.registry import utility_config
-
-@utility_config(name='PyAMS default skin', provides=ISkin)
-class PyAMSDefaultSkin(object):
-    """PyAMS default skin"""
-
-    label = _("PyAMS default skin")
-    layer = IPyAMSDefaultLayer
-
-
-

This annotation registers a utility, named PyAMS default skin, providing the ISkin interface. It’s the developer -responsibility to provide all attributes and methods required by the provided interface.

-
-
-

Registering local utilities

-

A local utility is a persistent object, registered in a local site manager, and providing a specific interface (if -a component provides several interfaces, it can be registered several times).

-

Some components can be required by a given package, and created automatically via the pyams_upgrade command line -script; this process relies on the ISiteGenerations interface, for example for the timezone utility, a component -provided by PyAMS_utils package to handle server timezone and display times correctly:

-
from pyams_utils.interfaces.site import ISiteGenerations
-from pyams_utils.interfaces.timezone import IServerTimezone
-
-from persistent import Persistent
-from pyams_utils.registry import utility_config
-from pyams_utils.site import check_required_utilities
-from pyramid.events import subscriber
-from zope.container.contained import Contained
-from zope.interface import implementer
-from zope.schema.fieldproperty import FieldProperty
-
-@implementer(IServerTimezone)
-class ServerTimezoneUtility(Persistent, Contained):
-
-    timezone = FieldProperty(IServerTimezone['timezone'])
-
-REQUIRED_UTILITIES = ((IServerTimezone, '', ServerTimezoneUtility, 'Server timezone'),)
-
-@subscriber(INewLocalSite)
-def handle_new_local_site(event):
-    """Create a new ServerTimezoneUtility when a site is created"""
-    site = event.manager.__parent__
-    check_required_utilities(site, REQUIRED_UTILITIES)
-
-@utility_config(name='PyAMS timezone', provides=ISiteGenerations)
-class TimezoneGenerationsChecker(object):
-    """Timezone generations checker"""
-
-    generation = 1
-
-    def evolve(self, site, current=None):
-        """Check for required utilities"""
-        check_required_utilities(site, REQUIRED_UTILITIES)
-
-
-

Some utilities can also be created manually by an administrator through the management interface, and registered -automatically after their creation. For example, this is how a ZEO connection utility (which is managing settings to -define a ZEO connection) is registered:

-
from pyams_utils.interfaces.site import IOptionalUtility
-from pyams_utils.interfaces.zeo import IZEOConnection
-from zope.annotation.interfaces import IAttributeAnnotatable
-from zope.lifecycleevent.interfaces import IObjectAddedEvent, IObjectRemovedEvent
-
-from persistent import Persistent
-from pyramid.events import subscriber
-from zope.container.contained import Contained
-
-@implementer(IZEOConnection)
-class ZEOConnection(object):
-    """ZEO connection object. See source code to get full implementation..."""
-
-@implementer(IOptionalUtility, IAttributeAnnotatable)
-class ZEOConnectionUtility(ZEOConnection, Persistent, Contained):
-    """Persistent ZEO connection utility"""
-
-@subscriber(IObjectAddedEvent, context_selector=IZEOConnection)
-def handle_added_connection(event):
-    """Register new ZEO connection when added"""
-    manager = event.newParent
-    manager.registerUtility(event.object, IZEOConnection, name=event.object.name)
-
-@subscriber(IObjectRemovedEvent, context_selector=IZEOConnection)
-def handle_removed_connection(event):
-    """Un-register ZEO connection when deleted"""
-    manager = event.oldParent
-    manager.unregisterUtility(event.object, IZEOConnection, name=event.object.name)
-
-
-

context_selector is a custom subscriber predicate, so that subscriber event is activated only if object concerned -by an event is providing given interface.

-
-
-

Looking for utilities

-

ZCA provides the getUtility and queryUtility functions to look for a utility. But these methods only applies to -global registry.

-

PyAMS package provides equivalent functions, which are looking for components into local registry before looking into -the global one. For example:

-
from pyams_security.interfaces import ISecurityManager
-from pyams_utils.registry import query_utility
-
-manager = query_utility(ISecurityManager)
-if manager is not None:
-    print("Manager is there!")
-
-
-

All ZCA utility functions have been ported to use local registry: registered_utilities, query_utility, -get_utility, get_utilities_for, get_all_utilities_registered_for functions all follow the equivalent ZCA -functions API, but are looking for utilities in the local registry before looking in the global registry.

-
-
-

Registering adapters

-

An adapter is also a kind of utility. But instead of just providing an interface, it adapts an input object, -providing a given interface, to provide another interface. An adapter can also be named, so that you can choose which -adapter to use at a given time.

-

PyAMS_utils provide another annotation, to help registering adapters without using ZCML files. An adapter can be a -function which directly returns an object providing the requested interface, or an object which provides the interface.

-

The first example is an adapter which adapts any persistent object to get it’s associated transaction manager:

-
from persistent.interfaces import IPersistent
-from transaction.interfaces import ITransactionManager
-from ZODB.interfaces import IConnection
-
-from pyams_utils.adapter import adapter_config
-
-@adapter_config(context=IPersistent, provides=ITransactionManager)
-def get_transaction_manager(obj):
-    conn = IConnection(obj)
-    try:
-        return conn.transaction_manager
-    except AttributeError:
-        return conn._txn_mgr
-
-
-

This is another adapter which adapts any contained object to the IPathElements interface; this interface can be -used to build index that you can use to find objects based on a parent object:

-
from pyams_utils.interfaces.traversing import IPathElements
-from zope.intid.interfaces import IIntIds
-from zope.location.interfaces import IContained
-
-from pyams_utils.adapter import ContextAdapter
-from pyams_utils.registry import query_utility
-from pyramid.location import lineage
-
-@adapter_config(context=IContained, provides=IPathElements)
-class PathElementsAdapter(ContextAdapter):
-    """Contained object path elements adapter"""
-
-    @property
-    def parents(self):
-        intids = query_utility(IIntIds)
-        if intids is None:
-            return []
-        return [intids.register(parent) for parent in lineage(self.context)]
-
-
-

An adapter can also be a multi-adapter, when several input objects are requested to provide a given interface. For -example, many adapters require a context and a request, eventually a view, to provide another feature. This is how, -for example, we define a custom name column in a security manager table displaying a list of plug-ins:

-
from pyams_zmi.layer import IAdminLayer
-from z3c.table.interfaces import IColumn
-
-from pyams_skin.table import I18nColumn
-from z3c.table.column import GetAttrColumn
-
-@adapter_config(name='name', context=(Interface, IAdminLayer, SecurityManagerPluginsTable), provides=IColumn)
-class SecurityManagerPluginsNameColumn(I18nColumn, GetAttrColumn):
-    """Security manager plugins name column"""
-
-    _header = _("Name")
-    attrName = 'title'
-    weight = 10
-
-
-

As you can see, adapted objects can be given as interfaces and/or as classes.

-
-
-

Registering vocabularies

-

A vocabulary is a custom factory which can be used as source for several field types, like choices or lists. -Vocabularies have to be registered in a custom registry, so PyAMS_utils provide another annotation to register them. -This example is based on the Timezone component which allows you to select a timezone between a list of references:

-
import pytz
-from pyams_utils.vocabulary import vocabulary_config
-from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
-
-@vocabulary_config(name='PyAMS timezones')
-class TimezonesVocabulary(SimpleVocabulary):
-    """Timezones vocabulary"""
-
-    def __init__(self, *args, **kw):
-        terms = [SimpleTerm(t, t, t) for t in pytz.all_timezones]
-        super(TimezonesVocabulary, self).__init__(terms)
-
-
-
-
- - -