diff options
| author | Michael Merickel <michael@merickel.org> | 2016-04-07 01:16:45 -0500 |
|---|---|---|
| committer | Michael Merickel <michael@merickel.org> | 2016-04-07 01:27:06 -0500 |
| commit | a3db3cab713fecc0c83c742cfe3f0736b1d94a92 (patch) | |
| tree | ba3017bb9b0d6ddaed31bbfb64e9eac3c64d0e40 | |
| parent | ecc9d82ef6bd50a08dcbee7286ba4581d3caa902 (diff) | |
| download | pyramid-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.rst | 16 | ||||
| -rw-r--r-- | docs/narr/hooks.rst | 9 | ||||
| -rw-r--r-- | pyramid/config/views.py | 72 | ||||
| -rw-r--r-- | pyramid/tests/test_viewderivers.py | 89 | ||||
| -rw-r--r-- | pyramid/viewderivers.py | 16 |
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' |
