diff options
| -rw-r--r-- | CHANGES.txt | 46 | ||||
| -rw-r--r-- | TODO.txt | 77 | ||||
| -rw-r--r-- | docs/narr/events.rst | 3 | ||||
| -rw-r--r-- | docs/narr/firstapp.rst | 17 | ||||
| -rw-r--r-- | docs/narr/hooks.rst | 9 | ||||
| -rw-r--r-- | docs/narr/i18n.rst | 8 | ||||
| -rw-r--r-- | docs/narr/security.rst | 5 | ||||
| -rw-r--r-- | docs/narr/traversal.rst | 5 | ||||
| -rw-r--r-- | docs/narr/urldispatch.rst | 25 | ||||
| -rw-r--r-- | docs/narr/views.rst | 13 | ||||
| -rw-r--r-- | repoze/bfg/configuration.py | 186 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_configuration.py | 225 | ||||
| -rw-r--r-- | repoze/bfg/view.py | 23 |
13 files changed, 460 insertions, 182 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 36c2afa7c..841eedd6c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -20,6 +20,52 @@ Features ``repoze.bfg.request`` API chapter. It can be used to influence response values before a concrete response object has been created. +- Each of the follow methods of the Configurator now allow the + below-named arguments to be passed as "dotted name strings" + (e.g. "foo.bar.baz") rather than as actual implementation objects + that must be imported: + + setup_registry + root_factory, authentication_policy, authorization_policy, + debug_logger, locale_negotiator, request_factory, + renderer_globals_factory + + add_subscriber + subscriber, iface + + derive_view + view + + add_view + view, for_, context, request_type, containment + + add_route() + view, view_for, factory, for_, view_context + + scan + package + + add_renderer + factory + + set_forbidden_view + view + + set_notfound_view + view + + set_request_factory + factory + + set_renderer_globals_factory() + factory + + set_locale_negotiator + negotiator + + testing_add_subscriber + event_iface + Backwards Incompatibilities --------------------------- @@ -63,80 +63,11 @@ - Raise an exception when a value in response_headerlist is not a string or decide to encode. -- These methods of Configurator should allow the arguments it receives - named below them to be strings: - - __init__ - - [ ] package - - setup_registry - - [ ] root_factory - [.] authentication_policy - [.] authorization_policy - [ ] locale_negotiator - [ ] request_factory - [ ] renderer_globals_factory - - add_subscriber() - - [ ] subscriber - [ ] iface - - derive_view() - - [ ] view - - add_view() - - [ ] view - [ ] for_ - [ ] request_type - [ ] containment - [ ] context - - add_route() - - [.] view - [.] view_for - [ ] factory - [.] for_ - [.] view_context - - scan() - - [ ] package - - add_renderer() - - [ ] factory - - set_forbidden_view() - - [ ] view - - set_notfound_view() - - [ ] view - - set_request_factory() - - [ ] factory - - set_renderer_globals_factory() - - [ ] factory - - set_locale_negotiator() - - [ ] negotiator - - testing_add_subscriber - - [ ] event_iface - - Make NewResponse event carry request. - Change docs so that we dont use INewReponse, but instead just NewResponse (likewise for INewRequest, etc). + +- Change "Cleaning up After a Request" in the urldispatch chapter to + use ``request.add_response_callback``. + diff --git a/docs/narr/events.rst b/docs/narr/events.rst index 7f78139bb..6b46cfd9c 100644 --- a/docs/narr/events.rst +++ b/docs/narr/events.rst @@ -58,7 +58,8 @@ need to use ZCML for the same purpose: The first argument to :meth:`repoze.bfg.configuration.Configurator.add_subscriber` is the - subscriber function; the second argument is the event type. + subscriber function (or a :term:`Python dotted name` which refers + to a subscriber callable); the second argument is the event type. .. topic:: Configuring an Event Listener Through ZCML diff --git a/docs/narr/firstapp.rst b/docs/narr/firstapp.rst index 01ad88704..deac69fda 100644 --- a/docs/narr/firstapp.rst +++ b/docs/narr/firstapp.rst @@ -235,14 +235,15 @@ arguments is known as a view configuration :term:`predicate`. The line ``config.add_view(hello_world)`` registers the ``hello_world`` function as a view callable. The ``add_view`` method -of a Configurator must be called with a view callable object as its -first argument, so the first argument passed is the ``hello_world`` -function. This line calls ``add_view`` with a *default* value for the -:term:`predicate` argument, named ``name``. The ``name`` predicate -defaults to a value equalling the empty string (``''``). This means -that we're instructing :mod:`repoze.bfg` to invoke the ``hello_world`` -view callable when the :term:`view name` is the empty string. We'll -learn in later chapters what a :term:`view name` is, and under which +of a Configurator must be called with a view callable object or a +:term:`dotted Python name` as its first argument, so the first +argument passed is the ``hello_world`` function. This line calls +``add_view`` with a *default* value for the :term:`predicate` +argument, named ``name``. The ``name`` predicate defaults to a value +equalling the empty string (``''``). This means that we're +instructing :mod:`repoze.bfg` to invoke the ``hello_world`` view +callable when the :term:`view name` is the empty string. We'll learn +in later chapters what a :term:`view name` is, and under which circumstances a request will have a view name that is the empty string; in this particular application, it means that the ``hello_world`` view callable will be invoked when the root URL ``/`` diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 70e3f3759..b774c2012 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -351,7 +351,8 @@ request object. The class (aka "factory") that :mod:`repoze.bfg` uses to create a request object instance can be changed by passing a ``request_factory`` argument to the constructor of the -:term:`configurator`. +:term:`configurator`. This argument can be either a callable or a +:term:`Python dotted name` representing a callable. .. code-block:: python :linenos: @@ -392,7 +393,7 @@ method: pass config = Configurator() - config.set_request_factory(MyRequestFactory) + config.set_request_factory(MyRequest) .. _adding_renderer_globals: @@ -411,7 +412,9 @@ renderer. A callback that :mod:`repoze.bfg` will call every time a renderer is invoked can be added by passing a ``renderer_globals_factory`` -argument to the constructor of the :term:`configurator`. +argument to the constructor of the :term:`configurator`. This +callback can either be a callable object or a :term:`Python dotted +name` representing such a callable. .. code-block:: python :linenos: diff --git a/docs/narr/i18n.rst b/docs/narr/i18n.rst index db1414cce..f456fe03c 100644 --- a/docs/narr/i18n.rst +++ b/docs/narr/i18n.rst @@ -1013,10 +1013,10 @@ configuration using either imperative configuration or ZCML. .. topic:: Using Imperative Configuration - Pass an object which can act as the negotiator as the - ``locale_negotiator`` argument of the - :class:`repoze.bfg.configuration.Configurator` instance during - application startup. + Pass an object which can act as the negotiator (or a :term:`Python + dotted name` referring to the object) as the ``locale_negotiator`` + argument of the :class:`repoze.bfg.configuration.Configurator` + instance during application startup. For example: diff --git a/docs/narr/security.rst b/docs/narr/security.rst index 13e6d632a..3b1de27ad 100644 --- a/docs/narr/security.rst +++ b/docs/narr/security.rst @@ -110,6 +110,11 @@ For example: config = Configurator(authentication_policy=authentication_policy, authorization_policy=authorization_policy) +.. note:: the ``authentication_policy`` and ``authorization_policy`` + arguments may also be passed to the Configurator as :ref:`dotted + Python name` values, each representing the dotted name path to a + suitable implementation global defined at Python module scope. + The above configuration enables a policy which compares the value of an "auth ticket" cookie passed in the request's environment which contains a reference to a single :term:`principal` against the diff --git a/docs/narr/traversal.rst b/docs/narr/traversal.rst index a99df7ec8..bcad6dd07 100644 --- a/docs/narr/traversal.rst +++ b/docs/narr/traversal.rst @@ -183,7 +183,10 @@ Using the ``root_factory`` argument to a :class:`repoze.bfg.configuration.Configurator` constructor tells your :mod:`repoze.bfg` application to call this root factory to generate a root object whenever a request enters the application. This root -factory is also known as the global root factory. +factory is also known as the global root factory. A root factory can +alternately be passed to the ``Configurator`` as a :term:`dotted +Python name` which refers to a root factory object defined in a +different module. A root factory is passed a :term:`request` object and it is expected to return an object which represents the root of the object graph. diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 07e96763d..9779113e5 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -448,11 +448,11 @@ represent neither predicates nor view configuration information. application. ``factory`` - A reference to a Python object (often a function or a class) that - will generate a :mod:`repoze.bfg` :term:`context` object when this - route matches. For example, ``mypackage.models.MyFactoryClass``. If - this argument is not specified, the traversal root factory will be - used. + A Python object (often a function or a class) or a :term:`Python + dotted name` to such an object that will generate a + :mod:`repoze.bfg` :term:`context` object when this route + matches. For example, ``mypackage.models.MyFactoryClass``. If this + argument is not specified, the traversal root factory will be used. ``traverse`` If you would like to cause the :term:`context` to be something other @@ -573,15 +573,16 @@ represent neither predicates nor view configuration information. **View-Related Arguments** ``view`` - A reference to a Python object that will be used as a view callable - when this route matches. e.g. ``mypackage.views.my_view``. + A Python object or a :term:`dotted Python name` to such an object + that will be used as a view callable when this route + matches. e.g. ``mypackage.views.my_view``. ``view_context`` - A reference to a class or an :term:`interface` that the - :term:`context` of the view should match for the view named by the - route to be used. This argument is only useful if the ``view`` - attribute is used. If this attribute is not specified, the default - (``None``) will be used. + A class or an :term:`interface` (or a :term:`dotted Python name` to + such an object) that the :term:`context` of the view should match + for the view named by the route to be used. This argument is only + useful if the ``view`` attribute is used. If this attribute is not + specified, the default (``None``) will be used. If the ``view`` argument is not provided, this argument has no effect. diff --git a/docs/narr/views.rst b/docs/narr/views.rst index eebaa63de..4d60f8a9e 100644 --- a/docs/narr/views.rst +++ b/docs/narr/views.rst @@ -620,7 +620,8 @@ For example, to add a renderer which renders views which have a The first argument is the renderer name. The second argument is a reference to an implementation of a - :term:`renderer factory`. + :term:`renderer factory` or a :term:`dotted Python name` referring + to such an object. Adding a New Renderer +++++++++++++++++++++ @@ -1537,8 +1538,8 @@ Or replaces the need to add this imperative configuration stanza: .. ignore-next-block .. code-block:: python - config.add_view(name='my_view', request_method='POST', context=MyModel, - permission='read') + config.add_view('.views.my_view', name='my_view', request_method='POST', + context=MyModel, permission='read') All arguments to ``bfg_view`` may be omitted. For example: @@ -1748,6 +1749,12 @@ example: # repoze.bfg.configuration.Configurator class config.add_view(hello_world, name='hello.html') +The first argument, ``view``, is required. It must either be a Python +object which is the view itself or a :term:`dotted Python name` to +such an object. All other arguments are optional. See +:meth:`repoze.bfg.configuration.Configurator.add_view` for more +information. + .. index:: single: model interfaces diff --git a/repoze/bfg/configuration.py b/repoze/bfg/configuration.py index 90336fe21..4ea52c571 100644 --- a/repoze/bfg/configuration.py +++ b/repoze/bfg/configuration.py @@ -126,15 +126,17 @@ class Configurator(object): :func:`repoze.bfg.settings.get_settings` APIs. If the ``root_factory`` argument is passed, it should be an object - representing the default :term:`root factory` for your - application. If it is ``None``, a default root factory will be - used. + representing the default :term:`root factory` for your application + or a :term:`dotted Python name` to same. If it is ``None``, a + default root factory will be used. If ``authentication_policy`` is passed, it should be an instance - of an :term:`authentication policy`. + of an :term:`authentication policy` or a :term:`dotted Python + name` to same. - If ``authorization_policy`` is passed, it should be an instance - of an :term:`authorization policy`. + If ``authorization_policy`` is passed, it should be an instance of + an :term:`authorization policy` or a :term:`dotted Python name` to + same. .. note:: A ``ConfigurationError`` will be raised when an authorization policy is supplied without also supplying an @@ -142,18 +144,21 @@ class Configurator(object): If ``renderers`` is passed, it should be a list of tuples representing a set of :term:`renderer` factories which should be - configured into this application. If it is not passed, a default - set of renderer factories is used. + configured into this application (each tuple representing a set of + positional values that should be passed to + :meth:`repoze.bfg.configuration.Configurator.add_renderer`). If + it is not passed, a default set of renderer factories is used. If ``debug_logger`` is not passed, a default debug logger that logs to stderr will be used. If it is passed, it should be an instance of the :class:`logging.Logger` (PEP 282) standard library - class. The debug logger is used by :mod:`repoze.bfg` itself to - log warnings and authorization debugging information. + class or a :term:`dotted Python name` to same. The debug logger + is used by :mod:`repoze.bfg` itself to log warnings and + authorization debugging information. - If ``locale_negotiator`` is passed, it should be a - :term:`locale negotiator` implementation. See - :ref:`custom_locale_negotiator`. + If ``locale_negotiator`` is passed, it should be a :term:`locale + negotiator` implementation or a :term:`dotted Python name` to + same. See :ref:`custom_locale_negotiator`. """ manager = manager # for testing injection @@ -201,6 +206,7 @@ class Configurator(object): """ Add a :term:`root factory` to the current configuration state. If the ``factory`` argument is ``None`` a default root factory will be registered.""" + factory = self.maybe_dotted(factory) if factory is None: factory = DefaultRootFactory self.registry.registerUtility(factory, IRootFactory) @@ -209,11 +215,14 @@ class Configurator(object): def _set_authentication_policy(self, policy, _info=u''): """ Add a :mod:`repoze.bfg` :term:`authentication policy` to the current configuration.""" + policy = self.maybe_dotted(policy) self.registry.registerUtility(policy, IAuthenticationPolicy, info=_info) def _set_authorization_policy(self, policy, _info=u''): """ Add a :mod:`repoze.bfg` :term:`authorization policy` to - the current configuration state.""" + the current configuration state (also accepts a :term:`dotted + Python name`.""" + policy = self.maybe_dotted(policy) self.registry.registerUtility(policy, IAuthorizationPolicy, info=_info) def _make_spec(self, path_or_spec): @@ -230,6 +239,7 @@ class Configurator(object): attr=None, renderer_name=None, wrapper_viewname=None, viewname=None, accept=None, order=MAX_ORDER, phash=DEFAULT_PHASH): + view = self.maybe_dotted(view) authn_policy = self.registry.queryUtility(IAuthenticationPolicy) authz_policy = self.registry.queryUtility(IAuthorizationPolicy) settings = self.registry.queryUtility(ISettings) @@ -338,6 +348,7 @@ class Configurator(object): self._fix_registry() self._set_settings(settings) self._set_root_factory(root_factory) + debug_logger = self.maybe_dotted(debug_logger) if debug_logger is None: debug_logger = make_stream_logger('repoze.bfg.debug', sys.stderr) registry = self.registry @@ -352,10 +363,13 @@ class Configurator(object): self.add_view(default_exceptionresponse_view, context=IExceptionResponse) if locale_negotiator: + locale_negotiator = self.maybe_dotted(locale_negotiator) registry.registerUtility(locale_negotiator, ILocaleNegotiator) if request_factory: + request_factory = self.maybe_dotted(request_factory) self.set_request_factory(request_factory) if renderer_globals_factory: + renderer_globals_factory=self.maybe_dotted(renderer_globals_factory) self.set_renderer_globals_factory(renderer_globals_factory) # getSiteManager is a unit testing dep injection @@ -404,8 +418,10 @@ class Configurator(object): def derive_view(self, view, attr=None, renderer=None): """ + Create a :term:`view callable` using the function, instance, - or class provided as ``view`` object. + or class (or :term:`dotted Python name` referring to the same) + provided as ``view`` object. This is API is useful to framework extenders who create pluggable systems which need to register 'proxy' view @@ -453,6 +469,9 @@ class Configurator(object): that accepts no arguments that returns a :term:`response` object. + - A :term:`dotted Python name` which refers to any of the + kinds of objects above. + This API returns a callable which accepts the arguments ``context, request`` and which returns the result of calling the provided ``view`` object. @@ -463,25 +482,30 @@ class Configurator(object): 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. - """ + The ``renderer`` keyword argument should be a renderer + name. If supplied, it will cause 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 - ``subscriber`` argument represents a callable object; it will - be called with a single object ``event`` whenever + ``subscriber`` argument represents a callable object (or a + :ref:`Python dotted name` which identifies a callable); it + will be called with a single object ``event`` whenever :mod:`repoze.bfg` emits an :term:`event` associated with the - ``iface``. Using the default ``iface`` value, ``None`` will - cause the subscriber to be registered for all event types. See - :ref:`events_chapter` for more information about events and - subscribers.""" + ``iface``, which may be an :term:`interface` or a class or a + :term:`dotted Python name` to a global object representing an + interface or a class. Using the default ``iface`` value, + ``None`` will cause the subscriber to be registered for all + event types. See :ref:`events_chapter` for more information + about events and subscribers.""" + dotted = self.maybe_dotted + subscriber, iface = dotted(subscriber), dotted(iface) if iface is None: iface = (Interface,) if not isinstance(iface, (tuple, list)): @@ -592,8 +616,9 @@ class Configurator(object): view - A reference to a :term:`view callable`. This argument is - required unless a ``renderer`` argument also exists. If a + A :term:`view callable` or a :term:`dotted Python name` + which refers to a view callable. This argument is required + unless a ``renderer`` argument also exists. If a ``renderer`` argument is passed, and a ``view`` argument is not provided, the view callable defaults to a callable that returns an empty dictionary (see @@ -685,8 +710,9 @@ class Configurator(object): context - An object representing Python class that the :term:`context` - must be an instance of, *or* the :term:`interface` that the + An object or a :term:`dotted Python name` referring to an + interface or class object that the :term:`context` must be + an instance of, *or* the :term:`interface` that the :term:`context` must provide in order for this view to be found and called. This predicate is true when the :term:`context` is an instance of the represented class or @@ -743,11 +769,11 @@ class Configurator(object): containment - This value should be a reference to a Python class or - :term:`interface` that a parent object in the - :term:`lineage` must provide in order for this view to be - found and called. The nodes in your object graph must be - "location-aware" to use this feature. See + This value should be a Python class or :term:`interface` or + a :term:`dotted Python name` to such an object that a parent + object in the :term:`lineage` must provide in order for this + view to be found and called. The nodes in your object graph + must be "location-aware" to use this feature. See :ref:`location_aware` for more information about location-awareness. @@ -814,6 +840,10 @@ class Configurator(object): .. note:: This feature is new as of :mod:`repoze.bfg` 1.2. """ + view = self.maybe_dotted(view) + context = self.maybe_dotted(context) + for_ = self.maybe_dotted(for_) + containment = self.maybe_dotted(containment) if not view: if renderer: @@ -829,6 +859,7 @@ class Configurator(object): request_type = None if request_type is not None: + request_type = self.maybe_dotted(request_type) if not IInterface.providedBy(request_type): raise ConfigurationError( 'request_type must be an interface, not %s' % request_type) @@ -1014,7 +1045,8 @@ class Configurator(object): factory - A reference to a Python object (often a function or a class) + A Python object (often a function or a class) or a + :term:`dotted Python name` which refers to the same object that will generate a :mod:`repoze.bfg` :term:`context` object when this route matches. For example, ``mypackage.models.MyFactoryClass``. If this argument is @@ -1174,17 +1206,18 @@ class Configurator(object): view - A reference to a Python object that will be used as a view - callable when this route + A Python object or :term:`dotted Python name` to the same + object that will be used as a view callable when this route matches. e.g. ``mypackage.views.my_view``. view_context - A reference to a class or an :term:`interface` that the - :term:`context` of the view should match for the view named - by the route to be used. This argument is only useful if - the ``view`` attribute is used. If this attribute is not - specified, the default (``None``) will be used. + A class or an :term:`interface` or :term:`dotted Python + name` to the same object which the :term:`context` of the + view should match for the view named by the route to be + used. This argument is only useful if the ``view`` + attribute is used. If this attribute is not specified, the + default (``None``) will be used. If the ``view`` argument is not provided, this argument has no effect. @@ -1299,6 +1332,7 @@ class Configurator(object): if mapper is None: mapper = RoutesMapper() self.registry.registerUtility(mapper, IRoutesMapper) + factory = self.maybe_dotted(factory) return mapper.connect(path, name, factory, predicates=predicates) def scan(self, package=None, categories=None, _info=u''): @@ -1307,9 +1341,10 @@ class Configurator(object): :class:`repoze.bfg.view.bfg_view`. Any decorated object found will influence the current configuration state. - The ``package`` argument should be a reference to a Python - :term:`package` or module object. If ``package`` is ``None``, - the package of the *caller* is used. + The ``package`` argument should be a Python :term:`package` or + module object (or a :term:`dotted Python name` which refers to + such a package or module). If ``package`` is ``None``, the + package of the *caller* is used. The ``categories`` argument, if provided, should be the :term:`Venusian` 'scan categories' to use during scanning. @@ -1328,6 +1363,7 @@ class Configurator(object): (e.g. ``('bfg', 'myframework')``) to limit the decorators called to the set of categories required. """ + package = self.maybe_dotted(package) if package is None: # pragma: no cover package = caller_package() @@ -1335,13 +1371,15 @@ class Configurator(object): scanner.scan(package, categories=categories) def add_renderer(self, name, factory, _info=u''): - """ Add a :mod:`repoze.bfg` :term:`renderer` factory to the current - configuration state. + """ + Add a :mod:`repoze.bfg` :term:`renderer` factory to the + current configuration state. The ``name`` argument is the renderer name. The ``factory`` argument is Python reference to an - implementation of a :term:`renderer` factory. + implementation of a :term:`renderer` factory or a + :term:`dotted Python name` to same. Note that this function must be called *before* any ``add_view`` invocation that names the renderer name as an @@ -1350,6 +1388,7 @@ class Configurator(object): in the sequence of renderers passed as ``renderer`` than it is to use this method. """ + factory = self.maybe_dotted(factory) self.registry.registerUtility( factory, IRendererFactory, name=name, info=_info) @@ -1423,7 +1462,8 @@ class Configurator(object): be found. The exception causing the registered view to be called is however still available as ``request.exception``. - The ``view`` argument should be a :term:`view callable`. + The ``view`` argument should be a :term:`view callable` or a + :term:`dotted Python name` which refers to a view callable. The ``attr`` argument should be the attribute of the view callable used to retrieve the response (see the ``add_view`` @@ -1468,7 +1508,8 @@ class Configurator(object): be found. The exception causing the registered view to be called is however still available as ``request.exception``. - The ``view`` argument should be a :term:`view callable`. + The ``view`` argument should be a :term:`view callable` or a + :term:`dotted Python name` which refers to a view callable. The ``attr`` argument should be the attribute of the view callable used to retrieve the response (see the ``add_view`` @@ -1493,31 +1534,36 @@ class Configurator(object): wrapper=wrapper, _info=_info) def set_request_factory(self, factory): - """ The object passed as ``factory`` will be used by the - :mod:`repoze.bfg` router to create all request objects. - This factory object must have the same methods and attributes - as the :class:`repoze.bfg.request.Request` class (particularly - ``__call__`` and ``blank``). + """ The object passed as ``factory`` should be an object (or a + :term:`dotted Python name` which refers to an object) which + will be used by the :mod:`repoze.bfg` router to create all + request objects. This factory object must have the same + methods and attributes as the + :class:`repoze.bfg.request.Request` class (particularly + ``__call__``, and ``blank``). .. note:: Using the :meth:``request_factory`` argument to the :class:`repoze.bfg.configuration.Configurator` constructor can be used to achieve the same purpose. """ + factory = self.maybe_dotted(factory) self.registry.registerUtility(factory, IRequestFactory) def set_renderer_globals_factory(self, factory): - """ The object passed as ``factory`` will be used by the - :mod:`repoze.bfg` rendering machinery as a renderers global - factory (see :ref:`adding_renderer_globals`). The factory - must return a dictionary of items that will be merged intto - the *system* dictionary passed in to every renderer used by - the application. + """ The object passed as ``factory`` should be an object (or a + :term:`dotted Python name` which refers to an object) that + will be used by the :mod:`repoze.bfg` rendering machinery as a + renderers global factory (see :ref:`adding_renderer_globals`). + The factory must return a dictionary of items that will be + merged intto the *system* dictionary passed in to every + renderer used by the application. .. note:: Using the :meth:`renderer_globals_factory` argument to the :class:`repoze.bfg.configuration.Configurator` constructor can be used to achieve the same purpose. """ + factory = self.maybe_dotted(factory) self.registry.registerUtility(factory, IRendererGlobalsFactory) def set_locale_negotiator(self, negotiator): @@ -1525,10 +1571,14 @@ class Configurator(object): Set the :term:`locale negotiator` for this application. The :term:`locale negotiator` is a callable which accepts a :term:`request` object and which returns a :term:`locale - name`. Later calls to this method override earlier calls; - there can be only one locale negotiator active at a time - within an application. See :ref:`activating_translation` for - more information. + name`. The ``negotiator`` argument should be the locale + negotiator implementation or a :term:`dotted Python` name + which refers to such an implementation. + + Later calls to this method override earlier calls; there can + be only one locale negotiator active at a time within an + application. See :ref:`activating_translation` for more + information. .. note: This API is new as of :mod:`repoze.bfg` version 1.3. @@ -1536,6 +1586,7 @@ class Configurator(object): the :class:`repoze.bfg.configuration.Configurator` constructor can be used to achieve the same purpose. """ + negotiator = self.maybe_dotted(negotiator) self.registry.registerUtility(negotiator, ILocaleNegotiator) def add_translation_dirs(self, *specs): @@ -1762,6 +1813,7 @@ class Configurator(object): The default value of ``event_iface`` (``None``) implies a subscriber registered for *any* kind of event. """ + event_iface = self.maybe_dotted(event_iface) L = [] def subscriber(*event): L.extend(event) diff --git a/repoze/bfg/tests/test_configuration.py b/repoze/bfg/tests/test_configuration.py index 159429a06..be9cd942c 100644 --- a/repoze/bfg/tests/test_configuration.py +++ b/repoze/bfg/tests/test_configuration.py @@ -306,6 +306,16 @@ class ConfiguratorTests(unittest.TestCase): result = reg.getUtility(IDebugLogger) self.assertEqual(logger, result) + def test_setup_registry_debug_logger_dottedname(self): + from repoze.bfg.registry import Registry + from repoze.bfg.interfaces import IDebugLogger + reg = Registry() + config = self._makeOne(reg) + config.setup_registry(debug_logger='repoze.bfg.tests') + result = reg.getUtility(IDebugLogger) + import repoze.bfg.tests + self.assertEqual(result, repoze.bfg.tests) + def test_setup_registry_authentication_policy(self): from repoze.bfg.registry import Registry from repoze.bfg.interfaces import IAuthenticationPolicy @@ -316,6 +326,28 @@ class ConfiguratorTests(unittest.TestCase): result = reg.getUtility(IAuthenticationPolicy) self.assertEqual(policy, result) + def test_setup_registry_authentication_policy_dottedname(self): + from repoze.bfg.registry import Registry + from repoze.bfg.interfaces import IAuthenticationPolicy + reg = Registry() + config = self._makeOne(reg) + config.setup_registry(authentication_policy='repoze.bfg.tests') + result = reg.getUtility(IAuthenticationPolicy) + import repoze.bfg.tests + self.assertEqual(result, repoze.bfg.tests) + + def test_setup_registry_authorization_policy_dottedname(self): + from repoze.bfg.registry import Registry + from repoze.bfg.interfaces import IAuthorizationPolicy + reg = Registry() + config = self._makeOne(reg) + dummy = object() + config.setup_registry(authentication_policy=dummy, + authorization_policy='repoze.bfg.tests') + result = reg.getUtility(IAuthorizationPolicy) + import repoze.bfg.tests + self.assertEqual(result, repoze.bfg.tests) + def test_setup_registry_authorization_policy_only(self): from repoze.bfg.registry import Registry from repoze.bfg.exceptions import ConfigurationError @@ -334,6 +366,25 @@ class ConfiguratorTests(unittest.TestCase): config.setup_registry() self.failUnless(reg.getUtility(IRootFactory)) + def test_setup_registry_dottedname_root_factory(self): + from repoze.bfg.registry import Registry + from repoze.bfg.interfaces import IRootFactory + reg = Registry() + config = self._makeOne(reg) + import repoze.bfg.tests + config.setup_registry(root_factory='repoze.bfg.tests') + self.assertEqual(reg.getUtility(IRootFactory), repoze.bfg.tests) + + def test_setup_registry_locale_negotiator_dottedname(self): + from repoze.bfg.registry import Registry + from repoze.bfg.interfaces import ILocaleNegotiator + reg = Registry() + config = self._makeOne(reg) + import repoze.bfg.tests + config.setup_registry(locale_negotiator='repoze.bfg.tests') + utility = reg.getUtility(ILocaleNegotiator) + self.assertEqual(utility, repoze.bfg.tests) + def test_setup_registry_locale_negotiator(self): from repoze.bfg.registry import Registry from repoze.bfg.interfaces import ILocaleNegotiator @@ -354,6 +405,16 @@ class ConfiguratorTests(unittest.TestCase): utility = reg.getUtility(IRequestFactory) self.assertEqual(utility, factory) + def test_setup_registry_request_factory_dottedname(self): + from repoze.bfg.registry import Registry + from repoze.bfg.interfaces import IRequestFactory + reg = Registry() + config = self._makeOne(reg) + import repoze.bfg.tests + config.setup_registry(request_factory='repoze.bfg.tests') + utility = reg.getUtility(IRequestFactory) + self.assertEqual(utility, repoze.bfg.tests) + def test_setup_registry_renderer_globals_factory(self): from repoze.bfg.registry import Registry from repoze.bfg.interfaces import IRendererGlobalsFactory @@ -364,6 +425,16 @@ class ConfiguratorTests(unittest.TestCase): utility = reg.getUtility(IRendererGlobalsFactory) self.assertEqual(utility, factory) + def test_setup_registry_renderer_globals_factory_dottedname(self): + from repoze.bfg.registry import Registry + from repoze.bfg.interfaces import IRendererGlobalsFactory + reg = Registry() + config = self._makeOne(reg) + import repoze.bfg.tests + config.setup_registry(renderer_globals_factory='repoze.bfg.tests') + utility = reg.getUtility(IRendererGlobalsFactory) + self.assertEqual(utility, repoze.bfg.tests) + def test_setup_registry_alternate_renderers(self): from repoze.bfg.registry import Registry from repoze.bfg.interfaces import IRendererFactory @@ -445,6 +516,18 @@ class ConfiguratorTests(unittest.TestCase): config.registry.notify(object()) self.assertEqual(len(L), 1) + def test_add_subscriber_dottednames(self): + import repoze.bfg.tests + from repoze.bfg.interfaces import INewRequest + config = self._makeOne() + config.add_subscriber('repoze.bfg.tests', + 'repoze.bfg.interfaces.INewRequest') + handlers = list(config.registry.registeredHandlers()) + self.assertEqual(len(handlers), 1) + handler = handlers[0] + self.assertEqual(handler.handler, repoze.bfg.tests) + self.assertEqual(handler.required, (INewRequest,)) + def test_add_object_event_subscriber(self): from zope.interface import implements from zope.interface import Interface @@ -538,7 +621,7 @@ class ConfiguratorTests(unittest.TestCase): self.assertRaises(ConfigurationError, config.add_view, view, '', None, None, True, True) - def test_add_view_with_request_type_string(self): + def test_add_view_with_request_type_methodname_string(self): view = lambda *arg: 'OK' config = self._makeOne() config.add_view(view=view, request_type='GET') @@ -551,6 +634,20 @@ class ConfiguratorTests(unittest.TestCase): result = wrapper(None, request) self.assertEqual(result, 'OK') + def test_add_view_with_request_type(self): + from zope.interface import directlyProvides + from repoze.bfg.interfaces import IRequest + view = lambda *arg: 'OK' + config = self._makeOne() + config.add_view(view=view, + request_type='repoze.bfg.interfaces.IRequest') + wrapper = self._getViewCallable(config) + request = DummyRequest() + self._assertNotFound(wrapper, None, request) + directlyProvides(request, IRequest) + result = wrapper(None, request) + self.assertEqual(result, 'OK') + def test_add_view_view_callable_None_with_renderer(self): config = self._makeOne() self._registerRenderer(config, name='dummy') @@ -568,6 +665,12 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(wrapper.__name__, view.__name__) self.assertEqual(wrapper.__doc__, view.__doc__) + def test_add_view_view_callable_dottedname(self): + config = self._makeOne() + config.add_view(view='repoze.bfg.tests.test_configuration.dummy_view') + wrapper = self._getViewCallable(config) + self.assertEqual(wrapper(None, None), 'OK') + def test_add_view_with_function_callable(self): view = lambda *arg: 'OK' config = self._makeOne() @@ -654,6 +757,22 @@ class ConfiguratorTests(unittest.TestCase): wrapper = self._getViewCallable(config, IDummy) self.assertEqual(wrapper, view) + def test_add_view_context_as_dottedname(self): + view = lambda *arg: 'OK' + config = self._makeOne() + config.add_view(context='repoze.bfg.tests.test_configuration.IDummy', + view=view) + wrapper = self._getViewCallable(config, IDummy) + self.assertEqual(wrapper, view) + + def test_add_view_for__as_dottedname(self): + view = lambda *arg: 'OK' + config = self._makeOne() + config.add_view(for_='repoze.bfg.tests.test_configuration.IDummy', + view=view) + wrapper = self._getViewCallable(config, IDummy) + self.assertEqual(wrapper, view) + def test_add_view_for_as_class(self): # ``for_`` is older spelling for ``context`` from zope.interface import implementedBy @@ -1487,6 +1606,18 @@ class ConfiguratorTests(unittest.TestCase): context = DummyContext() self._assertNotFound(wrapper, context, None) + def test_add_view_with_containment_dottedname(self): + from zope.interface import directlyProvides + view = lambda *arg: 'OK' + config = self._makeOne() + config.add_view( + view=view, + containment='repoze.bfg.tests.test_configuration.IDummy') + wrapper = self._getViewCallable(config) + context = DummyContext() + directlyProvides(context, IDummy) + self.assertEqual(wrapper(context, None), 'OK') + def test_add_view_with_path_info_badregex(self): from repoze.bfg.exceptions import ConfigurationError view = lambda *arg: 'OK' @@ -1590,6 +1721,19 @@ class ConfiguratorTests(unittest.TestCase): self._assertRoute(config, 'name', 'path') self.assertEqual(route.name, 'name') + def test_add_route_with_factory(self): + config = self._makeOne() + factory = object() + route = config.add_route('name', 'path', factory=factory) + self.assertEqual(route.factory, factory) + + def test_add_route_with_factory_dottedname(self): + config = self._makeOne() + route = config.add_route( + 'name', 'path', + factory='repoze.bfg.tests.test_configuration.dummyfactory') + self.assertEqual(route.factory, dummyfactory) + def test_add_route_with_xhr(self): config = self._makeOne() config.add_route('name', 'path', xhr=True) @@ -1942,6 +2086,14 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(config.registry.getUtility(ILocaleNegotiator), negotiator) + def test_set_locale_negotiator_dottedname(self): + from repoze.bfg.interfaces import ILocaleNegotiator + config = self._makeOne() + config.set_locale_negotiator( + 'repoze.bfg.tests.test_configuration.dummyfactory') + self.assertEqual(config.registry.getUtility(ILocaleNegotiator), + dummyfactory) + def test_set_request_factory(self): from repoze.bfg.interfaces import IRequestFactory config = self._makeOne() @@ -1949,6 +2101,30 @@ class ConfiguratorTests(unittest.TestCase): config.set_request_factory(factory) self.assertEqual(config.registry.getUtility(IRequestFactory), factory) + def test_set_request_factory_dottedname(self): + from repoze.bfg.interfaces import IRequestFactory + config = self._makeOne() + config.set_request_factory( + 'repoze.bfg.tests.test_configuration.dummyfactory') + self.assertEqual(config.registry.getUtility(IRequestFactory), + dummyfactory) + + def test_set_renderer_globals_factory(self): + from repoze.bfg.interfaces import IRendererGlobalsFactory + config = self._makeOne() + factory = object() + config.set_renderer_globals_factory(factory) + self.assertEqual(config.registry.getUtility(IRendererGlobalsFactory), + factory) + + def test_set_renderer_globals_factory_dottedname(self): + from repoze.bfg.interfaces import IRendererGlobalsFactory + config = self._makeOne() + config.set_renderer_globals_factory( + 'repoze.bfg.tests.test_configuration.dummyfactory') + self.assertEqual(config.registry.getUtility(IRendererGlobalsFactory), + dummyfactory) + def test_add_translation_dirs_missing_dir(self): from repoze.bfg.exceptions import ConfigurationError config = self._makeOne() @@ -1997,6 +2173,13 @@ class ConfiguratorTests(unittest.TestCase): self.failIf(result is view) self.assertEqual(result(None, None), 'OK') + def test_derive_view_dottedname(self): + config = self._makeOne() + result = config.derive_view( + 'repoze.bfg.tests.test_configuration.dummy_view') + self.failIf(result is dummy_view) + self.assertEqual(result(None, None), 'OK') + def test_derive_view_with_renderer(self): def view(request): return 'OK' @@ -2367,6 +2550,14 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(config.registry.getUtility(IRendererFactory, 'name'), renderer) + def test_add_renderer_dottedname_factory(self): + from repoze.bfg.interfaces import IRendererFactory + config = self._makeOne() + import repoze.bfg.tests + config.add_renderer('name', 'repoze.bfg.tests') + self.assertEqual(config.registry.getUtility(IRendererFactory, 'name'), + repoze.bfg.tests) + def test_scan_integration(self): from zope.interface import alsoProvides from repoze.bfg.interfaces import IRequest @@ -2466,6 +2657,22 @@ class ConfiguratorTests(unittest.TestCase): result = render_view_to_response(ctx, req, 'pod_notinit') self.assertEqual(result, None) + def test_scan_integration_dottedname_package(self): + from zope.interface import alsoProvides + from repoze.bfg.interfaces import IRequest + from repoze.bfg.view import render_view_to_response + config = self._makeOne() + config.scan('repoze.bfg.tests.grokkedapp') + + ctx = DummyContext() + req = DummyRequest() + alsoProvides(req, IRequest) + req.registry = config.registry + + req.method = 'GET' + result = render_view_to_response(ctx, req, '') + self.assertEqual(result, 'grokked') + def test_testing_securitypolicy(self): from repoze.bfg.testing import DummySecurityPolicy config = self._makeOne() @@ -2520,6 +2727,17 @@ class ConfiguratorTests(unittest.TestCase): config.registry.notify(object()) self.assertEqual(len(L), 1) + def test_testing_add_subscriber_dottedname(self): + config = self._makeOne() + L = config.testing_add_subscriber( + 'repoze.bfg.tests.test_configuration.IDummy') + event = DummyEvent() + config.registry.notify(event) + self.assertEqual(len(L), 1) + self.assertEqual(L[0], event) + config.registry.notify(object()) + self.assertEqual(len(L), 1) + def test_testing_add_subscriber_multiple(self): config = self._makeOne() L = config.testing_add_subscriber((Interface, IDummy)) @@ -3999,3 +4217,8 @@ class DummyStaticURLInfo: def add(self, name, spec, **kw): self.added.append((name, spec, kw)) +def dummy_view(request): + return 'OK' + +def dummyfactory(request): + """ """ diff --git a/repoze/bfg/view.py b/repoze/bfg/view.py index 0273c1e72..2a154df87 100644 --- a/repoze/bfg/view.py +++ b/repoze/bfg/view.py @@ -200,9 +200,11 @@ class bfg_view(object): ``request_param``, ``containment``, ``xhr``, ``accept``, ``header`` and ``path_info``. - If ``context`` is not supplied, the interface - ``zope.interface.Interface`` (matching any context) is used. An alias - for ``context`` is ``for_``. + ``context`` should be a Python object or :term:`dotted Python + name` representing the context type that must be found for this + view to be called. If ``context`` is not supplied, the interface + ``zope.interface.Interface`` (matching any context) is used. An + alias for ``context`` is ``for_``. If ``permission`` is not supplied, no permission is registered for this view (it's accessible by any caller). @@ -248,12 +250,15 @@ class bfg_view(object): right hand side of the expression (``123``) for the view to "match" the current request. - If ``containment`` is not supplied, this view will be called when - the context of the request has any (or no) :term:`lineage`. If - ``containment`` *is* supplied, it must be a class or - :term:`interface`, denoting that the view 'matches' the current - request only if any graph :term:`lineage` node possesses this - class or interface. + ``containment`` should be a Python object or :term:`dotted Python + name` representing a class or interface type which must be found + as one of the context's location parents for this view to be + called. If ``containment`` is not supplied, this view will be + called when the context of the request has any (or no) + :term:`lineage`. If ``containment`` *is* supplied, it must be a + class or :term:`interface`, denoting that the view'matches' the + current request only if any graph :term:`lineage` node possesses + this class or interface. If ``xhr`` is specified, it must be a boolean value. If the value is ``True``, the view will only be invoked if the request's |
