summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Merickel <michael@merickel.org>2016-04-07 01:16:45 -0500
committerMichael Merickel <michael@merickel.org>2016-04-07 01:27:06 -0500
commita3db3cab713fecc0c83c742cfe3f0736b1d94a92 (patch)
treeba3017bb9b0d6ddaed31bbfb64e9eac3c64d0e40
parentecc9d82ef6bd50a08dcbee7286ba4581d3caa902 (diff)
downloadpyramid-a3db3cab713fecc0c83c742cfe3f0736b1d94a92.tar.gz
pyramid-a3db3cab713fecc0c83c742cfe3f0736b1d94a92.tar.bz2
pyramid-a3db3cab713fecc0c83c742cfe3f0736b1d94a92.zip
separate the viewderiver module and allow overriding the mapper
-rw-r--r--docs/api/viewderivers.rst16
-rw-r--r--docs/narr/hooks.rst9
-rw-r--r--pyramid/config/views.py72
-rw-r--r--pyramid/tests/test_viewderivers.py89
-rw-r--r--pyramid/viewderivers.py16
5 files changed, 156 insertions, 46 deletions
diff --git a/docs/api/viewderivers.rst b/docs/api/viewderivers.rst
new file mode 100644
index 000000000..a4ec107b6
--- /dev/null
+++ b/docs/api/viewderivers.rst
@@ -0,0 +1,16 @@
+.. _viewderivers_module:
+
+:mod:`pyramid.viewderivers`
+---------------------------
+
+.. automodule:: pyramid.viewderivers
+
+ .. attribute:: INGRESS
+
+ Constant representing the request ingress, for use in ``under``
+ arguments to :meth:`pyramid.config.Configurator.add_view_deriver`.
+
+ .. attribute:: MAPPED_VIEW
+
+ Constant representing the closest view deriver, 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 a32e94d1a..3a1ad8363 100644
--- a/docs/narr/hooks.rst
+++ b/docs/narr/hooks.rst
@@ -1580,12 +1580,6 @@ There are several built-in view derivers that :app:`Pyramid` will automatically
apply to any view. Below they are defined in order from furthest to closest to
the user-defined :term:`view callable`:
-``authdebug_view``
-
- Used to output useful debugging information when
- ``pyramid.debug_authorization`` is enabled. This element is a no-op
- otherwise.
-
``secured_view``
Enforce the ``permission`` defined on the view. This element is a no-op if no
@@ -1593,6 +1587,9 @@ the user-defined :term:`view callable`:
default permission was assigned via
:meth:`pyramid.config.Configurator.set_default_permission`.
+ This element will also output useful debugging information when
+ ``pyramid.debug_authorization`` is enabled.
+
``owrapped_view``
Invokes the wrapped view defined by the ``wrapper`` option.
diff --git a/pyramid/config/views.py b/pyramid/config/views.py
index 2a019726f..1e161177b 100644
--- a/pyramid/config/views.py
+++ b/pyramid/config/views.py
@@ -79,6 +79,8 @@ import pyramid.config.predicates
import pyramid.viewderivers
from pyramid.viewderivers import (
+ INGRESS,
+ MAPPED_VIEW,
preserve_view_attrs,
view_description,
requestonly,
@@ -89,6 +91,7 @@ from pyramid.viewderivers import (
from pyramid.config.util import (
DEFAULT_PHASH,
MAX_ORDER,
+ as_sorted_tuple,
)
urljoin = urlparse.urljoin
@@ -1029,16 +1032,14 @@ class ViewsConfiguratorMixin(object):
def _apply_view_derivers(self, info):
d = pyramid.viewderivers
- # These derivations have fixed order
+
+ # These derivers are not really derivers and so have fixed order
outer_derivers = [('attr_wrapped_view', d.attr_wrapped_view),
('predicated_view', d.predicated_view)]
- inner_derivers = [('mapped_view', d.mapped_view)]
view = info.original_view
derivers = self.registry.getUtility(IViewDerivers)
- for name, deriver in reversed(
- outer_derivers + derivers.sorted() + inner_derivers
- ):
+ for name, deriver in reversed(outer_derivers + derivers.sorted()):
view = wraps_view(deriver)(view, info)
return view
@@ -1090,7 +1091,7 @@ class ViewsConfiguratorMixin(object):
self.add_view_predicate(name, factory)
@action_method
- def add_view_deriver(self, deriver, name, under=None, over=None):
+ def add_view_deriver(self, deriver, name=None, under=None, over=None):
"""
.. versionadded:: 1.7
@@ -1105,9 +1106,11 @@ class ViewsConfiguratorMixin(object):
restrictions on the name of a view deriver. If left unspecified, the
name will be constructed from the name of the ``deriver``.
- The ``under`` and ``over`` options may be used to control the ordering
+ The ``under`` and ``over`` options can be used to control the ordering
of view derivers by providing hints about where in the view pipeline
- the deriver is used.
+ the deriver is used. Each option may be a string or a list of strings.
+ At least one view deriver in each, the over and under directions, must
+ exist to fully satisfy the constraints.
``under`` means closer to the user-defined :term:`view callable`,
and ``over`` means closer to view pipeline ingress.
@@ -1122,10 +1125,37 @@ class ViewsConfiguratorMixin(object):
"""
deriver = self.maybe_dotted(deriver)
+ if name is None:
+ name = deriver.__name__
+
+ if name in (INGRESS,):
+ raise ConfigurationError('%s is a reserved view deriver name'
+ % name)
+
if under is None and over is None:
under = 'decorated_view'
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)
+
+ if under is None:
+ raise ConfigurationError('must specify an "under" constraint for '
+ 'the %s view deriver' % name)
+ else:
+ under = as_sorted_tuple(under)
+
+ if over is not None and INGRESS in over:
+ raise ConfigurationError('%s cannot be over view deriver INGRESS'
+ % name)
+
+ if MAPPED_VIEW in under:
+ raise ConfigurationError('%s cannot be under view deriver '
+ 'MAPPED_VIEW' % name)
+
discriminator = ('view deriver', name)
intr = self.introspectable(
'view derivers',
@@ -1139,7 +1169,12 @@ class ViewsConfiguratorMixin(object):
def register():
derivers = self.registry.queryUtility(IViewDerivers)
if derivers is None:
- derivers = TopologicalSorter()
+ derivers = TopologicalSorter(
+ default_before=None,
+ default_after=INGRESS,
+ first=INGRESS,
+ last=MAPPED_VIEW,
+ )
self.registry.registerUtility(derivers, IViewDerivers)
derivers.add(name, deriver, before=over, after=under)
self.action(discriminator, register, introspectables=(intr,),
@@ -1148,24 +1183,23 @@ class ViewsConfiguratorMixin(object):
def add_default_view_derivers(self):
d = pyramid.viewderivers
derivers = [
- ('authdebug_view', d.authdebug_view),
('secured_view', d.secured_view),
('owrapped_view', d.owrapped_view),
('http_cached_view', d.http_cached_view),
('decorated_view', d.decorated_view),
+ ('rendered_view', d.rendered_view),
]
- last = pyramid.util.FIRST
+ last = INGRESS
for name, deriver in derivers:
- self.add_view_deriver(deriver, name=name, under=last)
+ self.add_view_deriver(
+ deriver,
+ name=name,
+ under=last,
+ over=MAPPED_VIEW,
+ )
last = name
- # ensure rendered_view is over LAST
- self.add_view_deriver(
- d.rendered_view,
- 'rendered_view',
- under=last,
- over=pyramid.util.LAST,
- )
+ self.add_view_deriver(d.mapped_view, name=MAPPED_VIEW, under=last)
def derive_view(self, view, attr=None, renderer=None):
"""
diff --git a/pyramid/tests/test_viewderivers.py b/pyramid/tests/test_viewderivers.py
index dfac827dc..dd142769b 100644
--- a/pyramid/tests/test_viewderivers.py
+++ b/pyramid/tests/test_viewderivers.py
@@ -1103,14 +1103,13 @@ class TestDerivationOrder(unittest.TestCase):
from pyramid.interfaces import IViewDerivers
self.config.add_view_deriver(None, 'deriv1')
- self.config.add_view_deriver(None, 'deriv2', over='deriv1')
- self.config.add_view_deriver(None, 'deriv3', under='deriv2')
+ self.config.add_view_deriver(None, 'deriv2', 'decorated_view', 'deriv1')
+ self.config.add_view_deriver(None, 'deriv3', 'deriv2', 'deriv1')
derivers = self.config.registry.getUtility(IViewDerivers)
derivers_sorted = derivers.sorted()
dlist = [d for (d, _) in derivers_sorted]
self.assertEqual([
- 'authdebug_view',
'secured_view',
'owrapped_view',
'http_cached_view',
@@ -1119,6 +1118,7 @@ class TestDerivationOrder(unittest.TestCase):
'deriv3',
'deriv1',
'rendered_view',
+ 'mapped_view',
], dlist)
def test_right_order_implicit(self):
@@ -1132,7 +1132,6 @@ class TestDerivationOrder(unittest.TestCase):
derivers_sorted = derivers.sorted()
dlist = [d for (d, _) in derivers_sorted]
self.assertEqual([
- 'authdebug_view',
'secured_view',
'owrapped_view',
'http_cached_view',
@@ -1141,31 +1140,32 @@ class TestDerivationOrder(unittest.TestCase):
'deriv2',
'deriv1',
'rendered_view',
+ 'mapped_view',
], dlist)
def test_right_order_under_rendered_view(self):
from pyramid.interfaces import IViewDerivers
- self.config.add_view_deriver(None, 'deriv1', under='rendered_view')
+ self.config.add_view_deriver(None, 'deriv1', 'rendered_view', 'mapped_view')
derivers = self.config.registry.getUtility(IViewDerivers)
derivers_sorted = derivers.sorted()
dlist = [d for (d, _) in derivers_sorted]
self.assertEqual([
- 'authdebug_view',
'secured_view',
'owrapped_view',
'http_cached_view',
'decorated_view',
'rendered_view',
'deriv1',
+ 'mapped_view',
], dlist)
def test_right_order_under_rendered_view_others(self):
from pyramid.interfaces import IViewDerivers
- self.config.add_view_deriver(None, 'deriv1', under='rendered_view')
+ self.config.add_view_deriver(None, 'deriv1', 'rendered_view', 'mapped_view')
self.config.add_view_deriver(None, 'deriv2')
self.config.add_view_deriver(None, 'deriv3')
@@ -1173,7 +1173,6 @@ class TestDerivationOrder(unittest.TestCase):
derivers_sorted = derivers.sorted()
dlist = [d for (d, _) in derivers_sorted]
self.assertEqual([
- 'authdebug_view',
'secured_view',
'owrapped_view',
'http_cached_view',
@@ -1182,6 +1181,7 @@ class TestDerivationOrder(unittest.TestCase):
'deriv2',
'rendered_view',
'deriv1',
+ 'mapped_view',
], dlist)
@@ -1218,11 +1218,11 @@ class TestAddDeriver(unittest.TestCase):
def __init__(self):
self.response = DummyResponse()
- def deriv1(view, value, **kw):
+ def deriv1(view, info):
flags['deriv1'] = True
return view
- def deriv2(view, value, **kw):
+ def deriv2(view, info):
flags['deriv2'] = True
return view
@@ -1240,27 +1240,84 @@ class TestAddDeriver(unittest.TestCase):
self.assertTrue(flags.get('deriv2'))
def test_add_multi_derivers_ordered(self):
+ from pyramid.viewderivers import INGRESS
response = DummyResponse()
view = lambda *arg: response
response.deriv = []
- def deriv1(view, value, **kw):
+ def deriv1(view, info):
response.deriv.append('deriv1')
return view
- def deriv2(view, value, **kw):
+ def deriv2(view, info):
response.deriv.append('deriv2')
return view
- def deriv3(view, value, **kw):
+ def deriv3(view, info):
response.deriv.append('deriv3')
return view
self.config.add_view_deriver(deriv1, 'deriv1')
- self.config.add_view_deriver(deriv2, 'deriv2', under='deriv1')
- self.config.add_view_deriver(deriv3, 'deriv3', over='deriv2')
+ self.config.add_view_deriver(deriv2, 'deriv2', INGRESS, 'deriv1')
+ self.config.add_view_deriver(deriv3, 'deriv3', 'deriv2', 'deriv1')
result = self.config._derive_view(view)
- self.assertEqual(response.deriv, ['deriv2', 'deriv3', 'deriv1'])
+ self.assertEqual(response.deriv, ['deriv1', 'deriv3', 'deriv2'])
+
+ def test_add_deriver_without_name(self):
+ from pyramid.interfaces import IViewDerivers
+ def deriv1(view, info): pass
+ self.config.add_view_deriver(deriv1)
+ derivers = self.config.registry.getUtility(IViewDerivers)
+ self.assertTrue('deriv1' in derivers.names)
+
+ def test_add_deriver_reserves_ingress(self):
+ from pyramid.exceptions import ConfigurationError
+ from pyramid.viewderivers import INGRESS
+ def deriv1(view, info): pass
+ 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):
+ from pyramid.exceptions import ConfigurationError
+ def deriv1(view, info): pass
+ try:
+ self.config.add_view_deriver(deriv1, over='rendered_view')
+ except ConfigurationError as ex:
+ self.assertTrue('must specify an "under" constraint' in ex.args[0])
+ else: # pragma: no cover
+ raise AssertionError
+
+ 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, under='rendered_view', over=INGRESS)
+ except ConfigurationError as ex:
+ self.assertTrue('cannot be over view deriver INGRESS' in ex.args[0])
+ else: # pragma: no cover
+ raise AssertionError
+
+ def test_add_deriver_enforces_mapped_view_is_last(self):
+ from pyramid.exceptions import ConfigurationError
+ def deriv1(view, info): pass
+ try:
+ self.config.add_view_deriver(
+ deriv1, 'deriv1', 'mapped_view', 'rendered_view')
+ except ConfigurationError as ex:
+ self.assertTrue('cannot be under view deriver MAPPED_VIEW' in ex.args[0])
+ else: # pragma: no cover
+ raise AssertionError
class TestDeriverIntegration(unittest.TestCase):
diff --git a/pyramid/viewderivers.py b/pyramid/viewderivers.py
index 99baf46f9..f97099cc8 100644
--- a/pyramid/viewderivers.py
+++ b/pyramid/viewderivers.py
@@ -260,6 +260,13 @@ def http_cached_view(view, info):
http_cached_view.options = ('http_cache',)
def secured_view(view, info):
+ for wrapper in (_secured_view, _authdebug_view):
+ view = wraps_view(wrapper)(view, info)
+ return view
+
+secured_view.options = ('permission',)
+
+def _secured_view(view, info):
permission = info.options.get('permission')
if permission == NO_PERMISSION_REQUIRED:
# allow views registered within configurations that have a
@@ -291,9 +298,7 @@ def secured_view(view, info):
return wrapped_view
-secured_view.options = ('permission',)
-
-def authdebug_view(view, info):
+def _authdebug_view(view, info):
wrapped_view = view
settings = info.settings
permission = info.options.get('permission')
@@ -330,8 +335,6 @@ def authdebug_view(view, info):
return wrapped_view
-authdebug_view.options = ('permission',)
-
def predicated_view(view, info):
preds = info.predicates
if not preds:
@@ -451,3 +454,6 @@ def decorated_view(view, info):
return decorator(view)
decorated_view.options = ('decorator',)
+
+MAPPED_VIEW = 'mapped_view'
+INGRESS = 'INGRESS'