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

convert response callbacks into a tween #2762

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
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
10 changes: 8 additions & 2 deletions docs/api/tweens.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

.. autofunction:: excview_tween_factory

.. autofunction:: response_callbacks_tween_factory

.. attribute:: MAIN

Constant representing the main Pyramid handling function, for use in
Expand All @@ -21,5 +23,9 @@
.. attribute:: EXCVIEW

Constant representing the exception view tween, for use in ``under``
and ``over`` arguments to
:meth:`pyramid.config.Configurator.add_tween`.
and ``over`` arguments to :meth:`pyramid.config.Configurator.add_tween`.

.. attribute:: RESPONSE_CALLBACKS

Constant representing the response callbacks tween for use in ``under``
and ``over`` arguments to :meth:`pyramid.config.Configurator.add_tween`.
15 changes: 11 additions & 4 deletions docs/narr/hooks.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1249,6 +1249,7 @@ very last tween factory added) as its request handler function. For example:
The above example will generate an implicit tween chain that looks like this::

INGRESS (implicit)
pyramid.tweens.response_callbacks_tween_factory (implicit)
myapp.tween_factory2
myapp.tween_factory1
pyramid.tweens.excview_tween_factory (implicit)
Expand Down Expand Up @@ -1276,6 +1277,7 @@ Allowable values for ``under`` or ``over`` (or both) are:

- one of the constants :attr:`pyramid.tweens.MAIN`,
:attr:`pyramid.tweens.INGRESS`, or :attr:`pyramid.tweens.EXCVIEW`, or
:attr:`pyramid.tweens.RESPONSE_CALLBACKS`, or

- an iterable of any combination of the above. This allows the user to specify
fallbacks if the desired tween is not included, as well as compatibility
Expand All @@ -1296,7 +1298,8 @@ order) the main Pyramid request handler.

import pyramid.tweens

config.add_tween('myapp.tween_factory', over=pyramid.tweens.MAIN)
config.add_tween('myapp.tween_factory',
under=pyramid.tweens.EXCVIEW, over=pyramid.tweens.MAIN)

The above example will generate an implicit tween chain that looks like this::

Expand All @@ -1315,7 +1318,7 @@ factory "above" the main handler but "below" a separately added tween factory:
import pyramid.tweens

config.add_tween('myapp.tween_factory1',
over=pyramid.tweens.MAIN)
under=pyramid.tweens.EXCVIEW, over=pyramid.tweens.MAIN)
config.add_tween('myapp.tween_factory2',
over=pyramid.tweens.MAIN,
under='myapp.tween_factory1')
Expand All @@ -1328,8 +1331,12 @@ The above example will generate an implicit tween chain that looks like this::
myapp.tween_factory2
MAIN (implicit)

Specifying neither ``over`` nor ``under`` is equivalent to specifying
``under=INGRESS``.
The default value for ``over`` is :attr:`pyramid.tweens.EXCVIEW` and ``under``
is :attr:`pyramid.tweens.RESPONSE_CALLBACKS`. This places the tween somewhere
between the two in the tween ordering. If the tween should be placed elsewhere,
such as under ``EXCVIEW``, then you MUST also specify ``over`` to
something later in the order (such as ``MAIN``), or a ``CyclicDependencyError``
will be raised when trying to sort the tweens.

If all options for ``under`` (or ``over``) cannot be found in the current
configuration, it is an error. If some options are specified purely for
Expand Down
75 changes: 55 additions & 20 deletions pyramid/config/tweens.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,28 @@
from pyramid.exceptions import ConfigurationError

from pyramid.tweens import (
MAIN,
INGRESS,
EXCVIEW,
INGRESS,
MAIN,
RESPONSE_CALLBACKS,
)

from pyramid.config.util import (
action_method,
as_sorted_tuple,
TopologicalSorter,
)
from pyramid.util import is_string_or_iterable

