Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add an IExecutionPolicy #2964

Merged
merged 3 commits into from
Mar 1, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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