[Zope3-checkins] CVS: Zope3/src/zope/proxy/context - decorators.txt:1.1.2.1

Steve Alexander steve@cat-box.net
Sun, 18 May 2003 16:15:40 -0400


Update of /cvs-repository/Zope3/src/zope/proxy/context
In directory cvs.zope.org:/tmp/cvs-serv28729

Added Files:
      Tag: stevea-decorators-branch
	decorators.txt 
Log Message:
Added document explaining decorators.

I still need to produce the diagrams.

Also, I should go over the TODO items in the document, and fix the
implementation.


=== Added File Zope3/src/zope/proxy/context/decorators.txt ===
Decorators
==========

A decorator is a new kind of component for Zope 3. In a way, it crosses the
line between a wrapper and a component.

The term "decorator" comes from the object-oriented vocabulary of patterns.
A decorator wraps another object, providing all of the original object's
functionality, and also adding some of its own. A typical, and literal,
example of a decorator is from GUI frameworks: you have a "panel" component
that represents an area an application can draw to the screen. You can
decorate a panel with a BorderedPanel, which behaves like the original panel,
but intercepts the "draw yourself" operation to draw an ornamental border
around the application's content.

For Zope 3, we're mostly interested in decoration so that we can write
content classes in Python that need have no Zope 3 specific code in.
Such code is easier to test, and easier to reuse both within Zope 3 and in
other applications not based on Zope.
For security declarations, and views, and adaption, we can already do this.
We declare security protections for a class in ZCML. We look at a class'
interfaces to decide what kinds of views and adapters we can use with it.

The other Zope 3 dependency that we often find is code written to provide and
maintain the appropriate context wrapping. Context wrapping is important as
it allows all kinds of "local" or "placeful" operations within Zope, such as
looking up services and views, and getting appropriate security declarations.

The main use for decorators in Zope 3 is to allow us to separate the
context-dependent code from the regular code of a content class.
This was previously done by adapting a regular content object to a context-
dependent interface. In the case of Containers, before getting items from
a container or manipulating its contents, you had to adapt the container to
IZopeContainer. IZopeContainer is pretty much the same as IContainer, but in
addition to the responsibilities of IContainer, it promises to properly
context-wrap objects you get using __getitem__, to send the proper events
when you remove or add objects, to remove wrappers on objects before adding
them to the container, and it provides the additional 'rename' operation.
Unfortunately, an IZopeContainer instance (if it is an adapter) does not
behave the same as an IContainer instance: you cannot get the absolute URL of
an IZopeContainer. If your original container implements other interfaces,
these will be "lost" or rather "obscured" because it has been adapted to
IZopeContainer. This makes for unnecessary complexity in applications.

The effect of using a decorator is somewhat like dynamically introducing a
mixin-class to an object at runtime. The object temporarily appears to have
new behaviours.

Defining terms
==============

Context-dependent code
Context-dependent functionality    Code that performs the appropriate wrapping
                                   and unwrapping

inner                              The object that a decorator is wrapping
outer                              The context wrapper of the decorator
inner object                       Unambiguous term for "decorated object"
                                   (Not to be confused with 'inner context',
                                    which is a term used when talking about
                                    nested wrappers.)
mixin object                       The object that is providing decorations
mixin factory                      The callable that creates a mixin object

Diagram
=======

The following diagram shows how decoration of folders works, as a design, and
as objects at runtime.

[ insert class diagram and object diagram ]

When the wrapper is asked for a particular attribute, it does the following:

* If the attribute's name is in the wrapper's "attrdict" (a dictionary of
  attributes) then the value from the attrdict is returned.

* If the name is listed as one that should be decorated, the attribute
  access is made on the mixin object instead of the inner object.
  If the mixin object doesn't exist, then the mixin factory is called to
  create the mixin object. So, the mixin object is created lazily.

* Otherwise, the attribute access is made on the inner object, as it would
  be with a simpler kind of wrapper.


The mixin factory takes two arguments: inner, outer. 'inner' is a reference
to the inner object. 'outer' is a reference to the decorator wrapper object.

(This is a feature that is important for context decorators, but should be
different for other kinds of decorators. A future task will be to separate
out the functionality of a general purpose decorator into a separate type.)

Question: Why use 'inner' and 'outer' rather than 'context' and something
          else?

Answer:   The term 'context' is ambiguous when we are talking about
          decorators. For an adapter, 'context' means two things:

          * The "place" to use for local services etc.

          * The thing that is being adapted from.

          Additionally, for a wrapper, 'context' often means 'parent'.

          For context decorators, these things are separate. After much
          thought and experimentation, we settled on "inner" and "outer" as
          this avoids any ambiguous reference to "context", and looks
          fairly obvious in the code of decorator mixin classes.

          * 'inner' is the object being decorated.

          * 'outer' is the decorator wrapper itself. Use this for 'local
            context'.

Context decorators
==================

Now, I'm going to talk about context decorators from the point of view of
Zope 3 applications. From this point of view, there is no attrdict, mixin
factories are always classes, and decorators are associated with content
classes.

A decorator has the security checks of its inner object and its mixin,
combined. Where there is an overlap, the permissions defined with the mixin
override the ones on the object.

This is achieved by setting a special Checker in the decorator's attrdict
against the name __Security_checker__.

TODO: if the permissions were both required, then there would be less problem
      about reusing trusted checkers for subclasses.

A decorator provides the interfaces of its inner object plus those implemented
by the mixin class.
This is achieved by setting a combined InterfaceSpecification in the
decorator's attrdict against the name __providedBy__.

A decorator's attrdict is not settable. Trying to set one of these attributes
results in an AttributeError.

ZCML for context decorators
===========================

