diff options
| -rw-r--r-- | docs/api/interfaces.rst | 6 | ||||
| -rw-r--r-- | pyramid/config/derivations.py | 16 | ||||
| -rw-r--r-- | pyramid/config/views.py | 15 | ||||
| -rw-r--r-- | pyramid/interfaces.py | 20 | ||||
| -rw-r--r-- | pyramid/tests/test_config/test_derivations.py | 11 | ||||
| -rw-r--r-- | pyramid/util.py | 3 |
6 files changed, 68 insertions, 3 deletions
diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst index de2a664a4..635d3c5b6 100644 --- a/docs/api/interfaces.rst +++ b/docs/api/interfaces.rst @@ -91,3 +91,9 @@ Other Interfaces .. autointerface:: ICacheBuster :members: + + .. autointerface:: IViewDeriver + :members: + + .. autointerface:: IViewDeriverInfo + :members: diff --git a/pyramid/config/derivations.py b/pyramid/config/derivations.py index 0e109f425..99baf46f9 100644 --- a/pyramid/config/derivations.py +++ b/pyramid/config/derivations.py @@ -205,6 +205,8 @@ def mapped_view(view, info): mapped_view = mapper(**info.options)(view) return mapped_view +mapped_view.options = ('mapper', 'attr') + def owrapped_view(view, info): wrapper_viewname = info.options.get('wrapper') viewname = info.options.get('name') @@ -224,6 +226,8 @@ def owrapped_view(view, info): return wrapped_response return _owrapped_view +owrapped_view.options = ('name', 'wrapper') + def http_cached_view(view, info): if info.settings.get('prevent_http_cache', False): return view @@ -253,6 +257,8 @@ def http_cached_view(view, info): return wrapper +http_cached_view.options = ('http_cache',) + def secured_view(view, info): permission = info.options.get('permission') if permission == NO_PERMISSION_REQUIRED: @@ -285,6 +291,8 @@ def secured_view(view, info): return wrapped_view +secured_view.options = ('permission',) + def authdebug_view(view, info): wrapped_view = view settings = info.settings @@ -322,6 +330,8 @@ def authdebug_view(view, info): return wrapped_view +authdebug_view.options = ('permission',) + def predicated_view(view, info): preds = info.predicates if not preds: @@ -363,6 +373,8 @@ def attr_wrapped_view(view, info): attr_view.__permission__ = info.options.get('permission') return attr_view +attr_wrapped_view.options = ('accept', 'attr', 'permission') + def rendered_view(view, info): # one way or another this wrapper must produce a Response (unless # the renderer is a NullRendererHelper) @@ -430,8 +442,12 @@ def rendered_view(view, info): return rendered_view +rendered_view.options = ('renderer',) + def decorated_view(view, info): decorator = info.options.get('decorator') if decorator is None: return view return decorator(view) + +decorated_view.options = ('decorator',) diff --git a/pyramid/config/views.py b/pyramid/config/views.py index 6511ecd1e..c3e5d360e 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -755,10 +755,15 @@ class ViewsConfiguratorMixin(object): # called. valid_predicates = predlist.names() pvals = {} + dvals = {} for (k, v) in ovals.items(): if k in valid_predicates: pvals[k] = v + else: + dvals[k] = v + + self._check_view_options(**dvals) order, preds, phash = predlist.make(self, **pvals) @@ -1012,6 +1017,16 @@ class ViewsConfiguratorMixin(object): introspectables.append(perm_intr) self.action(discriminator, register, introspectables=introspectables) + def _check_view_options(self, **kw): + # we only need to validate deriver options because the predicates + # were checked by the predlist + derivers = self.registry.getUtility(IViewDerivers) + for deriver in derivers.values(): + for opt in getattr(deriver, 'options', []): + kw.pop(opt, None) + if kw: + raise ConfigurationError('Unknown view options: %s' % (kw,)) + def _apply_view_derivers(self, info): d = pyramid.config.derivations # These inner derivations have fixed order diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index 6791befce..692bf3d6d 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -1187,8 +1187,21 @@ class IJSONAdapter(Interface): class IPredicateList(Interface): """ Interface representing a predicate list """ -class IViewDerivers(Interface): - """ Interface for view derivers list """ +class IViewDeriver(Interface): + options = Attribute('An list of supported options to be passed to ' + ':meth:`pyramid.config.Configurator.add_view`. ' + 'This attribute is optional.') + + def __call__(view, info): + """ + Derive a new view from the supplied view. + + View options, package information and registry are available on + ``info``, an instance of :class:`pyramid.interfaces.IViewDeriverInfo`. + + The ``view`` is a callable accepting ``(context, request)``. + + """ class IViewDeriverInfo(Interface): """ An object implementing this interface is passed to every @@ -1204,6 +1217,9 @@ class IViewDeriverInfo(Interface): predicates = Attribute('The list of predicates active on the view') original_view = Attribute('The original view object being wrapped') +class IViewDerivers(Interface): + """ Interface for view derivers list """ + class ICacheBuster(Interface): """ A cache buster modifies the URL generation machinery for diff --git a/pyramid/tests/test_config/test_derivations.py b/pyramid/tests/test_config/test_derivations.py index ec7ef1434..12fcc300a 100644 --- a/pyramid/tests/test_config/test_derivations.py +++ b/pyramid/tests/test_config/test_derivations.py @@ -1293,15 +1293,16 @@ class TestDeriverIntegration(unittest.TestCase): def deriv1(view, info): response.deriv.append(info.options['deriv1']) return view + deriv1.options = ('deriv1',) def deriv2(view, info): response.deriv.append(info.options['deriv2']) return view + deriv2.options = ('deriv2',) self.config.add_view_deriver('deriv1', deriv1) self.config.add_view_deriver('deriv2', deriv2) self.config.add_view(view, deriv1='test1', deriv2='test2') - self.config.commit() wrapper = self._getViewCallable(self.config) request = self._makeRequest(self.config) @@ -1309,6 +1310,14 @@ class TestDeriverIntegration(unittest.TestCase): self.assertEqual(wrapper(None, request), response) self.assertEqual(['test1', 'test2'], response.deriv) + def test_unexpected_view_options(self): + from pyramid.exceptions import ConfigurationError + def deriv1(view, info): pass + self.config.add_view_deriver('deriv1', deriv1) + self.assertRaises( + ConfigurationError, + lambda: self.config.add_view(lambda r: {}, deriv1='test1')) + from zope.interface import implementer from pyramid.interfaces import ( diff --git a/pyramid/util.py b/pyramid/util.py index 0a73cedaf..27140936c 100644 --- a/pyramid/util.py +++ b/pyramid/util.py @@ -380,6 +380,9 @@ class TopologicalSorter(object): self.first = first self.last = last + def values(self): + return self.name2val.values() + def remove(self, name): """ Remove a node from the sort input """ self.names.remove(name) |
