[Zope3-checkins] CVS: Zope3/src/zope/configuration - README.txt:1.1 backward.py:1.1 config.py:1.1 fields.py:1.1 __init__.py:1.3 interfaces.py:1.2 xmlconfig.py:1.10 meta.py:NONE metameta.zcml:NONE metametaconfigure.py:NONE metametaconfigurefordocgen.py:NONE metametafordocgen.zcml:NONE
Jim Fulton
jim@zope.com
Mon, 28 Jul 2003 18:23:20 -0400
Update of /cvs-repository/Zope3/src/zope/configuration
In directory cvs.zope.org:/tmp/cvs-serv30798/src/zope/configuration
Modified Files:
__init__.py interfaces.py xmlconfig.py
Added Files:
README.txt backward.py config.py fields.py
Removed Files:
meta.py metameta.zcml metametaconfigure.py
metametaconfigurefordocgen.py metametafordocgen.zcml
Log Message:
Major refactoring of the configuration system. Now use a totally new
architecture based on:
- Using schema to describe directive attributes
- Treating most configuration handlers as decorators of configuration
contexts.
The benefits of this are:
- Conversion of directive input can be automated.
- Much better error reporting. We can now tell people
what attributes are missing, are extra, or have invalid
values.
- It's possible to extend existing directives without modifying them.
Extending subdirectives can even be in different namespaces, if
desired.
- There is a includeOverrides directive for including files that
contain directives overriding directives in other files.
- It is possible to have many more sorts of "grouping" directives.
This will allow things like directives that provide defaults for
other directives and perhaps even some sort of macro system.
The basic refactoring is done, but there are a number of things to be
done:
- Write new documentation on how to write directives the new and
better way.
- Update the documentation generation tool to use the new
configuration framework.
- Add i18n support. This was the motivation for the refactoring. Now
this is easy. :)
=== Added File Zope3/src/zope/configuration/README.txt ===
==========================
Zope configuration system
==========================
The zope configuration system provides an extensible system for
supporting variouse kinds of configurations.
It is based on the idea of configuration directives. Users of the
configuration system provide configuration directives in some
language that express configuration choices. The intent is that the
language be pluggable. An XML language is provided by default.
Configuration is performed in three stages. In the first stage,
directives are processed to compute configuration actions.
Configuration actions consist of:
- A discriminator
- A callable
- Positional arguments
- Keyword arguments
The actions are essentially delayed function calls. Two or more
actions conflict if they have the same discriminator. The
configuration system has rules for resolving conflicts. If conflicts
cannot be resolved, an error will result. Conflict resolution
typically discards all but one of the conflicting actions, so that
the remaining action of the originally-conflicting actions no longer
conflicts. Non-conflicting actions are executed in the order that
they were created by passing the positional and non-positional
arguments to the action callable.
The system is extensible. There is a meta-configuration language for
defining configuration directives. A directive is defined by
providing meta data about the directive and handler code to process
the directive. There are four kinds of directives:
- Simple directives compute configuration actions. Their handlers
are typically functions that take a context and zero or more
keyword arguments and return a sequence of configuration actions.
- Grouping directives collect information to be used by nested
directives. They are called with a context object which they adapt
to some interface that extends IConfigurationContext.
Other directives can be nested in grouping directives.
- Complex directives are directives that have subdirectives.
Subdirectives have handlers that are simply methods of complex
directives. Complex diretives are handled by factories, typically
classes, that create objects that have methods for handling
subdirectives. These objects also have __call__ methods that are
called when processing of subdirectives is finished.
- Subdirectives are nested in complex directives. They are like
simple directives except that they hane handlers that are complex
directive methods.
Directives are defined for an interface, which is
IConfigurationContext by default.
=== Added File Zope3/src/zope/configuration/backward.py ===
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Pre-zcml-geddon backward compatability
Rather than revisit all of the old meta configurations, we'll
support the old configurations for a time until they can be converted.
There are two aspects of this:
1. Supporting complex directives (as opposed to simple and grouping
directives). This support is actually provided in config.py.
We'll probably support complex directives indefinately, as there
are some pretty complicated handlers in place now that we don't
want to take time to rewrite any time soon.
2. Supporting the old-style meta-configuration ZCML directives:
zope:directives, zope:directive, zope:description, and
zope:attribute. Hopefully, we can get rid of these sooner by
converting the existing meta configurations to use the new
meta: directives and schema. Certainly directives with message ids
will need to be converted.
This file contains the implementations of the old-style meta
configurations.
$Id: backward.py,v 1.1 2003/07/28 22:22:39 jim Exp $
"""
from keyword import iskeyword
from zope.configuration import config, xmlconfig
from zope import interface
from zope import schema
class IDescribed(interface.Interface):
name = schema.TextLine(
__doc__=
"""Directive name
The name of the directive being defined
""",
)
description = schema.Text(
__doc__=
"""Directive discription
This should document how the directive is used.
""",
default=u"",
)
class ISubdirectiveInfo(IDescribed):
"""Information common to all directive definitions have
"""
attributes = schema.Bytes(
__doc__=
"""Attribute names
This is a space-speratated list of attribute names. This is
used to provide a mimimal specification the attributes used.
""",
default="",
)
class IDirectiveInfo(ISubdirectiveInfo):
"""Information common to all directive definitions have
"""
handler = config.fields.GlobalObject(
__doc__=
"""Directive handler
The dotted name of the directive handler
""",
)
class ISubdirectiveContext(ISubdirectiveInfo, config.IConfigurationContext):
pass
class IDirectiveContext(IDirectiveInfo, ISubdirectiveContext):
pass
class Described:
interface.implements(IDescribed)
description = u''
def _merge_description_and_info(self):
r"""Combind a description, given as an attribute with info text
>>> d = Described()
>>> d.info = Described() # Any object with attributes will do
>>> d.info.text = u''
>>> d._merge_description_and_info()
>>> d.info.text
u''
>>> d.info.text = u' \n '
>>> d._merge_description_and_info()
>>> d.info.text
u' \n '
>>> d.description = u'test directive'
>>> d._merge_description_and_info()
>>> d.info.text
u'test directive'
>>> d.info.text = u'blah\nblah'
>>> d._merge_description_and_info()
>>> d.info.text
u'test directive\n\nblah\nblah'
"""
if self.description:
if self.info.text.strip():
self.info.text = self.description + u"\n\n" + self.info.text
else:
self.info.text = self.description
class Attributed(config.GroupingContextDecorator):
"""Compute schema definitions from simple attribute specifications
The attribute specifications can be given as simple names in the
constructor:
>>> context = config.ConfigurationMachine()
>>> x = Attributed(config, attributes=u"a b c")
Or the can be provides as keys added to the attributes disctionary:
>>> x.attributes['foo'] = schema.Int(title=u"Foo")
When tha _schema_from_attrs method is called, a schema is computed:
>>> x._schema_from_attrs()
>>> for name in x.schema:
... f = x.schema[name]
... print f.__class__.__name__, f.__name__, f.title, int(f.required)
Text a a 0
Text c c 0
Text b b 0
Int foo Foo 1
"""
interface.implementsOnly(IDescribed)
def attribute(self, name, required=False, description=u''):
aname = str(name)
if iskeyword(name):
aname += '_'
self.attributes[aname] = schema.Text(
title = unicode(aname),
required = required,
description = description,
)
def __init__(self, context, attributes=u'', **kw):
config.GroupingContextDecorator.__init__(self, context, **kw)
self.attributes = attrs = {}
for name in attributes.strip().split():
self.attribute(name)
def _schema_from_attrs(self):
schema = interface.Interface.__class__(
"schema generated from attributes",
(interface.Interface, ),
self.attributes,
)
if not self.attributes:
# No attribute definitions, allow keyword args
schema.setTaggedValue('keyword_arguments', True)
self.schema = schema
class Directive(Attributed, Described):
"""Handler for the directive directive
Actual definition of the directive is delayed until
sub(meta)directives have been handled.
See the test in tests/test_backward
"""
interface.implements(IDirectiveContext)
usedIn = config.IConfigurationContext
def __init__(self, context, **kw):
Attributed.__init__(self, context, **kw)
self.subdirectives = {}
def after(self):
self._schema_from_attrs()
self._merge_description_and_info()
if self.subdirectives:
# we have subdirectives, so set up a complex directive
complex = config.ComplexDirectiveDefinition(self)
complex.handler = self.handler
complex.update(self.subdirectives)
complex.before()
else:
config.defineSimpleDirective(self, self.name, self.schema,
self.handler, self.namespace)
class Subdirective(Attributed, Described):
"""Handler for the directive directive
Actual definition of the directive is delayed until
sub(meta)directives have been handled.
>>> context = config.ConfigurationMachine()
>>> d = Directive(context)
>>> len(d.subdirectives)
0
>>> s = Subdirective(d, name="foo", attributes=u"a b")
>>> len(d.subdirectives)
0
>>> class Info:
... text=u'spam'
>>> s.info = Info()
>>> s.after()
>>> len(d.subdirectives)
1
>>> schema, info = d.subdirectives['foo']
>>> list(schema)
['a', 'b']
>>> info.text
u'spam'
"""
interface.implements(ISubdirectiveContext)
def after(self):
self._schema_from_attrs()
self._merge_description_and_info()
self.context.subdirectives[self.name] = self.schema, self.info
class IAttribute(IDescribed):
required = config.fields.Bool(
__doc__=
"""Required
Is the attribute required?
""",
required=True,
default=False,
)
class Attribute(config.GroupingContextDecorator, Described):
r"""Simple attribute specification
Provide a very simple specification of an attribute and add it to
the attributes dictionary of the containing context.
>>> context = config.ConfigurationMachine()
>>> d = Directive(context, attributes=u"a")
>>> len(d.attributes)
1
>>> a = Attribute(d, name="a", description=u"blah")
>>> class Info:
... text=u'spam'
>>> a.info = Info()
>>> d.attributes['a'].description
u''
>>> a.after()
>>> d.attributes['a'].description
u'blah\n\nspam'
>>> d.attributes['a'].required
0
>>> d.attributes['a'].__class__.__name__
'Text'
>>> a = Attribute(d, name="b", description=u"eek", required=True)
>>> class Info:
... text=u'spam'
>>> a.info = Info()
>>> a.after()
>>> d.attributes['b'].description
u'eek\n\nspam'
>>> d.attributes['b'].required
1
>>> len(d.attributes)
2
"""
required = False
def after(self):
self._merge_description_and_info()
self.context.attribute(self.name, self.required, self.info.text)
class Description(config.GroupingContextDecorator):
r"""Provide descriptions for surrounding directives
This works a bit hard to be an effective noop, since
it has the same effect as providing text data.
>>> context = config.ConfigurationMachine()
>>> d = Directive(context, attributes=u"a")
>>> class Info:
... text=u'spam \n'
>>> d.info = Info()
>>> des = Description(d)
>>> des.info = Info()
>>> des.info.text = u"blah\nblah"
>>> des.after()
>>> d.info.text
u'spam\n\nblah\nblah'
"""
def after(self):
"""Merge our info with containing directive's info
"""
if not self.info.text.strip():
return
context = self.context
old = context.info.text.rstrip()
if old:
context.info.text = old + u"\n\n" + self.info.text
else:
context.info.text += self.info.text
def bootstrap(context):
# zope:directives
context((config.metans, 'groupingDirective'),
name='directives',
namespace=config.zopens,
handler="zope.configuration.config.DirectivesHandler",
schema="zope.configuration.config.IDirectivesInfo"
)
# zope:directive
context((config.metans, 'groupingDirective'),
name='directive',
namespace=config.zopens,
usedIn="zope.configuration.config.IDirectivesContext",
handler="zope.configuration.backward.Directive",
schema="zope.configuration.backward.IDirectiveInfo"
)
# zope:subdirective
context((config.metans, 'groupingDirective'),
name='subdirective',
namespace=config.zopens,
usedIn="zope.configuration.backward.IDirectiveContext",
handler="zope.configuration.backward.Subdirective",
schema="zope.configuration.backward.ISubdirectiveInfo"
)
# zope:attribute
context((config.metans, 'groupingDirective'),
name='attribute',
namespace=config.zopens,
usedIn="zope.configuration.backward.ISubdirectiveContext",
handler="zope.configuration.backward.Attribute",
schema="zope.configuration.backward.IAttribute"
)
# zope:discription
context((config.metans, 'groupingDirective'),
name='description',
namespace=config.zopens,
usedIn="zope.configuration.backward.IDescribed",
handler="zope.configuration.backward.Description",
schema="zope.interface.Interface"
)
=== Added File Zope3/src/zope/configuration/config.py ===
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Configuration processort
See README.txt and notes.txt.
$Id: config.py,v 1.1 2003/07/28 22:22:39 jim Exp $
"""
from keyword import iskeyword
from zope.configuration.exceptions import ConfigurationError
from zope.configuration.interfaces import IConfigurationContext
from zope.interface.adapter import AdapterRegistry
from zope.interface import Interface, implements, directlyProvides
from zope.interface.interfaces import IInterface
from zope.schema.errornames import WrongType
import zope.schema
from zope.schema.interfaces import IField, IFromUnicode
from zope.configuration import fields
import os.path
import sys
zopens = 'http://namespaces.zope.org/zope'
metans = 'http://namespaces.zope.org/meta'
testns = 'http://namespaces.zope.org/test'
_import_chickens = {}, {}, ("*",) # dead chickens needed by __import__
class ConfigurationContext(object):
"""Mix-in that implements IConfigurationContext
Subclasses provide a ``package`` attribute and a ``basepath``
attribute. If the base path is not None, relative paths are
converted to absolute paths using the the base path. If the
package is not none, relative imports are performed relative to
the package.
In general, the basepath and package attributes should be
consistent. When a package is provided, the base path should be
set to the path of the package directory.
Subclasses also provide an ``actions`` attribute, which is a list
of actions, an ``includepath`` attribute, and an ``info``
attribute.
The include path is appended to each action and is used when
resolving conflicts among actions. Normally, only the a
ConfigurationMachine provides the actions attribute. Decorators
simply use the actions of the context they decorate. The
``includepath`` attribute is a tuple of names. Each name is
typically the name of an included configuration file.
The ``info`` attribute contains descriptive information helpful
when reporting errors. If not set, it defaults to an empty string.
The actions attribute is a sequence of tuples with items:
- discriminator, a value that identifies the action. Two actions
that have the same (non None) discriminator conflict.
- an object that is called to execute the action,
- positional arguments for the action
- keyword arguments for the action
- a tuple of include file names (defaults to ())
- an object that has descriptive information about
the action (defaults to '')
For brevity, trailing items after the callable in the tuples are
ommitted if they are empty.
"""
def resolve(self, dottedname):
"""Resolve a dotted name to an object
Examples:
>>> c = ConfigurationContext()
>>> import zope, zope.interface
>>> c.resolve('zope') is zope
1
>>> c.resolve('zope.interface') is zope.interface
1
>>> c.resolve('zope.configuration.eek')
Traceback (most recent call last):
...
ConfigurationError: Module zope.configuration has no global eek
>>> c.resolve('.config.ConfigurationContext')
Traceback (most recent call last):
...
AttributeError: 'ConfigurationContext' object has no attribute """ \
"""'package'
>>> import zope.configuration
>>> c.package = zope.configuration
>>> c.resolve('.config.ConfigurationContext') is ConfigurationContext
1
>>> c.resolve('..interface') is zope.interface
1
"""
name = dottedname.strip()
names = name.split('.')
if not names[0]:
# Got a relative name. Conver it to abs using package info
if self.package is None:
raise ConfigurationError(
"Can't use leading dots in dotted names, "
"no package has been set.")
pnames = self.package.__name__.split(".")
pnames.append('')
while names and not names[0]:
try:
names.pop(0)
except IndexError:
raise ConfigurationError("Invalid global name", name)
try:
pnames.pop()
except IndexError:
raise ConfigurationError("Invalid global name", name)
names[0:0] = pnames
# Now we should have an absolute dotted name
# Split off object name:
oname, mname = names[-1], '.'.join(names[:-1])
# Import the module
if not mname:
# Just got a single name. Must me a module
return __import__(oname, *_import_chickens)
try:
mod = __import__(mname, *_import_chickens)
except ImportError:
raise ConfigurationError("Couldn't import %s" % mname)
try:
return getattr(mod, oname)
except AttributeError:
# No such name, maybe it's a module that we still need to import
try:
return __import__(mname+'.'+oname, *_import_chickens)
except ImportError:
raise ConfigurationError("Module %s has no global %s"
% (mname, oname))
def path(self, filename):
"""
Examples:
>>> c = ConfigurationContext()
>>> c.path("/x/y/z") == os.path.normpath("/x/y/z")
1
>>> c.path("y/z")
Traceback (most recent call last):
...
AttributeError: 'ConfigurationContext' object has no attribute """ \
"""'package'
>>> import zope.configuration
>>> c.package = zope.configuration
>>> import os
>>> d = os.path.split(zope.configuration.__file__)[0]
>>> c.path("y/z") == d + os.path.normpath("/y/z")
1
>>> c.path("y/./z") == d + os.path.normpath("/y/z")
1
>>> c.path("y/../z") == d + os.path.normpath("/z")
1
"""
filename = os.path.normpath(filename)
if os.path.isabs(filename):
return filename
# Got a relative path, combine with base path.
# If we have no basepath, compute the base path from the package
# path.
basepath = getattr(self, 'basepath', '')
if not basepath:
if self.package is None:
basepath = os.getcwd()
else:
basepath = os.path.split(self.package.__file__)[0]
self.basepath = basepath
return os.path.join(basepath, filename)
def action(self, discriminator, callable=None, args=(), kw={}):
"""Add an action with the given discriminator, callable and arguments
For testing purposes, the callable and arguments may be ommitted.
In that case, a default noop callable is used.
The discriminator must be given, but it can be None, to indicate that
the action never conflicts.
Let's look at some examples:
>>> c = ConfigurationContext()
Normally, the context gets actions from subclasses. We'll provide
an actions attribute ourselves:
>>> c.actions = []
We'll use a test callable that has a convenient string representation
>>> from zope.configuration.tests.directives import f
>>> c.action(1, f, (1, ), {'x': 1})
>>> c.actions
[(1, f, (1,), {'x': 1})]
>>> c.action(None)
>>> c.actions
[(1, f, (1,), {'x': 1}), (None, None)]
Now set the include path and info:
>>> c.includepath = ('foo.zcml',)
>>> c.info = "?"
>>> c.action(None)
>>> c.actions[-1]
(None, None, (), {}, ('foo.zcml',), '?')
"""
action = (discriminator, callable, args, kw,
getattr(self, 'includepath', ()),
getattr(self, 'info', ''),
)
# remove trailing false items
while (len(action) > 2) and not action[-1]:
action = action[:-1]
self.actions.append(action)
class ConfigurationAdapterRegistry(object):
"""Simple adapter registry that manages directives as adapters
>>> r = ConfigurationAdapterRegistry()
>>> c = ConfigurationMachine()
>>> r.factory(c, ('http://www.zope.com','xxx'))
Traceback (most recent call last):
...
ConfigurationError: ('Unknown directive', 'http://www.zope.com', 'xxx')
>>> from zope.configuration.interfaces import IConfigurationContext
>>> def f():
... pass
>>> r.register(IConfigurationContext, ('http://www.zope.com', 'xxx'), f)
>>> r.factory(c, ('http://www.zope.com','xxx')) is f
1
>>> r.factory(c, ('http://www.zope.com','yyy')) is f
Traceback (most recent call last):
...
ConfigurationError: ('Unknown directive', 'http://www.zope.com', 'yyy')
>>> r.register(IConfigurationContext, 'yyy', f)
>>> r.factory(c, ('http://www.zope.com','yyy')) is f
1
"""
def __init__(self):
self._registry = {}
def register(self, interface, name, factory):
r = self._registry.get(name)
if r is None:
r = AdapterRegistry()
self._registry[name] = r
r.register(interface, Interface, factory)
def factory(self, context, name):
r = self._registry.get(name)
if r is None:
# Try namespace-independent name
ns, n = name
r = self._registry.get(n)
if r is None:
raise ConfigurationError("Unknown directive", ns, n)
f = r.getForObject(context, Interface)
if f is None:
raise ConfigurationError(
"The directive %s cannot ne used in this context" % (name, ))
return f
class ConfigurationMachine(ConfigurationAdapterRegistry, ConfigurationContext):
"""Configuration machine
Example:
>>> machine = ConfigurationMachine()
>>> ns = "http://www.zope.org/testing"
Register a directive:
>>> machine((metans, "directive"),
... namespace=ns, name="simple",
... schema="zope.configuration.tests.directives.ISimple",
... handler="zope.configuration.tests.directives.simple")
and try it out:
>>> machine((ns, "simple"), a=u"aa", c=u"cc")
>>> machine.actions
[(('simple', u'aa', u'xxx', 'cc'), f, (u'aa', u'xxx', 'cc'))]
A more extensive example can be found in the unit tests.
"""
implements(IConfigurationContext)
package = None
basepath = None
includepath = ()
info = ''
def __init__(self):
ConfigurationAdapterRegistry.__init__(self)
self.actions = []
self.stack = [RootStackItem(self)]
_bootstrap(self)
def begin(self, __name, __data=None, __info=None, **kw):
if __data:
if kw:
raise TypeError("Can't provide a mapping object and keyword "
"arguments")
else:
__data = kw
self.stack.append(self.stack[-1].contained(__name, __data, __info))
def end(self):
self.stack.pop().finish()
def __call__(self, __name, __info=None, **__kw):
self.begin(__name, __kw, __info)
self.end()
def getInfo(self):
return self.stack[-1].context.info
def setInfo(self, info):
self.stack[-1].context.info = info
def execute_actions(self, clear=True, testing=False):
"""Execute the configuration actions
This calls the action callables after resolving conflicts
For example:
>>> output = []
>>> def f(*a, **k):
... output.append(('f', a, k))
>>> context = ConfigurationMachine()
>>> context.actions = [
... (1, f, (1,)),
... (1, f, (11,), {}, ('x', )),
... (2, f, (2,)),
... ]
>>> context.execute_actions()
>>> output
[('f', (1,), {}), ('f', (2,), {})]
If the action raises an error, we convert it to a
ConfigurationExecutionError.
>>> output = []
>>> def bad():
... bad.xxx
>>> context.actions = [
... (1, f, (1,)),
... (1, f, (11,), {}, ('x', )),
... (2, f, (2,)),
... (3, bad, (), {}, (), 'oops')
... ]
>>> try:
... v = context.execute_actions()
... except ConfigurationExecutionError, v:
... pass
>>> print v
exceptions.AttributeError: 'function' object has no attribute 'xxx'
in:
oops
Note that actions executed before the error still have an effect:
>>> output
[('f', (1,), {}), ('f', (2,), {})]
"""
for action in resolveConflicts(self.actions):
(discriminator, callable, args, kw, includepath, info
) = expand_action(*action)
if callable is None:
continue
try:
callable(*args, **kw)
except:
if testing:
raise
t, v = sys.exc_info()[:2]
v = ConfigurationExecutionError(t, v, info)
t = ConfigurationExecutionError
raise t, v, sys.exc_info()[2]
if clear:
del self.actions[:]
class ConfigurationExecutionError(ConfigurationError):
"""An error occurred during execution of a configuration action
"""
def __init__(self, etype, evalue, info):
self.etype, self.evalue, self.info = etype, evalue, info
def __str__(self):
return "%s: %s\n in:\n %s" % (self.etype, self.evalue, self.info)
##############################################################################
# Stack items
class IStackItem(Interface):
"""Configuration machine stack items
Stack items are created when a directive if being processed.
A stack item is created for each directive use.
"""
def contained(name, data, info):
"""Begin processing a contained directive
The data are a dictionary of attribute names mapped to unicode
strings.
The info argument is an object that can be converted to a
string and that contains information about the directive.
The begin method returns the next item to be placed on the stack.
"""
def finish():
"""Finish processing a directive
"""
class SimpleStackItem(object):
"""Simple stack item
A simple stack item can't have anything added after it. It can
only be removed. It is used for simple directives and
subdirectives, which can't contain other directives.
It also defers any computation until the end of the directive
has been reached.
"""
implements(IStackItem)
def __init__(self, context, handler, info, *argdata):
newcontext = GroupingContextDecorator(context)
newcontext.info = info
self.context = newcontext
self.handler = handler
self.argdata = argdata
def contained(self, name, data, info):
raise ConfigurationError("Invalid directive %s" % str(name))
def finish(self):
# We're going to use the context that was passed to us, which wasn't
# created for the directive. We want to set it's info to the one
# passed to us while we make the call, so we'll save the old one
# and restore it.
context = self.context
args = toargs(context, *self.argdata)
actions = self.handler(context, **args)
if actions:
# we allow the handler to return nothing
for action in actions:
context.action(*action)
class RootStackItem(object):
def __init__(self, context):
self.context = context
def contained(self, name, data, info):
"""Handle a contained directive
We have to compute a new stack item by getting a named adapter
for the current context object.
"""
factory = self.context.factory(self.context, name)
if factory is None:
raise ConfigurationError("Invalid directive", name)
adapter = factory(self.context, data, info)
return adapter
def finish(self):
pass
class GroupingStackItem(RootStackItem):
"""Stack item for a grouping directive
A grouping stack item is in the stack when a grouping directive is
being processed. Grouping directives group other directives.
Often, they just manage common data, but they may also take
actions, either before or after contained directives are executed.
A grouping stack item us created with a grouping directive
definition, a configuration context,
and directive data.
To see how this works, let's look at an example:
We need a context. We'll just use a configuration machine
>>> context = ConfigurationMachine()
We need a callable to use in configuration actions. We'll use a
convenient one from the tests:
>>> from zope.configuration.tests.directives import f
We need a handler for the grouping directive. This is a class
that implements a context decorator. The decorator must also
provide ``before`` and ``after`` methods that are called before
and after any contained directives are processed. We'll typically
subclass ``GroupingContextDecorator``, which provides context
decoration, and default ``before`` and ``after`` methods.
>>> class SampleGrouping(GroupingContextDecorator):
... def before(self):
... self.context.action(('before', self.x, self.y), f)
... def after(self):
... self.context.action(('after'), f)
We'll use our decorator to decorate out initial context, providing
keyword arguments x and y:
>>> dec = SampleGrouping(context, x=1, y=2)
Note that the keyword arguments are made attributes of the
decorator.
Now we'll create the stack item.
>>> item = GroupingStackItem(dec)
The before method was called, which we can verify by looking at
the context actions:
>>> context.actions
[(('before', 1, 2), f)]
Subdirectives will get looked up as adapters of the context.
We'll create a simple handler:
>>> def simple(context, data, info):
... context.action(("simple", context.x, context.y, data), f)
... return info
and register it with the context:
This handler isn't really a propert handler, because it doesn't
return a new context. It will do for this example.
>>> context.register(IConfigurationContext, (testns, 'simple'), simple)
Now we'll call the contained method on the stack item:
>>> item.contained((testns, 'simple'), {'z': 'zope'}, "someinfo")
'someinfo'
we can verify thet the simple method was called by looking at the
context actions:
>>> from pprint import PrettyPrinter
>>> pprint=PrettyPrinter(width=60).pprint
>>> pprint(context.actions)
[(('before', 1, 2), f),
(('simple', 1, 2, {'z': 'zope'}), f)]
Finally, we call finish, which calls the decorator after method:
>>> item.finish()
>>> pprint(context.actions)
[(('before', 1, 2), f),
(('simple', 1, 2, {'z': 'zope'}), f),
('after', f)]
"""
implements(IStackItem)
def __init__(self, context):
RootStackItem.__init__(self, context)
actions = context.before()
if actions:
for action in actions:
context.action(*action)
def finish(self):
actions = self.context.after()
if actions:
for action in actions:
self.context.action(*action)
class ComplexStackItem(object):
"""Complex stack item
A complex stack item is in the stack when a complex directive is
being processed. It only allows subdirectives to be used.
A complex stack item us created with a complex directive
definition (IComplexDirectiveContext), a configuration context,
and directive data.
To see how this works, let's look at an example:
We need a context. We'll just use a configuration machine
>>> context = ConfigurationMachine()
We need a callable to use in configuration actions. We'll use a
convenient one from the tests:
>>> from zope.configuration.tests.directives import f
We need a handler for the complex directive. This is a class
with a method for each subdirective:
>>> class Handler:
... def __init__(self, context, x, y):
... self.context, self.x, self.y = context, x, y
... context.action('init', f)
... def sub(self, context, a, b):
... context.action(('sub', a, b), f)
... def __call__(self):
... self.context.action(('call', self.x, self.y), f)
We need a complex directive definition:
>>> class Ixy(Interface):
... x = zope.schema.TextLine()
... y = zope.schema.TextLine()
>>> definition = ComplexDirectiveDefinition(
... context, name="test", schema=Ixy,
... handler=Handler)
>>> class Iab(Interface):
... a = zope.schema.TextLine()
... b = zope.schema.TextLine()
>>> definition['sub'] = Iab, ''
OK, now that we have the context, handler and definition, we're
ready to use a stack item.
>>> item = ComplexStackItem(definition, context, {'x': u'xv', 'y': u'yv'},
... 'foo')
When we created the definition, the handler (factory) was called.
>>> context.actions
[('init', f, (), {}, (), 'foo')]
If a subdirective is provided, the ``contained`` method of the stack item
is called. It will lookup the subdirective schema and call the
corresponding method on the handler instance:
>>> simple = item.contained(('somenamespace', 'sub'),
... {'a': u'av', 'b': u'bv'}, 'baz')
>>> simple.finish()
Note that the name passed to ``contained`` is a 2-part name, consisting of
a namespace and a name within the namespace.
>>> from pprint import PrettyPrinter
>>> pprint=PrettyPrinter(width=60).pprint
>>> pprint(context.actions)
[('init', f, (), {}, (), 'foo'),
(('sub', u'av', u'bv'), f, (), {}, (), 'baz')]
The new stack item returned by contained is one that doesn't allow
any more subdirectives,
When all of the subdirectives have been provided, the ``finish``
method is called:
>>> item.finish()
The stack item will call the handler if it is callable.
>>> pprint(context.actions)
[('init', f, (), {}, (), 'foo'),
(('sub', u'av', u'bv'), f, (), {}, (), 'baz'),
(('call', u'xv', u'yv'), f, (), {}, (), 'foo')]
"""
implements(IStackItem)
def __init__(self, meta, context, data, info):
self.meta = meta
self.context = context
self.info = info
# Call the handler contructor
# Need to save and restore old info
oldinfo = context.info
context.info = info
args = toargs(context, meta.schema, data)
self.handler = self.meta.handler(context, **args)
context.info = oldinfo
def contained(self, name, data, info):
"""Handle a subdirective
"""
# Look up the subdirective meta data on our meta object
ns, name = name
schema = self.meta.get(name)
if schema is None:
raise ConfigurationError("Invalid directive", name)
schema = schema[0] # strip off info
handler = getattr(self.handler, name)
return SimpleStackItem(self.context, handler, info, schema, data)
def finish(self):
# when we're done, we call the handler, which might return more actions
# Need to save and restore old info
oldinfo = self.context.info
self.context.info = self.info
try:
actions = self.handler()
except AttributeError, v:
if v[0] == '__call__':
return # noncallable
raise
except TypeError:
return # non callable
if actions:
# we allow the handler to return nothing
for action in actions:
self.context.action(*action)
self.context.info = oldinfo
##############################################################################
# Helper classes
class GroupingContextDecorator(ConfigurationContext):
"""Helper mix-in class for building grouping directives
See the discussion (and test) id GroupingStackItem.
"""
implements(IConfigurationContext)
def __init__(self, context, **kw):
self.context = context
for name, v in kw.items():
setattr(self, name, v)
directlyProvides(self)
def __getattr__(self, name,
getattr=getattr, setattr=setattr):
v = getattr(self.context, name)
# cache result in self
setattr(self, name, v)
return v
def before(self):
pass
def after(self):
pass
##############################################################################
# Directive-definition
class DirectiveSchema(fields.GlobalObject):
"""A field that contains a global variable value that must be a schema
"""
def _validate(self, value):
super(fields.GlobalObject, self)._validate(value)
if not IInterface.isImplementedBy(value):
raise zope.schema.ValidationError(WrongType, value)
class IDirectivesInfo(Interface):
"""Schema for the ``directives`` directive
"""
namespace = zope.schema.URI(
title=u"Namespace",
description=u"The namespace that directives' names will be defined in",
)
class IDirectivesContext(IDirectivesInfo, IConfigurationContext):
pass
class DirectivesHandler(GroupingContextDecorator):
"""Handler for the directives directive
This is just a grouping directive that adds a namespace attribute
to the normal directive context.
"""
implements(IDirectivesContext)
class IDirectiveInfo(Interface):
"""Information common to all directive definitions have
"""
name = zope.schema.TextLine(
title = u"Directive name",
description = u"The name of the directive being defined",
)
schema = DirectiveSchema(
title = u"Directive handler",
description = u"The dotted name of the directive handler",
)
class IFullInfo(IDirectiveInfo):
"""Information that all top-level directives (not subdirectives) have
"""
handler = fields.GlobalObject(
title = u"Directive handler",
description = u"The dotted name of the directive handler",
)
usedIn = fields.GlobalObject(
title = u"The directive types the directive can be used in",
description = (u"The interface of the directives that can contain "
u"the directive"
),
default = IConfigurationContext,
value_type = zope.schema.InterfaceField(),
)
class IStandaloneDirectiveInfo(IDirectivesInfo, IFullInfo):
"""Info for full directives defined outside a directives directives
"""
def defineSimpleDirective(context, name, schema, handler,
namespace='', usedIn=IConfigurationContext):
"""Define a simple directive
Define and register a factory that invokes the simple directive
and returns a new stack item, which is always the same simple stack item.
If the namespace is '*', the directive is registered for all namespaces.
for example:
>>> context = ConfigurationMachine()
>>> from zope.configuration.tests.directives import f
>>> class Ixy(Interface):
... x = zope.schema.TextLine()
... y = zope.schema.TextLine()
>>> def s(context, x, y):
... context.action(('s', x, y), f)
>>> defineSimpleDirective(context, 's', Ixy, s, testns)
>>> context((testns, "s"), x=u"vx", y=u"vy")
>>> context.actions
[(('s', u'vx', u'vy'), f)]
>>> context(('http://www.zope.com/t1', "s"), x=u"vx", y=u"vy")
Traceback (most recent call last):
...
ConfigurationError: ('Unknown directive', 'http://www.zope.com/t1', 's')
>>> context = ConfigurationMachine()
>>> defineSimpleDirective(context, 's', Ixy, s, "*")
>>> context(('http://www.zope.com/t1', "s"), x=u"vx", y=u"vy")
>>> context.actions
[(('s', u'vx', u'vy'), f)]
"""
namespace = namespace or context.namespace
if namespace != '*':
name = namespace, name
def factory(context, data, info):
return SimpleStackItem(context, handler, info, schema, data)
context.register(usedIn, name, factory)
def defineGroupingDirective(context, name, schema, handler,
namespace='', usedIn=IConfigurationContext):
"""Define a grouping directive
Define and register a factory that sets up a grouping directive.
If the namespace is '*', the directive is registered for all namespaces.
for example:
>>> context = ConfigurationMachine()
>>> from zope.configuration.tests.directives import f
>>> class Ixy(Interface):
... x = zope.schema.TextLine()
... y = zope.schema.TextLine()
We won't bother creating a special grouping directive class. We'll
just use GroupingContextDecorator, which simple sets up a grouping
context that has extra attributes defined by a schema:
>>> defineGroupingDirective(context, 'g', Ixy,
... GroupingContextDecorator, testns)
>>> context.begin((testns, "g"), x=u"vx", y=u"vy")
>>> context.stack[-1].context.x
u'vx'
>>> context.stack[-1].context.y
u'vy'
>>> context(('http://www.zope.com/t1', "g"), x=u"vx", y=u"vy")
Traceback (most recent call last):
...
ConfigurationError: ('Unknown directive', 'http://www.zope.com/t1', 'g')
>>> context = ConfigurationMachine()
>>> defineGroupingDirective(context, 'g', Ixy,
... GroupingContextDecorator, "*")
>>> context.begin(('http://www.zope.com/t1', "g"), x=u"vx", y=u"vy")
>>> context.stack[-1].context.x
u'vx'
>>> context.stack[-1].context.y
u'vy'
"""
namespace = namespace or context.namespace
if namespace != '*':
name = namespace, name
def factory(context, data, info):
args = toargs(context, schema, data)
newcontext = handler(context, **args)
newcontext.info = info
return GroupingStackItem(newcontext)
context.register(usedIn, name, factory)
class IComplexDirectiveContext(IFullInfo, IConfigurationContext):
pass
class ComplexDirectiveDefinition(GroupingContextDecorator, dict):
"""Handler for defining complex directives
See the description and tests for ComplexStackItem.
"""
implements(IComplexDirectiveContext)
def before(self):
def factory(context, data, info):
return ComplexStackItem(self, context, data, info)
self.context.register(self.usedIn, (self.namespace, self.name),
factory)
def subdirective(context, name, schema):
context.context[name] = schema, context.info
##############################################################################
# Argument conversion
def toargs(context, schema, data):
"""Marshal data to an argument dictionary using a schema
Names that are python keywords have am underscore added
as a suffix in the schema and in the argument list, but are used
without the underscore in the data.
The fields in the schema must all implement IFromUnicode.
All of the items in the data must have corresponding fields in the
schema unless the schema has a true tagged value named
'keyword_arguments'.
Here's an example:
>>> from zope import schema
>>> class schema(Interface):
... in_ = zope.schema.Int(constraint=lambda v: v > 0)
... f = zope.schema.Float()
... n = zope.schema.TextLine(min_length=1, default=u"rob")
... x = zope.schema.BytesLine(required=False)
... u = zope.schema.URI()
>>> context = ConfigurationMachine()
>>> from pprint import PrettyPrinter
>>> pprint=PrettyPrinter(width=50).pprint
>>> pprint(toargs(context, schema,
... {'in': u'1', 'f': u'1.2', 'n': u'bob', 'x': u'x.y.z',
... 'u': u'http://www.zope.org' }))
{'f': 1.2,
'in_': 1,
'n': u'bob',
'u': 'http://www.zope.org',
'x': 'x.y.z'}
If we have extra data, we'll get an error:
>>> toargs(context, schema,
... {'in': u'1', 'f': u'1.2', 'n': u'bob', 'x': u'x.y.z',
... 'u': u'http://www.zope.org', 'a': u'1'})
Traceback (most recent call last):
...
ConfigurationError: ('Unrecognized parameters:', 'a')
Unless we set a tagged value to say that extra arguments are ok:
>>> schema.setTaggedValue('keyword_arguments', True)
>>> pprint(toargs(context, schema,
... {'in': u'1', 'f': u'1.2', 'n': u'bob', 'x': u'x.y.z',
... 'u': u'http://www.zope.org', 'a': u'1'}))
{'a': u'1',
'f': 1.2,
'in_': 1,
'n': u'bob',
'u': 'http://www.zope.org',
'x': 'x.y.z'}
If we ommit required data we get an error telling us what was omitted:
>>> pprint(toargs(context, schema,
... {'in': u'1', 'f': u'1.2', 'n': u'bob', 'x': u'x.y.z'}))
Traceback (most recent call last):
...
ConfigurationError: ('Missing parameter:', 'u')
Although we can omit not-required data:
>>> pprint(toargs(context, schema,
... {'in': u'1', 'f': u'1.2', 'n': u'bob',
... 'u': u'http://www.zope.org', 'a': u'1'}))
{'a': u'1',
'f': 1.2,
'in_': 1,
'n': u'bob',
'u': 'http://www.zope.org'}
And we can ommit required fields if they have valid defaults
(defaults that are valid values):
>>> pprint(toargs(context, schema,
... {'in': u'1', 'f': u'1.2',
... 'u': u'http://www.zope.org', 'a': u'1'}))
{'a': u'1',
'f': 1.2,
'in_': 1,
'n': u'rob',
'u': 'http://www.zope.org'}
We also get an error if any data was invalid:
>>> pprint(toargs(context, schema,
... {'in': u'0', 'f': u'1.2', 'n': u'bob', 'x': u'x.y.z',
... 'u': u'http://www.zope.org', 'a': u'1'}))
Traceback (most recent call last):
...
ConfigurationError: ('Invalid value for', 'in',""" \
""" "(u'Constraint not satisfied', 0)")
"""
data = dict(data)
args = {}
for name, field in schema.namesAndDescriptions(True):
field = field.bind(context)
n = name
if n.endswith('_') and iskeyword(n[:-1]):
n = n[:-1]
s = data.get(n, data)
if s is not data:
s = unicode(s)
del data[n]
try:
args[str(name)] = field.fromUnicode(s)
except zope.schema.ValidationError, v:
raise ConfigurationError("Invalid value for", n, str(v))
elif field.required:
# if the default is valid, we can use that:
default = field.default
try:
field.validate(default)
except zope.schema.ValidationError:
raise ConfigurationError("Missing parameter:", n)
args[str(name)] = default
if data:
# we had data left over
try:
keyword_arguments = schema.getTaggedValue('keyword_arguments')
except KeyError:
keyword_arguments = False
if not keyword_arguments:
raise ConfigurationError("Unrecognized parameters:", *data)
for name in data:
args[str(name)] = data[name]
return args
##############################################################################
# Conflict resolution
def expand_action(discriminator, callable=None, args=(), kw={},
includepath=(), info=''):
return (discriminator, callable, args, kw,
includepath, info)
def resolveConflicts(actions):
"""Resolve conflicting actions
Given an actions list, identify and try to resolve conflicting actions.
Actions conflict if they have the same non-null discriminator.
Conflicting actions can be resolved if the include path of one of
the actions is a prefix of the includepaths of the other
conflicting actions and is unequal to the include paths in the
other conflicting actions.
Here are some examples to illustrate how this works:
>>> from zope.configuration.tests.directives import f
>>> from pprint import PrettyPrinter
>>> pprint=PrettyPrinter(width=60).pprint
>>> pprint(resolveConflicts([
... (None, f),
... (1, f, (1,), {}, (), 'first'),
... (1, f, (2,), {}, ('x',), 'second'),
... (1, f, (3,), {}, ('y',), 'third'),
... (4, f, (4,), {}, ('y',)),
... (3, f, (3,), {}, ('y',)),
... (None, f, (5,), {}, ('y',)),
... ]))
[(None, f),
(1, f, (1,), {}, (), 'first'),
(4, f, (4,), {}, ('y',)),
(3, f, (3,), {}, ('y',)),
(None, f, (5,), {}, ('y',))]
>>> try:
... v = resolveConflicts([
... (None, f),
... (1, f, (2,), {}, ('x',), 'eek'),
... (1, f, (3,), {}, ('y',), 'ack'),
... (4, f, (4,), {}, ('y',)),
... (3, f, (3,), {}, ('y',)),
... (None, f, (5,), {}, ('y',)),
... ])
... except ConfigurationConflictError, v:
... pass
>>> print v
Conflicting configuration actions
For: 1
eek
ack
"""
# organize actions by discriminators
unique = {}
output = []
for i in range(len(actions)):
(discriminator, callable, args, kw, includepath, info
) = expand_action(*(actions[i]))
if discriminator is None:
# The discriminator is None, so this directive can
# never conflict. We can add it directly to the
# configuration actions.
output.append(
(i, discriminator, callable, args, kw, includepath, info)
)
continue
a = unique.setdefault(discriminator, [])
a.append(
(includepath, i, callable, args, kw, info)
)
# Check for conflicts
conflicts = {}
for discriminator, dups in unique.items():
# We need to sort the actions by the paths so that the shortest
# path with a given prefix comes first:
dups.sort()
(basepath, i, callable, args, kw, baseinfo) = dups[0]
output.append(
(i, discriminator, callable, args, kw, basepath, baseinfo)
)
for includepath, i, callable, args, kw, info in dups[1:]:
# Test whether path is a prefix of opath
if (includepath[:len(basepath)] != basepath # not a prefix
or
(includepath == basepath)
):
if discriminator not in conflicts:
conflicts[discriminator] = [baseinfo]
conflicts[discriminator].append(info)
if conflicts:
raise ConfigurationConflictError(conflicts)
# Now put the output back in the original order, and return it:
output.sort()
r = []
for o in output:
action = o[1:]
while len(action) > 2 and not action[-1]:
action = action[:-1]
r.append(action)
return r
class ConfigurationConflictError(ConfigurationError):
def __init__(self, conflicts):
self._conflicts = conflicts
def __str__(self):
r = ["Conflicting configuration actions"]
for discriminator, infos in self._conflicts.items():
r.append(" For: %s" % (discriminator, ))
for info in infos:
for line in unicode(info).rstrip().split(u'\n'):
r.append(u" "+line)
return "\n".join(r)
##############################################################################
# Bootstap code
def _bootstrap(context):
# Set enough machinery to register other directives
# Define the directive (simple directive) directive by calling it's
# handler directly
defineSimpleDirective(
context,
namespace=metans, name='directive',
schema=IStandaloneDirectiveInfo,
handler=defineSimpleDirective)
# OK, now that we have that, we can use the machine to define the
# other directives. This isn't the easiest way to proceed, but it lets
# us eat our own dogfood. :)
# Standalone groupingDirective
context((metans, 'directive'),
name='groupingDirective',
namespace=metans,
handler="zope.configuration.config.defineGroupingDirective",
schema="zope.configuration.config.IStandaloneDirectiveInfo"
)
# Now we can use the grouping directive to define the directives directive
context((metans, 'groupingDirective'),
name='directives',
namespace=metans,
handler="zope.configuration.config.DirectivesHandler",
schema="zope.configuration.config.IDirectivesInfo"
)
# directive and groupingDirective inside directives
context((metans, 'directive'),
name='directive',
namespace=metans,
usedIn="zope.configuration.config.IDirectivesContext",
handler="zope.configuration.config.defineSimpleDirective",
schema="zope.configuration.config.IFullInfo"
)
context((metans, 'directive'),
name='groupingDirective',
namespace=metans,
usedIn="zope.configuration.config.IDirectivesContext",
handler="zope.configuration.config.defineGroupingDirective",
schema="zope.configuration.config.IFullInfo"
)
# Setup complex directive directive, both standalone, and in
# directives directive
context((metans, 'groupingDirective'),
name='complexDirective',
namespace=metans,
handler="zope.configuration.config.ComplexDirectiveDefinition",
schema="zope.configuration.config.IStandaloneDirectiveInfo"
)
context((metans, 'groupingDirective'),
name='complexDirective',
namespace=metans,
usedIn="zope.configuration.config.IDirectivesContext",
handler="zope.configuration.config.ComplexDirectiveDefinition",
schema="zope.configuration.config.IFullInfo"
)
# Finally, setup subdirective directive
context((metans, 'directive'),
name='subdirective',
namespace=metans,
usedIn="zope.configuration.config.IComplexDirectiveContext",
handler="zope.configuration.config.subdirective",
schema="zope.configuration.config.IDirectiveInfo"
)
import zope.configuration.backward
zope.configuration.backward.bootstrap(context)
=== Added File Zope3/src/zope/configuration/fields.py ===
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
"""Configuration-specific schema fields
$Id: fields.py,v 1.1 2003/07/28 22:22:39 jim Exp $
"""
from zope import schema
from zope.schema.interfaces import IFromUnicode
from zope.configuration.exceptions import ConfigurationError
from zope.interface import implements
class GlobalObject(schema.Field):
"""An object that can be accesses as a module global
"""
implements(IFromUnicode)
def __init__(self, value_type=None, **kw):
self.value_type = value_type
super(GlobalObject, self).__init__(**kw)
def _validate(self, value):
super(GlobalObject, self)._validate(value)
if self.value_type is not None:
self.value_type.validate(value)
def fromUnicode(self, u):
"""
Examples:
First, we need to set up a stub name resolver:
>>> d = {'x': 1, 'y': 42, 'z': 'zope'}
>>> class fakeresolver(dict):
... def resolve(self, n):
... return self[n]
>>> fake = fakeresolver(d)
>>> g = GlobalObject(value_type=schema.Int())
>>> gg = g.bind(fake)
>>> gg.fromUnicode("x")
1
>>> gg.fromUnicode(" x \\n ")
1
>>> gg.fromUnicode("y")
42
>>> gg.fromUnicode("z")
Traceback (most recent call last):
...
ValidationError: (u'Wrong type', 'zope', (<type 'int'>, <type 'long'>))
>>> g = GlobalObject(constraint=lambda x: x%2 == 0)
>>> gg = g.bind(fake)
>>> gg.fromUnicode("x")
Traceback (most recent call last):
...
ValidationError: (u'Constraint not satisfied', 1)
>>> gg.fromUnicode("y")
42
>>>
"""
name = str(u.strip())
try:
value = self.context.resolve(name)
except ConfigurationError, v:
raise schema.ValidationError(v)
self.validate(value)
return value
class GlobalObjects(schema.Sequence):
"""A sequence of global objects
"""
implements(IFromUnicode)
def fromUnicode(self, u):
"""
Examples:
First, we need to set up a stub name resolver:
>>> d = {'x': 1, 'y': 42, 'z': 'zope', 'x.y.x': 'foo'}
>>> class fakeresolver(dict):
... def resolve(self, n):
... return self[n]
>>> fake = fakeresolver(d)
>>> g = GlobalObjects()
>>> gg = g.bind(fake)
>>> gg.fromUnicode(" \\n x y z \\n")
[1, 42, 'zope']
>>> g = GlobalObjects(value_type=
... schema.Int(constraint=lambda x: x%2 == 0))
>>> gg = g.bind(fake)
>>> gg.fromUnicode("x y")
Traceback (most recent call last):
...
ValidationError: (u'Wrong contained type', """ \
"""[Constraint not satisfied 1])
>>> gg.fromUnicode("z y")
Traceback (most recent call last):
...
ValidationError: (u'Wrong contained type', """ \
"""[Wrong type zope (<type 'int'>, <type 'long'>)])
>>> gg.fromUnicode("y y")
[42, 42]
>>>
"""
values = [self.context.resolve(name)
for name in str(u.strip()).split()]
self.validate(values)
return values
class Bool(schema.Bool):
implements(IFromUnicode)
def fromUnicode(self, u):
u = u.lower()
if u in ('1', 'true', 'yes', 't', 'y'):
return True
if u in ('0', 'false', 'no', 'f', 'n'):
return False
raise schema.ValidationError
=== Zope3/src/zope/configuration/__init__.py 1.2 => 1.3 ===
--- Zope3/src/zope/configuration/__init__.py:1.2 Wed Dec 25 09:13:33 2002
+++ Zope3/src/zope/configuration/__init__.py Mon Jul 28 18:22:39 2003
@@ -20,18 +20,3 @@
def namespace(suffix):
return 'http://namespaces.zope.org/'+suffix
-import sys, os
-from zope.configuration.xmlconfig import XMLConfig
-
-def config(dir):
- try:
- XMLConfig(os.path.join(dir, 'site.zcml'))()
- except:
- # Use the ExceptionFormatter to provide XMLconfig debug info
- from zope.exceptions.exceptionformatter import format_exception
- exc_info = ['='*72, '\nZope Configuration Error\n', '='*72, '\n'] \
- + apply(format_exception, sys.exc_info())
- sys.stderr.write(''.join(exc_info))
- sys.exit(0) # Fatal config error
-
-__all__ = ["namespace", "config"]
=== Zope3/src/zope/configuration/interfaces.py 1.1 => 1.2 ===
--- Zope3/src/zope/configuration/interfaces.py:1.1 Mon Dec 30 21:52:10 2002
+++ Zope3/src/zope/configuration/interfaces.py Mon Jul 28 18:22:39 2003
@@ -13,68 +13,61 @@
##############################################################################
from zope.interface import Interface
+from zope.schema import BytesLine
-class INonEmptyDirective(Interface):
+class IConfigurationContext(Interface):
+ """Configuration Context
- def __call__(context,**kw):
- """Compute subdirective handler
-
- context -- an execution context that the directive may use for
- things like resolving names
-
- kw -- a dictionary containing the values of any attributes
- that were specified on the directive
+ The configuration context manages information about the state of
+ the configuration system, such as the package containing the
+ configuration file. More importantly, it provides methods for
+ importing objects and opening files relative to the package.
+ """
- Return an ISubdirectiveHandler.
+ package = BytesLine(
+ title=u"The current package name",
+ description=u"""\
+ This is the name of the package containing the configuration
+ file being executed. If the configuration file was not
+ included by package, then this is None.
+ """,
+ required=False,
+ )
+
+ def resolve(dottedname):
+ """Resolve a dotted name to an object
+
+ A dotted name is constructed by concatenating a dotted module
+ name with a global name within the module using a dot. For
+ example, the object named "spam" in the foo.bar module has a
+ dotted name of foo.bar.spam. If the current package is a
+ prefix of a dotted name, then the package name can be relaced
+ with a leading dot, So, for example, if the configuration file
+ is in the foo package, then the dotted name foo.bar.spam can
+ be shortened to .bar.spam.
+
+ If the current package is multiple levels deel, multiple
+ leading dots can be used to refer to higher-level modules.
+ For example, if the current package is x.y.z, the dotted
+ object name ..foo refers to x.y.foo.
"""
-class IEmptyDirective(Interface):
-
- def __call__(context,**kw):
- """Compute configuration actions
-
- context -- an execution context that the directive may use for
- things like resolving names
-
- kw -- a dictionary containing the values of any attributes
- that were specified on the directive
-
- Return a sequence of configuration actions. Each action is a
- tuple with:
-
- - A discriminator, value used to identify conflicting
- actions. Actions conflict if they have the same values
- for their discriminators.
-
- - callable object
+ def path(filename):
+ """Compute a full file name for the given file
- - argument tuple
-
- - and, optionally, a keyword argument dictionary.
-
- The callable object will be called with the argument tuple and
- keyword arguments to perform the action.
+ If the filename is relative to the package, then the returned
+ name will include the package path, otherwise, the original
+ file name is returned.
"""
+ def action(self, discriminator, callable, args=(), kw={}):
+ """Record a configuration action
-class ISubdirectiveHandler(Interface):
- """Handle subdirectives
-
- Provide methods for registered subdirectives. The methods are
- typically IEmptyDirective objects. They could, theoretically be
- INonEmptyDirective objects.
-
- Also provide a call that can provide additional configuration
- actions.
-
- """
-
- def __call__():
- """Return a sequence of configuration actions.
-
- See IEmptyDirective for a definition of configuration actions.
-
- This method should be called *after* any subdirective methods are
- called during the processing of the (sub)directive whose subdirectives
- are being processed. It may return an empty list.
+ The job of most directives is to compute actions for later processing.
+ The action method is used to record those actions. The discriminator
+ is used to to find actions that conflict. Actions conflict if they
+ have the same discriminator. The exception to this is the
+ special case of the discriminator with the value None. An
+ actions with a discriminator of None never conflicts with
+ other actions.
"""
=== Zope3/src/zope/configuration/xmlconfig.py 1.9 => 1.10 ===
--- Zope3/src/zope/configuration/xmlconfig.py:1.9 Mon Jun 23 10:44:13 2003
+++ Zope3/src/zope/configuration/xmlconfig.py Mon Jul 28 18:22:39 2003
@@ -11,7 +11,12 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
-"""
+"""Support for the XML configuration file format
+
+Note, for a detailed description of the way that conflicting
+configuration actions are resolved, see the detailed example in
+test_includeOverrides in tests/text_xmlconfig.py
+
$Id$
"""
@@ -19,50 +24,49 @@
import os
import sys
import logging
+import zope.configuration.config as config
from keyword import iskeyword
-from types import StringType
-from os.path import abspath
-
+from zope import schema
from xml.sax import make_parser
from xml.sax.xmlreader import InputSource
from xml.sax.handler import ContentHandler, feature_namespaces
from xml.sax import SAXParseException
-from zope.configuration import name
-from zope.configuration.meta import begin, sub, end
from zope.configuration.exceptions import ConfigurationError
-
-# marker used in Context class and XMLConfig class to indicate
-# that a particular zcml file was given no "package" attribute
-# when included, and the same went for all of its parents.
-_NO_MODULE_GIVEN = object()
-
+from zope.interface import Interface, implements
logger = logging.getLogger("config")
class ZopeXMLConfigurationError(ConfigurationError):
- """Zope XML Configuration error"""
+ """Zope XML Configuration error
- def __init__(self, locator, mess, etype=None):
- if etype is None:
- if not isinstance(mess, StringType):
- try:
- mess = "\n%s:\n %s" % (mess.__class__.__name__, mess)
- except AttributeError:
- mess = str(mess)
- else:
- mess = "\n%s: %s" % (etype.__name__, mess)
+ These errors are wrappers for other errors. The include configuration
+ info and the wrapped error type and value:
- self.lno = locator.getLineNumber()
- self.cno = locator.getColumnNumber()
- self.sid = locator.getSystemId()
- self.mess = mess
+ >>> v = ZopeXMLConfigurationError("blah", AttributeError, "xxx")
+ >>> print v
+ 'blah'
+ AttributeError: xxx
+
+ """
- def __str__(self):
- return 'File "%s", line %s.%s\n\t%s' % (
- self.sid, self.lno, self.cno, self.mess)
+ def __init__(self, info, etype, evalue):
+ self.info, self.etype, self.evalue = info, etype, evalue
+ def __str__(self):
+ # Only use the repr of the info. This is because we expect to
+ # get a parse info and we only want the location information.
+ return "%s\n %s: %s" % (
+ `self.info`, self.etype.__name__, self.evalue)
class ZopeSAXParseException(ConfigurationError):
+ """Sax Parser errors, reformatted in an emacs friendly way
+
+ >>> v = ZopeSAXParseException("foo.xml:12:3:Not well formed")
+ >>> print v
+ File "foo.xml", line 12.3, Not well formed
+
+ """
+
def __init__(self, v):
self._v = v
@@ -74,237 +78,239 @@
else:
return str(v)
-class ConfigurationExecutionError(ZopeXMLConfigurationError):
- """An error occurred during execution of a configuration action
- """
+class ParserInfo:
+ """Information about a directive based on parser data
- def __init__(self, locator, mess, etype=None):
- if etype is None:
- if isinstance(mess, StringType):
- try:
- mess = "%s: %s" % (mess.__class__.__name__, mess)
- except AttributeError:
- mess = str(mess)
- else:
- mess = "\n%s: %s" % (etype.__name__, mess)
+ This includes the directive location, as well as text data
+ contained in the directive.
+
+ >>> info = ParserInfo('tests//sample.zcml', 1, 1)
+ >>> info
+ File "tests//sample.zcml", line 1.1
+
+ >>> print info
+ File "tests//sample.zcml", line 1.1
+
+ >>> info.characters("blah\\n")
+ >>> info.characters("blah")
+ >>> info.text
+ u'blah\\nblah'
+
+ >>> info.end(7,16)
+ >>> info
+ File "tests//sample.zcml", line 1.1-7.16
+
+ >>> print info
+ File "tests//sample.zcml", line 1.1-7.16
+ <zopeConfigure xmlns='http://namespaces.zope.org/zope'>
+ <!-- zope.configure -->
+ <directives namespace="http://namespaces.zope.org/zope">
+ <directive name="hook" attributes="name implementation module"
+ handler="zope.configuration.metaconfigure.hook" />
+ </directives>
+ </zopeConfigure>
+
+
+ """
- self.lno, self.cno, self.sid = locator
- self.mess = mess
+ text = u''
+ def __init__(self, file, line, column):
+ self.file, self.line, self.column = file, line, column
+ self.eline, self.ecolumn = line, column
+
+ def end(self, line, column):
+ self.eline, self.ecolumn = line, column
+
+ def __repr__(self):
+ if (self.line, self.column) == (self.eline, self.ecolumn):
+ return 'File "%s", line %s.%s' % (
+ self.file, self.line, self.column)
+
+ return 'File "%s", line %s.%s-%s.%s' % (
+ self.file, self.line, self.column, self.eline, self.ecolumn)
+ def __str__(self):
+ if (self.line, self.column) == (self.eline, self.ecolumn):
+ return 'File "%s", line %s.%s' % (
+ self.file, self.line, self.column)
+
+ file = self.file
+ if file == 'tests//sample.zcml':
+ # special case for testing
+ file = os.path.join(os.path.split(__file__)[0],
+ 'tests', 'sample.zcml')
+
+ try:
+ f = open(file)
+ except IOError:
+ src = " Could not read source."
+ else:
+ lines = f.readlines()[self.line-1:self.eline]
+ lines[0] = lines[0][self.column-1:]
+ lines[-1] = lines[-1][:self.ecolumn]
+ src = ''.join([u" "+l for l in lines])
+
+ return "%s\n%s" % (`self`, src)
+
+ def characters(self, characters):
+ self.text += characters
+
+
class ConfigurationHandler(ContentHandler):
+ """Interface toi the cml parser
- __top_name = 'zopeConfigure'
+ Translate parser events into calls into the configuration system.
+ """
- def __init__(self, actions, context, directives=None, testing=0):
- self.__stack = []
- self.__actions = actions
- self.__directives = directives
- self.__context = context
- self.__testing = testing
+ def __init__(self, context, testing=0):
+ self.context = context
+ self.testing = testing
def setDocumentLocator(self, locator):
- self.__locator = locator
+ self.locator = locator
def characters(self, text):
- stack = self.__stack
- if len(stack) > 1:
- base = stack[-1][0]
- if hasattr(base, 'zcmlText'):
- base.zcmlText(text)
+ self.context.getInfo().characters(text)
def startElementNS(self, name, qname, attrs):
- stack = self.__stack
- if not stack:
- if name[1] != self.__top_name:
- raise ZopeXMLConfigurationError(
- self.__locator, "Invalid top element: %s %s" % name)
-
- for (ns, aname), value in attrs.items():
- if ns is None:
- self.__context.file_attr(aname, value)
-
- stack.append(None)
- return
-
- kw = {}
+
+ data = {}
for (ns, aname), value in attrs.items():
if ns is None:
aname = str(aname)
- if iskeyword(aname): aname += '_'
- kw[aname] = value
+ data[aname] = value
- if len(stack) == 1:
- try:
- stack.append(
- begin(self.__directives, name, self.__context, **kw)
- )
- except Exception, v:
- if self.__testing:
- raise
- raise ZopeXMLConfigurationError, (
- self.__locator, v), sys.exc_info()[2]
-
- else:
- subs = self.__stack[-1]
- if subs is None:
- raise ZopeXMLConfigurationError(self.__locator,
- 'Invalid sub-directive')
- try:
- stack.append(sub(subs, name, self.__context, **kw))
- except Exception, v:
- if self.__testing:
- raise
- raise ZopeXMLConfigurationError, (
- self.__locator, v), sys.exc_info()[2]
-
- def endElementNS(self, name, qname):
- subs = self.__stack.pop()
- # to fool compiler that thinks actions is used before assignment:
- actions = ()
-
- if subs is not None:
- try:
- actions = end(subs)
- except Exception, v:
- if self.__testing:
- raise
- raise ZopeXMLConfigurationError, (
- self.__locator, str(v)), sys.exc_info()[2]
-
- append = self.__actions.append
+ info = ParserInfo(
+ self.locator.getSystemId(),
+ self.locator.getLineNumber(),
+ self.locator.getColumnNumber(),
+ )
try:
- for des, callable, args, kw in actions:
- append((self.__context,
- (self.__locator.getLineNumber(),
- self.__locator.getColumnNumber(),
- self.__locator.getSystemId(),
- ), des, callable, args, kw))
+ self.context.begin(name, data, info)
except:
- print 'endElementNS', actions
- raise
-
-
-class ZopeConflictingConfigurationError(ZopeXMLConfigurationError):
- "Zope XML Configuration error"
-
- def __init__(self, l1, l2, des):
- self.l1 = l1
- self.l2 = l2
- self.des = des
-
- def __str__(self):
- return """Conflicting configuration action:
- %s
- File "%s", line %s column %s
- File "%s", line %s column %s
- """ % (self.des,
- self.l1[2], self.l1[0], self.l1[1],
- self.l2[2], self.l2[0], self.l2[1],
- )
-
-
-class Context:
- def __init__(self, stack, module):
- self.__stackcopy = tuple(stack)
- if module is _NO_MODULE_GIVEN:
- self.__package = None
- elif module is None:
- self.__package = 'zopeproducts'
- else:
- self.__package = module.__name__
-
- def _stackcopy(self):
- return self.__stackcopy
-
- def resolve(self, dottedname):
- return name.resolve(dottedname, self.__package)
-
- def getNormalizedName(self, dottedname):
- return name.getNormalizedName(dottedname, self.__package)
-
- def path(self, file=None):
- return name.path(file, self.__package)
-
- def file_attr(self, name, value):
- if name == 'package':
- self.__package = value
- else:
- raise TypeError, "Unrecognized config file attribute: %s" % name
-
- def packageWasSet(self):
- return self.__package is not None
-
- def package(self):
- return self.__package
+ if self.testing:
+ raise
+ raise ZopeXMLConfigurationError, (
+ info, sys.exc_info()[0], sys.exc_info()[1]
+ ), sys.exc_info()[2]
+ self.context.setInfo(info)
+
-def xmlconfig(file, actions=None, context=None, directives=None, testing=0):
- if context is None:
- context = name
+ def endElementNS(self, name, qname):
+ info = self.context.getInfo()
+ info.end(
+ self.locator.getLineNumber(),
+ self.locator.getColumnNumber(),
+ )
+
+ try:
+ self.context.end()
+ except:
+ if self.testing:
+ raise
+ raise ZopeXMLConfigurationError, (
+ info, sys.exc_info()[0], sys.exc_info()[1]
+ ), sys.exc_info()[2]
+
+
+class IZopeConfigure(Interface):
+
+ package = config.fields.GlobalObject(__doc__="""Package
+
+ The package to be used for evaluating relative imports and file names.
+ """,
+ required=False)
+
+ domain = schema.BytesLine(__doc__="""Internationalization domain
+
+ This is a name for the software project. It must be a legal file-system
+ name as it will be used to contruct names for directories containing
+ translation data.
+
+ The domain defines a namespace for the message ids used by a project.
+ """,
+ required=False)
+
+class ZopeConfigure(config.GroupingContextDecorator):
+
+ implements(config.IConfigurationContext)
+
+ def __init__(self, context, **kw):
+ super(ZopeConfigure, self).__init__(context, **kw)
+ if 'package' in kw:
+ # if we have a package, we want to also define basepath
+ # so we don't acquire one
+ self.basepath = os.path.split(self.package.__file__)[0]
+
+
+def _register_configure(context):
+
+ # We have to use the direct definition function to define
+ # a directive for all namespaces.
+ config.defineGroupingDirective(
+ context,
+ name="zopeConfigure",
+ namespace="*",
+ schema=IZopeConfigure,
+ handler=ZopeConfigure,
+ )
+ config.defineGroupingDirective(
+ context,
+ name="configure",
+ namespace="*",
+ schema=IZopeConfigure,
+ handler=ZopeConfigure,
+ )
+
- if actions is None:
- call = actions = []
- else:
- call = 0
+def processxmlfile(file, context, testing=0):
+ """Process a configuration file
+ See examples in tests/text_xmlconfig.py
+ """
src = InputSource(getattr(file, 'name', '<string>'))
src.setByteStream(file)
parser = make_parser()
- parser.setContentHandler(
- ConfigurationHandler(actions, context,directives,
- testing=testing)
- )
+ parser.setContentHandler(ConfigurationHandler(context, testing=testing))
parser.setFeature(feature_namespaces, 1)
try:
parser.parse(src)
except SAXParseException:
raise ZopeSAXParseException, sys.exc_info()[1], sys.exc_info()[2]
- if call:
- descriptors = {}
- for level, loc, des, callable, args, kw in call:
- if des is not None:
- if des in descriptors:
- raise ZopeConflictingConfigurationError(
- descriptors[des], loc, des)
- descriptors[des] = loc
-
- callable(*args, **kw)
-
-def testxmlconfig(file, actions=None, context=None, directives=None):
- """xmlconfig that doesn't raise configuration errors
-
- This is useful for testing, as it doesn't mask exception types.
- """
- return xmlconfig(file, actions, context, directives, testing=1)
-
-
-class ZopeConfigurationConflictError(ZopeXMLConfigurationError):
-
- def __init__(self, conflicts):
- self._conflicts = conflicts
-
- def __str__(self):
- r = ["Conflicting configuration actions"]
- for dis, locs in self._conflicts.items():
- r.append('for: %s' % (dis,))
- for loc in locs:
- r.append(' File "%s", line %s column %s' %
- (loc[2], loc[0], loc[1]))
-
- return "\n".join(r)
-
-
-def inopen(filename):
- # XXX I don't really like the name of this function
+def openInOrPlain(filename):
"""Open a file, falling back to filename.in.
If the requested file does not exist and filename.in does, fall
back to filename.in. If opening the original filename fails for
any other reason, allow the failure to propogate.
+
+ For example, the tests/samplepackage dirextory has files:
+
+ configure.zcml
+ configure.zcml.in
+ foo.zcml.in
+
+ If we open configure.zcml, we'll get that file:
+
+ >>> here = os.path.split(__file__)[0]
+ >>> path = os.path.join(here, 'tests', 'samplepackage', 'configure.zcml')
+ >>> f = openInOrPlain(path)
+ >>> f.name[-14:]
+ 'configure.zcml'
+
+ But if we open foo.zcml, we'll get foo.zcml.in, since there isn't a
+ foo.zcml:
+
+ >>> path = os.path.join(here, 'tests', 'samplepackage', 'foo.zcml')
+ >>> f = openInOrPlain(path)
+ >>> f.name[-11:]
+ 'foo.zcml.in'
+
"""
try:
fp = open(filename)
@@ -317,133 +323,177 @@
raise
return fp
+class IInclude(Interface):
+
+ file = schema.BytesLine(
+ __doc__=
+ """Configuration file name
+
+ The name of a configuration file to be included,
+ relative to the directive containing the
+ including configuration file.
+
+ """,
+ default="configure.zcml",
+ )
+
+ package = config.fields.GlobalObject(
+ __doc__=
+ """Include packahe
+
+ Include the named file (or configure.zcml)
+ from the directory of this package.
+ """,
+ required=False,
+ )
+
+
+def include(_context, file, package=None):
+ """Include a zcml file
+
+ See examples in tests/text_xmlconfig.py
+ """
+
+ logger.debug("include %s" % file)
+
+ # This is a tad tricky. We want to behave a a grouping directive.
+ context = config.GroupingContextDecorator(_context)
+ if package is not None:
+ context.package = package
+ context.basepath = None
+ path = context.path(file)
+ f = openInOrPlain(path)
+
+ logger.debug("include %s" % f.name)
+
+ context.basepath = os.path.split(path)[0]
+ context.includepath = _context.includepath + (f.name, )
+ _context.stack.append(config.GroupingStackItem(context))
+
+ processxmlfile(f, context)
+ f.close()
+ assert _context.stack[-1].context is context
+ _context.stack.pop()
+
+def includeOverrides(_context, file, package=None):
+ """Include zcml file containing overrides
+
+ The actions in the included file are added to the context as if they
+ were in the including file directly.
+
+ See the detailed example in test_includeOverrides in
+ tests/text_xmlconfig.py
+ """
+
+ # We need to remember how many actions we had before
+ nactions = len(_context.actions)
+
+ # We'll give the new actions this include path
+ includepath = _context.includepath
+
+ # Now we'll include the file. We'll munge the actions after
+ include(_context, file, package)
+
+ # Now we'll grab the new actions, resolve conflicts,
+ # and munge the includepath:
+ newactions = []
+ for action in config.resolveConflicts(_context.actions[nactions:]):
+ (discriminator, callable, args, kw, oldincludepath, info
+ ) = config.expand_action(*action)
+ newactions.append(
+ (discriminator, callable, args, kw, includepath, info)
+ )
+
+ # and replace the new actions with the munched new actions:
+ _context.actions[nactions:] = newactions
+
+def _registerIncludes(context):
+ # Register the include directives
+ config.defineSimpleDirective(
+ context, "include", IInclude, include, namespace="*")
+ config.defineSimpleDirective(
+ context, "includeOverrides", IInclude, includeOverrides, namespace="*")
+
+ _register_configure(context)
+
+def file(name, package=None, context=None, execute=True):
+ """Execute a zcml file
+ """
+
+ if context is None:
+ context = config.ConfigurationMachine()
+ _registerIncludes(context)
+ context.package = package
+
+ include(context, name, package)
+ if execute:
+ context.execute_actions()
+
+ return context
+
+def string(s, context=None, name="test.string", execute=True):
+ """Execute a zcml string
+ """
+ from StringIO import StringIO
+
+ if context is None:
+ context = config.ConfigurationMachine()
+ _registerIncludes(context)
+
+ f = StringIO(s)
+ f.name = name
+ processxmlfile(f, context)
+
+ if execute:
+ context.execute_actions()
+
+ return context
+
+
+##############################################################################
+# Backward compatability, mainly for tests
+
+
+_context = None
+def _clearContext():
+ global _context
+ _context = config.ConfigurationMachine()
+ _registerIncludes(_context)
+
+def _getContext():
+ global _context
+ if _context is None:
+ _clearContext()
+ from zope.testing.cleanup import addCleanUp
+ addCleanUp(_clearContext)
+ return _context
class XMLConfig:
+ """Provide high-level handling of configuration files.
- def __init__(self, file_name, module=_NO_MODULE_GIVEN):
- if module is not None and module is not _NO_MODULE_GIVEN:
- module_dir = abspath(os.path.split(module.__file__)[0])
- file_name = os.path.join(module_dir, file_name)
-
- self._actions = []
- self._directives = {('*', 'include'):
- (self.include, {})}
-
- f = inopen(file_name)
- self._stack = [file_name]
- xmlconfig(f, self._actions,
- Context(self._stack, module=module),
- self._directives)
- f.close()
-
- def include(self, _context, file='configure.zcml', package=None):
- if package is None and _context.packageWasSet():
- package = _context.package()
- subpackages = False
- if package is not None:
- if package.endswith('.*'):
- # <include package="package.*" /> includes all subpackages
- subpackages = True
- parent = package = package[:-2]
- if package == "":
- package = "."
- try:
- package = _context.resolve(package)
- if len(package.__path__) != 1:
- print ("Module Path: '%s' has wrong number of elements"
- % str(package.__path__))
- # XXX: This should work for 99% of cases
- # We may want to revisit this with a more robust
- # mechanism later. Specifically, sometimes __path__
- # will have more than one element. Also, we could
- # use package.__file__, and lop the tail off that.
- prefix = package.__path__[0]
- except (ImportError, AttributeError, ValueError), v:
- raise # XXX the raise below hides the real error
- raise ValueError("Invalid package attribute: %s\n(%s)"
- % (package, `v`))
- else:
- prefix = os.path.dirname(self._stack[-1])
+ See examples in tests/text_xmlconfig.py
+ """
- if subpackages:
- for subdir in os.listdir(prefix):
- file_name = os.path.join(prefix, subdir, file)
- if not os.access(file_name, os.F_OK):
- continue
- subpackage = "%s.%s" % (parent, subdir)
- subpackage = _context.resolve(subpackage)
- self._include(file_name, subpackage)
- else:
- file_name = os.path.join(prefix, file)
- self._include(file_name, package)
- return ()
-
- def _include(self, file_name, package):
- logger.debug("include %s" % file_name)
- f = inopen(file_name)
- self._stack.append(file_name)
- xmlconfig(f, self._actions, Context(self._stack, package),
- self._directives)
- self._stack.pop()
- f.close()
+ def __init__(self, file_name, module=None):
+ context = _getContext()
+ include(context, file_name, module)
+ self.context = context
def __call__(self):
- self.organize()
+ self.context.execute_actions()
+
+def xmlconfig(file, testing=False):
+ context = _getContext()
+ processxmlfile(file, context, testing=testing)
+ context.execute_actions(testing=testing)
+
- def __iter__(self):
- return iter(self._actions)
+def testxmlconfig(file, context=None):
+ """xmlconfig that doesn't raise configuration errors
- def organize(self):
- actions = self._actions
+ This is useful for testing, as it doesn't mask exception types.
+ """
+ context = _getContext()
+ processxmlfile(file, context, testing=True)
+ context.execute_actions(testing=True)
- # organize actions by discriminators
- unique = {}
- cactions = []
- for i in range(len(actions)):
- context, loc, des, callable, args, kw = actions[i]
- if des is None:
- # The descriminator is None, so this directive can
- # never conflict. We can add it directly to the
- # configuration actions.
- cactions.append((i, loc, (callable, args, kw)))
- continue
-
- a = unique.setdefault(des, [])
- a.append((context._stackcopy(), i, loc, (callable, args, kw)))
-
- # Check for conflicts
- conflicts = {}
- for des, actions in unique.items():
-
- # We need to sort the actions by the paths so that the shortest
- # path with a given prefix comes first:
- actions.sort()
-
- path, i, loc, f = actions[0]
- for opath, i, oloc, f in actions[1:]:
- # Test whether path is a prefix of opath
- if opath[:len(path)] != path or (opath == path):
- if des not in conflicts:
- conflicts[des] = [loc]
- conflicts[des].append(oloc)
-
- if conflicts:
- raise ZopeConfigurationConflictError(conflicts)
-
- # Now order the configuration directives
- for des, actions in unique.items():
- path, i, loc, f = actions.pop(0)
- cactions.append((i, loc, f))
-
- unique = None
-
- cactions.sort()
-
- # Call actions
- for i, loc, f in cactions:
- try:
- callable, args, kw = f
- callable(*args, **kw)
- except Exception, v:
- raise ConfigurationExecutionError, (
- loc, v, sys.exc_info()[0]), sys.exc_info()[2]
=== Removed File Zope3/src/zope/configuration/meta.py ===
=== Removed File Zope3/src/zope/configuration/metameta.zcml ===
=== Removed File Zope3/src/zope/configuration/metametaconfigure.py ===
=== Removed File Zope3/src/zope/configuration/metametaconfigurefordocgen.py ===
=== Removed File Zope3/src/zope/configuration/metametafordocgen.zcml ===