[Zope-Checkins] CVS: Zope3/lib/python/Zope/Configuration - ConfigurationDirectiveInterfaces.py:1.1.2.1 meta.py:1.1.2.6 name.py:1.1.2.4 xmlconfig.py:1.1.2.4
Jim Fulton
jim@zope.com
Thu, 3 Jan 2002 14:29:55 -0500
Update of /cvs-repository/Zope3/lib/python/Zope/Configuration
In directory cvs.zope.org:/tmp/cvs-serv16747/Configuration
Modified Files:
Tag: Zope-3x-branch
meta.py name.py xmlconfig.py
Added Files:
Tag: Zope-3x-branch
ConfigurationDirectiveInterfaces.py
Log Message:
Refactored configuration framework:
- Configuration directives must be written to a
a different framework. See
ConfigurationDirectiveInterfaces.
- Configuration directives now don't take actions immediately.
Instead, they return a sequence of discriminators and callables
objects with arguments. This allows configuration to be defered to
allow overriding and conflct detection.
- Can now detect conflicting directives
- Can override directives. Directives in including configuration files
override directives in included files. Conflicting directives are
decided based on discriminators.
- Added new directives for defining directives. All directives, except
for a few bootstrap irectives, are now configurable in the
configuration file. This makes directives a little more discoverable
and facilitates extension of directives.
=== Added File Zope3/lib/python/Zope/Configuration/ConfigurationDirectiveInterfaces.py ===
##############################################################################
#
# Copyright (c) 2001 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 directives
Revision information: $Id: ConfigurationDirectiveInterfaces.py,v 1.1.2.1 2002/01/03 19:29:24 jim Exp $
"""
from Interface import Interface
class IEmptyDirective(Interface):
def __call__(**kw):
"""Compute configuration actions
Return a sequence of configuration actions. Each action is a
tuple with:
- A discriminator, value used to identify conflicting
actions. Actions cnflict of they have the same value values
for their discriminators.
- callable object
- 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.
"""
class INonEmptyDirective(Interface):
def __call__(**kw):
"""Compute complex directive handler
Return an IComplexDirectiveHandler
"""
class ISubdirectiveHandler(Interface):
"""Handle subdirectives
Provide mehods for registered subdirectives.
Also provide a call that can provide additional configuration actions.
"""
def __call__():
"""Return a sequence of configuration actions."""
=== Zope3/lib/python/Zope/Configuration/meta.py 1.1.2.5 => 1.1.2.6 ===
"""Registration of registration directives
-Currently, a directive:
-
- Is a callable object that is called with keyword
- arguments and may do something.
-
- If it returns None is returned, then a directive may not have
- subdirectives.
-
- If an object is returned, then its methods will be treated as (sub)
- directives, and it will be called without arguments when we get
- are done with the (outer) directive.
-
-This will all change soon. :/
+See ConfigurationDirectiveInterfaces
"""
-_directives={}
+from ConfigurationDirectiveInterfaces import INonEmptyDirective
+from ConfigurationDirectiveInterfaces import ISubdirectiveHandler
+from name import resolve
+
+_directives = {}
class InvalidDirective(Exception):
"An invalid directive was used"
-def register(namespace, name, callable):
- _directives[namespace, name] = callable
+class BrokenDirective(Exception):
+ "A directive is implemented incorrectly"
-def execute(_namespace, _name, **kw):
- try: callable=_directives[_namespace, _name]
+def register(name, callable):
+ subdirs = {}
+ _directives[name] = callable, subdirs
+ return subdirs
+
+def registersub(directives, name):
+ subdirs = {}
+ directives[name] = subdirs
+ return subdirs
+
+def _exe(callable, subs, kw):
+ r = callable(**kw)
+
+ if subs or INonEmptyDirective.isImplementedBy(callable):
+ return r, subs
+ else:
+ return lambda: r, subs
+
+def begin(_custom_directives, _name, **kw):
+ if _custom_directives and (_name in _custom_directives):
+ callable, subs = _custom_directives[_name]
+ else:
+ try:
+ callable, subs = _directives[_name]
+ except KeyError:
+ raise InvalidDirective(_name)
+
+ return _exe(callable, subs, kw)
+
+def sub(subs, _name, **kw):
+
+ base, subdirs = subs
+
+ try:
+ subs = subdirs[_name]
except KeyError:
- raise InvalidDirective(_namespace, _name)
- return callable(**kw)
+ raise InvalidDirective(_name)
+
+ callable = getattr(base, _name[1])
+
+ return _exe(callable, subs, kw)
+
+defaultkw = ({},)
+def end(base):
+ actions = base[0]()
+ ractions = []
+ for action in actions:
+ if len(action) < 3 or len(action) > 4:
+ raise BrokenDirective(action)
+ if len(action) == 3:
+ action += defaultkw
+ ractions.append(action)
+ return ractions
+
+class DirectiveNamespace:
+
+ def __init__(self, namespace):
+ self._namespace = namespace
+
+ def directive(self, name, handler, attributes='', namespace=None):
+ namespace = namespace or self._namespace
+ subs = register((namespace, name), resolve(handler))
+ return Subdirective(subs, namespace)
+
+ def __call__(self):
+ return ()
+
+def Directive(namespace, name, handler, attributes=''):
+ subs = register((namespace, name), resolve(handler))
+ return Subdirective(subs, namespace)
+
+Directive.__implements__ = INonEmptyDirective
+
+class InvaliDirectiveDefinition(Exception): pass
+
+class Subdirective:
+ """This is the meta-meta-directive"""
+ #
+ # Unlike other directives, it doesn't return any actions, but
+ # takes action right away, since it's actions are needed to process other
+ # directives.
+ #
+ # For this reason, this isn't a good directive example.
+
+ __implements__ = ISubdirectiveHandler
+
+ def __init__(self, subs, namespace=None):
+ self._subs = subs
+ self._namespace = namespace
+
+ def subdirective(self, name, attributes='', namespace=None):
+ namespace = namespace or self._namespace
+ if not namespace:
+ raise InvaliDirectiveDefinition(name)
+
+ subs = registersub(self._subs, (namespace, name))
+ return Subdirective(subs)
+
+ def __call__(self):
+ return ()
def _clear():
"To support unit tests"
_directives.clear()
+ _directives[('http://namespaces.zope.org/zope', 'directive')] = (
+ Directive, {
+ ('http://namespaces.zope.org/zope', 'subdirective'): {
+ ('http://namespaces.zope.org/zope', 'subdirective'): {
+ ('http://namespaces.zope.org/zope', 'subdirective'): {
+ }}}})
+ _directives[('http://namespaces.zope.org/zope', 'directives')] = (
+ DirectiveNamespace, {
+ ('http://namespaces.zope.org/zope', 'directive'): {
+ ('http://namespaces.zope.org/zope', 'subdirective'): {
+ ('http://namespaces.zope.org/zope', 'subdirective'): {
+ ('http://namespaces.zope.org/zope', 'subdirective'): {
+ }}}}})
+
+_clear()
=== Zope3/lib/python/Zope/Configuration/name.py 1.1.2.3 => 1.1.2.4 ===
"""
+import sys
from types import ModuleType
def resolve(name, _silly=('__doc__',), _globals={}):
if name[:1]=='.':
name='Zope.Products'+name
+
+ if name[-1:] == '.':
+ #XXX finish ending dots
+ name = name[:-1]
+
names=name.split('.')
last=names[-1]
mod='.'.join(names[:-1])
while 1:
- m=__import__(mod, _globals, _globals, _silly)
+ m=sys.modules.get(mod)
+ if m is None:
+ m=__import__(mod, _globals, _globals, _silly)
try:
a=getattr(m, last)
except AttributeError:
=== Zope3/lib/python/Zope/Configuration/xmlconfig.py 1.1.2.3 => 1.1.2.4 ===
from xml.sax.xmlreader import InputSource
from xml.sax.handler import ContentHandler, feature_namespaces
-from meta import execute
+from meta import begin, sub, end
from keyword import iskeyword
-import sys
+import sys, os
class ZopeXMLConfigurationError(Exception):
"Zope XML Configuration error"
@@ -30,12 +30,28 @@
return "%s at line %s column %s of %s" % (
self.mess, self.lno, self.cno, self.sid)
+class ConfigurationExecutionError(ZopeXMLConfigurationError):
+ """An error occurred during execution of a configuration action
+ """
+
+ def __init__(self, locator, mess):
+ if type(mess) is not type(''):
+ try:
+ mess = "%s: %s" % (mess.__class__.__name__, mess)
+ except AttributeError:
+ mess=str(mess)
+ self.lno, self.cno, self.sid = locator
+ self.mess=mess
+
class ConfigurationHandler(ContentHandler):
__top_name = 'http://namespaces.zope.org/zope', 'zopeConfigure'
- def __init__(self):
- self.__stack=[]
+ def __init__(self, actions, level=None, directives=None):
+ self.__stack = []
+ self.__level = level
+ self.__actions = actions
+ self.__directives = directives
def setDocumentLocator(self, locator):
self.__locator=locator
@@ -57,43 +73,167 @@
kw[aname]=value
if len(stack) == 1:
- try:
- stack.append(execute(*name, **kw))
+ try:
+ stack.append(begin(self.__directives, name, **kw))
except Exception, v:
raise ZopeXMLConfigurationError, (
- self.__locator, str(v)), sys.exc_info()[2]
+ self.__locator, v), sys.exc_info()[2]
- self.__ns = name[0]
else:
- if self.__ns != name[0]:
- raise ZopeXMLConfigurationError(self.__locator,
- 'Namespace missmatch')
- ob = self.__stack[-1]
- if ob is None:
+ subs = self.__stack[-1]
+ if subs is None:
raise ZopeXMLConfigurationError(self.__locator,
'Invalid sub-directive')
try:
- stack.append(getattr(ob, name[1])(**kw))
+ stack.append(sub(subs, name, **kw))
except Exception, v:
raise ZopeXMLConfigurationError, (
- self.__locator, str(v)), sys.exc_info()[2]
+ self.__locator, v), sys.exc_info()[2]
def endElementNS(self, name, qname):
- ob = self.__stack.pop()
- if ob is not None:
+ subs = self.__stack.pop()
+ # to fool compiler that thinks actions is used before assignment:
+ actions = ()
+
+ if subs is not None:
try:
- ob()
+ actions = end(subs)
except Exception, v:
raise ZopeXMLConfigurationError, (
self.__locator, str(v)), sys.exc_info()[2]
+ append = self.__actions.append
-def xmlconfig(file):
- src=InputSource(getattr(file, 'name', '<string>'))
- src.setByteStream(file)
+ try:
+ for des, callable, args, kw in actions:
+ append((self.__level,
+ (self.__locator.getLineNumber(),
+ self.__locator.getColumnNumber(),
+ self.__locator.getSystemId(),
+ ), des, callable, args, kw))
+ 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
+ at line %s column %s of %s
+ and% at line %s column %s of %s
+ """ % ((self.des,) + self.l1 + self.l2)
+
+def xmlconfig(file, actions=None, level=None, directives=None):
+ if actions is None:
+ call=actions=[]
+ else:
+ call=0
+ src=InputSource(getattr(file, 'name', '<string>'))
+ src.setByteStream(file)
parser=make_parser()
- parser.setContentHandler(ConfigurationHandler())
+ parser.setContentHandler(ConfigurationHandler(actions, level, directives))
parser.setFeature(feature_namespaces, 1)
parser.parse(src)
+
+ if call:
+ descriptors = {}
+ for level, loc, des, callable, args, kw in call:
+ if des in descriptors:
+ raise ZopeConflictingConfigurationError(
+ descriptors[des], loc, des)
+ descriptors[des]=loc
+ callable(*args, **kw)
+
+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(" at line %s column %s of %s" % loc)
+
+ return "\n".join(r)
+
+class XMLConfig:
+
+ def __init__(self, file_name):
+ self._actions = []
+ self._directives = {('http://namespaces.zope.org/zope', 'include'):
+ (self.include, {})}
+
+ f = open(file_name)
+ self._stack=[file_name]
+ xmlconfig(f, self._actions, tuple(self._stack), self._directives)
+
+ def include(self, file):
+ file_name = os.path.join(os.path.dirname(self._stack[-1]), file)
+
+ f = open(file_name)
+ self._stack.append(file_name)
+ xmlconfig(f, self._actions, tuple(self._stack), self._directives)
+ self._stack.pop()
+ return ()
+
+ def __call__(self):
+ self.organize()
+
+ def __iter__(self): return iter(self._actions)
+
+ def organize(self):
+ actions = self._actions
+
+ # organize actions by discriminators
+ unique = {}
+ for i in range(len(actions)):
+ path, loc, des, callable, args, kw = actions[i]
+ a = unique.setdefault(des, [])
+ a.append((path, i, loc, (callable, args, kw)))
+
+ # Check for conflicts
+ conflicts = {}
+ for des, actions in unique.items():
+ path, i, loc, f = actions[0]
+ for opath, i, oloc, f in actions[1:]:
+ if opath[:len(path)] != path:
+ if des not in conflicts:
+ conflicts[des] = [loc]
+ conflicts[des].append(oloc)
+
+ if conflicts:
+ raise ZopeConfigurationConflictError(conflicts)
+
+ # Now order the configuration directives
+ cactions = []
+ 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)
+
+
+
+
+
+