summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/api/viewderivers.rst7
-rw-r--r--docs/narr/hooks.rst34
-rw-r--r--pyramid/config/views.py51
-rw-r--r--pyramid/tests/test_viewderivers.py49
-rw-r--r--pyramid/viewderivers.py2
5 files changed, 85 insertions, 58 deletions
diff --git a/docs/api/viewderivers.rst b/docs/api/viewderivers.rst
index a4ec107b6..2a141501e 100644
--- a/docs/api/viewderivers.rst
+++ b/docs/api/viewderivers.rst
@@ -10,7 +10,8 @@
Constant representing the request ingress, for use in ``under``
arguments to :meth:`pyramid.config.Configurator.add_view_deriver`.
- .. attribute:: MAPPED_VIEW
+ .. attribute:: VIEW
- Constant representing the closest view deriver, for use in ``over``
- arguments to :meth:`pyramid.config.Configurator.add_view_deriver`.
+ Constant representing the :term:`view callable` at the end of the view
+ pipeline, for use in ``over`` arguments to
+ :meth:`pyramid.config.Configurator.add_view_deriver`.
diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst
index 3a1ad8363..2c3782387 100644
--- a/docs/narr/hooks.rst
+++ b/docs/narr/hooks.rst
@@ -1617,6 +1617,14 @@ the user-defined :term:`view callable`:
view pipeline interface to accept ``(context, request)`` from all previous
view derivers.
+.. warning::
+
+ Any view derivers defined ``under`` the ``rendered_view`` are not
+ guaranteed to receive a valid response object. Rather they will receive the
+ result from the :term:`view mapper` which is likely the original response
+ returned from the view. This is possibly a dictionary for a renderer but it
+ may be any Python object that may be adapted into a response.
+
Custom View Derivers
~~~~~~~~~~~~~~~~~~~~
@@ -1642,7 +1650,7 @@ view pipeline:
response.headers['X-View-Performance'] = '%.3f' % (end - start,)
return wrapper_view
- config.add_view_deriver(timing_view, 'timing view')
+ config.add_view_deriver(timing_view)
View derivers are unique in that they have access to most of the options
passed to :meth:`pyramid.config.Configurator.add_view` in order to decide what
@@ -1668,7 +1676,7 @@ token unless ``disable_csrf=True`` is passed to the view:
require_csrf_view.options = ('disable_csrf',)
- config.add_view_deriver(require_csrf_view, 'require_csrf_view')
+ config.add_view_deriver(require_csrf_view)
def protected_view(request):
return Response('protected')
@@ -1691,13 +1699,19 @@ By default, every new view deriver is added between the ``decorated_view`` and
``rendered_view`` built-in derivers. It is possible to customize this ordering
using the ``over`` and ``under`` options. Each option can use the names of
other view derivers in order to specify an ordering. There should rarely be a
-reason to worry about the ordering of the derivers.
+reason to worry about the ordering of the derivers except when the deriver
+depends on other operations in the view pipeline.
Both ``over`` and ``under`` may also be iterables of constraints. For either
option, if one or more constraints was defined, at least one must be satisfied,
else a :class:`pyramid.exceptions.ConfigurationError` will be raised. This may
be used to define fallback constraints if another deriver is missing.
+Two sentinel values exist, :attr:`pyramid.viewderivers.INGRESS` and
+:attr:`pyramid.viewderivers.VIEW`, which may be used when specifying
+constraints at the edges of the view pipeline. For example, to add a deriver
+at the start of the pipeline you may use ``under=INGRESS``.
+
It is not possible to add a view deriver under the ``mapped_view`` as the
:term:`view mapper` is intimately tied to the signature of the user-defined
:term:`view callable`. If you simply need to know what the original view
@@ -1707,8 +1721,12 @@ deriver.
.. warning::
- Any view derivers defined ``under`` the ``rendered_view`` are not
- guaranteed to receive a valid response object. Rather they will receive the
- result from the :term:`view mapper` which is likely the original response
- returned from the view. This is possibly a dictionary for a renderer but it
- may be any Python object that may be adapted into a response.
+ The default constraints for any view deriver are ``over='rendered_view'``
+ and ``under='decorated_view'``. When escaping these constraints you must
+ take care to avoid cyclic dependencies between derivers. For example, if
+ you want to add a new view deriver before ``secured_view`` then
+ simply specifying ``over='secured_view'`` is not enough, because the
+ default is also under ``decorated view`` there will be an unsatisfiable
+ cycle. You must specify a valid ``under`` constraint as well, such as
+ ``under=INGRESS`` to fall between INGRESS and ``secured_view`` at the
+ beginning of the view pipeline.
diff --git a/pyramid/config/views.py b/pyramid/config/views.py
index 1e161177b..3f6a9080d 100644
--- a/pyramid/config/views.py
+++ b/pyramid/config/views.py
@@ -80,7 +80,7 @@ import pyramid.viewderivers
from pyramid.viewderivers import (
INGRESS,
- MAPPED_VIEW,
+ VIEW,
preserve_view_attrs,
view_description,
requestonly,
@@ -1115,9 +1115,12 @@ class ViewsConfiguratorMixin(object):
``under`` means closer to the user-defined :term:`view callable`,
and ``over`` means closer to view pipeline ingress.
- Specifying neither ``under`` nor ``over`` is equivalent to specifying
- ``over='rendered_view'`` and ``under='decorated_view'``, placing the
- deriver somewhere between the ``decorated_view`` and ``rendered_view``
+ The default value for ``over`` is ``rendered_view`` and ``under`` is
+ ``decorated_view``. This places the deriver somewhere between the two
+ in the view pipeline. If the deriver should be placed elsewhere in the
+ pipeline, such as above ``decorated_view``, then you MUST also specify
+ ``under`` to something earlier in the order, or a
+ ``CyclicDependencyError`` will be raised when trying to sort the
derivers.
See :ref:`view_derivers` for more information.
@@ -1128,33 +1131,30 @@ class ViewsConfiguratorMixin(object):
if name is None:
name = deriver.__name__
- if name in (INGRESS,):
+ if name in (INGRESS, VIEW):
raise ConfigurationError('%s is a reserved view deriver name'
% name)
- if under is None and over is None:
+ if under is None:
under = 'decorated_view'
+
+ if over is None:
over = 'rendered_view'
- if over is None and name != MAPPED_VIEW:
- raise ConfigurationError('must specify an "over" constraint for '
- 'the %s view deriver' % name)
- elif over is not None:
- over = as_sorted_tuple(over)
+ over = as_sorted_tuple(over)
+ under = as_sorted_tuple(under)
- if under is None:
- raise ConfigurationError('must specify an "under" constraint for '
- 'the %s view deriver' % name)
- else:
- under = as_sorted_tuple(under)
+ if INGRESS in over:
+ raise ConfigurationError('%s cannot be over INGRESS' % name)
- if over is not None and INGRESS in over:
- raise ConfigurationError('%s cannot be over view deriver INGRESS'
- % name)
+ # ensure everything is always over mapped_view
+ if VIEW in over and name != 'mapped_view':
+ over = as_sorted_tuple(over + ('mapped_view',))
- if MAPPED_VIEW in under:
- raise ConfigurationError('%s cannot be under view deriver '
- 'MAPPED_VIEW' % name)
+ if VIEW in under:
+ raise ConfigurationError('%s cannot be under VIEW' % name)
+ if 'mapped_view' in under:
+ raise ConfigurationError('%s cannot be under "mapped_view"' % name)
discriminator = ('view deriver', name)
intr = self.introspectable(
@@ -1173,7 +1173,7 @@ class ViewsConfiguratorMixin(object):
default_before=None,
default_after=INGRESS,
first=INGRESS,
- last=MAPPED_VIEW,
+ last=VIEW,
)
self.registry.registerUtility(derivers, IViewDerivers)
derivers.add(name, deriver, before=over, after=under)
@@ -1188,6 +1188,7 @@ class ViewsConfiguratorMixin(object):
('http_cached_view', d.http_cached_view),
('decorated_view', d.decorated_view),
('rendered_view', d.rendered_view),
+ ('mapped_view', d.mapped_view),
]
last = INGRESS
for name, deriver in derivers:
@@ -1195,12 +1196,10 @@ class ViewsConfiguratorMixin(object):
deriver,
name=name,
under=last,
- over=MAPPED_VIEW,
+ over=VIEW,
)
last = name
- self.add_view_deriver(d.mapped_view, name=MAPPED_VIEW, under=last)
-
def derive_view(self, view, attr=None, renderer=None):
"""
Create a :term:`view callable` using the function, instance,
diff --git a/pyramid/tests/test_viewderivers.py b/pyramid/tests/test_viewderivers.py
index dd142769b..1823beb4d 100644
--- a/pyramid/tests/test_viewderivers.py
+++ b/pyramid/tests/test_viewderivers.py
@@ -1239,6 +1239,25 @@ class TestAddDeriver(unittest.TestCase):
self.assertFalse(flags.get('deriv1'))
self.assertTrue(flags.get('deriv2'))
+ def test_override_mapped_view(self):
+ from pyramid.viewderivers import VIEW
+ response = DummyResponse()
+ view = lambda *arg: response
+ flags = {}
+
+ def deriv1(view, info):
+ flags['deriv1'] = True
+ return view
+
+ result = self.config._derive_view(view)
+ self.assertFalse(flags.get('deriv1'))
+
+ flags.clear()
+ self.config.add_view_deriver(
+ deriv1, name='mapped_view', under='rendered_view', over=VIEW)
+ result = self.config._derive_view(view)
+ self.assertTrue(flags.get('deriv1'))
+
def test_add_multi_derivers_ordered(self):
from pyramid.viewderivers import INGRESS
response = DummyResponse()
@@ -1277,34 +1296,25 @@ class TestAddDeriver(unittest.TestCase):
self.assertRaises(
ConfigurationError, self.config.add_view_deriver, deriv1, INGRESS)
- def test_add_deriver_enforces_over_is_defined(self):
- from pyramid.exceptions import ConfigurationError
- def deriv1(view, info): pass
- try:
- self.config.add_view_deriver(deriv1, under='rendered_view')
- except ConfigurationError as ex:
- self.assertTrue('must specify an "over" constraint' in ex.args[0])
- else: # pragma: no cover
- raise AssertionError
-
- def test_add_deriver_enforces_under_is_defined(self):
+ def test_add_deriver_enforces_ingress_is_first(self):
from pyramid.exceptions import ConfigurationError
+ from pyramid.viewderivers import INGRESS
def deriv1(view, info): pass
try:
- self.config.add_view_deriver(deriv1, over='rendered_view')
+ self.config.add_view_deriver(deriv1, over=INGRESS)
except ConfigurationError as ex:
- self.assertTrue('must specify an "under" constraint' in ex.args[0])
+ self.assertTrue('cannot be over INGRESS' in ex.args[0])
else: # pragma: no cover
raise AssertionError
- def test_add_deriver_enforces_ingress_is_first(self):
+ def test_add_deriver_enforces_view_is_last(self):
from pyramid.exceptions import ConfigurationError
- from pyramid.viewderivers import INGRESS
+ from pyramid.viewderivers import VIEW
def deriv1(view, info): pass
try:
- self.config.add_view_deriver(deriv1, under='rendered_view', over=INGRESS)
+ self.config.add_view_deriver(deriv1, under=VIEW)
except ConfigurationError as ex:
- self.assertTrue('cannot be over view deriver INGRESS' in ex.args[0])
+ self.assertTrue('cannot be under VIEW' in ex.args[0])
else: # pragma: no cover
raise AssertionError
@@ -1312,10 +1322,9 @@ class TestAddDeriver(unittest.TestCase):
from pyramid.exceptions import ConfigurationError
def deriv1(view, info): pass
try:
- self.config.add_view_deriver(
- deriv1, 'deriv1', 'mapped_view', 'rendered_view')
+ self.config.add_view_deriver(deriv1, 'deriv1', under='mapped_view')
except ConfigurationError as ex:
- self.assertTrue('cannot be under view deriver MAPPED_VIEW' in ex.args[0])
+ self.assertTrue('cannot be under "mapped_view"' in ex.args[0])
else: # pragma: no cover
raise AssertionError
diff --git a/pyramid/viewderivers.py b/pyramid/viewderivers.py
index f97099cc8..8061e5d4a 100644
--- a/pyramid/viewderivers.py
+++ b/pyramid/viewderivers.py
@@ -455,5 +455,5 @@ def decorated_view(view, info):
decorated_view.options = ('decorator',)
-MAPPED_VIEW = 'mapped_view'
+VIEW = 'VIEW'
INGRESS = 'INGRESS'