diff --git a/docs/api/config.rst b/docs/api/config.rst index 62f138b762..c76d3d5ffa 100644 --- a/docs/api/config.rst +++ b/docs/api/config.rst @@ -70,6 +70,7 @@ .. automethod:: add_subscriber_predicate .. automethod:: add_view_predicate .. automethod:: add_view_deriver + .. automethod:: set_execution_policy .. automethod:: set_request_factory .. automethod:: set_root_factory .. automethod:: set_session_factory diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst index 521d65d2b3..a212ba7a97 100644 --- a/docs/api/interfaces.rst +++ b/docs/api/interfaces.rst @@ -65,6 +65,9 @@ Other Interfaces .. autointerface:: IResponseFactory :members: + .. autointerface:: IRouter + :members: + .. autointerface:: IViewMapperFactory :members: diff --git a/docs/glossary.rst b/docs/glossary.rst index 0f299c169e..0a46fac3b1 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -1154,3 +1154,8 @@ Glossary coverage A measurement of code coverage, usually expressed as a percentage of which lines of code have been executed over which lines are executable, typically run during test execution. + + execution policy + A policy which wraps the :term:`router` by creating the request object + and sending it through the request pipeline. + See :class:`pyramid.config.Configurator.set_execution_policy`. diff --git a/pyramid/config/factories.py b/pyramid/config/factories.py index f0b6252ae8..c8633cc471 100644 --- a/pyramid/config/factories.py +++ b/pyramid/config/factories.py @@ -3,6 +3,7 @@ from pyramid.interfaces import ( IDefaultRootFactory, + IExecutionPolicy, IRequestFactory, IResponseFactory, IRequestExtensions, @@ -10,6 +11,7 @@ ISessionFactory, ) +from pyramid.router import default_execution_policy from pyramid.traversal import DefaultRootFactory from pyramid.util import ( @@ -231,6 +233,29 @@ def set_request_property(self, callable, name=None, reify=False): 'set_request_propery() is deprecated as of Pyramid 1.5; use ' 'add_request_method() with the property=True argument instead') + @action_method + def set_execution_policy(self, policy): + """ + Override the :app:`Pyramid` :term:`execution policy` in the + current configuration. The ``policy`` argument must be an instance + of an :class:`pyramid.interfaces.IExecutionPolicy` or a + :term:`dotted Python name` that points at an instance of an + execution policy. + + """ + policy = self.maybe_dotted(policy) + if policy is None: + policy = default_execution_policy + + def register(): + self.registry.registerUtility(policy, IExecutionPolicy) + + intr = self.introspectable('execution policy', None, + self.object_description(policy), + 'execution policy') + intr['policy'] = policy + self.action(IExecutionPolicy, register, introspectables=(intr,)) + @implementer(IRequestExtensions) class _RequestExtensions(object): diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index 450cd9c24f..bbb4754e45 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -682,7 +682,48 @@ class IRouter(Interface): registry = Attribute( """Component architecture registry local to this application.""") -class ISettings(Interface): + def make_request(environ): + """ + Create a new request object. + + This method initializes a new :class:`pyramid.interfaces.IRequest` + object using the application's + :class:`pyramid.interfaces.IRequestFactory`. + """ + + def invoke_request(request): + """ + Invoke the :app:`Pyramid` request pipeline. + + See :ref:`router_chapter` for information on the request pipeline. + """ + +class IExecutionPolicy(Interface): + def __call__(environ, router): + """ + This callable triggers the router to process a raw WSGI environ dict + into a response and controls the :app:`Pyramid` request pipeline. + + The ``environ`` is the raw WSGI environ. + + The ``router`` is an :class:`pyramid.interfaces.IRouter` object which + should be used to create a request object and send it into the + processing pipeline. + + The return value should be a :class:`pyramid.interfaces.IResponse` + object or an exception that will be handled by WSGI middleware. + + The default execution policy simple creates a request and sends it + through the pipeline: + + .. code-block:: python + + def simple_execution_policy(environ, router): + request = router.make_request(environ) + return router.invoke_request(request) + """ + +class ISettings(IDict): """ Runtime settings utility for pyramid; represents the deployment settings for the application. Implements a mapping interface.""" diff --git a/pyramid/router.py b/pyramid/router.py index fd11925e99..8b7b7b6bc2 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -5,6 +5,7 @@ from pyramid.interfaces import ( IDebugLogger, + IExecutionPolicy, IRequest, IRequestExtensions, IRootFactory, @@ -49,6 +50,8 @@ def __init__(self, registry): self.routes_mapper = q(IRoutesMapper) self.request_factory = q(IRequestFactory, default=Request) self.request_extensions = q(IRequestExtensions) + self.execution_policy = q( + IExecutionPolicy, default=default_execution_policy) self.orig_handle_request = self.handle_request tweens = q(ITweens) if tweens is not None: @@ -182,19 +185,36 @@ def invoke_subrequest(self, request, use_tweens=False): :term:`tween` in the tween stack closest to the request ingress. If ``use_tweens`` is ``False``, the request will be sent to the main router handler, and no tweens will be invoked. - + See the API for pyramid.request for complete documentation. """ + request.registry = self.registry + request.invoke_subrequest = self.invoke_subrequest + return self.invoke_request( + request, + _use_tweens=use_tweens, + _apply_extensions=True, + ) + + def make_request(self, environ): + request = self.request_factory(environ) + request.registry = self.registry + request.invoke_subrequest = self.invoke_subrequest + extensions = self.request_extensions + if extensions is not None: + apply_request_extensions(request, extensions=extensions) + return request + + def invoke_request(self, request, + _use_tweens=True, _apply_extensions=False): registry = self.registry has_listeners = self.registry.has_listeners notify = self.registry.notify - threadlocals = {'registry':registry, 'request':request} + threadlocals = {'registry': registry, 'request': request} manager = self.threadlocal_manager manager.push(threadlocals) - request.registry = registry - request.invoke_subrequest = self.invoke_subrequest - - if use_tweens: + + if _use_tweens: handle_request = self.handle_request else: handle_request = self.orig_handle_request @@ -203,7 +223,7 @@ def invoke_subrequest(self, request, use_tweens=False): try: extensions = self.request_extensions - if extensions is not None: + if _apply_extensions and extensions is not None: apply_request_extensions(request, extensions=extensions) response = handle_request(request) @@ -211,7 +231,7 @@ def invoke_subrequest(self, request, use_tweens=False): request._process_response_callbacks(response) has_listeners and notify(NewResponse(request, response)) - + return response finally: @@ -229,6 +249,10 @@ def __call__(self, environ, start_response): within the application registry; call ``start_response`` and return an iterable. """ - request = self.request_factory(environ) - response = self.invoke_subrequest(request, use_tweens=True) - return response(request.environ, start_response) + response = self.execution_policy(environ, self) + return response(environ, start_response) + + +def default_execution_policy(environ, router): + request = router.make_request(environ) + return router.invoke_request(request) diff --git a/pyramid/tests/pkgs/subrequestapp/__init__.py b/pyramid/tests/pkgs/subrequestapp/__init__.py index b8f44cd7f4..e4b1d386ae 100644 --- a/pyramid/tests/pkgs/subrequestapp/__init__.py +++ b/pyramid/tests/pkgs/subrequestapp/__init__.py @@ -7,7 +7,8 @@ def view_one(request): return response def view_two(request): - return 'This came from view_two' + # check that request.foo is valid for a subrequest + return 'This came from view_two, foo=%s' % (request.foo,) def view_three(request): subreq = Request.blank('/view_four') @@ -46,5 +47,6 @@ def main(): config.add_view(view_three, route_name='three') config.add_view(view_four, route_name='four') config.add_view(view_five, route_name='five') + config.add_request_method(lambda r: 'bar', 'foo', property=True) return config diff --git a/pyramid/tests/test_config/test_factories.py b/pyramid/tests/test_config/test_factories.py index 452d762f8f..eb1f3534c7 100644 --- a/pyramid/tests/test_config/test_factories.py +++ b/pyramid/tests/test_config/test_factories.py @@ -144,6 +144,24 @@ def get_bad_name(): self.assertRaises(ConfigurationError, get_bad_name) + def test_set_execution_policy(self): + from pyramid.interfaces import IExecutionPolicy + config = self._makeOne(autocommit=True) + def dummy_policy(environ, router): pass + config.set_execution_policy(dummy_policy) + registry = config.registry + result = registry.queryUtility(IExecutionPolicy) + self.assertEqual(result, dummy_policy) + + def test_set_execution_policy_to_None(self): + from pyramid.interfaces import IExecutionPolicy + from pyramid.router import default_execution_policy + config = self._makeOne(autocommit=True) + config.set_execution_policy(None) + registry = config.registry + result = registry.queryUtility(IExecutionPolicy) + self.assertEqual(result, default_execution_policy) + class TestDeprecatedFactoriesMixinMethods(unittest.TestCase): def setUp(self): from zope.deprecation import __show__ @@ -203,4 +221,3 @@ def foo(self): pass config.set_request_property(bar, name='bar') self.assertRaises(ConfigurationConflictError, config.commit) - diff --git a/pyramid/tests/test_integration.py b/pyramid/tests/test_integration.py index c2786c391c..85c4466a42 100644 --- a/pyramid/tests/test_integration.py +++ b/pyramid/tests/test_integration.py @@ -610,7 +610,7 @@ def tearDown(self): def test_one(self): res = self.testapp.get('/view_one', status=200) - self.assertTrue(b'This came from view_two' in res.body) + self.assertTrue(b'This came from view_two, foo=bar' in res.body) def test_three(self): res = self.testapp.get('/view_three', status=500) diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py index 7aa42804c5..a5da5c627f 100644 --- a/pyramid/tests/test_router.py +++ b/pyramid/tests/test_router.py @@ -1271,6 +1271,19 @@ class DummyContext: start_response = DummyStartResponse() self.assertRaises(PredicateMismatch, router, environ, start_response) + def test_custom_execution_policy(self): + from pyramid.interfaces import IExecutionPolicy + from pyramid.request import Request + from pyramid.response import Response + registry = self.config.registry + def dummy_policy(environ, router): + return Response(status=200, body=b'foo') + registry.registerUtility(dummy_policy, IExecutionPolicy) + router = self._makeOne() + resp = Request.blank('/').get_response(router) + self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.body, b'foo') + class DummyPredicate(object): def __call__(self, info, request): return True