class TweensConfiguratorMixin(object):
def add_tween(self, tween_factory, under=None, over=None):
"""
.. versionadded:: 1.2

.. versionchanged:: 1.9
The default values of ``under=INGRESS`` and ``over=MAIN`` were
been changed to ``under=RESPONSE_CALLBACKS`` and
``over=EXCVIEW``.

Add a 'tween factory'. A :term:`tween` (a contraction of 'between')
is a bit of code that sits between the Pyramid router's main request
handling function and the upstream WSGI component that uses
Expand Down Expand Up @@ -80,15 +86,21 @@ def add_tween(self, tween_factory, under=None, over=None):
If all options for ``under`` (or ``over``) cannot be found in the
current configuration, it is an error. If some options are specified
purely for compatibilty with other tweens, just add a fallback of
MAIN or INGRESS. For example, ``under=('mypkg.someothertween',
'mypkg.someothertween2', INGRESS)``. This constraint will require
``MAIN``, ``INGRESS``, ``RESPONSE_CALLBACKS``, or ``EXCVIEW``. For
example, ``under=('mypkg.someothertween', 'mypkg.someothertween2',
RESPONSE_CALLBACKS)``. This constraint will require
the tween to be located under both the 'mypkg.someothertween' tween,
the 'mypkg.someothertween2' tween, and INGRESS. If any of these is
not in the current configuration, this constraint will only organize
itself based on the tweens that are present.

Specifying neither ``over`` nor ``under`` is equivalent to specifying
``under=INGRESS``.
the 'mypkg.someothertween2' tween, and ``RESPONSE_CALLBACKS``. If any
of these is not in the current configuration, this constraint will
only organize itself based on the tweens that are present.

The default value for ``over`` is ``EXCVIEW`` and ``under`` is
``RESPONSE_CALLBACKS``. This places the deriver somewhere between the
two in the tween ordering. If the deriver should be placed elsewhere,
such as under ``EXCVIEW``, then you MUST also specify ``over`` to
something later in the order (such as ``MAIN``), or a
``CyclicDependencyError`` will be raised when trying to sort the
tweens.

Implicit tween ordering is obviously only best-effort. Pyramid will
attempt to present an implicit order of tweens as best it can, but
Expand All @@ -107,7 +119,16 @@ def add_tween(self, tween_factory, under=None, over=None):
explicit=False)

def add_default_tweens(self):
self.add_tween(EXCVIEW)
self.add_tween(
'pyramid.tweens.excview_tween_factory',
over=MAIN,
under=[RESPONSE_CALLBACKS, INGRESS],
)
self.add_tween(
'pyramid.tweens.response_callbacks_tween_factory',
over=[EXCVIEW, MAIN],
under=INGRESS,
)

@action_method
def _add_tween(self, tween_factory, under=None, over=None, explicit=False):
Expand All @@ -125,27 +146,41 @@ def _add_tween(self, tween_factory, under=None, over=None, explicit=False):

tween_factory = self.maybe_dotted(tween_factory)

# ensure over/under contain only strings
for t, p in [('over', over), ('under', under)]:
if p is not None:
if not is_string_or_iterable(p):
if is_nonstr_iter(p):
for v in p:
if not isinstance(v, string_types):
raise ConfigurationError(
'"%s" must contain strings, not %s' % (t, v))
elif not isinstance(p, string_types):
raise ConfigurationError(
'"%s" must be a string or iterable, not %s' % (t, p))

if over is INGRESS or is_nonstr_iter(over) and INGRESS in over:
if over is None:
over = EXCVIEW

if under is None:
under = RESPONSE_CALLBACKS

over = as_sorted_tuple(over)
under = as_sorted_tuple(under)

if INGRESS in over:
raise ConfigurationError('%s cannot be over INGRESS' % name)

if under is MAIN or is_nonstr_iter(under) and MAIN in under:
if MAIN in under:
raise ConfigurationError('%s cannot be under MAIN' % name)

registry = self.registry
introspectables = []

tweens = registry.queryUtility(ITweens)
if tweens is None:
tweens = Tweens()
registry.registerUtility(tweens, ITweens)

def register():
tweens = registry.queryUtility(ITweens)
if tweens is None:
tweens = Tweens()
registry.registerUtility(tweens, ITweens)
if explicit:
tweens.add_explicit(name, tween_factory)
else:
Expand Down
8 changes: 7 additions & 1 deletion pyramid/config/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from pyramid.compat import (
bytes_,
getargspec,
is_nonstr_iter
is_nonstr_iter,
)

from pyramid.compat import im_func
Expand All @@ -23,6 +23,12 @@
MAX_ORDER = 1 << 30
DEFAULT_PHASH = md5().hexdigest()

def as_sorted_tuple(val):
if not is_nonstr_iter(val):
val = (val,)
val = tuple(sorted(val))
return val

class not_(object):
"""

Expand Down
10 changes: 10 additions & 0 deletions pyramid/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
iteritems_,
)

from pyramid.events import NewResponse
from pyramid.decorator import reify
from pyramid.i18n import LocalizerRequestMixin
from pyramid.response import Response, _get_response_factory
Expand Down Expand Up @@ -332,3 +333,12 @@ def apply_request_extensions(request, extensions=None):

InstancePropertyHelper.apply_properties(
request, extensions.descriptors)

def _execute_response_callbacks(request, response):
""" Execute response callbacks and emit a NewResponse event."""
registry = request.registry
if getattr(request, 'response_callbacks', False):
request._process_response_callbacks(response)

if registry.has_listeners:
registry.notify(NewResponse(request, response))
13 changes: 6 additions & 7 deletions pyramid/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@
from pyramid.events import (
ContextFound,
NewRequest,
NewResponse,
BeforeTraversal,
)

from pyramid.httpexceptions import HTTPNotFound
from pyramid.request import Request
from pyramid.view import _call_view
from pyramid.request import apply_request_extensions
from pyramid.request import _execute_response_callbacks
from pyramid.threadlocal import manager

