diff options
| author | Casey Duncan <casey.duncan@gmail.com> | 2015-04-15 10:06:10 -0400 |
|---|---|---|
| committer | Casey Duncan <casey.duncan@gmail.com> | 2015-04-15 10:06:10 -0400 |
| commit | c2c5894d83f4bf43efd476336c7c065cd5984716 (patch) | |
| tree | 0cf4ec21c0a53242297641d2355fb8ca0bca9c26 | |
| parent | c4f2afdaf9bd53d95fbd5cdf378fc9104d34562c (diff) | |
| download | pyramid-c2c5894d83f4bf43efd476336c7c065cd5984716.tar.gz pyramid-c2c5894d83f4bf43efd476336c7c065cd5984716.tar.bz2 pyramid-c2c5894d83f4bf43efd476336c7c065cd5984716.zip | |
wip derivations
| -rw-r--r-- | pyramid/config/__init__.py | 6 | ||||
| -rw-r--r-- | pyramid/config/derivations.py | 448 | ||||
| -rw-r--r-- | pyramid/config/views.py | 520 |
3 files changed, 527 insertions, 447 deletions
diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py index 549144fab..b5cf35ff0 100644 --- a/pyramid/config/__init__.py +++ b/pyramid/config/__init__.py @@ -63,6 +63,7 @@ from pyramid.util import ( WeakOrderedSet, action_method, object_description, + TopologicalSorter, ) from pyramid.config.adapters import AdaptersConfiguratorMixin @@ -311,6 +312,7 @@ class Configurator( self.autocommit = autocommit self.route_prefix = route_prefix self.introspection = introspection + self.derivationlist = TopologicalSorter() if registry is None: registry = Registry(self.package_name) self.registry = registry @@ -378,6 +380,7 @@ class Configurator( self.add_default_response_adapters() self.add_default_renderers() self.add_default_view_predicates() + self.add_default_view_derivations() self.add_default_route_predicates() if exceptionresponse_view is not None: @@ -521,10 +524,11 @@ class Configurator( self.registry.registerUtility(predlist, IPredicateList, name=name) return predlist + def _add_predicate(self, type, name, factory, weighs_more_than=None, weighs_less_than=None): factory = self.maybe_dotted(factory) - discriminator = ('%s predicate' % type, name) + discriminator = ('%s option' % type, name) intr = self.introspectable( '%s predicates' % type, discriminator, diff --git a/pyramid/config/derivations.py b/pyramid/config/derivations.py new file mode 100644 index 000000000..448c0b79b --- /dev/null +++ b/pyramid/config/derivations.py @@ -0,0 +1,448 @@ +import inspect + +from zope.interface import ( + implementer, + provider, + ) + +from pyramid.security import NO_PERMISSION_REQUIRED +from pyramid.response import Response + +from pyramid.interfaces import ( + IAuthenticationPolicy, + IAuthorizationPolicy, + IDebugLogger, + IResponse, + IViewMapper, + IViewMapperFactory, + ) + +from pyramid.compat import ( + is_bound_method, + is_unbound_method, + ) + +from pyramid.config.util import ( + DEFAULT_PHASH, + MAX_ORDER, + takes_one_arg, + ) + +from pyramid.exceptions import ( + ConfigurationError, + PredicateMismatch, + ) + +from pyramid.util import object_description +from pyramid import renderers + + +def view_description(view): + try: + return view.__text__ + except AttributeError: + # custom view mappers might not add __text__ + return object_description(view) + +def requestonly(view, attr=None): + return takes_one_arg(view, attr=attr, argname='request') + +@implementer(IViewMapper) +@provider(IViewMapperFactory) +class DefaultViewMapper(object): + def __init__(self, **kw): + self.attr = kw.get('attr') + + def __call__(self, view): + if is_unbound_method(view) and self.attr is None: + raise ConfigurationError(( + 'Unbound method calls are not supported, please set the class ' + 'as your `view` and the method as your `attr`' + )) + + if inspect.isclass(view): + view = self.map_class(view) + else: + view = self.map_nonclass(view) + return view + + def map_class(self, view): + ronly = requestonly(view, self.attr) + if ronly: + mapped_view = self.map_class_requestonly(view) + else: + mapped_view = self.map_class_native(view) + mapped_view.__text__ = 'method %s of %s' % ( + self.attr or '__call__', object_description(view)) + return mapped_view + + def map_nonclass(self, view): + # We do more work here than appears necessary to avoid wrapping the + # view unless it actually requires wrapping (to avoid function call + # overhead). + mapped_view = view + ronly = requestonly(view, self.attr) + if ronly: + mapped_view = self.map_nonclass_requestonly(view) + elif self.attr: + mapped_view = self.map_nonclass_attr(view) + if inspect.isroutine(mapped_view): + # This branch will be true if the view is a function or a method. + # We potentially mutate an unwrapped object here if it's a + # function. We do this to avoid function call overhead of + # injecting another wrapper. However, we must wrap if the + # function is a bound method because we can't set attributes on a + # bound method. + if is_bound_method(view): + _mapped_view = mapped_view + def mapped_view(context, request): + return _mapped_view(context, request) + if self.attr is not None: + mapped_view.__text__ = 'attr %s of %s' % ( + self.attr, object_description(view)) + else: + mapped_view.__text__ = object_description(view) + return mapped_view + + def map_class_requestonly(self, view): + # its a class that has an __init__ which only accepts request + attr = self.attr + def _class_requestonly_view(context, request): + inst = view(request) + request.__view__ = inst + if attr is None: + response = inst() + else: + response = getattr(inst, attr)() + return response + return _class_requestonly_view + + def map_class_native(self, view): + # its a class that has an __init__ which accepts both context and + # request + attr = self.attr + def _class_view(context, request): + inst = view(context, request) + request.__view__ = inst + if attr is None: + response = inst() + else: + response = getattr(inst, attr)() + return response + return _class_view + + def map_nonclass_requestonly(self, view): + # its a function that has a __call__ which accepts only a single + # request argument + attr = self.attr + def _requestonly_view(context, request): + if attr is None: + response = view(request) + else: + response = getattr(view, attr)(request) + return response + return _requestonly_view + + def map_nonclass_attr(self, view): + # its a function that has a __call__ which accepts both context and + # request, but still has an attr + def _attr_view(context, request): + response = getattr(view, self.attr)(context, request) + return response + return _attr_view + + +def wraps_view(wrapper): + def inner(view, default, **kw): + wrapper_view = wrapper(view, default, **kw) + return preserve_view_attrs(view, wrapper_view) + return inner + +def preserve_view_attrs(view, wrapper): + if view is None: + return wrapper + + if wrapper is view: + return view + + original_view = getattr(view, '__original_view__', None) + + if original_view is None: + original_view = view + + wrapper.__wraps__ = view + wrapper.__original_view__ = original_view + wrapper.__module__ = view.__module__ + wrapper.__doc__ = view.__doc__ + + try: + wrapper.__name__ = view.__name__ + except AttributeError: + wrapper.__name__ = repr(view) + + # attrs that may not exist on "view", but, if so, must be attached to + # "wrapped view" + for attr in ('__permitted__', '__call_permissive__', '__permission__', + '__predicated__', '__predicates__', '__accept__', + '__order__', '__text__'): + try: + setattr(wrapper, attr, getattr(view, attr)) + except AttributeError: + pass + + return wrapper + +@wraps_view +def mapped_view(view, default, **kw): + mapper = kw.get('mapper') + if mapper is None: + mapper = getattr(view, '__view_mapper__', None) + if mapper is None: + mapper = kw['registry'].queryUtility(IViewMapperFactory) + if mapper is None: + mapper = DefaultViewMapper + + mapped_view = mapper(**kw)(view) + return mapped_view + +@wraps_view +def owrapped_view(view, default, **kw): + wrapper_viewname = kw.get('wrapper_viewname') + viewname = kw.get('viewname') + if not wrapper_viewname: + return view + def _owrapped_view(context, request): + response = view(context, request) + request.wrapped_response = response + request.wrapped_body = response.body + request.wrapped_view = view + wrapped_response = render_view_to_response(context, request, + wrapper_viewname) + if wrapped_response is None: + raise ValueError( + 'No wrapper view named %r found when executing view ' + 'named %r' % (wrapper_viewname, viewname)) + return wrapped_response + return _owrapped_view + +@wraps_view +def http_cached_view(view, default, **kw): + if kw['registry'].settings.get('prevent_http_cache', False): + return view + + seconds = kw.get('http_cache') + + if seconds is None: + return view + + options = {} + + if isinstance(seconds, (tuple, list)): + try: + seconds, options = seconds + except ValueError: + raise ConfigurationError( + 'If http_cache parameter is a tuple or list, it must be ' + 'in the form (seconds, options); not %s' % (seconds,)) + + def wrapper(context, request): + response = view(context, request) + prevent_caching = getattr(response.cache_control, 'prevent_auto', + False) + if not prevent_caching: + response.cache_expires(seconds, **options) + return response + + return wrapper + +@wraps_view +def secured_view(view, default, **kw): + permission = kw.get('permission') + if permission == NO_PERMISSION_REQUIRED: + # allow views registered within configurations that have a + # default permission to explicitly override the default + # permission, replacing it with no permission at all + permission = None + + wrapped_view = view + authn_policy = kw['registry'].queryUtility(IAuthenticationPolicy) + authz_policy = kw['registry'].queryUtility(IAuthorizationPolicy) + + if authn_policy and authz_policy and (permission is not None): + def _permitted(context, request): + principals = authn_policy.effective_principals(request) + return authz_policy.permits(context, principals, + permission) + def _secured_view(context, request): + result = _permitted(context, request) + if result: + return view(context, request) + view_name = getattr(view, '__name__', view) + msg = getattr( + request, 'authdebug_message', + 'Unauthorized: %s failed permission check' % view_name) + raise HTTPForbidden(msg, result=result) + _secured_view.__call_permissive__ = view + _secured_view.__permitted__ = _permitted + _secured_view.__permission__ = permission + wrapped_view = _secured_view + + return wrapped_view + +@wraps_view +def authdebug_view(view, default, **kw): + 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) + if settings and settings.get('debug_authorization', False): + def _authdebug_view(context, request): + view_name = getattr(request, 'view_name', None) + + if authn_policy and authz_policy: + if permission is NO_PERMISSION_REQUIRED: + msg = 'Allowed (NO_PERMISSION_REQUIRED)' + elif permission is None: + msg = 'Allowed (no permission registered)' + else: + principals = authn_policy.effective_principals( + request) + msg = str(authz_policy.permits(context, principals, + permission)) + else: + msg = 'Allowed (no authorization policy in use)' + + view_name = getattr(request, 'view_name', None) + url = getattr(request, 'url', None) + msg = ('debug_authorization of url %s (view name %r against ' + 'context %r): %s' % (url, view_name, context, msg)) + if logger: logger.debug(msg) + if request is not None: + request.authdebug_message = msg + return view(context, request) + + wrapped_view = _authdebug_view + + return wrapped_view + +@wraps_view +def predicated_view(view, default, **kw): + preds = kw.get('predicates', ()) + if not preds: + return view + def predicate_wrapper(context, request): + for predicate in preds: + if not predicate(context, request): + view_name = getattr(view, '__name__', view) + raise PredicateMismatch( + 'predicate mismatch for view %s (%s)' % ( + view_name, predicate.text())) + return view(context, request) + def checker(context, request): + return all((predicate(context, request) for predicate in + preds)) + predicate_wrapper.__predicated__ = checker + predicate_wrapper.__predicates__ = preds + return predicate_wrapper + +@wraps_view +def attr_wrapped_view(view, default, **kw): + kw = kw + accept, order, phash = (kw.get('accept', None), + kw.get('order', MAX_ORDER), + kw.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 + if ( + (accept is None) and + (order == MAX_ORDER) and + (phash == DEFAULT_PHASH) + ): + return view # defaults + def attr_view(context, request): + return view(context, request) + 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') + return attr_view + +@wraps_view +def rendered_view(view, default, **kw): + # one way or another this wrapper must produce a Response (unless + # the renderer is a NullRendererHelper) + renderer = kw.get('renderer') + registry = kw['registry'] + if renderer is None: + # register a default renderer if you want super-dynamic + # rendering. registering a default renderer will also allow + # override_renderer to work if a renderer is left unspecified for + # a view registration. + def viewresult_to_response(context, request): + result = view(context, request) + if result.__class__ is Response: # common case + response = result + else: + response = registry.queryAdapterOrSelf(result, IResponse) + if response is None: + if result is None: + append = (' You may have forgotten to return a value ' + 'from the view callable.') + elif isinstance(result, dict): + append = (' You may have forgotten to define a ' + 'renderer in the view configuration.') + else: + append = '' + + msg = ('Could not convert return value of the view ' + 'callable %s into a response object. ' + 'The value returned was %r.' + append) + + raise ValueError(msg % (view_description(view), result)) + + return response + + return viewresult_to_response + + if renderer is renderers.null_renderer: + return view + + def rendered_view(context, request): + result = view(context, request) + if result.__class__ is Response: # potential common case + response = result + else: + # this must adapt, it can't do a simple interface check + # (avoid trying to render webob responses) + response = registry.queryAdapterOrSelf(result, IResponse) + if response is None: + attrs = getattr(request, '__dict__', {}) + if 'override_renderer' in attrs: + # renderer overridden by newrequest event or other + renderer_name = attrs.pop('override_renderer') + view_renderer = renderers.RendererHelper( + name=renderer_name, + package=kw.get('package'), + registry = registry) + else: + view_renderer = renderer.clone() + if '__view__' in attrs: + view_inst = attrs.pop('__view__') + else: + view_inst = getattr(view, '__original_view__', view) + response = view_renderer.render_view(request, result, view_inst, + context) + return response + + return rendered_view + +@wraps_view +def decorated_view(view, default, **kw): + decorator = kw.get('decorator') + if decorator is None: + return view + return decorator(view) diff --git a/pyramid/config/views.py b/pyramid/config/views.py index e2da950be..ac3803712 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -75,6 +75,7 @@ from pyramid.view import ( AppendSlashNotFoundViewFactory, ) +import pyramid.util from pyramid.util import ( object_description, viewdefaults, @@ -82,6 +83,15 @@ from pyramid.util import ( ) import pyramid.config.predicates +import pyramid.config.derivations + +# bw compat +from pyramid.config.derivations import ( + preserve_view_attrs, + view_description, + requestonly, + DefaultViewMapper, +) from pyramid.config.util import ( DEFAULT_PHASH, @@ -92,434 +102,6 @@ from pyramid.config.util import ( urljoin = urlparse.urljoin url_parse = urlparse.urlparse -def view_description(view): - try: - return view.__text__ - except AttributeError: - # custom view mappers might not add __text__ - return object_description(view) - -def wraps_view(wrapper): - def inner(self, view): - wrapper_view = wrapper(self, view) - return preserve_view_attrs(view, wrapper_view) - return inner - -def preserve_view_attrs(view, wrapper): - if view is None: - return wrapper - - if wrapper is view: - return view - - original_view = getattr(view, '__original_view__', None) - - if original_view is None: - original_view = view - - wrapper.__wraps__ = view - wrapper.__original_view__ = original_view - wrapper.__module__ = view.__module__ - wrapper.__doc__ = view.__doc__ - - try: - wrapper.__name__ = view.__name__ - except AttributeError: - wrapper.__name__ = repr(view) - - # attrs that may not exist on "view", but, if so, must be attached to - # "wrapped view" - for attr in ('__permitted__', '__call_permissive__', '__permission__', - '__predicated__', '__predicates__', '__accept__', - '__order__', '__text__'): - try: - setattr(wrapper, attr, getattr(view, attr)) - except AttributeError: - pass - - return wrapper - -class ViewDeriver(object): - def __init__(self, **kw): - self.kw = kw - self.registry = kw['registry'] - self.authn_policy = self.registry.queryUtility(IAuthenticationPolicy) - self.authz_policy = self.registry.queryUtility(IAuthorizationPolicy) - self.logger = self.registry.queryUtility(IDebugLogger) - - def __call__(self, view): - return self.attr_wrapped_view( - self.predicated_view( - self.authdebug_view( - self.secured_view( - self.owrapped_view( - self.http_cached_view( - self.decorated_view( - self.rendered_view( - self.mapped_view( - view))))))))) - - @wraps_view - def mapped_view(self, view): - mapper = self.kw.get('mapper') - if mapper is None: - mapper = getattr(view, '__view_mapper__', None) - if mapper is None: - mapper = self.registry.queryUtility(IViewMapperFactory) - if mapper is None: - mapper = DefaultViewMapper - - mapped_view = mapper(**self.kw)(view) - return mapped_view - - @wraps_view - def owrapped_view(self, view): - wrapper_viewname = self.kw.get('wrapper_viewname') - viewname = self.kw.get('viewname') - if not wrapper_viewname: - return view - def _owrapped_view(context, request): - response = view(context, request) - request.wrapped_response = response - request.wrapped_body = response.body - request.wrapped_view = view - wrapped_response = render_view_to_response(context, request, - wrapper_viewname) - if wrapped_response is None: - raise ValueError( - 'No wrapper view named %r found when executing view ' - 'named %r' % (wrapper_viewname, viewname)) - return wrapped_response - return _owrapped_view - - @wraps_view - def http_cached_view(self, view): - if self.registry.settings.get('prevent_http_cache', False): - return view - - seconds = self.kw.get('http_cache') - - if seconds is None: - return view - - options = {} - - if isinstance(seconds, (tuple, list)): - try: - seconds, options = seconds - except ValueError: - raise ConfigurationError( - 'If http_cache parameter is a tuple or list, it must be ' - 'in the form (seconds, options); not %s' % (seconds,)) - - def wrapper(context, request): - response = view(context, request) - prevent_caching = getattr(response.cache_control, 'prevent_auto', - False) - if not prevent_caching: - response.cache_expires(seconds, **options) - return response - - return wrapper - - @wraps_view - def secured_view(self, view): - permission = self.kw.get('permission') - if permission == NO_PERMISSION_REQUIRED: - # allow views registered within configurations that have a - # default permission to explicitly override the default - # permission, replacing it with no permission at all - permission = None - - wrapped_view = view - if self.authn_policy and self.authz_policy and (permission is not None): - def _permitted(context, request): - principals = self.authn_policy.effective_principals(request) - return self.authz_policy.permits(context, principals, - permission) - def _secured_view(context, request): - result = _permitted(context, request) - if result: - return view(context, request) - view_name = getattr(view, '__name__', view) - msg = getattr( - request, 'authdebug_message', - 'Unauthorized: %s failed permission check' % view_name) - raise HTTPForbidden(msg, result=result) - _secured_view.__call_permissive__ = view - _secured_view.__permitted__ = _permitted - _secured_view.__permission__ = permission - wrapped_view = _secured_view - - return wrapped_view - - @wraps_view - def authdebug_view(self, view): - wrapped_view = view - settings = self.registry.settings - permission = self.kw.get('permission') - if settings and settings.get('debug_authorization', False): - def _authdebug_view(context, request): - view_name = getattr(request, 'view_name', None) - - if self.authn_policy and self.authz_policy: - if permission is NO_PERMISSION_REQUIRED: - msg = 'Allowed (NO_PERMISSION_REQUIRED)' - elif permission is None: - msg = 'Allowed (no permission registered)' - else: - principals = self.authn_policy.effective_principals( - request) - msg = str(self.authz_policy.permits(context, principals, - permission)) - else: - msg = 'Allowed (no authorization policy in use)' - - view_name = getattr(request, 'view_name', None) - url = getattr(request, 'url', None) - msg = ('debug_authorization of url %s (view name %r against ' - 'context %r): %s' % (url, view_name, context, msg)) - self.logger and self.logger.debug(msg) - if request is not None: - request.authdebug_message = msg - return view(context, request) - - wrapped_view = _authdebug_view - - return wrapped_view - - @wraps_view - def predicated_view(self, view): - preds = self.kw.get('predicates', ()) - if not preds: - return view - def predicate_wrapper(context, request): - for predicate in preds: - if not predicate(context, request): - view_name = getattr(view, '__name__', view) - raise PredicateMismatch( - 'predicate mismatch for view %s (%s)' % ( - view_name, predicate.text())) - return view(context, request) - def checker(context, request): - return all((predicate(context, request) for predicate in - preds)) - predicate_wrapper.__predicated__ = checker - predicate_wrapper.__predicates__ = preds - return predicate_wrapper - - @wraps_view - def attr_wrapped_view(self, view): - kw = self.kw - accept, order, phash = (kw.get('accept', None), - kw.get('order', MAX_ORDER), - kw.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 - if ( - (accept is None) and - (order == MAX_ORDER) and - (phash == DEFAULT_PHASH) - ): - return view # defaults - def attr_view(context, request): - return view(context, request) - attr_view.__accept__ = accept - attr_view.__order__ = order - attr_view.__phash__ = phash - attr_view.__view_attr__ = self.kw.get('attr') - attr_view.__permission__ = self.kw.get('permission') - return attr_view - - @wraps_view - def rendered_view(self, view): - # one way or another this wrapper must produce a Response (unless - # the renderer is a NullRendererHelper) - renderer = self.kw.get('renderer') - if renderer is None: - # register a default renderer if you want super-dynamic - # rendering. registering a default renderer will also allow - # override_renderer to work if a renderer is left unspecified for - # a view registration. - return self._response_resolved_view(view) - if renderer is renderers.null_renderer: - return view - return self._rendered_view(view, renderer) - - def _rendered_view(self, view, view_renderer): - def rendered_view(context, request): - result = view(context, request) - if result.__class__ is Response: # potential common case - response = result - else: - registry = self.registry - # this must adapt, it can't do a simple interface check - # (avoid trying to render webob responses) - response = registry.queryAdapterOrSelf(result, IResponse) - if response is None: - attrs = getattr(request, '__dict__', {}) - if 'override_renderer' in attrs: - # renderer overridden by newrequest event or other - renderer_name = attrs.pop('override_renderer') - renderer = renderers.RendererHelper( - name=renderer_name, - package=self.kw.get('package'), - registry = registry) - else: - renderer = view_renderer.clone() - if '__view__' in attrs: - view_inst = attrs.pop('__view__') - else: - view_inst = getattr(view, '__original_view__', view) - response = renderer.render_view(request, result, view_inst, - context) - return response - - return rendered_view - - def _response_resolved_view(self, view): - registry = self.registry - def viewresult_to_response(context, request): - result = view(context, request) - if result.__class__ is Response: # common case - response = result - else: - response = registry.queryAdapterOrSelf(result, IResponse) - if response is None: - if result is None: - append = (' You may have forgotten to return a value ' - 'from the view callable.') - elif isinstance(result, dict): - append = (' You may have forgotten to define a ' - 'renderer in the view configuration.') - else: - append = '' - - msg = ('Could not convert return value of the view ' - 'callable %s into a response object. ' - 'The value returned was %r.' + append) - - raise ValueError(msg % (view_description(view), result)) - - return response - - return viewresult_to_response - - @wraps_view - def decorated_view(self, view): - decorator = self.kw.get('decorator') - if decorator is None: - return view - return decorator(view) - -@implementer(IViewMapper) -@provider(IViewMapperFactory) -class DefaultViewMapper(object): - def __init__(self, **kw): - self.attr = kw.get('attr') - - def __call__(self, view): - if is_unbound_method(view) and self.attr is None: - raise ConfigurationError(( - 'Unbound method calls are not supported, please set the class ' - 'as your `view` and the method as your `attr`' - )) - - if inspect.isclass(view): - view = self.map_class(view) - else: - view = self.map_nonclass(view) - return view - - def map_class(self, view): - ronly = requestonly(view, self.attr) - if ronly: - mapped_view = self.map_class_requestonly(view) - else: - mapped_view = self.map_class_native(view) - mapped_view.__text__ = 'method %s of %s' % ( - self.attr or '__call__', object_description(view)) - return mapped_view - - def map_nonclass(self, view): - # We do more work here than appears necessary to avoid wrapping the - # view unless it actually requires wrapping (to avoid function call - # overhead). - mapped_view = view - ronly = requestonly(view, self.attr) - if ronly: - mapped_view = self.map_nonclass_requestonly(view) - elif self.attr: - mapped_view = self.map_nonclass_attr(view) - if inspect.isroutine(mapped_view): - # This branch will be true if the view is a function or a method. - # We potentially mutate an unwrapped object here if it's a - # function. We do this to avoid function call overhead of - # injecting another wrapper. However, we must wrap if the - # function is a bound method because we can't set attributes on a - # bound method. - if is_bound_method(view): - _mapped_view = mapped_view - def mapped_view(context, request): - return _mapped_view(context, request) - if self.attr is not None: - mapped_view.__text__ = 'attr %s of %s' % ( - self.attr, object_description(view)) - else: - mapped_view.__text__ = object_description(view) - return mapped_view - - def map_class_requestonly(self, view): - # its a class that has an __init__ which only accepts request - attr = self.attr - def _class_requestonly_view(context, request): - inst = view(request) - request.__view__ = inst - if attr is None: - response = inst() - else: - response = getattr(inst, attr)() - return response - return _class_requestonly_view - - def map_class_native(self, view): - # its a class that has an __init__ which accepts both context and - # request - attr = self.attr - def _class_view(context, request): - inst = view(context, request) - request.__view__ = inst - if attr is None: - response = inst() - else: - response = getattr(inst, attr)() - return response - return _class_view - - def map_nonclass_requestonly(self, view): - # its a function that has a __call__ which accepts only a single - # request argument - attr = self.attr - def _requestonly_view(context, request): - if attr is None: - response = view(request) - else: - response = getattr(view, attr)(request) - return response - return _requestonly_view - - def map_nonclass_attr(self, view): - # its a function that has a __call__ which accepts both context and - # request, but still has an attr - def _attr_view(context, request): - response = getattr(view, self.attr)(context, request) - return response - return _attr_view - -def requestonly(view, attr=None): - return takes_one_arg(view, attr=attr, argname='request') - @implementer(IMultiView) class MultiView(object): @@ -1226,7 +808,7 @@ class ViewsConfiguratorMixin(object): phash = view_intr['phash'] # __no_permission_required__ handled by _secure_view - deriver = ViewDeriver( + derived_view = self._apply_view_derivations(view, registry=self.registry, permission=permission, predicates=preds, @@ -1242,7 +824,6 @@ class ViewsConfiguratorMixin(object): decorator=decorator, http_cache=http_cache, ) - derived_view = deriver(view) derived_view.__discriminator__ = lambda *arg: discriminator # __discriminator__ is used by superdynamic systems # that require it for introspection after manual view lookup; @@ -1401,6 +982,17 @@ class ViewsConfiguratorMixin(object): introspectables.append(perm_intr) self.action(discriminator, register, introspectables=introspectables) + def _apply_view_derivations(self, view, **kw): + d = pyramid.config.derivations + # These inner derivations have fixed order + inner_derivers = [('mapped_view', (d.mapped_view, None)), + ('rendered_view', (d.rendered_view, None))] + + for name, val in inner_derivers + self.derivationlist.sorted(): + derivation, default = val + view = derivation(view, default, **kw) + return view + @action_method def add_view_predicate(self, name, factory, weighs_more_than=None, weighs_less_than=None): @@ -1448,6 +1040,43 @@ class ViewsConfiguratorMixin(object): ): self.add_view_predicate(name, factory) + @action_method + def add_view_derivation(self, name, factory, default, + weighs_more_than=None, + weighs_less_than=None): + factory = self.maybe_dotted(factory) + discriminator = ('view option', name) + intr = self.introspectable( + '%s derivation' % type, + discriminator, + '%s derivation named %s' % (type, name), + '%s derivation' % type) + intr['name'] = name + intr['factory'] = factory + intr['weighs_more_than'] = weighs_more_than + intr['weighs_less_than'] = weighs_less_than + def register(): + self.derivationlist.add(name, (factory, default), + after=weighs_more_than, + before=weighs_less_than) + self.action(discriminator, register, introspectables=(intr,), + order=PHASE1_CONFIG) # must be registered early + + def add_default_view_derivations(self): + d = pyramid.config.derivations + derivers = [ + ('decorated_view', d.decorated_view), + ('http_cached_view', d.http_cached_view), + ('owrapped_view', d.owrapped_view), + ('secured_view', d.secured_view), + ('authdebug_view', d.authdebug_view), + ('predicated_view', d.predicated_view), + ] + after = pyramid.util.FIRST + for name, deriver in derivers: + self.add_view_derivation(name, deriver, default=None, weighs_more_than=after) + after = name + def derive_view(self, view, attr=None, renderer=None): """ Create a :term:`view callable` using the function, instance, @@ -1546,22 +1175,21 @@ class ViewsConfiguratorMixin(object): package=self.package, registry=self.registry) - deriver = ViewDeriver(registry=self.registry, - permission=permission, - predicates=predicates, - attr=attr, - renderer=renderer, - wrapper_viewname=wrapper_viewname, - viewname=viewname, - accept=accept, - order=order, - phash=phash, - package=self.package, - mapper=mapper, - decorator=decorator, - http_cache=http_cache) - - return deriver(view) + return self._apply_view_derivations(view, + registry=self.registry, + permission=permission, + predicates=predicates, + attr=attr, + renderer=renderer, + wrapper_viewname=wrapper_viewname, + viewname=viewname, + accept=accept, + order=order, + phash=phash, + package=self.package, + mapper=mapper, + decorator=decorator, + http_cache=http_cache) @viewdefaults @action_method |
