Infrastructure for maintaining a registry of available behaviors
Project description
==============
plone.behavior
==============
This package provides optional support for "behaviors". A behavior is
a re-usable 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 dotted name of the interface is the unique name for the
behavior, from which the 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, and that 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
------------
This package comes with support for registering behaviors and factories. It
does not, however, 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 you must implement.
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).
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.
Usage
-----
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, implements
class ILockingSupport(Interface):
"""Support locking
"""
def lock():
"""Lock an object
"""
def unlock():
"""Unlock an object
"""
class LockingSupport(object):
implements(ILockingSupport)
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 need 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
title="Locking support"
description="Optional object-level locking"
provides=".interfaces.ILockingSupport"
factory=".locking.LockingSupport"
/>
</configure>
After this is done - and presuming an appropriate IBehaviorAssignable adapter
exists for the context - you can adapt a context to ILockingSupport as
normal::
locking = ILockingSupport(context, None)
if locking is not None:
locking.lock()
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
(including views and viewlets) for instances providing a particular behavior.
Like the IBehaviorAssignable plumbing, marker interface support needs to be
enabled on a per-application basis. It can be done with a custom
__providedBy__ decorator or an IObjectCreatedEvent handler for applying the
marker. A sample event handler is provided with this package, but is not
registered by default
Please see behavior.txt, directives.txt and annotation.txt for more details.
=========================
plone.behavior: Behaviors
=========================
Please see README.txt at the root of this egg for more details on what
behaviors are and how to use them.
See directives.txt in this directory for details on how to register new
types of behaviors using ZCML.
Usage
-----
To use this package, you must first provide a suitable IBehaviorAssignable
adapter. This is normally done by the framework. plone.dexterity, for example,
will provide a suitable adapter.
Then, for each behavior:
* Write an interface describing the behavior.
* Write a factory (much like an adapter factory) that contains the logic of
the behavior. This is optional if your interface is a marker interface that
can be directly provided by the object.
* Register the behavior. This consists of a utility providing IBehavior and
an adapter factory based on IBehaviorAdapterFactory. The <plone:behavior />
ZCML directive makes this easy. See directives.txt.
An example might be::
<plone:behavior
title="Locking"
description="Support object-level locking"
provides=".interfaces.ILocking"
factory=".locking.LockingBehaviorFactory"
/>
Once the behavior has been registered, you can use standard adaptation idioms
to attempt to use it, e.g.::
locking = ILocking(context, None)
if locking is not None:
locking.lock()
Here, ILocking is a registered behavior interface. The adaptation will only
succeed if the context support behaviors (i.e. it can be adapted to
IBehaviorAssignable), and if the ILocking behavior is currently enabled for
this type of context.
Example
-------
As an example, let's create a basic behavior that's described by the
interface ILockingSupport:
>>> from zope.interface import implements
>>> from zope.interface import Interface
>>> class ILockingSupport(Interface):
... def lock():
... "Lock the context"
...
... def unlock():
... "Unlock the context"
>>> class LockingSupport(object):
... implements(ILockingSupport)
... def __init__(self, context):
... self.context = context
...
... def lock(self):
... print 'Locked', repr(self.context)
...
... def unlock(self):
... print 'Unlocked', repr(self.context)
The availability of this new behavior is indicated by registering a named
utility providing IBehavior. There is a default implementation of this
interface that makes this easy:
>>> from plone.behavior.registration import BehaviorRegistration
>>> registration = BehaviorRegistration(
... title=u"Locking support",
... description=u"Provides content-level locking",
... interface=ILockingSupport,
... marker=None,
... factory=LockingSupport)
>>> from zope.component import provideUtility
>>> provideUtility(registration, name=ILockingSupport.__identifier__)
NOTE: By convention, the behavior name should be the same as the identifier
of its interface. This convention is maintained by the <plone:behavior />
ZCML directive.
We also need to register an adapter factory that can create an instance of
an ILockingSupport for any context. This is a bit different to a standard
adapter factory (which is normally just a class with a constructor that
takes the context as an argument), because we want this factory to be
able to adapt almost anything, but return None (and thus fail to adapt) if
the behavior isn't currently enabled for the context.
To get these semantics, we can use the BehaviorAdapterFactory helper
class.
>>> from plone.behavior.factory import BehaviorAdapterFactory
>>> factory = BehaviorAdapterFactory(registration)
>>> from zope.interface import implements
>>> from zope.component import provideAdapter
>>> provideAdapter(factory=factory, adapts=(Interface,), provides=ILockingSupport)
One this is registered, it will be possible to adapt any context to
ILockingSupport, if:
* The context can be adapted to IBehaviorAssignable. This is an
interface that is used to determine if a particular object supports
a particular behavior.
* The behavior is enabled, i.e. the IBehaviorAssignable implementation
says it is.
Right now, neither of those things are true, so we'll get a TypeError when
trying to adapt:
>>> class IContextType(Interface): pass
>>> class SomeContext(object):
... implements(IContextType)
... def __repr__(self):
... return "<sample context>"
>>> context = SomeContext()
>>> behavior = ILockingSupport(context) # doctest: +ELLIPSIS
Traceback (most recent call last):
...
TypeError: ('Could not adapt', ...)
Of course, we are more likely to want to code defensively:
>>> behavior = ILockingSupport(context, None)
>>> behavior is None
True
For the behavior to work, we need to define an IBehaviorAssignable adapter.
For the purposes of this test, we'll maintain a simple, global registry that
maps classes to a list of enabled behavior interfaces.
>>> BEHAVIORS = {}
The adapter can thus be registered like this:
>>> from plone.behavior.interfaces import IBehavior, IBehaviorAssignable
>>> from zope.component import adapts, getUtility
>>> class TestingBehaviorAssignable(object):
... implements(IBehaviorAssignable)
... adapts(Interface)
...
... def __init__(self, context):
... self.context = context
...
... def supports(self, behavior_interface):
... global BEHAVIORS
... return behavior_interface in BEHAVIORS.get(self.context.__class__, [])
...
... def enumerate_behaviors(self):
... global BEHAVIORS
... for iface in BEHAVIORS.get(self.context.__class__, []):
... yield getUtility(IBehavior, iface.__identifier__)
>>> provideAdapter(TestingBehaviorAssignable)
NOTE: Again, we are relying on the convention that the IBehavior utility
name is the identifier of the behavior interface.
At this point, we know that the context support behavior assignment (since
there is an adapter for it), but it's not yet enabled, so we still can't
adapt.
>>> behavior = ILockingSupport(context, None)
>>> behavior is None
True
However, if we enable the behavior for this type...
>>> BEHAVIORS.setdefault(SomeContext, set()).add(ILockingSupport)
...then we can adapt and use the behavior adapter:
>>> behavior = ILockingSupport(context, None)
>>> behavior is None
False
>>> behavior.lock()
Locked <sample context>
Marker interfaces
-----------------
Behaviors work without the aid of marker interfaces. However, it may sometimes
be desirable to apply a marker interface to newly created objects that support
a particular behavior, for example if you need to register specific views or
viewlets that should only be available when this behavior is supported.
Note that there is no need to use marker interfaces if the desired behavior
can be achieved using adapters only. For this, the standard plone.behavior
adapter pattern is better, because there is no dependency on per-instance
markers.
Marker interface support again requires some framework support not configured
by this package. One of two possible configurations is possible:
* A custom __providedBy__ descriptor that includes the markeres of all
enabled behaviors can be added to behavior-aware classes.
* An event handler can be installed that marks newly created instances with
the markers of all enabled behaviors.
The first approach is better in many ways, because it can be made more robust
in case a marker interface is removed or renamed, and because it is possible
to turn off behavior markers without finding all objects providing the
subtype and calling noLongerProvides() on them. However, it is also pretty
difficult to get this right, and it cannot be generalised (you can't make
any adapter lookups in the descriptor, since you'd get infinite recursion).
There's an implementation of such a descriptor in the plone.dexterity package,
which also uses some heavy caching.
An event handler is easier, and this package provides a simple one that you
can use. It is not registered by default, since it may not be desirable to
enable an event handler for every type of object.
For the purposes of this test, we will simulate the event handler by calling
it directly.
>>> from plone.behavior.markers import apply_markers
>>> from zope.lifecycleevent import ObjectCreatedEvent
Let us create another behavior. This time, we'll provide a marker interface
as well.
>>> from zope import schema
>>> class ITaggable(Interface):
... pass
>>> class ITagging(Interface):
... tags = schema.List(title=u"Tags on this object",
... value_type=schema.TextLine(title=u"Tag"))
>>> class Tagging(object):
... implements(ITagging)
... def __init__(self, context):
... self.context = context
...
... def get_tags(self, value):
... return getattr(self.context, '__tags__', [])
... def set_tags(self, value):
... self.context.__tags__ = value
... tags = property(get_tags, set_tags)
We will register this behavior as above, this time specifying the marker
interface explicitly. In real life, of course, we'd be more likely to use the
<plone:behavior /> ZCML directive with the 'marker' attribute. See
directives.txt for more details.
>>> from plone.behavior.registration import BehaviorRegistration
>>> registration = BehaviorRegistration(
... title=u"Tagging support",
... description=u"",
... interface=ITagging,
... marker=ITaggable,
... factory=Tagging)
>>> from zope.component import provideUtility
>>> provideUtility(registration, name=ITagging.__identifier__)
>>> factory = BehaviorAdapterFactory(registration)
>>> provideAdapter(factory=factory, adapts=(Interface,), provides=ITagging)
Let us now create a new object without the behavior being enabled. The marker
interface should not be applied.
>>> context1 = SomeContext()
>>> ITagging(context1, None) is not None
False
>>> ITaggable.providedBy(context1)
False
>>> apply_markers(context1, ObjectCreatedEvent(context1))
>>> ITaggable.providedBy(context1)
False
If we now turn on the behavior, the marker should be applied when the event
is fired.
>>> BEHAVIORS.setdefault(SomeContext, set()).add(ITagging)
>>> context2 = SomeContext()
>>> ITagging(context2, None) is not None
True
>>> ITaggable.providedBy(context2)
False
>>> apply_markers(context2, ObjectCreatedEvent(context2))
>>> ITaggable.providedBy(context2)
True
Note that since this is applied per-instance, old instances do not get the
marker interface automatically:
>>> ITaggable.providedBy(context1)
False
It may be useful to mark the content with the behavior interface directly for
cases where the marker is all that's needed for the behavior to work. In
these cases no factory is needed, because the object already provides the
behavior directly as indicated by the marker. Note that the same interface
is used as the 'interface' and 'marker':
>>> class IMarkerBehavior(Interface):
... pass
>>> from plone.behavior.registration import BehaviorRegistration
>>> registration = BehaviorRegistration(
... title=u"",
... description=u"",
... interface=IMarkerBehavior,
... marker=IMarkerBehavior,
... factory=None)
>>> from zope.component import provideUtility
>>> provideUtility(registration, name=IMarkerBehavior.__identifier__)
>>> factory = BehaviorAdapterFactory(registration)
>>> provideAdapter(factory=factory, adapts=(Interface,), provides=IMarkerBehavior)
>>> BEHAVIORS.setdefault(SomeContext, set()).add(IMarkerBehavior)
When we adapt an object using this behavior, we get the object itself back,
since it implements our behavior interface directly:
>>> context = SomeContext()
>>> IMarkerBehavior.providedBy(context)
False
>>> apply_markers(context, ObjectCreatedEvent(context))
>>> IMarkerBehavior.providedBy(context)
True
>>> IMarkerBehavior(context) is context
True
===============================
plone.behavior: ZCML directives
===============================
plone.behavior defines a ZCML directive, in meta.zcml as usual.
For the purpose of this test, we have defined a few dummy behaviors in
plone.behavior.tests:
* A standard behavior with an interface and a factory. It will be registered
for any context.
* An adapter behavior with a factory and an explicit context restriction.
* An adapter behavior where a context restriction is implied by the
adapts() declaration on the factory.
* A behavior with a marker marker interface.
* A behavior using the standard annotation factory
* A behavior providing a marker interface and using an adapter factory.
>>> configuration = """\
... <configure
... package="plone.behavior"
... xmlns="http://namespaces.zope.org/zope"
... xmlns:plone="http://namespaces.plone.org/plone"
... i18n_domain="plone.behavior.tests">
...
... <include package="plone.behavior" file="meta.zcml" />
...
... <plone:behavior
... title="Adapter behavior"
... description="A basic adapter behavior"
... provides=".tests.IAdapterBehavior"
... factory=".tests.AdapterBehavior"
... />
...
... <plone:behavior
... title="Context restricted behavior"
... provides=".tests.IRestrictedAdapterBehavior"
... factory=".tests.RestrictedAdapterBehavior"
... for=".tests.IMinimalContextRequirements"
... />
...
... <plone:behavior
... title="Factory-implied context restricted behavior"
... provides=".tests.IImpliedRestrictionAdapterBehavior"
... factory=".tests.ImpliedRestrictionAdapterBehavior"
... />
...
... <plone:behavior
... title="Marker interface behavior"
... provides=".tests.IMarkerBehavior"
... />
...
... <plone:behavior
... title="Annotation storage behavior"
... provides=".tests.IAnnotationStored"
... factory="plone.behavior.AnnotationStorage"
... />
...
... <plone:behavior
... title="Marker and adapter"
... provides=".tests.IMarkerAndAdapterBehavior"
... factory="plone.behavior.AnnotationStorage"
... marker=".tests.IMarkerAndAdapterMarker"
... />
...
... </configure>
... """
Let's first verify that we don't have the dummy data registered already:
>>> from zope.component import getGlobalSiteManager
>>> sm = getGlobalSiteManager()
>>> from plone.behavior.interfaces import IBehavior
>>> [u for u in sm.registeredUtilities() if u.name == u"plone.behavior.tests.IAdapterBehavior"]
[]
>>> from plone.behavior.tests import IAdapterBehavior
>>> [a for a in sm.registeredAdapters() if a.provided == IAdapterBehavior]
[]
We should now be able to load the sample configuration, which also includes the
meta.zcml file from plone.behavior:
>>> from StringIO import StringIO
>>> from zope.configuration import xmlconfig
>>> xmlconfig.xmlconfig(StringIO(configuration))
With this in place, the behaviors should be registered, e.g:
>>> from plone.behavior.interfaces import IBehavior
>>> [u for u in sm.registeredUtilities() if u.name == u"plone.behavior.tests.IAdapterBehavior"] # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
[UtilityRegistration(..., IBehavior, 'plone.behavior.tests.IAdapterBehavior', <BehaviorRegistration for plone.behavior.tests.IAdapterBehavior>, u''),
UtilityRegistration(..., IInterface, 'plone.behavior.tests.IAdapterBehavior', IAdapterBehavior, '')]
>>> from plone.behavior.tests import IAdapterBehavior
>>> [a for a in sm.registeredAdapters() if a.provided == IAdapterBehavior] # doctest: +ELLIPSIS
[AdapterRegistration(..., [Interface], IAdapterBehavior, '', <plone.behavior.factory.BehaviorAdapterFactory object at ...>, ...)]
Let us test the various utilities and the underlying adapters more carefully.
>>> from zope.component import getUtility
>>> from plone.behavior.interfaces import IBehavior
1) A standard behavior with an interface and a factory. It will be registered
for any context.
>>> dummy = getUtility(IBehavior, name=u"plone.behavior.tests.IAdapterBehavior")
>>> dummy.title
u'Adapter behavior'
>>> dummy.description
u'A basic adapter behavior'
>>> dummy.interface
<InterfaceClass plone.behavior.tests.IAdapterBehavior>
>>> dummy.marker is None
True
>>> dummy.factory
<class 'plone.behavior.tests.AdapterBehavior'>
>>> from plone.behavior.tests import IAdapterBehavior
>>> [a.required for a in sm.registeredAdapters() if a.provided == IAdapterBehavior][0]
(<InterfaceClass zope.interface.Interface>,)
2) An adapter behavior with a factory and an explicit context restriction.
>>> dummy = getUtility(IBehavior, name=u"plone.behavior.tests.IRestrictedAdapterBehavior")
>>> dummy.title
u'Context restricted behavior'
>>> dummy.description is None
True
>>> dummy.interface
<InterfaceClass plone.behavior.tests.IRestrictedAdapterBehavior>
>>> dummy.marker is None
True
>>> dummy.factory
<class 'plone.behavior.tests.RestrictedAdapterBehavior'>
>>> from plone.behavior.tests import IRestrictedAdapterBehavior
>>> [a.required for a in sm.registeredAdapters() if a.provided == IRestrictedAdapterBehavior][0]
(<InterfaceClass plone.behavior.tests.IMinimalContextRequirements>,)
3) An adapter behavior where a context restriction is implied by the adapts()
declaration on the factory.
>>> dummy = getUtility(IBehavior, name=u"plone.behavior.tests.IImpliedRestrictionAdapterBehavior")
>>> dummy.title
u'Factory-implied context restricted behavior'
>>> dummy.description is None
True
>>> dummy.interface
<InterfaceClass plone.behavior.tests.IImpliedRestrictionAdapterBehavior>
>>> dummy.marker is None
True
>>> dummy.factory
<class 'plone.behavior.tests.ImpliedRestrictionAdapterBehavior'>
>>> from plone.behavior.tests import IImpliedRestrictionAdapterBehavior
>>> [a.required for a in sm.registeredAdapters() if a.provided == IImpliedRestrictionAdapterBehavior][0]
(<InterfaceClass plone.behavior.tests.ISomeContext>,)
4) A behavior with a marker marker interface.
>>> dummy = getUtility(IBehavior, name=u"plone.behavior.tests.IMarkerBehavior")
>>> dummy.title
u'Marker interface behavior'
>>> dummy.description is None
True
>>> dummy.interface
<InterfaceClass plone.behavior.tests.IMarkerBehavior>
>>> dummy.marker
<InterfaceClass plone.behavior.tests.IMarkerBehavior>
>>> dummy.factory is None
True
>>> from plone.behavior.tests import IMarkerBehavior
>>> [a.required for a in sm.registeredAdapters() if a.provided == IMarkerBehavior]
[]
5) A behavior using the standard annotation factory
>>> dummy = getUtility(IBehavior, name=u"plone.behavior.tests.IAnnotationStored")
>>> dummy.title
u'Annotation storage behavior'
>>> dummy.description is None
True
>>> dummy.interface
<InterfaceClass plone.behavior.tests.IAnnotationStored>
>>> dummy.marker is None
True
>>> dummy.factory # doctest: +ELLIPSIS
<plone.behavior.annotation.AnnotationStorage object at ...>
>>> from plone.behavior.tests import IAnnotationStored
>>> [a.required for a in sm.registeredAdapters() if a.provided == IAnnotationStored][0]
(<InterfaceClass zope.annotation.interfaces.IAnnotatable>,)
6) A behavior providing a marker interface and using an adapter factory.
>>> dummy = getUtility(IBehavior, name=u"plone.behavior.tests.IMarkerAndAdapterBehavior")
>>> dummy.title
u'Marker and adapter'
>>> dummy.description is None
True
>>> dummy.interface
<InterfaceClass plone.behavior.tests.IMarkerAndAdapterBehavior>
>>> dummy.marker
<InterfaceClass plone.behavior.tests.IMarkerAndAdapterMarker>
>>> dummy.factory # doctest: +ELLIPSIS
<plone.behavior.annotation.AnnotationStorage object at ...>
>>> from plone.behavior.tests import IMarkerAndAdapterBehavior
>>> [a.required for a in sm.registeredAdapters() if a.provided == IMarkerAndAdapterBehavior][0]
(<InterfaceClass zope.annotation.interfaces.IAnnotatable>,)
==================================
plone.behavior: Annotation storage
==================================
plone.behavior comes with a standard behavior factory that can be used to
store the data of a schema interface in annotations. This means that it is
possible to create a simple "data only" behavior with just an interface.
We have created such an interface in plone.behavior.tests, called
IAnnotationStored. It has a single field, 'some_field'.
Let's show how this may be registered in ZCML.
>>> configuration = """\
... <configure
... xmlns="http://namespaces.zope.org/zope"
... xmlns:plone="http://namespaces.plone.org/plone"
... i18n_domain="plone.behavior.tests">
...
... <include package="zope.component" file="meta.zcml" />
... <include package="plone.behavior" file="meta.zcml" />
... <include package="zope.annotation" />
...
... <plone:behavior
... title="Annotation behavior"
... provides="plone.behavior.tests.IAnnotationStored"
... factory="plone.behavior.AnnotationStorage"
... />
...
... </configure>
... """
>>> from StringIO import StringIO
>>> from zope.configuration import xmlconfig
>>> xmlconfig.xmlconfig(StringIO(configuration))
Let us now test this. First, we'll need an annotatable context and an
IBehaviorAssignable adapter. See behaviors.txt for more details.
>>> from zope.interface import Interface, implements, alsoProvides
>>> from zope.component import provideAdapter, adapts, getUtility
>>> from zope.annotation.interfaces import IAttributeAnnotatable, IAnnotations
>>> from plone.behavior.interfaces import IBehavior, IBehaviorAssignable
>>> from plone.behavior.tests import IAnnotationStored
>>> BEHAVIORS = {}
>>> class TestingBehaviorAssignable(object):
... implements(IBehaviorAssignable)
... adapts(Interface)
...
... def __init__(self, context):
... self.context = context
...
... def supports(self, behavior_interface):
... global BEHAVIORS
... return behavior_interface in BEHAVIORS.get(self.context.__class__, [])
...
... def enumerate_behaviors(self):
... global BEHAVIORS
... for iface in BEHAVIORS.get(self.context.__class__, []):
... yield getUtility(IBehavior, iface.__identifier__)
>>> provideAdapter(TestingBehaviorAssignable)
>>> class Context(object):
... implements(IAttributeAnnotatable)
>>> BEHAVIORS[Context] = [IAnnotationStored]
>>> context = Context()
We can now adapt the context to our new interface.
>>> adapted = IAnnotationStored(context)
Before we've set anything, we get the field's missing_value
>>> adapted.some_field is IAnnotationStored['some_field'].missing_value
True
Let's look at the annotations also:
>>> sorted(IAnnotations(context).items())
[]
If we now set the value, it will be stored in annotations:
>>> adapted.some_field = u'New value'
>>> sorted(IAnnotations(context).items())
[('plone.behavior.tests.IAnnotationStored.some_field', u'New value')]
And of course we can get it back again:
>>> adapted.some_field
u'New value'
If we try to get some other field, we get an AttributeError:
>>> adapted.bogus_field #doctest: +ELLIPSIS
Traceback (most recent call last):
...
AttributeError: bogus_field
Of course, we can still set and then get some value on the adapter factory
itself, but it won't be persisted.
>>> adapted.bogus_field = 123
>>> adapted.bogus_field
123
=========
Changelog
=========
1.0b4 - 2009-06-07
------------------
* Allow a marker-interface-only behavior to be set by using the 'provides'
attribute (previously 'interface') in the <plone:behavior /> directive
without a 'factory' attribute. The 'marker' attribute (previously known as
'subtype') is now only required if there is a marker used in addition to
a behavior adapter with a separate interface ('provides') and factory.
[optilude]
* Rename the 'interface' attribute of <plone:behavior /> to 'provides' to
be more consistent with the <adapter /> directive. This is a backwards
incompatible change!
[optilude]
* Rename the 'subtype' attribute of <plone:behavior /> to 'marker' to
be more explicit about its purpose. This is a backwards
incompatible change!
[optilude]
1.0b3 - 2009-04-17
------------------
* Allow behaviors with no factory.
[alecm]
* Provide a vocabulary of available behaviors.
[davisagli]
1.0b1 - 2008-04-27
------------------
* Initial release
plone.behavior
==============
This package provides optional support for "behaviors". A behavior is
a re-usable 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 dotted name of the interface is the unique name for the
behavior, from which the 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, and that 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
------------
This package comes with support for registering behaviors and factories. It
does not, however, 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 you must implement.
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).
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.
Usage
-----
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, implements
class ILockingSupport(Interface):
"""Support locking
"""
def lock():
"""Lock an object
"""
def unlock():
"""Unlock an object
"""
class LockingSupport(object):
implements(ILockingSupport)
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 need 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
title="Locking support"
description="Optional object-level locking"
provides=".interfaces.ILockingSupport"
factory=".locking.LockingSupport"
/>
</configure>
After this is done - and presuming an appropriate IBehaviorAssignable adapter
exists for the context - you can adapt a context to ILockingSupport as
normal::
locking = ILockingSupport(context, None)
if locking is not None:
locking.lock()
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
(including views and viewlets) for instances providing a particular behavior.
Like the IBehaviorAssignable plumbing, marker interface support needs to be
enabled on a per-application basis. It can be done with a custom
__providedBy__ decorator or an IObjectCreatedEvent handler for applying the
marker. A sample event handler is provided with this package, but is not
registered by default
Please see behavior.txt, directives.txt and annotation.txt for more details.
=========================
plone.behavior: Behaviors
=========================
Please see README.txt at the root of this egg for more details on what
behaviors are and how to use them.
See directives.txt in this directory for details on how to register new
types of behaviors using ZCML.
Usage
-----
To use this package, you must first provide a suitable IBehaviorAssignable
adapter. This is normally done by the framework. plone.dexterity, for example,
will provide a suitable adapter.
Then, for each behavior:
* Write an interface describing the behavior.
* Write a factory (much like an adapter factory) that contains the logic of
the behavior. This is optional if your interface is a marker interface that
can be directly provided by the object.
* Register the behavior. This consists of a utility providing IBehavior and
an adapter factory based on IBehaviorAdapterFactory. The <plone:behavior />
ZCML directive makes this easy. See directives.txt.
An example might be::
<plone:behavior
title="Locking"
description="Support object-level locking"
provides=".interfaces.ILocking"
factory=".locking.LockingBehaviorFactory"
/>
Once the behavior has been registered, you can use standard adaptation idioms
to attempt to use it, e.g.::
locking = ILocking(context, None)
if locking is not None:
locking.lock()
Here, ILocking is a registered behavior interface. The adaptation will only
succeed if the context support behaviors (i.e. it can be adapted to
IBehaviorAssignable), and if the ILocking behavior is currently enabled for
this type of context.
Example
-------
As an example, let's create a basic behavior that's described by the
interface ILockingSupport:
>>> from zope.interface import implements
>>> from zope.interface import Interface
>>> class ILockingSupport(Interface):
... def lock():
... "Lock the context"
...
... def unlock():
... "Unlock the context"
>>> class LockingSupport(object):
... implements(ILockingSupport)
... def __init__(self, context):
... self.context = context
...
... def lock(self):
... print 'Locked', repr(self.context)
...
... def unlock(self):
... print 'Unlocked', repr(self.context)
The availability of this new behavior is indicated by registering a named
utility providing IBehavior. There is a default implementation of this
interface that makes this easy:
>>> from plone.behavior.registration import BehaviorRegistration
>>> registration = BehaviorRegistration(
... title=u"Locking support",
... description=u"Provides content-level locking",
... interface=ILockingSupport,
... marker=None,
... factory=LockingSupport)
>>> from zope.component import provideUtility
>>> provideUtility(registration, name=ILockingSupport.__identifier__)
NOTE: By convention, the behavior name should be the same as the identifier
of its interface. This convention is maintained by the <plone:behavior />
ZCML directive.
We also need to register an adapter factory that can create an instance of
an ILockingSupport for any context. This is a bit different to a standard
adapter factory (which is normally just a class with a constructor that
takes the context as an argument), because we want this factory to be
able to adapt almost anything, but return None (and thus fail to adapt) if
the behavior isn't currently enabled for the context.
To get these semantics, we can use the BehaviorAdapterFactory helper
class.
>>> from plone.behavior.factory import BehaviorAdapterFactory
>>> factory = BehaviorAdapterFactory(registration)
>>> from zope.interface import implements
>>> from zope.component import provideAdapter
>>> provideAdapter(factory=factory, adapts=(Interface,), provides=ILockingSupport)
One this is registered, it will be possible to adapt any context to
ILockingSupport, if:
* The context can be adapted to IBehaviorAssignable. This is an
interface that is used to determine if a particular object supports
a particular behavior.
* The behavior is enabled, i.e. the IBehaviorAssignable implementation
says it is.
Right now, neither of those things are true, so we'll get a TypeError when
trying to adapt:
>>> class IContextType(Interface): pass
>>> class SomeContext(object):
... implements(IContextType)
... def __repr__(self):
... return "<sample context>"
>>> context = SomeContext()
>>> behavior = ILockingSupport(context) # doctest: +ELLIPSIS
Traceback (most recent call last):
...
TypeError: ('Could not adapt', ...)
Of course, we are more likely to want to code defensively:
>>> behavior = ILockingSupport(context, None)
>>> behavior is None
True
For the behavior to work, we need to define an IBehaviorAssignable adapter.
For the purposes of this test, we'll maintain a simple, global registry that
maps classes to a list of enabled behavior interfaces.
>>> BEHAVIORS = {}
The adapter can thus be registered like this:
>>> from plone.behavior.interfaces import IBehavior, IBehaviorAssignable
>>> from zope.component import adapts, getUtility
>>> class TestingBehaviorAssignable(object):
... implements(IBehaviorAssignable)
... adapts(Interface)
...
... def __init__(self, context):
... self.context = context
...
... def supports(self, behavior_interface):
... global BEHAVIORS
... return behavior_interface in BEHAVIORS.get(self.context.__class__, [])
...
... def enumerate_behaviors(self):
... global BEHAVIORS
... for iface in BEHAVIORS.get(self.context.__class__, []):
... yield getUtility(IBehavior, iface.__identifier__)
>>> provideAdapter(TestingBehaviorAssignable)
NOTE: Again, we are relying on the convention that the IBehavior utility
name is the identifier of the behavior interface.
At this point, we know that the context support behavior assignment (since
there is an adapter for it), but it's not yet enabled, so we still can't
adapt.
>>> behavior = ILockingSupport(context, None)
>>> behavior is None
True
However, if we enable the behavior for this type...
>>> BEHAVIORS.setdefault(SomeContext, set()).add(ILockingSupport)
...then we can adapt and use the behavior adapter:
>>> behavior = ILockingSupport(context, None)
>>> behavior is None
False
>>> behavior.lock()
Locked <sample context>
Marker interfaces
-----------------
Behaviors work without the aid of marker interfaces. However, it may sometimes
be desirable to apply a marker interface to newly created objects that support
a particular behavior, for example if you need to register specific views or
viewlets that should only be available when this behavior is supported.
Note that there is no need to use marker interfaces if the desired behavior
can be achieved using adapters only. For this, the standard plone.behavior
adapter pattern is better, because there is no dependency on per-instance
markers.
Marker interface support again requires some framework support not configured
by this package. One of two possible configurations is possible:
* A custom __providedBy__ descriptor that includes the markeres of all
enabled behaviors can be added to behavior-aware classes.
* An event handler can be installed that marks newly created instances with
the markers of all enabled behaviors.
The first approach is better in many ways, because it can be made more robust
in case a marker interface is removed or renamed, and because it is possible
to turn off behavior markers without finding all objects providing the
subtype and calling noLongerProvides() on them. However, it is also pretty
difficult to get this right, and it cannot be generalised (you can't make
any adapter lookups in the descriptor, since you'd get infinite recursion).
There's an implementation of such a descriptor in the plone.dexterity package,
which also uses some heavy caching.
An event handler is easier, and this package provides a simple one that you
can use. It is not registered by default, since it may not be desirable to
enable an event handler for every type of object.
For the purposes of this test, we will simulate the event handler by calling
it directly.
>>> from plone.behavior.markers import apply_markers
>>> from zope.lifecycleevent import ObjectCreatedEvent
Let us create another behavior. This time, we'll provide a marker interface
as well.
>>> from zope import schema
>>> class ITaggable(Interface):
... pass
>>> class ITagging(Interface):
... tags = schema.List(title=u"Tags on this object",
... value_type=schema.TextLine(title=u"Tag"))
>>> class Tagging(object):
... implements(ITagging)
... def __init__(self, context):
... self.context = context
...
... def get_tags(self, value):
... return getattr(self.context, '__tags__', [])
... def set_tags(self, value):
... self.context.__tags__ = value
... tags = property(get_tags, set_tags)
We will register this behavior as above, this time specifying the marker
interface explicitly. In real life, of course, we'd be more likely to use the
<plone:behavior /> ZCML directive with the 'marker' attribute. See
directives.txt for more details.
>>> from plone.behavior.registration import BehaviorRegistration
>>> registration = BehaviorRegistration(
... title=u"Tagging support",
... description=u"",
... interface=ITagging,
... marker=ITaggable,
... factory=Tagging)
>>> from zope.component import provideUtility
>>> provideUtility(registration, name=ITagging.__identifier__)
>>> factory = BehaviorAdapterFactory(registration)
>>> provideAdapter(factory=factory, adapts=(Interface,), provides=ITagging)
Let us now create a new object without the behavior being enabled. The marker
interface should not be applied.
>>> context1 = SomeContext()
>>> ITagging(context1, None) is not None
False
>>> ITaggable.providedBy(context1)
False
>>> apply_markers(context1, ObjectCreatedEvent(context1))
>>> ITaggable.providedBy(context1)
False
If we now turn on the behavior, the marker should be applied when the event
is fired.
>>> BEHAVIORS.setdefault(SomeContext, set()).add(ITagging)
>>> context2 = SomeContext()
>>> ITagging(context2, None) is not None
True
>>> ITaggable.providedBy(context2)
False
>>> apply_markers(context2, ObjectCreatedEvent(context2))
>>> ITaggable.providedBy(context2)
True
Note that since this is applied per-instance, old instances do not get the
marker interface automatically:
>>> ITaggable.providedBy(context1)
False
It may be useful to mark the content with the behavior interface directly for
cases where the marker is all that's needed for the behavior to work. In
these cases no factory is needed, because the object already provides the
behavior directly as indicated by the marker. Note that the same interface
is used as the 'interface' and 'marker':
>>> class IMarkerBehavior(Interface):
... pass
>>> from plone.behavior.registration import BehaviorRegistration
>>> registration = BehaviorRegistration(
... title=u"",
... description=u"",
... interface=IMarkerBehavior,
... marker=IMarkerBehavior,
... factory=None)
>>> from zope.component import provideUtility
>>> provideUtility(registration, name=IMarkerBehavior.__identifier__)
>>> factory = BehaviorAdapterFactory(registration)
>>> provideAdapter(factory=factory, adapts=(Interface,), provides=IMarkerBehavior)
>>> BEHAVIORS.setdefault(SomeContext, set()).add(IMarkerBehavior)
When we adapt an object using this behavior, we get the object itself back,
since it implements our behavior interface directly:
>>> context = SomeContext()
>>> IMarkerBehavior.providedBy(context)
False
>>> apply_markers(context, ObjectCreatedEvent(context))
>>> IMarkerBehavior.providedBy(context)
True
>>> IMarkerBehavior(context) is context
True
===============================
plone.behavior: ZCML directives
===============================
plone.behavior defines a ZCML directive, in meta.zcml as usual.
For the purpose of this test, we have defined a few dummy behaviors in
plone.behavior.tests:
* A standard behavior with an interface and a factory. It will be registered
for any context.
* An adapter behavior with a factory and an explicit context restriction.
* An adapter behavior where a context restriction is implied by the
adapts() declaration on the factory.
* A behavior with a marker marker interface.
* A behavior using the standard annotation factory
* A behavior providing a marker interface and using an adapter factory.
>>> configuration = """\
... <configure
... package="plone.behavior"
... xmlns="http://namespaces.zope.org/zope"
... xmlns:plone="http://namespaces.plone.org/plone"
... i18n_domain="plone.behavior.tests">
...
... <include package="plone.behavior" file="meta.zcml" />
...
... <plone:behavior
... title="Adapter behavior"
... description="A basic adapter behavior"
... provides=".tests.IAdapterBehavior"
... factory=".tests.AdapterBehavior"
... />
...
... <plone:behavior
... title="Context restricted behavior"
... provides=".tests.IRestrictedAdapterBehavior"
... factory=".tests.RestrictedAdapterBehavior"
... for=".tests.IMinimalContextRequirements"
... />
...
... <plone:behavior
... title="Factory-implied context restricted behavior"
... provides=".tests.IImpliedRestrictionAdapterBehavior"
... factory=".tests.ImpliedRestrictionAdapterBehavior"
... />
...
... <plone:behavior
... title="Marker interface behavior"
... provides=".tests.IMarkerBehavior"
... />
...
... <plone:behavior
... title="Annotation storage behavior"
... provides=".tests.IAnnotationStored"
... factory="plone.behavior.AnnotationStorage"
... />
...
... <plone:behavior
... title="Marker and adapter"
... provides=".tests.IMarkerAndAdapterBehavior"
... factory="plone.behavior.AnnotationStorage"
... marker=".tests.IMarkerAndAdapterMarker"
... />
...
... </configure>
... """
Let's first verify that we don't have the dummy data registered already:
>>> from zope.component import getGlobalSiteManager
>>> sm = getGlobalSiteManager()
>>> from plone.behavior.interfaces import IBehavior
>>> [u for u in sm.registeredUtilities() if u.name == u"plone.behavior.tests.IAdapterBehavior"]
[]
>>> from plone.behavior.tests import IAdapterBehavior
>>> [a for a in sm.registeredAdapters() if a.provided == IAdapterBehavior]
[]
We should now be able to load the sample configuration, which also includes the
meta.zcml file from plone.behavior:
>>> from StringIO import StringIO
>>> from zope.configuration import xmlconfig
>>> xmlconfig.xmlconfig(StringIO(configuration))
With this in place, the behaviors should be registered, e.g:
>>> from plone.behavior.interfaces import IBehavior
>>> [u for u in sm.registeredUtilities() if u.name == u"plone.behavior.tests.IAdapterBehavior"] # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
[UtilityRegistration(..., IBehavior, 'plone.behavior.tests.IAdapterBehavior', <BehaviorRegistration for plone.behavior.tests.IAdapterBehavior>, u''),
UtilityRegistration(..., IInterface, 'plone.behavior.tests.IAdapterBehavior', IAdapterBehavior, '')]
>>> from plone.behavior.tests import IAdapterBehavior
>>> [a for a in sm.registeredAdapters() if a.provided == IAdapterBehavior] # doctest: +ELLIPSIS
[AdapterRegistration(..., [Interface], IAdapterBehavior, '', <plone.behavior.factory.BehaviorAdapterFactory object at ...>, ...)]
Let us test the various utilities and the underlying adapters more carefully.
>>> from zope.component import getUtility
>>> from plone.behavior.interfaces import IBehavior
1) A standard behavior with an interface and a factory. It will be registered
for any context.
>>> dummy = getUtility(IBehavior, name=u"plone.behavior.tests.IAdapterBehavior")
>>> dummy.title
u'Adapter behavior'
>>> dummy.description
u'A basic adapter behavior'
>>> dummy.interface
<InterfaceClass plone.behavior.tests.IAdapterBehavior>
>>> dummy.marker is None
True
>>> dummy.factory
<class 'plone.behavior.tests.AdapterBehavior'>
>>> from plone.behavior.tests import IAdapterBehavior
>>> [a.required for a in sm.registeredAdapters() if a.provided == IAdapterBehavior][0]
(<InterfaceClass zope.interface.Interface>,)
2) An adapter behavior with a factory and an explicit context restriction.
>>> dummy = getUtility(IBehavior, name=u"plone.behavior.tests.IRestrictedAdapterBehavior")
>>> dummy.title
u'Context restricted behavior'
>>> dummy.description is None
True
>>> dummy.interface
<InterfaceClass plone.behavior.tests.IRestrictedAdapterBehavior>
>>> dummy.marker is None
True
>>> dummy.factory
<class 'plone.behavior.tests.RestrictedAdapterBehavior'>
>>> from plone.behavior.tests import IRestrictedAdapterBehavior
>>> [a.required for a in sm.registeredAdapters() if a.provided == IRestrictedAdapterBehavior][0]
(<InterfaceClass plone.behavior.tests.IMinimalContextRequirements>,)
3) An adapter behavior where a context restriction is implied by the adapts()
declaration on the factory.
>>> dummy = getUtility(IBehavior, name=u"plone.behavior.tests.IImpliedRestrictionAdapterBehavior")
>>> dummy.title
u'Factory-implied context restricted behavior'
>>> dummy.description is None
True
>>> dummy.interface
<InterfaceClass plone.behavior.tests.IImpliedRestrictionAdapterBehavior>
>>> dummy.marker is None
True
>>> dummy.factory
<class 'plone.behavior.tests.ImpliedRestrictionAdapterBehavior'>
>>> from plone.behavior.tests import IImpliedRestrictionAdapterBehavior
>>> [a.required for a in sm.registeredAdapters() if a.provided == IImpliedRestrictionAdapterBehavior][0]
(<InterfaceClass plone.behavior.tests.ISomeContext>,)
4) A behavior with a marker marker interface.
>>> dummy = getUtility(IBehavior, name=u"plone.behavior.tests.IMarkerBehavior")
>>> dummy.title
u'Marker interface behavior'
>>> dummy.description is None
True
>>> dummy.interface
<InterfaceClass plone.behavior.tests.IMarkerBehavior>
>>> dummy.marker
<InterfaceClass plone.behavior.tests.IMarkerBehavior>
>>> dummy.factory is None
True
>>> from plone.behavior.tests import IMarkerBehavior
>>> [a.required for a in sm.registeredAdapters() if a.provided == IMarkerBehavior]
[]
5) A behavior using the standard annotation factory
>>> dummy = getUtility(IBehavior, name=u"plone.behavior.tests.IAnnotationStored")
>>> dummy.title
u'Annotation storage behavior'
>>> dummy.description is None
True
>>> dummy.interface
<InterfaceClass plone.behavior.tests.IAnnotationStored>
>>> dummy.marker is None
True
>>> dummy.factory # doctest: +ELLIPSIS
<plone.behavior.annotation.AnnotationStorage object at ...>
>>> from plone.behavior.tests import IAnnotationStored
>>> [a.required for a in sm.registeredAdapters() if a.provided == IAnnotationStored][0]
(<InterfaceClass zope.annotation.interfaces.IAnnotatable>,)
6) A behavior providing a marker interface and using an adapter factory.
>>> dummy = getUtility(IBehavior, name=u"plone.behavior.tests.IMarkerAndAdapterBehavior")
>>> dummy.title
u'Marker and adapter'
>>> dummy.description is None
True
>>> dummy.interface
<InterfaceClass plone.behavior.tests.IMarkerAndAdapterBehavior>
>>> dummy.marker
<InterfaceClass plone.behavior.tests.IMarkerAndAdapterMarker>
>>> dummy.factory # doctest: +ELLIPSIS
<plone.behavior.annotation.AnnotationStorage object at ...>
>>> from plone.behavior.tests import IMarkerAndAdapterBehavior
>>> [a.required for a in sm.registeredAdapters() if a.provided == IMarkerAndAdapterBehavior][0]
(<InterfaceClass zope.annotation.interfaces.IAnnotatable>,)
==================================
plone.behavior: Annotation storage
==================================
plone.behavior comes with a standard behavior factory that can be used to
store the data of a schema interface in annotations. This means that it is
possible to create a simple "data only" behavior with just an interface.
We have created such an interface in plone.behavior.tests, called
IAnnotationStored. It has a single field, 'some_field'.
Let's show how this may be registered in ZCML.
>>> configuration = """\
... <configure
... xmlns="http://namespaces.zope.org/zope"
... xmlns:plone="http://namespaces.plone.org/plone"
... i18n_domain="plone.behavior.tests">
...
... <include package="zope.component" file="meta.zcml" />
... <include package="plone.behavior" file="meta.zcml" />
... <include package="zope.annotation" />
...
... <plone:behavior
... title="Annotation behavior"
... provides="plone.behavior.tests.IAnnotationStored"
... factory="plone.behavior.AnnotationStorage"
... />
...
... </configure>
... """
>>> from StringIO import StringIO
>>> from zope.configuration import xmlconfig
>>> xmlconfig.xmlconfig(StringIO(configuration))
Let us now test this. First, we'll need an annotatable context and an
IBehaviorAssignable adapter. See behaviors.txt for more details.
>>> from zope.interface import Interface, implements, alsoProvides
>>> from zope.component import provideAdapter, adapts, getUtility
>>> from zope.annotation.interfaces import IAttributeAnnotatable, IAnnotations
>>> from plone.behavior.interfaces import IBehavior, IBehaviorAssignable
>>> from plone.behavior.tests import IAnnotationStored
>>> BEHAVIORS = {}
>>> class TestingBehaviorAssignable(object):
... implements(IBehaviorAssignable)
... adapts(Interface)
...
... def __init__(self, context):
... self.context = context
...
... def supports(self, behavior_interface):
... global BEHAVIORS
... return behavior_interface in BEHAVIORS.get(self.context.__class__, [])
...
... def enumerate_behaviors(self):
... global BEHAVIORS
... for iface in BEHAVIORS.get(self.context.__class__, []):
... yield getUtility(IBehavior, iface.__identifier__)
>>> provideAdapter(TestingBehaviorAssignable)
>>> class Context(object):
... implements(IAttributeAnnotatable)
>>> BEHAVIORS[Context] = [IAnnotationStored]
>>> context = Context()
We can now adapt the context to our new interface.
>>> adapted = IAnnotationStored(context)
Before we've set anything, we get the field's missing_value
>>> adapted.some_field is IAnnotationStored['some_field'].missing_value
True
Let's look at the annotations also:
>>> sorted(IAnnotations(context).items())
[]
If we now set the value, it will be stored in annotations:
>>> adapted.some_field = u'New value'
>>> sorted(IAnnotations(context).items())
[('plone.behavior.tests.IAnnotationStored.some_field', u'New value')]
And of course we can get it back again:
>>> adapted.some_field
u'New value'
If we try to get some other field, we get an AttributeError:
>>> adapted.bogus_field #doctest: +ELLIPSIS
Traceback (most recent call last):
...
AttributeError: bogus_field
Of course, we can still set and then get some value on the adapter factory
itself, but it won't be persisted.
>>> adapted.bogus_field = 123
>>> adapted.bogus_field
123
=========
Changelog
=========
1.0b4 - 2009-06-07
------------------
* Allow a marker-interface-only behavior to be set by using the 'provides'
attribute (previously 'interface') in the <plone:behavior /> directive
without a 'factory' attribute. The 'marker' attribute (previously known as
'subtype') is now only required if there is a marker used in addition to
a behavior adapter with a separate interface ('provides') and factory.
[optilude]
* Rename the 'interface' attribute of <plone:behavior /> to 'provides' to
be more consistent with the <adapter /> directive. This is a backwards
incompatible change!
[optilude]
* Rename the 'subtype' attribute of <plone:behavior /> to 'marker' to
be more explicit about its purpose. This is a backwards
incompatible change!
[optilude]
1.0b3 - 2009-04-17
------------------
* Allow behaviors with no factory.
[alecm]
* Provide a vocabulary of available behaviors.
[davisagli]
1.0b1 - 2008-04-27
------------------
* Initial release
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
plone.behavior-1.0b4.tar.gz
(30.6 kB
view hashes)