From 81a833da2adff04d11b9228406bbc1528be65c64 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 24 Jul 2010 07:04:49 +0000 Subject: - A new method of the ``Configurator`` exists: ``set_request_factory``. If used, this method will set the factory used by the :mod:`repoze.bfg` router to create all request objects. - The ``Configurator`` constructor takes an additional argument: ``request_factory``. If used, this argument will set the factory used by the :mod:`repoze.bfg` router to create all request objects. - The ``Hooks`` narrative chapter now contains a section about changing the request factory. --- CHANGES.txt | 10 ++++++ docs/narr/hooks.rst | 57 ++++++++++++++++++++++++++++++++++ repoze/bfg/configuration.py | 39 ++++++++++++++++++++--- repoze/bfg/interfaces.py | 10 ++++++ repoze/bfg/router.py | 6 ++-- repoze/bfg/scripting.py | 5 ++- repoze/bfg/tests/test_configuration.py | 15 +++++++++ repoze/bfg/tests/test_paster.py | 6 +++- repoze/bfg/tests/test_router.py | 8 +++++ repoze/bfg/tests/test_scripting.py | 23 +++++++++++++- repoze/bfg/tests/test_traversal.py | 14 +++++++++ repoze/bfg/traversal.py | 4 ++- 12 files changed, 187 insertions(+), 10 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index e9d0e0ce0..47720221b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -49,6 +49,13 @@ Features Note that the ``traverse`` argument is ignored when attached to a route that has a ``*traverse`` remainder marker in its path. +- A new method of the ``Configurator`` exists: + ``set_request_factory``. If used, this method will set the factory + used by the :mod:`repoze.bfg` router to create all request objects. + +- The ``Configurator`` constructor takes an additional argument: + ``request_factory``. If used, this argument will set the factory + used by the :mod:`repoze.bfg` router to create all request objects. Documentation ------------- @@ -65,6 +72,9 @@ Documentation - The ``Hybrid`` narrative chapter now contains a description of the ``traverse`` route argument. +- The ``Hooks`` narrative chapter now contains a section about + changing the request factory. + Bug Fixes --------- diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 77287c746..0614b48fd 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -337,6 +337,63 @@ class :class:`repoze.bfg.traversal.TraversalContextURL` in the `_ of the :term:`Repoze` Subversion repository. +.. _changing_the_request_factory: + +Changing the Request Factory +---------------------------- + +Whenever :mod:`repoze.bfg` handles a :term:`WSGI` request, it creates +a :term:`request` object based on the WSGI environment it has been +passed. By default, an instance of the +:class:`repoze.bfg.request.Request` class is created to represent the +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`. + +.. code-block:: python + :linenos: + + from repoze.bfg.request import Request + + class MyRequest(Request): + pass + + config = Configurator(request_factory=MyRequest) + +The same ``MyRequest`` class can alternately be registered via ZCML as +a request factory through the use of the ZCML ``utility`` directive. +In the below, we assume it lives in a package named +``mypackage.mymodule``. + +.. code-block:: xml + :linenos: + + + +Lastly, if you're doing imperative configuration, and you'd rather do +it after you've already constructed a :term:`configurator` it can also +be registered via the +:meth:`repoze.bfg.configuration.Configurator.set_request_factory` +method: + +.. code-block:: python + :linenos: + + from repoze.bfg.configuration import Configurator + from repoze.bfg.request import Request + + class MyRequest(Request): + pass + + config = Configurator() + config.set_request_factory(MyRequestFactory) + .. _registering_configuration_decorators: Registering Configuration Decorators diff --git a/repoze/bfg/configuration.py b/repoze/bfg/configuration.py index 3dafbc94a..ca5809cca 100644 --- a/repoze/bfg/configuration.py +++ b/repoze/bfg/configuration.py @@ -27,6 +27,7 @@ from repoze.bfg.interfaces import IMultiView from repoze.bfg.interfaces import IPackageOverrides from repoze.bfg.interfaces import IRendererFactory from repoze.bfg.interfaces import IRequest +from repoze.bfg.interfaces import IRequestFactory from repoze.bfg.interfaces import IResponseFactory from repoze.bfg.interfaces import IRootFactory from repoze.bfg.interfaces import IRouteRequest @@ -90,7 +91,7 @@ class Configurator(object): The Configurator accepts a number of arguments: ``registry``, ``package``, ``settings``, ``root_factory``, ``authentication_policy``, ``authorization_policy``, ``renderers`` - ``debug_logger`` and ``locale_negotiator``. + ``debug_logger``, ``locale_negotiator``, and ``request_factory``. If the ``registry`` argument is passed as a non-``None`` value, it must be an instance of the :class:`repoze.bfg.registry.Registry` @@ -149,6 +150,19 @@ class Configurator(object): :term:`locale negotiator` implementation. See :ref:`custom_locale_negotiator`. + If ``request_factory`` is passed, it should be an object that implements + the same methods and attributes as the :class:`repoze.bfg.request.Request` + class (particularly ``__call__`` and ``blank``). This will be the + factory used by the :mod:`repoze.bfg` router to create all request + objects. If this attribute is ``None``, + the :class:`repoze.bfg.request.Request` class will be used as the + request factory. + + .. note:: The + :meth:`repoze.bfg.configuration.Configurator.set_request_factory` + method can be used to achieve the same purpose as passing + ``request_factory``to the Configurator constructor any time after the + configurator has been constructed. """ manager = manager # for testing injection venusian = venusian # for testing injection @@ -156,7 +170,8 @@ class Configurator(object): root_factory=None, authentication_policy=None, authorization_policy=None, renderers=DEFAULT_RENDERERS, debug_logger=None, - locale_negotiator=None): + locale_negotiator=None, + request_factory=None): self.package = package or caller_package() self.registry = registry if registry is None: @@ -169,7 +184,8 @@ class Configurator(object): authorization_policy=authorization_policy, renderers=renderers, debug_logger=debug_logger, - locale_negotiator=locale_negotiator) + locale_negotiator=locale_negotiator, + request_factory=request_factory) def _set_settings(self, mapping): settings = Settings(mapping or {}) @@ -356,7 +372,7 @@ class Configurator(object): def setup_registry(self, settings=None, root_factory=None, authentication_policy=None, authorization_policy=None, renderers=DEFAULT_RENDERERS, debug_logger=None, - locale_negotiator=None): + locale_negotiator=None, request_factory=None): """ When you pass a non-``None`` ``registry`` argument to the :term:`Configurator` constructor, no initial 'setup' is performed against the registry. This is because the registry @@ -392,6 +408,8 @@ class Configurator(object): self.add_view(default_forbidden_view, context=Forbidden) if locale_negotiator: registry.registerUtility(locale_negotiator, ILocaleNegotiator) + if request_factory: + self.set_request_factory(request_factory) # getSiteManager is a unit testing dep injection def hook_zca(self, getSiteManager=None): @@ -1461,6 +1479,19 @@ class Configurator(object): return self.add_view(bwcompat_view, context=NotFound, 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``). + + .. note:: Using the :meth:``request_factory`` argument to the + :class:`repoze.bfg.configuration.Configurator` constructor + can be used to achieve the same purpose. + """ + self.registry.registerUtility(factory, IRequestFactory) + def set_locale_negotiator(self, negotiator): """ Set the :term:`locale negotiator` for this application. The diff --git a/repoze/bfg/interfaces.py b/repoze/bfg/interfaces.py index 814e558f6..992ec80d9 100644 --- a/repoze/bfg/interfaces.py +++ b/repoze/bfg/interfaces.py @@ -87,6 +87,16 @@ class IResponseFactory(Interface): should accept all the arguments that the webob.Response class accepts)""" +class IRequestFactory(Interface): + """ A utility which generates a request """ + def __call__(environ): + """ Return an object implementing IRequest, e.g. an instance + of ``repoze.bfg.request.Request``""" + + def blank(path): + """ Return an empty request object (see + ``webob.Request.blank``)""" + class IViewClassifier(Interface): """ *Internal only* marker interface for views.""" diff --git a/repoze/bfg/router.py b/repoze/bfg/router.py index d63eceb32..ed187240c 100644 --- a/repoze/bfg/router.py +++ b/repoze/bfg/router.py @@ -7,6 +7,7 @@ from repoze.bfg.interfaces import IRequest from repoze.bfg.interfaces import IRootFactory from repoze.bfg.interfaces import IRouteRequest from repoze.bfg.interfaces import IRouter +from repoze.bfg.interfaces import IRequestFactory from repoze.bfg.interfaces import IRoutesMapper from repoze.bfg.interfaces import ISettings from repoze.bfg.interfaces import ITraverser @@ -36,9 +37,10 @@ class Router(object): self.logger = q(IDebugLogger) self.root_factory = q(IRootFactory, default=DefaultRootFactory) self.routes_mapper = q(IRoutesMapper) + self.request_factory = q(IRequestFactory, default=Request) self.root_policy = self.root_factory # b/w compat self.registry = registry - settings = registry.queryUtility(ISettings) + settings = q(ISettings) if settings is not None: self.debug_notfound = settings['debug_notfound'] @@ -60,7 +62,7 @@ class Router(object): try: # create the request - request = Request(environ) + request = self.request_factory(environ) context = None threadlocals['request'] = request attrs = request.__dict__ diff --git a/repoze/bfg/scripting.py b/repoze/bfg/scripting.py index 8547eae62..ca0bea597 100644 --- a/repoze/bfg/scripting.py +++ b/repoze/bfg/scripting.py @@ -1,4 +1,5 @@ from repoze.bfg.request import Request +from repoze.bfg.interfaces import IRequestFactory def get_root(app, request=None): """ Return a tuple composed of ``(root, closer)`` when provided a @@ -11,7 +12,9 @@ def get_root(app, request=None): constructed and passed to the root factory if ``request`` is None.""" registry = app.registry if request is None: - request = Request.blank('/') + request_factory = registry.queryUtility( + IRequestFactory, default=Request) + request = request_factory.blank('/') request.registry = registry threadlocals = {'registry':registry, 'request':request} app.threadlocal_manager.push(threadlocals) diff --git a/repoze/bfg/tests/test_configuration.py b/repoze/bfg/tests/test_configuration.py index d5b212303..02fbc7a06 100644 --- a/repoze/bfg/tests/test_configuration.py +++ b/repoze/bfg/tests/test_configuration.py @@ -281,6 +281,15 @@ class ConfiguratorTests(unittest.TestCase): utility = reg.getUtility(ILocaleNegotiator) self.assertEqual(utility, 'abc') + def test_setup_registry_request_factory(self): + from repoze.bfg.registry import Registry + from repoze.bfg.interfaces import IRequestFactory + reg = Registry() + config = self._makeOne(reg) + config.setup_registry(request_factory='abc') + utility = reg.getUtility(IRequestFactory) + self.assertEqual(utility, 'abc') + def test_setup_registry_alternate_renderers(self): from repoze.bfg.registry import Registry from repoze.bfg.interfaces import IRendererFactory @@ -1896,6 +1905,12 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(config.registry.getUtility(ILocaleNegotiator), negotiator) + def test_set_request_factory(self): + from repoze.bfg.interfaces import IRequestFactory + config = self._makeOne() + config.set_request_factory('abc') + self.assertEqual(config.registry.getUtility(IRequestFactory), 'abc') + def test_add_translation_dirs_missing_dir(self): from repoze.bfg.exceptions import ConfigurationError config = self._makeOne() diff --git a/repoze/bfg/tests/test_paster.py b/repoze/bfg/tests/test_paster.py index 147e9854d..43837b5f0 100644 --- a/repoze/bfg/tests/test_paster.py +++ b/repoze/bfg/tests/test_paster.py @@ -144,7 +144,11 @@ class DummyIPShell(object): dummy_root = Dummy() -dummy_registry = Dummy() +class DummyRegistry(object): + def queryUtility(self, iface, default=None): + return default + +dummy_registry = DummyRegistry() class DummyInteractor: def __call__(self, banner, local): diff --git a/repoze/bfg/tests/test_router.py b/repoze/bfg/tests/test_router.py index 8702b9317..6e25b0584 100644 --- a/repoze/bfg/tests/test_router.py +++ b/repoze/bfg/tests/test_router.py @@ -118,6 +118,14 @@ class TestRouter(unittest.TestCase): router = self._makeOne() self.assertEqual(router.root_policy, rootfactory) + def test_request_factory(self): + from repoze.bfg.interfaces import IRequestFactory + class DummyRequestFactory(object): + pass + self.registry.registerUtility(DummyRequestFactory, IRequestFactory) + router = self._makeOne() + self.assertEqual(router.request_factory, DummyRequestFactory) + def test_call_traverser_default(self): from repoze.bfg.exceptions import NotFound environ = self._makeEnviron() diff --git a/repoze/bfg/tests/test_scripting.py b/repoze/bfg/tests/test_scripting.py index 4d45a6456..2663c4a0f 100644 --- a/repoze/bfg/tests/test_scripting.py +++ b/repoze/bfg/tests/test_scripting.py @@ -28,12 +28,33 @@ class TestGetRoot(unittest.TestCase): closer() self.assertEqual(len(app.threadlocal_manager.popped), 1) + def test_it_requestfactory_overridden(self): + app = DummyApp() + request = Dummy() + class DummyFactory(object): + @classmethod + def blank(cls, path): + return request + registry = DummyRegistry(DummyFactory) + app.registry = registry + root, closer = self._callFUT(app) + self.assertEqual(len(app.threadlocal_manager.pushed), 1) + pushed = app.threadlocal_manager.pushed[0] + self.assertEqual(pushed['request'], request) class Dummy: pass dummy_root = Dummy() -dummy_registry = Dummy() + +class DummyRegistry(object): + def __init__(self, result=None): + self.result = result + + def queryUtility(self, iface, default=None): + return self.result or default + +dummy_registry = DummyRegistry() class DummyApp: def __init__(self): diff --git a/repoze/bfg/tests/test_traversal.py b/repoze/bfg/tests/test_traversal.py index 29f11dd40..a9727902e 100644 --- a/repoze/bfg/tests/test_traversal.py +++ b/repoze/bfg/tests/test_traversal.py @@ -938,6 +938,20 @@ class TraverseTests(unittest.TestCase): self.assertEqual(result['view_name'], '') self.assertEqual(result['context'], model) + def test_requestfactory_overridden(self): + from repoze.bfg.interfaces import IRequestFactory + from repoze.bfg.request import Request + from repoze.bfg.threadlocal import get_current_registry + reg = get_current_registry() + class MyRequest(Request): + pass + reg.registerUtility(MyRequest, IRequestFactory) + model = DummyContext() + traverser = make_traverser({'context':model, 'view_name':''}) + self._registerTraverser(traverser) + self._callFUT(model, ['']) + self.assertEqual(model.request.__class__, MyRequest) + class TestDefaultRootFactory(unittest.TestCase): def _getTargetClass(self): from repoze.bfg.traversal import DefaultRootFactory diff --git a/repoze/bfg/traversal.py b/repoze/bfg/traversal.py index ce5b3225d..ce76f5be4 100644 --- a/repoze/bfg/traversal.py +++ b/repoze/bfg/traversal.py @@ -6,6 +6,7 @@ from zope.interface.interfaces import IInterface from repoze.lru import lru_cache from repoze.bfg.interfaces import IContextURL +from repoze.bfg.interfaces import IRequestFactory from repoze.bfg.interfaces import ITraverser from repoze.bfg.interfaces import VH_ROOT_KEY @@ -275,8 +276,9 @@ def traverse(model, path): if path and path[0] == '/': model = find_root(model) - request = Request.blank(path) reg = get_current_registry() + request_factory = reg.queryUtility(IRequestFactory, default=Request) + request = request_factory.blank(path) request.registry = reg traverser = reg.queryAdapter(model, ITraverser) if traverser is None: -- cgit v1.2.3