summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCasey Duncan <casey.duncan@gmail.com>2015-04-15 10:06:10 -0400
committerCasey Duncan <casey.duncan@gmail.com>2015-04-15 10:06:10 -0400
commitc2c5894d83f4bf43efd476336c7c065cd5984716 (patch)
tree0cf4ec21c0a53242297641d2355fb8ca0bca9c26
parentc4f2afdaf9bd53d95fbd5cdf378fc9104d34562c (diff)
downloadpyramid-c2c5894d83f4bf43efd476336c7c065cd5984716.tar.gz
pyramid-c2c5894d83f4bf43efd476336c7c065cd5984716.tar.bz2
pyramid-c2c5894d83f4bf43efd476336c7c065cd5984716.zip
wip derivations
-rw-r--r--pyramid/config/__init__.py6
-rw-r--r--pyramid/config/derivations.py448
-rw-r--r--pyramid/config/views.py520
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