summaryrefslogtreecommitdiff
path: root/repoze/bfg/configuration.py
diff options
context:
space:
mode:
Diffstat (limited to 'repoze/bfg/configuration.py')
-rw-r--r--repoze/bfg/configuration.py302
1 files changed, 185 insertions, 117 deletions
diff --git a/repoze/bfg/configuration.py b/repoze/bfg/configuration.py
index ff41b433e..59ae63989 100644
--- a/repoze/bfg/configuration.py
+++ b/repoze/bfg/configuration.py
@@ -3,6 +3,7 @@ import re
import sys
import threading
import inspect
+import pkg_resources
import venusian
@@ -57,6 +58,7 @@ from repoze.bfg.i18n import get_localizer
from repoze.bfg.log import make_stream_logger
from repoze.bfg.path import caller_package
from repoze.bfg.path import package_path
+from repoze.bfg.path import package_of
from repoze.bfg.registry import Registry
from repoze.bfg.request import route_request_iface
from repoze.bfg.resource import PackageOverrides
@@ -109,12 +111,13 @@ class Configurator(object):
are ignored.
If the ``package`` argument is passed, it must be a reference to a
- Python :term:`package` (e.g. ``sys.modules['thepackage']``). This
- value is used as a basis to convert relative paths passed to
- various configuration methods, such as methods which accept a
- ``renderer`` argument, into absolute paths. If ``None`` is passed
- (the default), the package is assumed to be the Python package in
- which the *caller* of the ``Configurator`` constructor lives.
+ Python :term:`package` (e.g. ``sys.modules['thepackage']``) or a
+ :term:`dotted Python name` to same. This value is used as a basis
+ to convert relative paths passed to various configuration methods,
+ such as methods which accept a ``renderer`` argument, into
+ absolute paths. If ``None`` is passed (the default), the package
+ is assumed to be the Python package in which the *caller* of the
+ ``Configurator`` constructor lives.
If the ``settings`` argument is passed, it should be a Python
dictionary representing the deployment settings for this
@@ -167,10 +170,15 @@ class Configurator(object):
locale_negotiator=None,
request_factory=None,
renderer_globals_factory=None):
- self.package = package or caller_package()
+ if package is None:
+ package = caller_package()
+ name_resolver = DottedNameResolver(package)
+ self.name_resolver = name_resolver
+ self.package_name = name_resolver.package_name
+ self.package = name_resolver.package
self.registry = registry
if registry is None:
- registry = Registry(self.package.__name__)
+ registry = Registry(self.package_name)
self.registry = registry
self.setup_registry(
settings=settings,
@@ -236,83 +244,13 @@ class Configurator(object):
def _make_spec(self, path_or_spec):
package, filename = resolve_resource_spec(path_or_spec,
- self.package.__name__)
+ self.package_name)
if package is None:
return filename # absolute filename
return '%s:%s' % (package, filename)
def _split_spec(self, path_or_spec):
- return resolve_resource_spec(path_or_spec, self.package.__name__)
-
- def derive_view(self, view, attr=None, renderer=None):
- """
- Create a :term:`view callable` using the function, instance,
- or class provided as ``view`` object.
-
- This is API is useful to framework extenders who create
- pluggable systems which need to register 'proxy' view
- callables for functions, instances, or classes which meet the
- requirements of being a :mod:`repoze.bfg` view callable. For
- example, a ``some_other_framework`` function in another
- framework may want to allow a user to supply a view callable,
- but he may want to wrap the view callable in his own before
- registering the wrapper as a :mod:`repoze.bfg` view callable.
- Because a :mod:`repoze.bfg` view callable can be any of a
- number of valid objects, the framework extender will not know
- how to call the user-supplied object. Running it through
- ``derive_view`` normalizes it to a callable which accepts two
- arguments: ``context`` and ``request``.
-
- For example:
-
- .. code-block:: python
-
- def some_other_framework(user_supplied_view):
- config = Configurator(reg)
- proxy_view = config.derive_view(user_supplied_view)
- def my_wrapper(context, request):
- do_something_that_mutates(request)
- return proxy_view(context, request)
- config.add_view(my_wrapper)
-
- The ``view`` object provided should be one of the following:
-
- - A function or another non-class callable object that accepts
- a :term:`request` as a single positional argument and which
- returns a :term:`response` object.
-
- - A function or other non-class callable object that accepts
- two positional arguments, ``context, request`` and which
- returns a :term:`response` object.
-
- - A class which accepts a single positional argument in its
- constructor named ``request``, and which has a ``__call__``
- method that accepts no arguments that returns a
- :term:`response` object.
-
- - A class which accepts two positional arguments named
- ``context, request``, and which has a ``__call__`` method
- that accepts no arguments that returns a :term:`response`
- object.
-
- This API returns a callable which accepts the arguments
- ``context, request`` and which returns the result of calling
- the provided ``view`` object.
-
- The ``attr`` keyword argument is most useful when the view
- object is a class. It names the method that should be used as
- the callable. If ``attr`` is not provided, the attribute
- effectively defaults to ``__call__``. See
- :ref:`class_as_view` for more information.
-
- The ``renderer`` keyword argument, if supplies, causes the
- returned callable to use a :term:`renderer` to convert the
- user-supplied view result to a :term:`response` object. If a
- ``renderer`` argument is not supplied, the user-supplied view
- must itself return a :term:`response` object.
- """
-
- return self._derive_view(view, attr=attr, renderer_name=renderer)
+ return resolve_resource_spec(path_or_spec, self.package_name)
def _derive_view(self, view, permission=None, predicates=(),
attr=None, renderer_name=None, wrapper_viewname=None,
@@ -372,6 +310,21 @@ class Configurator(object):
# API
+ def with_package(self, package):
+ """ Return a new Configurator instance with the same registry
+ as this configurator using the package supplied as the
+ ``package`` argument to the new configurator."""
+ return self.__class__(registry=self.registry, package=package)
+
+ def maybe_dotted(self, dotted):
+ """ Resolve the dotted name ``dotted`` to a global Python
+ object. If ``dotted`` is not a string, return it without
+ attempting to do any name resolution. If ``dotted`` is a
+ relative dotted name (e.g. ``.foo.bar``, consider it relative
+ to the ``package`` argument supplied to this Configurator's
+ constructor."""
+ return self.name_resolver.maybe_resolve(dotted)
+
def setup_registry(self, settings=None, root_factory=None,
authentication_policy=None, authorization_policy=None,
renderers=DEFAULT_RENDERERS, debug_logger=None,
@@ -462,6 +415,76 @@ class Configurator(object):
"""
return self.manager.pop()
+ def derive_view(self, view, attr=None, renderer=None):
+ """
+ Create a :term:`view callable` using the function, instance,
+ or class provided as ``view`` object.
+
+ This is API is useful to framework extenders who create
+ pluggable systems which need to register 'proxy' view
+ callables for functions, instances, or classes which meet the
+ requirements of being a :mod:`repoze.bfg` view callable. For
+ example, a ``some_other_framework`` function in another
+ framework may want to allow a user to supply a view callable,
+ but he may want to wrap the view callable in his own before
+ registering the wrapper as a :mod:`repoze.bfg` view callable.
+ Because a :mod:`repoze.bfg` view callable can be any of a
+ number of valid objects, the framework extender will not know
+ how to call the user-supplied object. Running it through
+ ``derive_view`` normalizes it to a callable which accepts two
+ arguments: ``context`` and ``request``.
+
+ For example:
+
+ .. code-block:: python
+
+ def some_other_framework(user_supplied_view):
+ config = Configurator(reg)
+ proxy_view = config.derive_view(user_supplied_view)
+ def my_wrapper(context, request):
+ do_something_that_mutates(request)
+ return proxy_view(context, request)
+ config.add_view(my_wrapper)
+
+ The ``view`` object provided should be one of the following:
+
+ - A function or another non-class callable object that accepts
+ a :term:`request` as a single positional argument and which
+ returns a :term:`response` object.
+
+ - A function or other non-class callable object that accepts
+ two positional arguments, ``context, request`` and which
+ returns a :term:`response` object.
+
+ - A class which accepts a single positional argument in its
+ constructor named ``request``, and which has a ``__call__``
+ method that accepts no arguments that returns a
+ :term:`response` object.
+
+ - A class which accepts two positional arguments named
+ ``context, request``, and which has a ``__call__`` method
+ that accepts no arguments that returns a :term:`response`
+ object.
+
+ This API returns a callable which accepts the arguments
+ ``context, request`` and which returns the result of calling
+ the provided ``view`` object.
+
+ The ``attr`` keyword argument is most useful when the view
+ object is a class. It names the method that should be used as
+ the callable. If ``attr`` is not provided, the attribute
+ effectively defaults to ``__call__``. See
+ :ref:`class_as_view` for more information.
+
+ The ``renderer`` keyword argument, if supplies, causes the
+ returned callable to use a :term:`renderer` to convert the
+ user-supplied view result to a :term:`response` object. If a
+ ``renderer`` argument is not supplied, the user-supplied view
+ must itself return a :term:`response` object.
+ """
+
+ return self._derive_view(view, attr=attr, renderer_name=renderer)
+
def add_subscriber(self, subscriber, iface=None, info=u''):
"""Add an event :term:`subscriber` for the event stream
implied by the supplied ``iface`` interface. The
@@ -894,7 +917,7 @@ class Configurator(object):
if old_view is not None:
break
- is_exception_view = isexception(context)
+ isexc = isexception(context)
def regclosure():
if hasattr(view, '__call_permissive__'):
@@ -902,9 +925,10 @@ class Configurator(object):
else:
view_iface = IView
self.registry.registerAdapter(
- derived_view, (IViewClassifier, request_iface, context),
+ derived_view,
+ (IViewClassifier, request_iface, context),
view_iface, name, info=_info)
- if is_exception_view:
+ if isexc:
self.registry.registerAdapter(
derived_view,
(IExceptionViewClassifier, request_iface, context),
@@ -947,18 +971,19 @@ class Configurator(object):
self.registry.adapters.unregister(
(IViewClassifier, request_iface, r_context),
view_type, name=name)
- if is_exception_view:
+ if isexc:
self.registry.adapters.unregister(
(IExceptionViewClassifier, request_iface, r_context),
view_type, name=name)
self.registry.registerAdapter(
- multiview, (IViewClassifier, request_iface, context),
- IMultiView, name, info=_info)
- if is_exception_view:
+ multiview,
+ (IViewClassifier, request_iface, context),
+ IMultiView, name=name, info=_info)
+ if isexc:
self.registry.registerAdapter(
multiview,
(IExceptionViewClassifier, request_iface, context),
- IMultiView, name, info=_info)
+ IMultiView, name=name, info=_info)
def add_route(self,
name,
@@ -2385,11 +2410,11 @@ class DottedNameResolver(object):
Two dotted name styles are supported during deserialization:
- ``pkg_resources``-style dotted names where non-module attributes
- of a module are separated from the rest of the path using a ':'
+ of a package are separated from the rest of the path using a ':'
e.g. ``package.module:attr``.
- ``zope.dottedname``-style dotted names where non-module
- attributes of a module are separated from the rest of the path
+ attributes of a package are separated from the rest of the path
using a '.' e.g. ``package.module.attr``.
These styles can be used interchangeably. If the serialization
@@ -2398,42 +2423,79 @@ class DottedNameResolver(object):
resolution mechanism will be chosen.
The constructor accepts a single argument named ``package`` which
- should be a Python module or package object; it is used when
- *relative* dotted names are supplied to the ``__call__`` method.
- A dotted name which has a ``.`` (dot) or ``:`` (colon) as its
- first character is treated as relative. E.g. if ``.minidom`` is
- supplied to ``deserialize``, and the ``package`` argument to this
- type was passed the ``xml`` module object, the resulting import
- would be for ``xml.minidom``. If a relative package name is
- supplied to ``deserialize``, and no ``package`` was supplied to
- the constructor, an :exc:`repoze.bfg.ConfigurationError` error
- will be raised.
+ should be a one of:
+
+ - a Python module or package object
+
+ - A fully qualified (not relative) dotted name to a module or package
+
+ - The value ``None``
+
+ The ``package`` is used when relative dotted names are supplied to
+ the resolver's ``resolve`` and ``maybe_resolve`` methods. A
+ dotted name which has a ``.`` (dot) or ``:`` (colon) as its first
+ character is treated as relative.
+
+ If the value ``None`` is supplied as the package name, the
+ resolver will only be able to resolve fully qualified (not
+ relative) names. Any attempt to resolve a relative name when the
+ ``package`` is ``None`` will result in an
+ :exc:`repoze.bfg.configuration.ConfigurationError` exception.
+
+ If a *module* or *module name* (as opposed to a package or package
+ name) is supplied as ``package``, its containing package is
+ computed and this package used to derive the package name (all
+ names are resolved relative to packages, never to modules). For
+ example, if the ``package`` argument to this type was passed the
+ string ``xml.dom.expatbuilder``, and ``.mindom`` is supplied to
+ the ``resolve`` method, the resulting import would be for
+ ``xml.minidom``, because ``xml.dom.expatbuilder`` is a module
+ object, not a package object.
+
+ If a *package* or *package name* (as opposed to a module or module
+ name) is supplied as ``package``, this package will be used to
+ relative compute dotted names. For example, if the ``package``
+ argument to this type was passed the string ``xml.dom``, and
+ ``.minidom`` is supplied to the ``resolve`` method, the resulting
+ import would be for ``xml.minidom``.
When a dotted name cannot be resolved, a
:class:`repoze.bfg.exceptions.ConfigurationError` error is raised.
"""
def __init__(self, package):
- self.package = package
+ if package is None:
+ self.package_name = None
+ self.package = None
+ else:
+ if isinstance(package, basestring):
+ try:
+ __import__(package)
+ except ImportError:
+ raise ConfigurationError(
+ 'The dotted name %r cannot be imported' % (package,))
+ package = sys.modules[package]
+ self.package = package_of(package)
+ self.package_name = self.package.__name__
def _pkg_resources_style(self, value):
""" package.module:attr style """
- import pkg_resources
if value.startswith('.') or value.startswith(':'):
- if not self.package:
+ if not self.package_name:
raise ConfigurationError(
- 'relative name %r irresolveable without package' % (value,))
+ 'relative name %r irresolveable without '
+ 'package_name' % (value,))
if value in ['.', ':']:
- value = self.package.__name__
+ value = self.package_name
else:
- value = self.package.__name__ + value
+ value = self.package_name + value
return pkg_resources.EntryPoint.parse(
'x=%s' % value).load(False)
def _zope_dottedname_style(self, value):
""" package.module.attr style """
- module = self.package and self.package.__name__ or None
+ module = self.package_name and self.package_name or None
if value == '.':
- if self.package is None:
+ if self.package_name is None:
raise ConfigurationError(
'relative name %r irresolveable without package' % (value,))
name = module.split('.')
@@ -2464,14 +2526,20 @@ class DottedNameResolver(object):
return found
- def __call__(self, dotted):
+ def resolve(self, dotted):
if not isinstance(dotted, basestring):
raise ConfigurationError('%r is not a string' % (dotted,))
- try:
- if ':' in dotted:
- return self._pkg_resources_style(dotted)
- else:
- return self._zope_dottedname_style(dotted)
- except ImportError:
- raise ConfigurationError(
- 'The dotted name %r cannot be imported' % (dotted,))
+ return self.maybe_resolve(dotted)
+
+ def maybe_resolve(self, dotted):
+ if isinstance(dotted, basestring):
+ try:
+ if ':' in dotted:
+ return self._pkg_resources_style(dotted)
+ else:
+ return self._zope_dottedname_style(dotted)
+ except ImportError:
+ raise ConfigurationError(
+ 'The dotted name %r cannot be imported' % (dotted,))
+ return dotted
+