Skip to content

Commit

Permalink
add an IExecutionPolicy that can wrap the router
Browse files Browse the repository at this point in the history
  • Loading branch information
mmerickel committed Feb 25, 2017
1 parent d3cb4b5 commit 0bee841
Show file tree
Hide file tree
Showing 10 changed files with 145 additions and 15 deletions.
1 change: 1 addition & 0 deletions docs/api/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions docs/api/interfaces.rst
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ Other Interfaces
.. autointerface:: IResponseFactory
:members:

.. autointerface:: IRouter
:members:

.. autointerface:: IViewMapperFactory
:members:

Expand Down
4 changes: 4 additions & 0 deletions docs/glossary.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1154,3 +1154,7 @@ 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`.
25 changes: 25 additions & 0 deletions pyramid/config/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@

from pyramid.interfaces import (
IDefaultRootFactory,
IExecutionPolicy,
IRequestFactory,
IResponseFactory,
IRequestExtensions,
IRootFactory,
ISessionFactory,
)

from pyramid.router import default_execution_policy
from pyramid.traversal import DefaultRootFactory

from pyramid.util import (
Expand Down Expand Up @@ -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):
Expand Down
43 changes: 42 additions & 1 deletion pyramid/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
46 changes: 35 additions & 11 deletions pyramid/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from pyramid.interfaces import (
IDebugLogger,
IExecutionPolicy,
IRequest,
IRequestExtensions,
IRootFactory,
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -203,15 +223,15 @@ 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)

if request.response_callbacks:
request._process_response_callbacks(response)

has_listeners and notify(NewResponse(request, response))

return response

finally:
Expand All @@ -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)
4 changes: 3 additions & 1 deletion pyramid/tests/pkgs/subrequestapp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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

19 changes: 18 additions & 1 deletion pyramid/tests/test_config/test_factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__
Expand Down Expand Up @@ -203,4 +221,3 @@ def foo(self): pass
config.set_request_property(bar, name='bar')
self.assertRaises(ConfigurationConflictError, config.commit)


2 changes: 1 addition & 1 deletion pyramid/tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
13 changes: 13 additions & 0 deletions pyramid/tests/test_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 0bee841

Please sign in to comment.