Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/pyramid/view.py : 41%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import itertools
2import sys
3import inspect
5import venusian
7from zope.interface import providedBy
9from pyramid.interfaces import (
10 IRoutesMapper,
11 IMultiView,
12 ISecuredView,
13 IView,
14 IViewClassifier,
15 IRequest,
16 IExceptionViewClassifier,
17)
19from pyramid.compat import decode_path_info
20from pyramid.compat import reraise as reraise_
22from pyramid.exceptions import ConfigurationError, PredicateMismatch
24from pyramid.httpexceptions import (
25 HTTPNotFound,
26 HTTPTemporaryRedirect,
27 default_exceptionresponse_view,
28)
30from pyramid.threadlocal import get_current_registry, manager
32from pyramid.util import hide_attrs
34_marker = object()
37def render_view_to_response(context, request, name='', secure=True):
38 """ Call the :term:`view callable` configured with a :term:`view
39 configuration` that matches the :term:`view name` ``name``
40 registered against the specified ``context`` and ``request`` and
41 return a :term:`response` object. This function will return
42 ``None`` if a corresponding :term:`view callable` cannot be found
43 (when no :term:`view configuration` matches the combination of
44 ``name`` / ``context`` / and ``request``).
46 If `secure`` is ``True``, and the :term:`view callable` found is
47 protected by a permission, the permission will be checked before calling
48 the view function. If the permission check disallows view execution
49 (based on the current :term:`authorization policy`), a
50 :exc:`pyramid.httpexceptions.HTTPForbidden` exception will be raised.
51 The exception's ``args`` attribute explains why the view access was
52 disallowed.
54 If ``secure`` is ``False``, no permission checking is done."""
56 registry = getattr(request, 'registry', None)
57 if registry is None:
58 registry = get_current_registry()
60 context_iface = providedBy(context)
61 # We explicitly pass in the interfaces provided by the request as
62 # request_iface to _call_view; we don't want _call_view to use
63 # request.request_iface, because render_view_to_response and friends are
64 # pretty much limited to finding views that are not views associated with
65 # routes, and the only thing request.request_iface is used for is to find
66 # route-based views. The render_view_to_response API is (and always has
67 # been) a stepchild API reserved for use of those who actually use
68 # traversal. Doing this fixes an infinite recursion bug introduced in
69 # Pyramid 1.6a1, and causes the render_view* APIs to behave as they did in
70 # 1.5 and previous. We should probably provide some sort of different API
71 # that would allow people to find views for routes. See
72 # https://github.com/Pylons/pyramid/issues/1643 for more info.
73 request_iface = providedBy(request)
75 response = _call_view(
76 registry,
77 request,
78 context,
79 context_iface,
80 name,
81 secure=secure,
82 request_iface=request_iface,
83 )
85 return response # NB: might be None
88def render_view_to_iterable(context, request, name='', secure=True):
89 """ Call the :term:`view callable` configured with a :term:`view
90 configuration` that matches the :term:`view name` ``name``
91 registered against the specified ``context`` and ``request`` and
92 return an iterable object which represents the body of a response.
93 This function will return ``None`` if a corresponding :term:`view
94 callable` cannot be found (when no :term:`view configuration`
95 matches the combination of ``name`` / ``context`` / and
96 ``request``). Additionally, this function will raise a
97 :exc:`ValueError` if a view function is found and called but the
98 view function's result does not have an ``app_iter`` attribute.
100 You can usually get the bytestring representation of the return value of
101 this function by calling ``b''.join(iterable)``, or just use
102 :func:`pyramid.view.render_view` instead.
104 If ``secure`` is ``True``, and the view is protected by a permission, the
105 permission will be checked before the view function is invoked. If the
106 permission check disallows view execution (based on the current
107 :term:`authentication policy`), a
108 :exc:`pyramid.httpexceptions.HTTPForbidden` exception will be raised; its
109 ``args`` attribute explains why the view access was disallowed.
111 If ``secure`` is ``False``, no permission checking is
112 done."""
113 response = render_view_to_response(context, request, name, secure)
114 if response is None:
115 return None
116 return response.app_iter
119def render_view(context, request, name='', secure=True):
120 """ Call the :term:`view callable` configured with a :term:`view
121 configuration` that matches the :term:`view name` ``name``
122 registered against the specified ``context`` and ``request``
123 and unwind the view response's ``app_iter`` (see
124 :ref:`the_response`) into a single bytestring. This function will
125 return ``None`` if a corresponding :term:`view callable` cannot be
126 found (when no :term:`view configuration` matches the combination
127 of ``name`` / ``context`` / and ``request``). Additionally, this
128 function will raise a :exc:`ValueError` if a view function is
129 found and called but the view function's result does not have an
130 ``app_iter`` attribute. This function will return ``None`` if a
131 corresponding view cannot be found.
133 If ``secure`` is ``True``, and the view is protected by a permission, the
134 permission will be checked before the view is invoked. If the permission
135 check disallows view execution (based on the current :term:`authorization
136 policy`), a :exc:`pyramid.httpexceptions.HTTPForbidden` exception will be
137 raised; its ``args`` attribute explains why the view access was
138 disallowed.
140 If ``secure`` is ``False``, no permission checking is done."""
141 iterable = render_view_to_iterable(context, request, name, secure)
142 if iterable is None:
143 return None
144 return b''.join(iterable)
147class view_config(object):
148 """ A function, class or method :term:`decorator` which allows a
149 developer to create view registrations nearer to a :term:`view
150 callable` definition than use :term:`imperative
151 configuration` to do the same.
153 For example, this code in a module ``views.py``::
155 from resources import MyResource
157 @view_config(name='my_view', context=MyResource, permission='read',
158 route_name='site1')
159 def my_view(context, request):
160 return 'OK'
162 Might replace the following call to the
163 :meth:`pyramid.config.Configurator.add_view` method::
165 import views
166 from resources import MyResource
167 config.add_view(views.my_view, context=MyResource, name='my_view',
168 permission='read', route_name='site1')
170 .. note: :class:`pyramid.view.view_config` is also importable, for
171 backwards compatibility purposes, as the name
172 :class:`pyramid.view.bfg_view`.
174 :class:`pyramid.view.view_config` supports the following keyword
175 arguments: ``context``, ``exception``, ``permission``, ``name``,
176 ``request_type``, ``route_name``, ``request_method``, ``request_param``,
177 ``containment``, ``xhr``, ``accept``, ``header``, ``path_info``,
178 ``custom_predicates``, ``decorator``, ``mapper``, ``http_cache``,
179 ``require_csrf``, ``match_param``, ``check_csrf``, ``physical_path``, and
180 ``view_options``.
182 The meanings of these arguments are the same as the arguments passed to
183 :meth:`pyramid.config.Configurator.add_view`. If any argument is left
184 out, its default will be the equivalent ``add_view`` default.
186 Two additional keyword arguments which will be passed to the
187 :term:`venusian` ``attach`` function are ``_depth`` and ``_category``.
189 ``_depth`` is provided for people who wish to reuse this class from another
190 decorator. The default value is ``0`` and should be specified relative to
191 the ``view_config`` invocation. It will be passed in to the
192 :term:`venusian` ``attach`` function as the depth of the callstack when
193 Venusian checks if the decorator is being used in a class or module
194 context. It's not often used, but it can be useful in this circumstance.
196 ``_category`` sets the decorator category name. It can be useful in
197 combination with the ``category`` argument of ``scan`` to control which
198 views should be processed.
200 See the :py:func:`venusian.attach` function in Venusian for more
201 information about the ``_depth`` and ``_category`` arguments.
203 .. seealso::
205 See also :ref:`mapping_views_using_a_decorator_section` for
206 details about using :class:`pyramid.view.view_config`.
208 .. warning::
210 ``view_config`` will work ONLY on module top level members
211 because of the limitation of ``venusian.Scanner.scan``.
213 """
215 venusian = venusian # for testing injection
217 def __init__(self, **settings):
218 if 'for_' in settings:
219 if settings.get('context') is None:
220 settings['context'] = settings['for_']
221 self.__dict__.update(settings)
222 self._get_info()
224 def _get_info(self):
225 depth = self.__dict__.get('_depth', 0)
226 frame = sys._getframe(depth + 2)
227 frameinfo = inspect.getframeinfo(frame)
228 sourceline = frameinfo[3][0].strip()
229 self._info = frameinfo[0], frameinfo[1], frameinfo[2], sourceline
231 def __call__(self, wrapped):
232 settings = self.__dict__.copy()
233 depth = settings.pop('_depth', 0)
234 category = settings.pop('_category', 'pyramid')
236 def callback(context, name, ob):
237 config = context.config.with_package(info.module)
238 config.add_view(view=ob, **settings)
240 info = self.venusian.attach(
241 wrapped, callback, category=category, depth=depth + 1
242 )
244 if info.scope == 'class':
245 # if the decorator was attached to a method in a class, or
246 # otherwise executed at class scope, we need to set an
247 # 'attr' into the settings if one isn't already in there
248 if settings.get('attr') is None:
249 settings['attr'] = wrapped.__name__
251 return wrapped
254bfg_view = view_config # bw compat (forever)
257def view_defaults(**settings):
258 """ A class :term:`decorator` which, when applied to a class, will
259 provide defaults for all view configurations that use the class. This
260 decorator accepts all the arguments accepted by
261 :meth:`pyramid.view.view_config`, and each has the same meaning.
263 See :ref:`view_defaults` for more information.
264 """
266 def wrap(wrapped):
267 wrapped.__view_defaults__ = settings
268 return wrapped
270 return wrap
273class AppendSlashNotFoundViewFactory(object):
274 """ There can only be one :term:`Not Found view` in any
275 :app:`Pyramid` application. Even if you use
276 :func:`pyramid.view.append_slash_notfound_view` as the Not
277 Found view, :app:`Pyramid` still must generate a ``404 Not
278 Found`` response when it cannot redirect to a slash-appended URL;
279 this not found response will be visible to site users.
281 If you don't care what this 404 response looks like, and you only
282 need redirections to slash-appended route URLs, you may use the
283 :func:`pyramid.view.append_slash_notfound_view` object as the
284 Not Found view. However, if you wish to use a *custom* notfound
285 view callable when a URL cannot be redirected to a slash-appended
286 URL, you may wish to use an instance of this class as the Not
287 Found view, supplying a :term:`view callable` to be used as the
288 custom notfound view as the first argument to its constructor.
289 For instance:
291 .. code-block:: python
293 from pyramid.httpexceptions import HTTPNotFound
294 from pyramid.view import AppendSlashNotFoundViewFactory
296 def notfound_view(context, request): return HTTPNotFound('nope')
298 custom_append_slash = AppendSlashNotFoundViewFactory(notfound_view)
299 config.add_view(custom_append_slash, context=HTTPNotFound)
301 The ``notfound_view`` supplied must adhere to the two-argument
302 view callable calling convention of ``(context, request)``
303 (``context`` will be the exception object).
305 .. deprecated:: 1.3
307 """
309 def __init__(
310 self, notfound_view=None, redirect_class=HTTPTemporaryRedirect
311 ):
312 if notfound_view is None:
313 notfound_view = default_exceptionresponse_view
314 self.notfound_view = notfound_view
315 self.redirect_class = redirect_class
317 def __call__(self, context, request):
318 path = decode_path_info(request.environ['PATH_INFO'] or '/')
319 registry = request.registry
320 mapper = registry.queryUtility(IRoutesMapper)
321 if mapper is not None and not path.endswith('/'):
322 slashpath = path + '/'
323 for route in mapper.get_routes():
324 if route.match(slashpath) is not None:
325 qs = request.query_string
326 if qs:
327 qs = '?' + qs
328 return self.redirect_class(
329 location=request.path + '/' + qs
330 )
331 return self.notfound_view(context, request)
334append_slash_notfound_view = AppendSlashNotFoundViewFactory()
335append_slash_notfound_view.__doc__ = """\
336For behavior like Django's ``APPEND_SLASH=True``, use this view as the
337:term:`Not Found view` in your application.
339When this view is the Not Found view (indicating that no view was found), and
340any routes have been defined in the configuration of your application, if the
341value of the ``PATH_INFO`` WSGI environment variable does not already end in
342a slash, and if the value of ``PATH_INFO`` *plus* a slash matches any route's
343path, do an HTTP redirect to the slash-appended PATH_INFO. Note that this
344will *lose* ``POST`` data information (turning it into a GET), so you
345shouldn't rely on this to redirect POST requests. Note also that static
346routes are not considered when attempting to find a matching route.
348Use the :meth:`pyramid.config.Configurator.add_view` method to configure this
349view as the Not Found view::
351 from pyramid.httpexceptions import HTTPNotFound
352 from pyramid.view import append_slash_notfound_view
353 config.add_view(append_slash_notfound_view, context=HTTPNotFound)
355.. deprecated:: 1.3
357"""
360class notfound_view_config(object):
361 """
362 .. versionadded:: 1.3
364 An analogue of :class:`pyramid.view.view_config` which registers a
365 :term:`Not Found View` using
366 :meth:`pyramid.config.Configurator.add_notfound_view`.
368 The ``notfound_view_config`` constructor accepts most of the same arguments
369 as the constructor of :class:`pyramid.view.view_config`. It can be used
370 in the same places, and behaves in largely the same way, except it always
371 registers a not found exception view instead of a 'normal' view.
373 Example:
375 .. code-block:: python
377 from pyramid.view import notfound_view_config
378 from pyramid.response import Response
380 @notfound_view_config()
381 def notfound(request):
382 return Response('Not found!', status='404 Not Found')
384 All arguments except ``append_slash`` have the same meaning as
385 :meth:`pyramid.view.view_config` and each predicate
386 argument restricts the set of circumstances under which this notfound
387 view will be invoked.
389 If ``append_slash`` is ``True``, when the Not Found View is invoked, and
390 the current path info does not end in a slash, the notfound logic will
391 attempt to find a :term:`route` that matches the request's path info
392 suffixed with a slash. If such a route exists, Pyramid will issue a
393 redirect to the URL implied by the route; if it does not, Pyramid will
394 return the result of the view callable provided as ``view``, as normal.
396 If the argument provided as ``append_slash`` is not a boolean but
397 instead implements :class:`~pyramid.interfaces.IResponse`, the
398 append_slash logic will behave as if ``append_slash=True`` was passed,
399 but the provided class will be used as the response class instead of
400 the default :class:`~pyramid.httpexceptions.HTTPTemporaryRedirect`
401 response class when a redirect is performed. For example:
403 .. code-block:: python
405 from pyramid.httpexceptions import (
406 HTTPMovedPermanently,
407 HTTPNotFound
408 )
410 @notfound_view_config(append_slash=HTTPMovedPermanently)
411 def aview(request):
412 return HTTPNotFound('not found')
414 The above means that a redirect to a slash-appended route will be
415 attempted, but instead of
416 :class:`~pyramid.httpexceptions.HTTPTemporaryRedirect`
417 being used, :class:`~pyramid.httpexceptions.HTTPMovedPermanently will
418 be used` for the redirect response if a slash-appended route is found.
420 See :ref:`changing_the_notfound_view` for detailed usage information.
422 .. versionchanged:: 1.9.1
423 Added the ``_depth`` and ``_category`` arguments.
425 """
427 venusian = venusian
429 def __init__(self, **settings):
430 self.__dict__.update(settings)
432 def __call__(self, wrapped):
433 settings = self.__dict__.copy()
434 depth = settings.pop('_depth', 0)
435 category = settings.pop('_category', 'pyramid')
437 def callback(context, name, ob):
438 config = context.config.with_package(info.module)
439 config.add_notfound_view(view=ob, **settings)
441 info = self.venusian.attach(
442 wrapped, callback, category=category, depth=depth + 1
443 )
445 if info.scope == 'class':
446 # if the decorator was attached to a method in a class, or
447 # otherwise executed at class scope, we need to set an
448 # 'attr' into the settings if one isn't already in there
449 if settings.get('attr') is None:
450 settings['attr'] = wrapped.__name__
452 settings['_info'] = info.codeinfo # fbo "action_method"
453 return wrapped
456class forbidden_view_config(object):
457 """
458 .. versionadded:: 1.3
460 An analogue of :class:`pyramid.view.view_config` which registers a
461 :term:`forbidden view` using
462 :meth:`pyramid.config.Configurator.add_forbidden_view`.
464 The forbidden_view_config constructor accepts most of the same arguments
465 as the constructor of :class:`pyramid.view.view_config`. It can be used
466 in the same places, and behaves in largely the same way, except it always
467 registers a forbidden exception view instead of a 'normal' view.
469 Example:
471 .. code-block:: python
473 from pyramid.view import forbidden_view_config
474 from pyramid.response import Response
476 @forbidden_view_config()
477 def forbidden(request):
478 return Response('You are not allowed', status='403 Forbidden')
480 All arguments passed to this function have the same meaning as
481 :meth:`pyramid.view.view_config` and each predicate argument restricts
482 the set of circumstances under which this notfound view will be invoked.
484 See :ref:`changing_the_forbidden_view` for detailed usage information.
486 .. versionchanged:: 1.9.1
487 Added the ``_depth`` and ``_category`` arguments.
489 """
491 venusian = venusian
493 def __init__(self, **settings):
494 self.__dict__.update(settings)
496 def __call__(self, wrapped):
497 settings = self.__dict__.copy()
498 depth = settings.pop('_depth', 0)
499 category = settings.pop('_category', 'pyramid')
501 def callback(context, name, ob):
502 config = context.config.with_package(info.module)
503 config.add_forbidden_view(view=ob, **settings)
505 info = self.venusian.attach(
506 wrapped, callback, category=category, depth=depth + 1
507 )
509 if info.scope == 'class':
510 # if the decorator was attached to a method in a class, or
511 # otherwise executed at class scope, we need to set an
512 # 'attr' into the settings if one isn't already in there
513 if settings.get('attr') is None:
514 settings['attr'] = wrapped.__name__
516 settings['_info'] = info.codeinfo # fbo "action_method"
517 return wrapped
520class exception_view_config(object):
521 """
522 .. versionadded:: 1.8
524 An analogue of :class:`pyramid.view.view_config` which registers an
525 :term:`exception view` using
526 :meth:`pyramid.config.Configurator.add_exception_view`.
528 The ``exception_view_config`` constructor requires an exception context,
529 and additionally accepts most of the same arguments as the constructor of
530 :class:`pyramid.view.view_config`. It can be used in the same places,
531 and behaves in largely the same way, except it always registers an
532 exception view instead of a "normal" view that dispatches on the request
533 :term:`context`.
535 Example:
537 .. code-block:: python
539 from pyramid.view import exception_view_config
540 from pyramid.response import Response
542 @exception_view_config(ValueError, renderer='json')
543 def error_view(request):
544 return {'error': str(request.exception)}
546 All arguments passed to this function have the same meaning as
547 :meth:`pyramid.view.view_config`, and each predicate argument restricts
548 the set of circumstances under which this exception view will be invoked.
550 .. versionchanged:: 1.9.1
551 Added the ``_depth`` and ``_category`` arguments.
553 """
555 venusian = venusian
557 def __init__(self, *args, **settings):
558 if 'context' not in settings and len(args) > 0:
559 exception, args = args[0], args[1:]
560 settings['context'] = exception
561 if len(args) > 0:
562 raise ConfigurationError('unknown positional arguments')
563 self.__dict__.update(settings)
565 def __call__(self, wrapped):
566 settings = self.__dict__.copy()
567 depth = settings.pop('_depth', 0)
568 category = settings.pop('_category', 'pyramid')
570 def callback(context, name, ob):
571 config = context.config.with_package(info.module)
572 config.add_exception_view(view=ob, **settings)
574 info = self.venusian.attach(
575 wrapped, callback, category=category, depth=depth + 1
576 )
578 if info.scope == 'class':
579 # if the decorator was attached to a method in a class, or
580 # otherwise executed at class scope, we need to set an
581 # 'attr' in the settings if one isn't already in there
582 if settings.get('attr') is None:
583 settings['attr'] = wrapped.__name__
585 settings['_info'] = info.codeinfo # fbo "action_method"
586 return wrapped
589def _find_views(
590 registry,
591 request_iface,
592 context_iface,
593 view_name,
594 view_types=None,
595 view_classifier=None,
596):
597 if view_types is None:
598 view_types = (IView, ISecuredView, IMultiView)
599 if view_classifier is None:
600 view_classifier = IViewClassifier
601 registered = registry.adapters.registered
602 cache = registry._view_lookup_cache
603 views = cache.get((request_iface, context_iface, view_name))
604 if views is None:
605 views = []
606 for req_type, ctx_type in itertools.product(
607 request_iface.__sro__, context_iface.__sro__
608 ):
609 source_ifaces = (view_classifier, req_type, ctx_type)
610 for view_type in view_types:
611 view_callable = registered(
612 source_ifaces, view_type, name=view_name
613 )
614 if view_callable is not None:
615 views.append(view_callable)
616 if views:
617 # do not cache view lookup misses. rationale: dont allow cache to
618 # grow without bound if somebody tries to hit the site with many
619 # missing URLs. we could use an LRU cache instead, but then
620 # purposeful misses by an attacker would just blow out the cache
621 # anyway. downside: misses will almost always consume more CPU than
622 # hits in steady state.
623 with registry._lock:
624 cache[(request_iface, context_iface, view_name)] = views
626 return views
629def _call_view(
630 registry,
631 request,
632 context,
633 context_iface,
634 view_name,
635 view_types=None,
636 view_classifier=None,
637 secure=True,
638 request_iface=None,
639):
640 if request_iface is None:
641 request_iface = getattr(request, 'request_iface', IRequest)
642 view_callables = _find_views(
643 registry,
644 request_iface,
645 context_iface,
646 view_name,
647 view_types=view_types,
648 view_classifier=view_classifier,
649 )
651 pme = None
652 response = None
654 for view_callable in view_callables:
655 # look for views that meet the predicate criteria
656 try:
657 if not secure:
658 # the view will have a __call_permissive__ attribute if it's
659 # secured; otherwise it won't.
660 view_callable = getattr(
661 view_callable, '__call_permissive__', view_callable
662 )
664 # if this view is secured, it will raise a Forbidden
665 # appropriately if the executing user does not have the proper
666 # permission
667 response = view_callable(context, request)
668 return response
669 except PredicateMismatch as _pme:
670 pme = _pme
672 if pme is not None:
673 raise pme
675 return response
678class ViewMethodsMixin(object):
679 """ Request methods mixin for BaseRequest having to do with executing
680 views """
682 def invoke_exception_view(
683 self, exc_info=None, request=None, secure=True, reraise=False
684 ):
685 """ Executes an exception view related to the request it's called upon.
686 The arguments it takes are these:
688 ``exc_info``
690 If provided, should be a 3-tuple in the form provided by
691 ``sys.exc_info()``. If not provided,
692 ``sys.exc_info()`` will be called to obtain the current
693 interpreter exception information. Default: ``None``.
695 ``request``
697 If the request to be used is not the same one as the instance that
698 this method is called upon, it may be passed here. Default:
699 ``None``.
701 ``secure``
703 If the exception view should not be rendered if the current user
704 does not have the appropriate permission, this should be ``True``.
705 Default: ``True``.
707 ``reraise``
709 A boolean indicating whether the original error should be reraised
710 if a :term:`response` object could not be created. If ``False``
711 then an :class:`pyramid.httpexceptions.HTTPNotFound`` exception
712 will be raised. Default: ``False``.
714 If a response is generated then ``request.exception`` and
715 ``request.exc_info`` will be left at the values used to render the
716 response. Otherwise the previous values for ``request.exception`` and
717 ``request.exc_info`` will be restored.
719 .. versionadded:: 1.7
721 .. versionchanged:: 1.9
722 The ``request.exception`` and ``request.exc_info`` properties will
723 reflect the exception used to render the response where previously
724 they were reset to the values prior to invoking the method.
726 Also added the ``reraise`` argument.
728 """
729 if request is None:
730 request = self
731 registry = getattr(request, 'registry', None)
732 if registry is None:
733 registry = get_current_registry()
735 if registry is None:
736 raise RuntimeError("Unable to retrieve registry")
738 if exc_info is None:
739 exc_info = sys.exc_info()
741 exc = exc_info[1]
742 attrs = request.__dict__
743 context_iface = providedBy(exc)
745 # clear old generated request.response, if any; it may
746 # have been mutated by the view, and its state is not
747 # sane (e.g. caching headers)
748 with hide_attrs(request, 'response', 'exc_info', 'exception'):
749 attrs['exception'] = exc
750 attrs['exc_info'] = exc_info
751 # we use .get instead of .__getitem__ below due to
752 # https://github.com/Pylons/pyramid/issues/700
753 request_iface = attrs.get('request_iface', IRequest)
755 manager.push({'request': request, 'registry': registry})
757 try:
758 response = _call_view(
759 registry,
760 request,
761 exc,
762 context_iface,
763 '',
764 view_types=None,
765 view_classifier=IExceptionViewClassifier,
766 secure=secure,
767 request_iface=request_iface.combined,
768 )
769 except Exception:
770 if reraise:
771 reraise_(*exc_info)
772 raise
773 finally:
774 manager.pop()
776 if response is None:
777 if reraise:
778 reraise_(*exc_info)
779 raise HTTPNotFound
781 # successful response, overwrite exception/exc_info
782 attrs['exception'] = exc
783 attrs['exc_info'] = exc_info
784 return response