from pyramid.traversal import (
Expand Down Expand Up @@ -208,8 +208,6 @@ def make_request(self, environ):
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}
manager = self.threadlocal_manager
manager.push(threadlocals)
Expand All @@ -225,12 +223,13 @@ def invoke_request(self, request,
extensions = self.request_extensions
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)
response = handle_request(request)

has_listeners and notify(NewResponse(request, response))
# bw-compat, in Pyramid < 1.9 callbacks were executed even
# when use_tweens was false
if not _use_tweens:
_execute_response_callbacks(request, response)

return response

Expand Down
56 changes: 53 additions & 3 deletions pyramid/tests/test_config/test_tweens.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def _makeOne(self, *arg, **kw):
def test_add_tweens_names_distinct(self):
from pyramid.interfaces import ITweens
from pyramid.tweens import excview_tween_factory
from pyramid.tweens import response_callbacks_tween_factory
def factory1(handler, registry): return handler
def factory2(handler, registry): return handler
config = self._makeOne()
Expand All @@ -27,6 +28,8 @@ def factory2(handler, registry): return handler
self.assertEqual(
implicit,
[
('pyramid.tweens.response_callbacks_tween_factory',
response_callbacks_tween_factory),
('pyramid.tests.test_config.dummy_tween_factory2',
dummy_tween_factory2),
('pyramid.tests.test_config.dummy_tween_factory',
Expand All @@ -39,6 +42,7 @@ def factory2(handler, registry): return handler
def test_add_tweens_names_with_underover(self):
from pyramid.interfaces import ITweens
from pyramid.tweens import excview_tween_factory
from pyramid.tweens import response_callbacks_tween_factory
from pyramid.tweens import MAIN
config = self._makeOne()
config.add_tween(
Expand All @@ -50,16 +54,39 @@ def test_add_tweens_names_with_underover(self):
under='pyramid.tests.test_config.dummy_tween_factory')
config.commit()
tweens = config.registry.queryUtility(ITweens)
implicit = tweens.implicit()
self.assertEqual(
implicit,
tweens.implicit(),
[
('pyramid.tweens.response_callbacks_tween_factory',
response_callbacks_tween_factory),
('pyramid.tests.test_config.dummy_tween_factory',
dummy_tween_factory),
('pyramid.tests.test_config.dummy_tween_factory2',
dummy_tween_factory2),
('pyramid.tweens.excview_tween_factory', excview_tween_factory),
])

def test_add_tween_default_order(self):
from pyramid.interfaces import ITweens
from pyramid.tweens import excview_tween_factory
from pyramid.tweens import response_callbacks_tween_factory
config = self._makeOne()
config.add_tween('pyramid.tests.test_config.dummy_tween_factory')
config.add_tween('pyramid.tests.test_config.dummy_tween_factory2',
under='pyramid.tests.test_config.dummy_tween_factory')
config.commit()
tweens = config.registry.queryUtility(ITweens)
self.assertEqual(
tweens.implicit(),
[
('pyramid.tweens.response_callbacks_tween_factory',
response_callbacks_tween_factory),
('pyramid.tests.test_config.dummy_tween_factory',
dummy_tween_factory),
('pyramid.tests.test_config.dummy_tween_factory2',
dummy_tween_factory2),
])
('pyramid.tweens.excview_tween_factory', excview_tween_factory),
])

def test_add_tweens_names_with_under_nonstringoriter(self):
from pyramid.exceptions import ConfigurationError
Expand All @@ -77,16 +104,39 @@ def test_add_tweens_names_with_over_nonstringoriter(self):
'pyramid.tests.test_config.dummy_tween_factory',
over=False)

def test_add_tweens_names_with_over_nonstriter(self):
from pyramid.exceptions import ConfigurationError
from pyramid.tweens import MAIN
config = self._makeOne()
self.assertRaises(
ConfigurationError,
config.add_tween,
'pyramid.tests.test_config.dummy_tween_factory',
over=[MAIN, object()])

def test_add_tweens_names_with_under_nonstriter(self):
from pyramid.exceptions import ConfigurationError
from pyramid.tweens import INGRESS
config = self._makeOne()
self.assertRaises(
ConfigurationError,
config.add_tween,
'pyramid.tests.test_config.dummy_tween_factory',
under=[INGRESS, object()])

def test_add_tween_dottedname(self):
from pyramid.interfaces import ITweens
from pyramid.tweens import excview_tween_factory
from pyramid.tweens import response_callbacks_tween_factory
config = self._makeOne()
config.add_tween('pyramid.tests.test_config.dummy_tween_factory')
config.commit()
tweens = config.registry.queryUtility(ITweens)
self.assertEqual(
tweens.implicit(),
[
('pyramid.tweens.response_callbacks_tween_factory',
response_callbacks_tween_factory),
('pyramid.tests.test_config.dummy_tween_factory',
dummy_tween_factory),
('pyramid.tweens.excview_tween_factory',
Expand Down
Loading