Skip to content

Commit

Permalink
add a router.execution_context context manager
Browse files Browse the repository at this point in the history
the context manager may be used by execution policies to push/pop
threadlocals if necessary
  • Loading branch information
mmerickel committed Jun 15, 2017
1 parent 2ea943e commit 912c5ef
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 8 deletions.
38 changes: 37 additions & 1 deletion pyramid/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -691,13 +691,46 @@ def make_request(environ):
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.
"""

def execution_context(request=None):
"""
The execution context can be used to configure the threadlocal
variables used by :app:`Pyramid` including those required for
:func:`pyramid.threadlocal.get_current_request` and
:func:`pyramid.threadlocal.get_current_registry`.
These values are set automatically by ``invoke_request`` but
can be used explicitly by execution policies that wish to perform
more complex tasks outside of the default request pipeline.
.. code-block:: python
request = router.make_request(environ)
with router.execution_context(request):
...
Alternatively the context may be used without the ``with`` statement
by manually invoking its ``begin()`` and ``end()`` methods.
.. code-block:: python
request = router.make_request(environ)
ctx = router.execution_context(request)
ctx.begin()
try:
...
finally:
ctx.end()
"""

class IExecutionPolicy(Interface):
Expand All @@ -722,7 +755,10 @@ def __call__(environ, router):
def simple_execution_policy(environ, router):
request = router.make_request(environ)
return router.invoke_request(request)
try:
return router.invoke_request(request)
except Exception:
return request.invoke_exception_view(reraise=True)
"""

class ISettings(IDict):
Expand Down
60 changes: 53 additions & 7 deletions pyramid/router.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from contextlib import contextmanager
import sys
from zope.interface import (
implementer,
Expand Down Expand Up @@ -252,6 +253,38 @@ def invoke_request(self, request, _use_tweens=True):
finally:
manager.pop()

def execution_context(self, request=None):
"""
The execution context can be used to configure the threadlocal
variables used by :app:`Pyramid` including those required for
:func:`pyramid.threadlocal.get_current_request` and
:func:`pyramid.threadlocal.get_current_registry`.
These values are set automatically by ``invoke_request`` but
can be used explicitly by execution policies that wish to perform
more complex tasks outside of the default request pipeline.
.. code-block:: python
request = router.make_request(environ)
with router.execution_context(request):
...
Alternatively the context may be used without the ``with`` statement
by manually invoking its ``begin()`` and ``end()`` methods.
.. code-block:: python
request = router.make_request(environ)
ctx = router.execution_context(request)
ctx.begin()
try:
...
finally:
ctx.end()
"""
return ExecutionContext(self.registry, request)

def __call__(self, environ, start_response):
"""
Accept ``environ`` and ``start_response``; create a
Expand All @@ -268,10 +301,23 @@ def default_execution_policy(environ, router):
try:
return router.invoke_request(request)
except Exception:
exc_info = sys.exc_info()
try:
return request.invoke_exception_view(exc_info)
except HTTPNotFound:
reraise(*exc_info)
finally:
del exc_info # avoid local ref cycle
return request.invoke_exception_view(reraise=True)

class ExecutionContext(object):
manager = manager

def __init__(self, registry, request=None):
self.registry = registry
self.request = request

def begin(self):
self.manager.push({'registry': self.registry, 'request': self.request})

def end(self):
self.manager.pop()

def __enter__(self):
self.begin()

def __exit__(self, *args):
self.end()
44 changes: 44 additions & 0 deletions pyramid/tests/test_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -1311,6 +1311,50 @@ class Exception2(Exception):
result = router(environ, start_response)
self.assertEqual(result, ["Hello, world"])

def test_execution_context_with_statement(self):
from pyramid.threadlocal import get_current_request
from pyramid.interfaces import IExecutionPolicy
from pyramid.request import Request
from pyramid.response import Response
registry = self.config.registry
dummy_req = object()
result = []
def dummy_policy(environ, router):
with router.execution_context(dummy_req):
result.append(get_current_request())
result.append(get_current_request())
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')
self.assertEqual(result[0], dummy_req)
self.assertEqual(result[1], None)

def test_execution_context_manually(self):
from pyramid.threadlocal import get_current_request
from pyramid.interfaces import IExecutionPolicy
from pyramid.request import Request
from pyramid.response import Response
registry = self.config.registry
dummy_req = object()
result = []
def dummy_policy(environ, router):
ctx = router.execution_context(dummy_req)
ctx.begin()
result.append(get_current_request())
ctx.end()
result.append(get_current_request())
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')
self.assertEqual(result[0], dummy_req)
self.assertEqual(result[1], None)

class DummyPredicate(object):
def __call__(self, info, request):
return True
Expand Down

0 comments on commit 912c5ef

Please sign in to comment.