From 0076004e2e2fd5de1e8193d8c66107672267d16c Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Fri, 4 Mar 2016 01:47:29 -0600 Subject: define deriver api as (view, info) using IViewDeriverInfo - drop the concept of default values from view derivers (we'll later bring this back with add_view_option(name, default=..) - define a new IViewDeriverInfo (mirroring IRendererInfo) which has attributes on it like the predicate list, registry and dictionary of options - define new deriver api as (view, info) --- pyramid/config/derivations.py | 79 +++++++------ pyramid/config/views.py | 161 +++++++++++++++----------- pyramid/interfaces.py | 16 ++- pyramid/tests/test_config/test_derivations.py | 105 ++++------------- 4 files changed, 169 insertions(+), 192 deletions(-) diff --git a/pyramid/config/derivations.py b/pyramid/config/derivations.py index c23e5c479..59efeb0f4 100644 --- a/pyramid/config/derivations.py +++ b/pyramid/config/derivations.py @@ -154,8 +154,8 @@ class DefaultViewMapper(object): def wraps_view(wrapper): - def inner(view, value, **kw): - wrapper_view = wrapper(view, value, **kw) + def inner(view, info): + wrapper_view = wrapper(view, info) return preserve_view_attrs(view, wrapper_view) return inner @@ -193,21 +193,21 @@ def preserve_view_attrs(view, wrapper): return wrapper -def mapped_view(view, value, **kw): - mapper = kw.get('mapper') +def mapped_view(view, info): + mapper = info.options.get('mapper') if mapper is None: mapper = getattr(view, '__view_mapper__', None) if mapper is None: - mapper = kw['registry'].queryUtility(IViewMapperFactory) + mapper = info.registry.queryUtility(IViewMapperFactory) if mapper is None: mapper = DefaultViewMapper - mapped_view = mapper(**kw)(view) + mapped_view = mapper(**info.options)(view) return mapped_view -def owrapped_view(view, value, **kw): - wrapper_viewname = kw.get('wrapper_viewname') - viewname = kw.get('viewname') +def owrapped_view(view, info): + wrapper_viewname = info.options.get('wrapper_viewname') + viewname = info.options.get('viewname') if not wrapper_viewname: return view def _owrapped_view(context, request): @@ -224,11 +224,11 @@ def owrapped_view(view, value, **kw): return wrapped_response return _owrapped_view -def http_cached_view(view, value, **kw): - if kw['registry'].settings.get('prevent_http_cache', False): +def http_cached_view(view, info): + if info.settings.get('prevent_http_cache', False): return view - seconds = kw.get('http_cache') + seconds = info.options.get('http_cache') if seconds is None: return view @@ -253,8 +253,8 @@ def http_cached_view(view, value, **kw): return wrapper -def secured_view(view, value, **kw): - permission = kw.get('permission') +def secured_view(view, info): + permission = info.options.get('permission') if permission == NO_PERMISSION_REQUIRED: # allow views registered within configurations that have a # default permission to explicitly override the default @@ -262,8 +262,8 @@ def secured_view(view, value, **kw): permission = None wrapped_view = view - authn_policy = kw['registry'].queryUtility(IAuthenticationPolicy) - authz_policy = kw['registry'].queryUtility(IAuthorizationPolicy) + authn_policy = info.registry.queryUtility(IAuthenticationPolicy) + authz_policy = info.registry.queryUtility(IAuthorizationPolicy) if authn_policy and authz_policy and (permission is not None): def _permitted(context, request): @@ -285,13 +285,13 @@ def secured_view(view, value, **kw): return wrapped_view -def authdebug_view(view, value, **kw): +def authdebug_view(view, info): wrapped_view = view - settings = kw['registry'].settings - permission = kw.get('permission') - authn_policy = kw['registry'].queryUtility(IAuthenticationPolicy) - authz_policy = kw['registry'].queryUtility(IAuthorizationPolicy) - logger = kw['registry'].queryUtility(IDebugLogger) + settings = info.settings + permission = info.options.get('permission') + authn_policy = info.registry.queryUtility(IAuthenticationPolicy) + authz_policy = info.registry.queryUtility(IAuthorizationPolicy) + logger = info.registry.queryUtility(IDebugLogger) if settings and settings.get('debug_authorization', False): def _authdebug_view(context, request): view_name = getattr(request, 'view_name', None) @@ -322,8 +322,8 @@ def authdebug_view(view, value, **kw): return wrapped_view -def predicated_view(view, value, **kw): - preds = kw.get('predicates', ()) +def predicated_view(view, info): + preds = info.predicates if not preds: return view def predicate_wrapper(context, request): @@ -341,11 +341,11 @@ def predicated_view(view, value, **kw): predicate_wrapper.__predicates__ = preds return predicate_wrapper -def attr_wrapped_view(view, value, **kw): - kw = kw - accept, order, phash = (kw.get('accept', None), - kw.get('order', MAX_ORDER), - kw.get('phash', DEFAULT_PHASH)) +def attr_wrapped_view(view, info): + opts = info.options + accept, order, phash = (opts.get('accept', None), + opts.get('order', MAX_ORDER), + opts.get('phash', DEFAULT_PHASH)) # this is a little silly but we don't want to decorate the original # function with attributes that indicate accept, order, and phash, # so we use a wrapper @@ -360,15 +360,14 @@ def attr_wrapped_view(view, value, **kw): attr_view.__accept__ = accept attr_view.__order__ = order attr_view.__phash__ = phash - attr_view.__view_attr__ = kw.get('attr') - attr_view.__permission__ = kw.get('permission') + attr_view.__view_attr__ = opts.get('attr') + attr_view.__permission__ = opts.get('permission') return attr_view -def rendered_view(view, value, **kw): +def rendered_view(view, info): # one way or another this wrapper must produce a Response (unless # the renderer is a NullRendererHelper) - renderer = kw.get('renderer') - registry = kw['registry'] + renderer = info.options.get('renderer') if renderer is None: # register a default renderer if you want super-dynamic # rendering. registering a default renderer will also allow @@ -379,7 +378,7 @@ def rendered_view(view, value, **kw): if result.__class__ is Response: # common case response = result else: - response = registry.queryAdapterOrSelf(result, IResponse) + response = info.registry.queryAdapterOrSelf(result, IResponse) if response is None: if result is None: append = (' You may have forgotten to return a value ' @@ -410,7 +409,7 @@ def rendered_view(view, value, **kw): else: # this must adapt, it can't do a simple interface check # (avoid trying to render webob responses) - response = registry.queryAdapterOrSelf(result, IResponse) + response = info.registry.queryAdapterOrSelf(result, IResponse) if response is None: attrs = getattr(request, '__dict__', {}) if 'override_renderer' in attrs: @@ -418,8 +417,8 @@ def rendered_view(view, value, **kw): renderer_name = attrs.pop('override_renderer') view_renderer = renderers.RendererHelper( name=renderer_name, - package=kw.get('package'), - registry=registry) + package=info.package, + registry=info.registry) else: view_renderer = renderer.clone() if '__view__' in attrs: @@ -432,8 +431,8 @@ def rendered_view(view, value, **kw): return rendered_view -def decorated_view(view, value, **kw): - decorator = kw.get('decorator') +def decorated_view(view, info): + decorator = info.options.get('decorator') if decorator is None: return view return decorator(view) diff --git a/pyramid/config/views.py b/pyramid/config/views.py index 3e27f0bfb..890fd2113 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -27,6 +27,7 @@ from pyramid.interfaces import ( IView, IViewClassifier, IViewDerivers, + IViewDeriverInfo, IViewMapperFactory, PHASE1_CONFIG, ) @@ -42,6 +43,8 @@ from pyramid.compat import ( is_nonstr_iter, ) +from pyramid.decorator import reify + from pyramid.exceptions import ( ConfigurationError, PredicateMismatch, @@ -653,12 +656,17 @@ class ViewsConfiguratorMixin(object): Pass a key/value pair here to use a third-party predicate or set a value for a view derivative option registered via :meth:`pyramid.config.Configurator.add_view_predicate` or - :meth:`pyramid.config.Configurator.add_view_derivation`. More than + :meth:`pyramid.config.Configurator.add_view_option`. More than one key/value pair can be used at the same time. See :ref:`view_and_route_predicates` for more information about third-party predicates. - .. versionadded: 1.7 + .. versionadded: 1.4a1 + + .. versionchanged: 1.7 + + Support setting arbitrary view options. Previously, only + predicate values could be supplied. """ if custom_predicates: @@ -726,21 +734,19 @@ class ViewsConfiguratorMixin(object): introspectables = [] ovals = view_options.copy() - ovals.update( - dict( - xhr=xhr, - request_method=request_method, - path_info=path_info, - request_param=request_param, - header=header, - accept=accept, - containment=containment, - request_type=request_type, - match_param=match_param, - check_csrf=check_csrf, - custom=predvalseq(custom_predicates), - ) - ) + ovals.update(dict( + xhr=xhr, + request_method=request_method, + path_info=path_info, + request_param=request_param, + header=header, + accept=accept, + containment=containment, + request_type=request_type, + match_param=match_param, + check_csrf=check_csrf, + custom=predvalseq(custom_predicates), + )) def discrim_func(): # We need to defer the discriminator until we know what the phash @@ -749,13 +755,10 @@ class ViewsConfiguratorMixin(object): # called. valid_predicates = predlist.names() pvals = {} - options = {} for (k, v) in ovals.items(): if k in valid_predicates: pvals[k] = v - else: - options[k] = v order, preds, phash = predlist.make(self, **pvals) @@ -763,7 +766,6 @@ class ViewsConfiguratorMixin(object): 'phash': phash, 'order': order, 'predicates': preds, - 'options': options }) return ('view', context, name, route_name, phash) @@ -781,26 +783,25 @@ class ViewsConfiguratorMixin(object): discriminator, view_desc, 'view') - view_intr.update( - dict(name=name, - context=context, - containment=containment, - request_param=request_param, - request_methods=request_method, - route_name=route_name, - attr=attr, - xhr=xhr, - accept=accept, - header=header, - path_info=path_info, - match_param=match_param, - check_csrf=check_csrf, - callable=view, - mapper=mapper, - decorator=decorator, - ) - ) - view_intr.update(**view_options) + view_intr.update(dict( + name=name, + context=context, + containment=containment, + request_param=request_param, + request_methods=request_method, + route_name=route_name, + attr=attr, + xhr=xhr, + accept=accept, + header=header, + path_info=path_info, + match_param=match_param, + check_csrf=check_csrf, + callable=view, + mapper=mapper, + decorator=decorator, + )) + view_intr.update(view_options) introspectables.append(view_intr) predlist = self.get_predlist('view') @@ -832,14 +833,12 @@ class ViewsConfiguratorMixin(object): # added by discrim_func above during conflict resolving preds = view_intr['predicates'] - opts = view_intr['options'] order = view_intr['order'] phash = view_intr['phash'] # __no_permission_required__ handled by _secure_view - derived_view = self._apply_view_derivations( + derived_view = self._derive_view( view, - registry=self.registry, permission=permission, predicates=preds, attr=attr, @@ -849,12 +848,11 @@ class ViewsConfiguratorMixin(object): accept=accept, order=order, phash=phash, - package=self.package, - mapper=mapper, decorator=decorator, + mapper=mapper, http_cache=http_cache, - options=opts, - ) + extra_options=view_options, + ) derived_view.__discriminator__ = lambda *arg: discriminator # __discriminator__ is used by superdynamic systems # that require it for introspection after manual view lookup; @@ -1013,19 +1011,20 @@ class ViewsConfiguratorMixin(object): introspectables.append(perm_intr) self.action(discriminator, register, introspectables=introspectables) - def _apply_view_derivations(self, view, **kw): + def _apply_view_derivations(self, info): d = pyramid.config.derivations # These inner derivations have fixed order - inner_derivers = [('mapped_view', (d.mapped_view, None))] + inner_derivers = [('mapped_view', d.mapped_view)] - outer_derivers = [('predicated_view', (d.predicated_view, None)), - ('attr_wrapped_view', (d.attr_wrapped_view, None)),] + outer_derivers = [('predicated_view', d.predicated_view), + ('attr_wrapped_view', d.attr_wrapped_view)] + view = info.orig_view derivers = self.registry.queryUtility(IViewDerivers, default=[]) - for name, val in inner_derivers + derivers.sorted() + outer_derivers: - derivation, default = val - value = kw['options'].get(name, default) - view = wraps_view(derivation)(view, value, **kw) + for name, derivation in ( + inner_derivers + derivers.sorted() + outer_derivers + ): + view = wraps_view(derivation)(view, info) return view @action_method @@ -1076,7 +1075,9 @@ class ViewsConfiguratorMixin(object): self.add_view_predicate(name, factory) @action_method - def add_view_derivation(self, name, factory, default, + def add_view_derivation(self, + name, + factory, under=None, over=None): if under is None and over is None: @@ -1098,9 +1099,7 @@ class ViewsConfiguratorMixin(object): if derivers is None: derivers = TopologicalSorter() self.registry.registerUtility(derivers, IViewDerivers) - derivers.add(name, (factory, default), - after=under, - before=over) + derivers.add(name, factory, after=under, before=over) self.action(discriminator, register, introspectables=(intr,), order=PHASE1_CONFIG) # must be registered early @@ -1115,10 +1114,15 @@ class ViewsConfiguratorMixin(object): ] after = pyramid.util.FIRST for name, deriver in derivers: - self.add_view_derivation(name, deriver, default=None, under=after) + self.add_view_derivation(name, deriver, under=after) after = name - self.add_view_derivation('rendered_view', d.rendered_view, default=None, under=pyramid.util.FIRST, over='decorated_view') + self.add_view_derivation( + 'rendered_view', + d.rendered_view, + under=pyramid.util.FIRST, + over='decorated_view', + ) def derive_view(self, view, attr=None, renderer=None): """ @@ -1199,11 +1203,11 @@ class ViewsConfiguratorMixin(object): return self._derive_view(view, attr=attr, renderer=renderer) # b/w compat - def _derive_view(self, view, permission=None, predicates=(), options=dict(), + def _derive_view(self, view, permission=None, predicates=(), attr=None, renderer=None, wrapper_viewname=None, viewname=None, accept=None, order=MAX_ORDER, phash=DEFAULT_PHASH, decorator=None, - mapper=None, http_cache=None): + mapper=None, http_cache=None, extra_options=None): view = self.maybe_dotted(view) mapper = self.maybe_dotted(mapper) if isinstance(renderer, string_types): @@ -1218,11 +1222,8 @@ class ViewsConfiguratorMixin(object): package=self.package, registry=self.registry) - return self._apply_view_derivations( - view, - registry=self.registry, + options = dict( permission=permission, - predicates=predicates, attr=attr, renderer=renderer, wrapper_viewname=wrapper_viewname, @@ -1230,13 +1231,23 @@ class ViewsConfiguratorMixin(object): accept=accept, order=order, phash=phash, - package=self.package, mapper=mapper, decorator=decorator, http_cache=http_cache, + ) + if extra_options: + options.update(extra_options) + + info = ViewDeriverInfo( + view=view, + registry=self.registry, + package=self.package, + predicates=predicates, options=options, ) + return self._apply_view_derivations(info) + @viewdefaults @action_method def add_forbidden_view( @@ -1624,6 +1635,18 @@ def isexception(o): (inspect.isclass(o) and (issubclass(o, Exception))) ) +@implementer(IViewDeriverInfo) +class ViewDeriverInfo(object): + def __init__(self, view, registry, package, predicates, options): + self.orig_view = view + self.registry = registry + self.package = package + self.predicates = predicates or [] + self.options = options or {} + + @reify + def settings(self): + return self.registry.settings @implementer(IStaticURLInfo) class StaticURLInfo(object): diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index 4c3e87f39..5ae7cfbf8 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -1189,7 +1189,21 @@ class IPredicateList(Interface): class IViewDerivers(Interface): """ Interface for view derivers list """ - + +class IViewDeriverInfo(Interface): + """ An object implementing this interface is passed to every + :term:`view deriver` during configuration.""" + registry = Attribute('The "current" application registry when the ' + 'view was created') + package = Attribute('The "current package" when the view ' + 'configuration statement was found') + settings = Attribute('The deployment settings dictionary related ' + 'to the current application') + options = Attribute('The view options passed to the view, including any ' + 'default values that were not overriden') + predicates = Attribute('The list of predicates active on the view') + orig_view = Attribute('The original view object being wrapped') + 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 7095d25bf..1c7a0be07 100644 --- a/pyramid/tests/test_config/test_derivations.py +++ b/pyramid/tests/test_config/test_derivations.py @@ -1097,9 +1097,9 @@ class TestDerivationOrder(unittest.TestCase): def test_right_order_user_sorted(self): from pyramid.interfaces import IViewDerivers - self.config.add_view_derivation('deriv1', None, default=None) - self.config.add_view_derivation('deriv2', None, default=None, over='deriv1') - self.config.add_view_derivation('deriv3', None, default=None, under='deriv2') + self.config.add_view_derivation('deriv1', None) + self.config.add_view_derivation('deriv2', None, over='deriv1') + self.config.add_view_derivation('deriv3', None, under='deriv2') derivers = self.config.registry.queryUtility(IViewDerivers, default=[]) derivers_sorted = derivers.sorted() @@ -1119,9 +1119,9 @@ class TestDerivationOrder(unittest.TestCase): def test_right_order_implicit(self): from pyramid.interfaces import IViewDerivers - self.config.add_view_derivation('deriv1', None, default=None) - self.config.add_view_derivation('deriv2', None, default=None) - self.config.add_view_derivation('deriv3', None, default=None) + self.config.add_view_derivation('deriv1', None) + self.config.add_view_derivation('deriv2', None) + self.config.add_view_derivation('deriv3', None) derivers = self.config.registry.queryUtility(IViewDerivers, default=[]) derivers_sorted = derivers.sorted() @@ -1141,7 +1141,7 @@ class TestDerivationOrder(unittest.TestCase): def test_right_order_over_rendered_view(self): from pyramid.interfaces import IViewDerivers - self.config.add_view_derivation('deriv1', None, default=None, over='rendered_view') + self.config.add_view_derivation('deriv1', None, over='rendered_view') derivers = self.config.registry.queryUtility(IViewDerivers, default=[]) derivers_sorted = derivers.sorted() @@ -1159,9 +1159,9 @@ class TestDerivationOrder(unittest.TestCase): def test_right_order_over_rendered_view_others(self): from pyramid.interfaces import IViewDerivers - self.config.add_view_derivation('deriv1', None, default=None, over='rendered_view') - self.config.add_view_derivation('deriv2', None, default=None) - self.config.add_view_derivation('deriv3', None, default=None) + self.config.add_view_derivation('deriv1', None, over='rendered_view') + self.config.add_view_derivation('deriv2', None) + self.config.add_view_derivation('deriv3', None) derivers = self.config.registry.queryUtility(IViewDerivers, default=[]) derivers_sorted = derivers.sorted() @@ -1192,33 +1192,18 @@ class TestAddDerivation(unittest.TestCase): response.deriv = False view = lambda *arg: response - def deriv(view, value, **kw): + def deriv(view, info): self.assertFalse(response.deriv) - self.assertEqual(value, None) response.deriv = True return view result = self.config._derive_view(view) self.assertFalse(response.deriv) - self.config.add_view_derivation('test_deriv', deriv, default=None) + self.config.add_view_derivation('test_deriv', deriv) result = self.config._derive_view(view) self.assertTrue(response.deriv) - def test_derivation_default(self): - response = DummyResponse() - response.deriv_value = None - test_default = object() - view = lambda *arg: response - - def deriv(view, value, **kw): - response.deriv_value = value - return view - - self.config.add_view_derivation('test_default_deriv', deriv, default=test_default) - result = self.config._derive_view(view) - self.assertEqual(response.deriv_value, test_default) - def test_override_derivation(self): flags = {} @@ -1235,36 +1220,18 @@ class TestAddDerivation(unittest.TestCase): return view view1 = AView() - self.config.add_view_derivation('test_deriv', deriv1, default=None) + self.config.add_view_derivation('test_deriv', deriv1) result = self.config._derive_view(view1) self.assertTrue(flags.get('deriv1')) self.assertFalse(flags.get('deriv2')) flags.clear() view2 = AView() - self.config.add_view_derivation('test_deriv', deriv2, default=None) + self.config.add_view_derivation('test_deriv', deriv2) result = self.config._derive_view(view2) self.assertFalse(flags.get('deriv1')) self.assertTrue(flags.get('deriv2')) - def test_override_derivation_default(self): - response = DummyResponse() - response.deriv_value = None - test_default1 = 'first default' - test_default2 = 'second default' - view = lambda *arg: response - - def deriv(view, value, **kw): - response.deriv_value = value - return view - - self.config.add_view_derivation('test_default_deriv', deriv, default=test_default1) - result = self.config._derive_view(view) - self.assertEqual(response.deriv_value, test_default1) - self.config.add_view_derivation('test_default_deriv', deriv, default=test_default2) - result = self.config._derive_view(view) - self.assertEqual(response.deriv_value, test_default2) - def test_add_multi_derivations_ordered(self): response = DummyResponse() view = lambda *arg: response @@ -1282,9 +1249,9 @@ class TestAddDerivation(unittest.TestCase): response.deriv.append('deriv3') return view - self.config.add_view_derivation('deriv1', deriv1, default=None) - self.config.add_view_derivation('deriv2', deriv2, default=None, over='deriv1') - self.config.add_view_derivation('deriv3', deriv3, default=None, under='deriv2') + self.config.add_view_derivation('deriv1', deriv1) + self.config.add_view_derivation('deriv2', deriv2, over='deriv1') + self.config.add_view_derivation('deriv3', deriv3, under='deriv2') result = self.config._derive_view(view) self.assertEqual(response.deriv, ['deriv2', 'deriv3', 'deriv1']) @@ -1323,16 +1290,16 @@ class TestDerivationIntegration(unittest.TestCase): view = lambda *arg: response response.deriv = [] - def deriv1(view, value, **kw): - response.deriv.append(kw['options']['deriv1']) + def deriv1(view, info): + response.deriv.append(info.options['deriv1']) return view - def deriv2(view, value, **kw): - response.deriv.append(kw['options']['deriv2']) + def deriv2(view, info): + response.deriv.append(info.options['deriv2']) return view - self.config.add_view_derivation('deriv1', deriv1, default=None) - self.config.add_view_derivation('deriv2', deriv2, default=None) + self.config.add_view_derivation('deriv1', deriv1) + self.config.add_view_derivation('deriv2', deriv2) self.config.add_view(view, deriv1='test1', deriv2='test2') self.config.commit() @@ -1342,32 +1309,6 @@ class TestDerivationIntegration(unittest.TestCase): self.assertEqual(wrapper(None, request), response) self.assertEqual(['test1', 'test2'], response.deriv) - def test_view_options_default_or_not(self): - response = DummyResponse() - view = lambda *arg: response - response.deriv = [] - - def deriv1(view, value, **kw): - response.deriv.append(value) - response.deriv.append(kw['options'].get('deriv1', None)) - return view - - def deriv2(view, value, **kw): - response.deriv.append(value) - response.deriv.append(kw['options'].get('deriv2', None)) - return view - - self.config.add_view_derivation('deriv1', deriv1, default=None) - self.config.add_view_derivation('deriv2', deriv2, default='test2') - self.config.add_view(view, deriv1='test1') - self.config.commit() - - wrapper = self._getViewCallable(self.config) - request = self._makeRequest(self.config) - request.method = 'GET' - self.assertEqual(wrapper(None, request), response) - self.assertEqual(['test1', 'test1', 'test2', None], response.deriv) - from zope.interface import implementer from pyramid.interfaces import ( -- cgit v1.2.3