Table of Contents
This package provides support for behaviors.
A behavior is a reusable aspect of an object that can be enabled or disabled without changing the component registry.
A behavior is described by an interface, and has metadata such as a title and a description. The behavior can be looked up by a given short name or by the dotted name of the interface. With this unique name behaviors metadata can be looked up. When the behavior is enabled for an object, you will be able to adapt the object to the interface. In some cases, the interface can be used as a marker interface as well.
As an example, let's say that your application needs to support object-level locking. This can be modeled via an adapter, but you want to leave it until runtime to determine whether locking is enabled for a particular object. You could then register locking as a behavior.
Requirements and Limitations:
- This package comes with support for registering behaviors and factories.
- It does not implement the policy for determining what behaviors are enabled on a particular object at a particular time.
That decision is deferred to an
IBehaviorAssignable
adapter, which must be implemented (plone.dexterity
implements this). - Like the
IBehaviorAssignable
plumbing, marker interface support needs to be enabled on a per-application basis. This package also does not directly support the adding of marker interfaces to instances. To do that, you can either use an event handler to mark an object when it is created, or a dynamic __providedBy__ descriptor that does the lookup on the fly (but you probably want some caching). A sample event handler is provided with this package, but is not registered by default - The intention is that behavior assignment is generic across an application, used for multiple, optional behaviors.
It probably doesn't make much sense to use
plone.behavior
for a single type of object. The means to keep track of which behaviors are enabled for what types of objects will be application specific.
A behavior is written much like an adapter, except that you don't specify the type of context being adapted directly. For example:
from zope.interface import Interface from zope.interface import implementer class ILockingSupport(Interface): """Support locking """ def lock(): """Lock an object """ def unlock(): """Unlock an object """ @implementer(ILockingSupport) class LockingSupport(object): def __init__(self, context): self.context = context def lock(self): # do something def unlock(self): # do something
This interface (which describes the type of behavior) and class (which describes the implementation of the behavior) then needs to be registered.
The simplest way to do that is to load the meta.zcml
file from this package and use ZCML:
<configure xmlns="http://namespaces.zope.org/zope" xmlns:plone="http://namespaces.plone.org/plone" i18n_domain="my.package"> <include package="plone.behavior" file="meta.zcml" /> <plone:behavior name="locking_support" title="Locking support" description="Optional object-level locking" provides=".interfaces.ILockingSupport" factory=".locking.LockingSupport" /> </configure>
After this is done you can adapt a context to ILockingSupport
as normal:
locking = ILockingSupport(context, None) if locking is not None: locking.lock()
The name
can be used for lookup instead of the full dotted name of the interface:
from plone.behavior.interfaces import IBehavior from zope.component import getUtility registration = getUtility(IBehavior, name='locking_support')
We also have a helper function to achieve this:
from plone.behavior.registration import lookup_behavior_registration registration = lookup_behavior_registration(name='locking_support')
You'll get an instance of LockingSupport
if context can be adapted to IBehaviorAssignable
(which, recall, is application specific),
and if the implementation of IBehaviorAssignable
says that this context supports this particular behavior.
It is also possible to let the provided interface act as a marker interface that is to be provided directly by the instance.
To achieve this, omit the factory
argument.
This is useful if you need to register other adapters for instances providing a particular behavior.
The plone:behavior
directive uses the namespace xmlns:plone="http://namespaces.plone.org/plone"
.
In order to enable it loading of its meta.zcml
is needed, use:
<include package="plone.behavior" file="meta.zcml" />
The directive supports the attributes:
title
- A user friendly title for this behavior (required).
description
- A longer description for this behavior (optional).
provides
- An interface to which the behavior can be adapted. This is what the conditional adapter factory will be registered as providing (required).
name
- Convenience lookup name for this behavior (optional).
The behavior will be always registered under the dotted name of
provides
attribute. This are usually long names.name
is a short name for this. Ifname
is given the behavior is registered additional under it. Anyway using short namespaces inname
is recommended. name_only
- If set to
yes
ortrue
the behavior is registered only under the given name, but not under the dotted path of theprovides
interface. This makesname
mandatory. marker
- A marker interface to be applied by the behavior.
If
factory
is not given, then this is optional and defaults to the value ofprovides
. If factory is givenmarker
is required and should be different fromprovides
- even if its not enforced. factory
The factory for this behavior (optional). If no factory is given, the behavior context is assumed to provide the interface given by
provides
itself.If factory provides
plone.behavior.interfaces.ISchemaAwareFactory
the factory is assumed to be a callable.ISchemaAwareFactory
is an interface for factories that should be initialised with a schema. It is called with the value given inprovides
as the only argument. The value returned is then used as the factory, another callable that can create appropriate behavior factories on demand.for
The type of object to register the conditional adapter factory for (optional). Must be omitted is no
factory
is given.The default is either to auto-detect what the factory adapts (i.e. using the
@adapter
decorator) or to fall back tozope.interface.Interface
(also written as*
in ZCML).Must be one element (no multiadapters, applies also for auto-detection).
former_dotted_names
In case a behavior is modified so that its dotted name changes, this field can be used to register the old name(s). Therefore, it is possible to retrieve the name(s) under which a behavior was formerly registered under.
If a call to
lookup_behavior_registration
does not find a behavior under the given name, it will look at the former dotted names to try and find the behavior.
Example usage, given
- some
context
(some arbitrary object) which isIBehaviorAssignable
, - an
IMyBehavior
interface intended to be used asprovides
, - an
IMyMarker
interface intended to be used asmarker
, - a
MyFactory
class implementingIMyBehavior
, - a
MySchemaAwareFactory
class implementingIMyBehavior
andplone.behavior.interfaces.ISchemaAwareFactory
, - an
IMyType
intended to be used asfor
. - some
typed_context
(some arbitrary object) which isIBehaviorAssignable
and providesIMyType
, - an
MyTypedFactory
class implementingIMyBehavior
and adaptingIMyType
,
title
and description
is trivial, so we don't cover it here in the explanation.
We don't cover name
too, because it's not having any effect in this usage.
To simplify it, we assume context
IBehaviorAssignable
always supports the behavior.
Also for simplifications sake we assume some magic applies the marker interface to context
I.e. both is done by plone.dexterity
.
Example 1 - only provides
given:
<plone:behavior title="Example 1" provides="IMyBehavior" />
marker
defaults toprovides
,- with
behavior = IMyBehavior(context)
thecontext
itself is returned, context
providesIBehavior
,
Example 2 - also factory
is given, so marker
is required:
Warning
Using the same Interface as marker and behavior works, but is not recommended and will be deprecated in future. It is semantically wrong!
Go for Example 3 instead!
<plone:behavior title="Example 1" provides="IMyBehavior" marker="IMyBehavior" factory="MyFactory" />
marker
is the same asprovides
,- with
behavior = IMyBehavior(context)
aMyFactory
instance is returned, context
providesIMyBehavior
,MyFactory
instance providesIMyBehavior
,- having
context
andMyFactory
providing both the same interface is ugly and not recommended!
Example 3 - in example 2 both, factory and context are providing the IMyBehavior
.
This may lead to confusion, so now better with a marker
:
<plone:behavior title="Example 1" provides="IMyBehavior" marker="IMyMarker" factory="MyFactory" />
- with
behavior = IMyBehavior(context)
aMyFactory
instance is returned, context
providesIMyMarker
,MyFactory
instance providesIMyBehavior
,
Example 4 - like example 3 but with an MySchemaAwareFactory
:
<plone:behavior title="Example 1" provides="IMyBehavior" marker="IMyMarker" factory="MySchemaAwareFactory" />
- with
behavior = IMyBehavior(context)
some factory instance is returned as a result from calling aMySchemaAwareFactory
instance withIMyBehavior
as argument, context
providesIMyMarker
,MyFactory
instance providesIMyBehavior
,
Example 5 - the behavior should be restricted to the typed_context
:
<plone:behavior title="Example 1" provides="IMyBehavior" marker="IMyMarker" factory="MyFactory" for="IMyType" />
- with
behavior = IMyBehavior(context, None)
it could not adapt andbehavior
isNone
, - with
behavior = IMyBehavior(typed_context)
aMyFactory
instance is returned, context
providesIMyMarker
,MyFactory
providesIMyBehavior
,
Example 6 - the behavior should be restricted to the typed_context
by auto-detection.
The MyTypedFactory
class adapts IMyType
using a class decorator @adapter(IMyType)
:
<plone:behavior title="Example 1" provides="IMyBehavior" marker="IMyMarker" factory="MyTypedFactory" />
- with
behavior = IMyBehavior(context, None)
it could not adapt andbehavior
isNone
, - with
behavior = IMyBehavior(typed_context)
aMyFactory
instance is returned, context
providesIMyMarker
,MyFactory
instance providesIMyBehavior
,
For more details please read the doctests in the source code: behavior.rst
, directives.rst
and annotation.rst
.
Contributors please read the document Process for Plone core's development
Sources are at the Plone code repository hosted at Github.