summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@agendaless.com>2010-07-24 07:04:49 +0000
committerChris McDonough <chrism@agendaless.com>2010-07-24 07:04:49 +0000
commit81a833da2adff04d11b9228406bbc1528be65c64 (patch)
tree736765ad3018e4b9e432b4af4bb923fdbdcc898f
parent8e18ea4a560b4456ace86bdef6060304de053238 (diff)
downloadpyramid-81a833da2adff04d11b9228406bbc1528be65c64.tar.gz
pyramid-81a833da2adff04d11b9228406bbc1528be65c64.tar.bz2
pyramid-81a833da2adff04d11b9228406bbc1528be65c64.zip
- 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.
-rw-r--r--CHANGES.txt10
-rw-r--r--docs/narr/hooks.rst57
-rw-r--r--repoze/bfg/configuration.py39
-rw-r--r--repoze/bfg/interfaces.py10
-rw-r--r--repoze/bfg/router.py6
-rw-r--r--repoze/bfg/scripting.py5
-rw-r--r--repoze/bfg/tests/test_configuration.py15
-rw-r--r--repoze/bfg/tests/test_paster.py6
-rw-r--r--repoze/bfg/tests/test_router.py8
-rw-r--r--repoze/bfg/tests/test_scripting.py23
-rw-r--r--repoze/bfg/tests/test_traversal.py14
-rw-r--r--repoze/bfg/traversal.py4
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
<http://svn.repoze.org/repoze.bfg/trunk/repoze/bfg/traversal.py>`_ 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:
+
+ <utility
+ component="mypackage.mymodule.MyRequest"
+ provides="repoze.bfg.interfaces.IRequestFactory"
+ />
+
+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: