Package zope :: Package configuration :: Package tests :: Module test_nested
[show private | hide private]
[frames | no frames]

Module zope.configuration.tests.test_nested

Creating nested directives

When using ZCML, you sometimes nest ZCML directives. This is typically
done either to:

- Avoid repetative input.  Information shared among multiple
  directives is provided in a surrounding directive.

- Put together information that is too complex or structured to express
  with a single set of directive parameters.

Grouping directives are used to handle both of these cases.  See the
documentation in ``../zopeconfigure.py``. This file describes the
implementation of the zope ``configure`` directive, which groups
directives that use a common package or internationalization domain.
The documentation in ``../zopeconfigure.py`` provides background for
the documentation here.  You should also have read the documentation
in ``test_simple.py``, which documents how to create simple
directives.

This file shows you how to handle the second case above. In this case,
we have grouping directives that are meant to collaborate with
specific contained directives.  To do this, you have the grouping
directives declare a more specific (or alternate) interface to
``IConfigurationContext``. Directives designed to work with those
grouping directives are registered for the new interface.

Let's look at example. Suppose we wanted to be able to define schema
using ZCML.  We'd use a grouping directive to specify schemas and
contained directives to specify fields within the schema.  We'll use a
schema registry to hold the defined schemas.

A schema has a name, an id, some documentation, and some fields.
We'll provide the name and the id as parameters. We'll define fields
as subdirectives and documentation as text contained in the schema
directive.  The schema directive uses the schema, ``ISchemaInfo`` for
it's parameters.

We also define the schema, ISchema, that specifies an attribute that
nested field directives will use to store the fields they define.

The class, ``Schema``, provides the handler for the schema directive. (If
you haven't read the documentation in ``zopeconfigure.py``, you need
to do so now.)  The constructor saves its arguments as attributes
and initializes its ``fields`` attribute.

The ``after`` method of the ``Schema`` class creates a schema and
computes an action to register the schema in the schema registry.  The
discriminator prevents two schema directives from registering the same
schema.

It's important to note that when we call the ``action`` method on
``self``, rather than on ``self.context``.  This is because, in a
grouping directive handler, the handler instance is itself a context.
When we call the ``action`` method, the method stores additional meta
data associated with the context it was called on. This meta data
includes an include path, used when resolving conflicting actions,
and an object that contains information about the XML source used
to invole the directive. If we called the action method on
``self.context``, the wrong meta data would be associated with the
configuration action.

The file ``schema.zcml`` contains the meta-configuration directive
that defines the schema directive.

To define fields, we'll create directives to define the fields.
Let's start with a ``text`` field.  ``ITextField`` defines the schema for
text field parameters. It extends ``IFieldInfo``, which defines data
common to all fields.  We also define a simple handler method,
textField, that takes a context and keyword arguments. (For
information on writing simple directives, see ``test_simple.py``.)
We've abstracted most of the logic into the function ``field``.

The ``field`` function computes a field instance using the
constructor, and the keyword arguments passed to it.  It also uses the
context information object to get the text content of the directive,
which it uses for the field description.

After computing the field instance, it gets the ``Schema`` instance,
which is the context of the context passed to the function. The
function checks to see if there is already a field with that name. If
there is, it raises an error. Otherwise, it saves the field. 

We also define an ``IIntInfo`` schema and ``intField`` handler
function to support defining integer fields. 

We register the ``text`` and ``int`` directives in ``schema.zcml``.
These are like the simple directive definition we saw in
``test_simple.py`` with an important exception.  We provide a
``usedIn`` parameter to say that these directives can *only* ne used
in a ``ISchema`` context. In other words, these can only be used
inside of ``schema`` directives.

The ``schema.zcml`` file also contains some sample ``schema``
directives.  We can execute the file:

>>> from zope.configuration import tests
>>> context = xmlconfig.file("schema.zcml", tests)

And verify that the schema registery has the schemas we expect:

>>> from pprint import PrettyPrinter
>>> pprint=PrettyPrinter(width=70).pprint
>>> pprint(list(schema_registry))
['zope.configuration.tests.test_nested.I1',
 'zope.configuration.tests.test_nested.I2']

>>> def sorted(x):
...     r = list(x)
...     r.sort()
...     return r

>>> i1 = schema_registry['zope.configuration.tests.test_nested.I1']
>>> sorted(i1)
['a', 'b']
>>> i1['a'].__class__.__name__
'Text'
>>> i1['a'].description.strip()
u'A\n\n          Blah blah'
>>> i1['a'].min_length
1
>>> i1['b'].__class__.__name__
'Int'
>>> i1['b'].description.strip()
u'B\n\n          Not feeling very creative'
>>> i1['b'].min
1
>>> i1['b'].max
10

>>> i2 = schema_registry['zope.configuration.tests.test_nested.I2']
>>> sorted(i2)
['x', 'y']


Now let's look at some error situations. For example, let's see what
happens if we use a field directive outside of a schema dorective.
(Note that we used the context we created above, so we don't have to
redefine our directives:

>>> try:
...    v = xmlconfig.string(
...      '<text xmlns="http://sample.namespaces.zope.org/schema" name="x" />',
...      context)
... except xmlconfig.ZopeXMLConfigurationError, v:
...   pass
>>> print v
File "<string>", line 1.0
    ConfigurationError: The directive (u'http://sample.namespaces.zope.org/schema', u'text') cannot be used in this context

Let's see what happens if we declare duplicate fields:

>>> try:
...    v = xmlconfig.string(
...      '''
...      <schema name="I3" id="zope.configuration.tests.test_nested.I3"
...              xmlns="http://sample.namespaces.zope.org/schema">
...        <text name="x" />
...        <text name="x" />
...      </schema>
...      ''',
...      context)
... except xmlconfig.ZopeXMLConfigurationError, v:
...   pass
>>> print v
File "<string>", line 5.7-5.24
    ValueError: ('Duplicate field', 'x')

$Id: test_nested.py,v 1.2 2005/06/24 16:52:28 nyergler Exp $

Classes
IFieldInfo  
IIntInfo  
ISchema Interface that distinguishes the schema directive
ISchemaInfo Parameter schema for the schema directive
ITextInfo  
Schema Handle schema directives

Function Summary
  field(context, constructor, name, **kw)
  intField(context, **kw)
  test_suite()
  textField(context, **kw)

Variable Summary
dict schema_registry = {}

Variable Details

schema_registry

Type:
dict
Value:
{}                                                                     

Generated by Epydoc 2.1 on Fri Jun 24 12:01:23 2005 http://epydoc.sf.net