Q: Why is a decorator registered for a particular class?

A: In general, interfaces such as IContainer do not make any promises about
   context awareness. Usually when I implement IContainer, I will choose to
   make my implementation ignorant of context. However, I may choose to
   make my implementation context-dependent. In the latter case, it would be
   wrong to apply extra context-aware behaviour. So, decoration is made
   dependent on implementation class, not on interface.

Q: Why is the context decorator to use "inherited" by subclasses?
   For example, if I register a decorator for use with SampleContainer,
   and considering that Folder derives from SampleContainer, why do I need
   explicitly to register the same decorator for Folder?

A: Explicit is better than implicit. In this case, if we allowed such
   "inheritence" of decorators, there is the risk that you would tighten
   the permissions on your subclass, but a Trusted decorator would still be
   registered that defines lesser permissions.
   Because you have to state the decorator in the content directive for
   each class you want to decorate, it is obvious how objects of that class
   will be wrapped at runtime.

Q: What is this about Trusted and Untrusted decorators?

A: The mixin for a decorator is either trusted or untrusted. If it is trusted,
   the mixin object's 'inner' attribute is the inner object. If it is
   untrusted, the mixin object's 'inner' attribute is a security proxied
   inner object.
   If all mixins were untrusted, we could allow "inheritence" of decorator
   registrations. But, this might be not such a good idea for reasons of
   explicitness. Also, see TODO above.

The <decorate> directive
========================

When you want to say that objects of a particular content class should be
decorated, put a <decorate> directive within the <content> directive of that
class:

  <content class="some.ContentClass">
    <decorate type="context" decorator="zope.app.some.decoratorid" />

    ...

The 'type' attribute is to allow for different kinds of decorator. Right now,
we're interested only in context decorators.
The 'decorator' attribute is a decorator id.

Q: Is 'type="context"' a dead chicken?

A: No. I anticipate other kinds of decorators will be registered by content
   class in the future. The alternative is to have an explicit
   "<contextdecorator ..." directive.

Only one context decorator is allowed to be registered for a class.

TODO: Note about combining interfaces at ZCML parse time, which might not
      be so good if the mixin class' interface declarations are changed
      by other zcml, or at some other time.
      This could be fixed by having some smarter way of remembering this,
      or by getting the interface from the mixin class when the decorator is
      created at runtime.

The <decorator> directive
=========================

Because a decorator is used for many classes, you define a decorator
independently of the <content> directive.

  <decorator
      class="zope.app.some.mixin.Class"
      trusted="trusted"
      names="foo bar __iter__"
      >
    <require permission="zope.View" attributes="foo bar" />
    <allow interface="Iterable" />
  </decorator>

TODO: Add decoratesInterface="some.Interface" attribute, for documentation
      only.

TODO: Make names *not* be taken from the permissions. The names specify the
      names that get forwarded to the decorator, and must be given explicitly.
      The permissions should be allowed to declare more names, but they only
      count if they are present in 'names'. This allows us to use interfaces
      to define permissions, even if we're only using some of the names from
      the interfaces. It also allows us to use the 'mimic' or 'like_class'
      permission declarations.

Note that __providedBy__ and __Security_checker__ are reserved names, and
cannot be used.
Note also that "slot" names, such as __getitem__ and __iter__ can be used only
if they appear in the list of supported names.
TODO: Give the place that the list can be found.

Advantages
==========

* Easy to separate tests for "standalone" classes from tests of context-
  dependent functionality.

* Reuse context-dependent functionality among different implementations.

* Transparent to clients: views and adapters can just use decorated objects
  without having to know to get a particular 'context-aware adapter'.

TODO: Check that view and adapter lookup will use decorators, or if they will
      strip of the decoration.
      Consider whether this is a good reason to split decoration and context
      wrapping.

ContextWrapper package API
==========================

Some code needs to put an object in a context wrapper.

Other code, such as code implementing traversal, wants to make sure that an
object is context-wrapped, and that its "parent" is set correctly, and to
insert extra items into the wrapper dict.

Previously, both cases were handled by zope.proxy.context.ContextWrapper.
Now, the former case is handle by ContextWrapper, and the latter case is
handled by zope.prox.context.ensureContextWrapped.
This was necessary anyway to allow content classes to take on some of their
own context-dependent behaviour, without having the traversal code double-
wrap the results.

Objects are context-wrapped upon traversal. An object that has a decorator
registered for its class will be context-decorated upon traversal instead.

The overhead in this for non-decorated objects is a getattr on the object,
a couple of comparisons to None, a function call and a dict lookup. The
function call could be optimised away, if necessary.

Future directions
=================

* We decided to keep the C implementation fairly simple, with just one
  mixin. However, it would be moderately straightforward to support
  an arbitrary number of mixins, each created lazily, each with its own
  set of attributes. If anyone can think of a good use-case for having
  multiple mixins, then I might change the implementation to support that.

* We developed decoration in Zope 3 thinking mostly about context decoration.
  The decorator implementation is a bit specialised towards that, with
  the mixin factory taking inner and outer as arguments, and being a
  subtype of Wrapper.
  It might be better to separate out decorators and context-wrappers, and
  have decorators as a subclass of Proxy, and a sibling of Wrapper.
  To implement context decorators, we have the option of using a decorator
  inside a Wrapper. I should look at whether this would make other things
  simpler, or more complex, and what the overhead of extra layers of wrapping
  is. One risk is that, as the decorator would have to hold a reference to
  the thing that wraps it, we'd have to be careful about re-wrapping a
  decorator.

* Are there other kinds of decorator that would be useful? For example, if
  the attrdict were exposed to the mixin, then it could selectively cache
  results in the attrdict.