tosh
2017-06-27 2c5f77d7776ae9b221d6e5af11c1710335d65bd3
commit | author | age
fb2824 1 import itertools
252fa5 2 import sys
CM 3
e6fa66 4 import venusian
CM 5
4174e4 6 from zope.interface import providedBy
7e2c6c 7
0c1c39 8 from pyramid.interfaces import (
CM 9     IRoutesMapper,
85d801 10     IMultiView,
MM 11     ISecuredView,
0c1c39 12     IView,
CM 13     IViewClassifier,
17c7f4 14     IRequest,
531428 15     IExceptionViewClassifier,
0c1c39 16     )
d66bfb 17
849196 18 from pyramid.compat import decode_path_info
6f2f04 19 from pyramid.compat import reraise as reraise_
0db4a1 20
e8c66a 21 from pyramid.exceptions import (
MM 22     ConfigurationError,
23     PredicateMismatch,
24 )
e6c2d2 25
0c1c39 26 from pyramid.httpexceptions import (
CM 27     HTTPFound,
e045cf 28     HTTPNotFound,
0c1c39 29     default_exceptionresponse_view,
CM 30     )
0db4a1 31
dda9fa 32 from pyramid.threadlocal import (
BJR 33     get_current_registry,
34     manager,
35     )
36
19016b 37 from pyramid.util import hide_attrs
70d504 38
f66290 39 _marker = object()
56d0fe 40
7e2c6c 41 def render_view_to_response(context, request, name='', secure=True):
b93d19 42     """ Call the :term:`view callable` configured with a :term:`view
c5f24b 43     configuration` that matches the :term:`view name` ``name``
b93d19 44     registered against the specified ``context`` and ``request`` and
CM 45     return a :term:`response` object.  This function will return
46     ``None`` if a corresponding :term:`view callable` cannot be found
47     (when no :term:`view configuration` matches the combination of
48     ``name`` / ``context`` / and ``request``).
8b1f6e 49
b93d19 50     If `secure`` is ``True``, and the :term:`view callable` found is
99edc5 51     protected by a permission, the permission will be checked before calling
CM 52     the view function.  If the permission check disallows view execution
53     (based on the current :term:`authorization policy`), a
54     :exc:`pyramid.httpexceptions.HTTPForbidden` exception will be raised.
55     The exception's ``args`` attribute explains why the view access was
56     disallowed.
b93d19 57
7e2c6c 58     If ``secure`` is ``False``, no permission checking is done."""
17ce57 59
849196 60     registry = getattr(request, 'registry', None)
CM 61     if registry is None:
62         registry = get_current_registry()
d66bfb 63
849196 64     context_iface = providedBy(context)
713bc5 65     # We explicitly pass in the interfaces provided by the request as
CM 66     # request_iface to _call_view; we don't want _call_view to use
67     # request.request_iface, because render_view_to_response and friends are
68     # pretty much limited to finding views that are not views associated with
69     # routes, and the only thing request.request_iface is used for is to find
70     # route-based views.  The render_view_to_response API is (and always has
71     # been) a stepchild API reserved for use of those who actually use
72     # traversal.  Doing this fixes an infinite recursion bug introduced in
73     # Pyramid 1.6a1, and causes the render_view* APIs to behave as they did in
74     # 1.5 and previous. We should probably provide some sort of different API
75     # that would allow people to find views for routes.  See
76     # https://github.com/Pylons/pyramid/issues/1643 for more info.
77     request_iface = providedBy(request)
849196 78
CM 79     response = _call_view(
80         registry,
81         request,
82         context,
83         context_iface,
84         name,
03c11e 85         secure=secure,
713bc5 86         request_iface=request_iface,
849196 87         )
CM 88
89     return response # NB: might be None
90
7e2c6c 91
CM 92 def render_view_to_iterable(context, request, name='', secure=True):
b93d19 93     """ Call the :term:`view callable` configured with a :term:`view
c5f24b 94     configuration` that matches the :term:`view name` ``name``
b93d19 95     registered against the specified ``context`` and ``request`` and
CM 96     return an iterable object which represents the body of a response.
97     This function will return ``None`` if a corresponding :term:`view
98     callable` cannot be found (when no :term:`view configuration`
99     matches the combination of ``name`` / ``context`` / and
100     ``request``).  Additionally, this function will raise a
101     :exc:`ValueError` if a view function is found and called but the
102     view function's result does not have an ``app_iter`` attribute.
8b1f6e 103
3d2dd3 104     You can usually get the bytestring representation of the return value of
CM 105     this function by calling ``b''.join(iterable)``, or just use
c81aad 106     :func:`pyramid.view.render_view` instead.
8b1f6e 107
99edc5 108     If ``secure`` is ``True``, and the view is protected by a permission, the
CM 109     permission will be checked before the view function is invoked.  If the
110     permission check disallows view execution (based on the current
111     :term:`authentication policy`), a
112     :exc:`pyramid.httpexceptions.HTTPForbidden` exception will be raised; its
113     ``args`` attribute explains why the view access was disallowed.
b93d19 114
CM 115     If ``secure`` is ``False``, no permission checking is
116     done."""
7e2c6c 117     response = render_view_to_response(context, request, name, secure)
CM 118     if response is None:
119         return None
120     return response.app_iter
121
885bfb 122 def render_view(context, request, name='', secure=True):
b93d19 123     """ Call the :term:`view callable` configured with a :term:`view
c5f24b 124     configuration` that matches the :term:`view name` ``name``
27d735 125     registered against the specified ``context`` and ``request``
c5f24b 126     and unwind the view response's ``app_iter`` (see
23de5b 127     :ref:`the_response`) into a single bytestring.  This function will
b93d19 128     return ``None`` if a corresponding :term:`view callable` cannot be
CM 129     found (when no :term:`view configuration` matches the combination
130     of ``name`` / ``context`` / and ``request``).  Additionally, this
131     function will raise a :exc:`ValueError` if a view function is
132     found and called but the view function's result does not have an
133     ``app_iter`` attribute. This function will return ``None`` if a
134     corresponding view cannot be found.
8b1f6e 135
99edc5 136     If ``secure`` is ``True``, and the view is protected by a permission, the
CM 137     permission will be checked before the view is invoked.  If the permission
138     check disallows view execution (based on the current :term:`authorization
139     policy`), a :exc:`pyramid.httpexceptions.HTTPForbidden` exception will be
140     raised; its ``args`` attribute explains why the view access was
b93d19 141     disallowed.
CM 142
885bfb 143     If ``secure`` is ``False``, no permission checking is done."""
CM 144     iterable = render_view_to_iterable(context, request, name, secure)
145     if iterable is None:
146         return None
0a8ea9 147     return b''.join(iterable)
885bfb 148
197f0c 149 class view_config(object):
8b1f6e 150     """ A function, class or method :term:`decorator` which allows a
CM 151     developer to create view registrations nearer to a :term:`view
c1eb0c 152     callable` definition than use :term:`imperative
8b1f6e 153     configuration` to do the same.
5a7f9a 154
8b1f6e 155     For example, this code in a module ``views.py``::
5a7f9a 156
3e2f12 157       from resources import MyResource
5a7f9a 158
3e2f12 159       @view_config(name='my_view', context=MyResource, permission='read',
197f0c 160                    route_name='site1')
5a7f9a 161       def my_view(context, request):
8b1f6e 162           return 'OK'
5a7f9a 163
b93d19 164     Might replace the following call to the
aff443 165     :meth:`pyramid.config.Configurator.add_view` method::
8b1f6e 166
CM 167        import views
3e2f12 168        from resources import MyResource
CM 169        config.add_view(views.my_view, context=MyResource, name='my_view',
34606c 170                        permission='read', route_name='site1')
5a7f9a 171
197f0c 172     .. note: :class:`pyramid.view.view_config` is also importable, for
CM 173              backwards compatibility purposes, as the name
174              :class:`pyramid.view.bfg_view`.
175
8eb19b 176     :class:`pyramid.view.view_config` supports the following keyword
e8c66a 177     arguments: ``context``, ``exception``, ``permission``, ``name``,
cf7d8b 178     ``request_type``, ``route_name``, ``request_method``, ``request_param``,
CM 179     ``containment``, ``xhr``, ``accept``, ``header``, ``path_info``,
602ac1 180     ``custom_predicates``, ``decorator``, ``mapper``, ``http_cache``,
769da1 181     ``require_csrf``, ``match_param``, ``check_csrf``, ``physical_path``, and
MM 182     ``view_options``.
5a7f9a 183
cf7d8b 184     The meanings of these arguments are the same as the arguments passed to
4c29ef 185     :meth:`pyramid.config.Configurator.add_view`.  If any argument is left
CM 186     out, its default will be the equivalent ``add_view`` default.
0b0e74 187
2c5f77 188     Two additional keyword arguments which will be passed to the
T 189     :term:`venusian` ``attach`` function are ``_depth`` and ``_category``.
190
191     ``_depth`` is provided for people who wish to reuse this class from another
192     decorator. The default value is ``0`` and should be specified relative to
193     the ``view_config`` invocation. It will be passed in to the
194     :term:`venusian` ``attach`` function as the depth of the callstack when
195     Venusian checks if the decorator is being used in a class or module
196     context. It's not often used, but it can be useful in this circumstance.
197
198     ``_category`` sets the decorator category name. It can be useful in
199     combination with the ``category`` argument of ``scan`` to control which
200     views should be processed.
201
202     See the :py:func:`venusian.attach` function in Venusian for more information.
2033ee 203     
SP 204     .. seealso::
205     
206         See also :ref:`mapping_views_using_a_decorator_section` for
207         details about using :class:`pyramid.view.view_config`.
a8d71c 208
2033ee 209     .. warning::
SP 210     
211         ``view_config`` will work ONLY on module top level members
212         because of the limitation of ``venusian.Scanner.scan``.
a8f669 213
5a7f9a 214     """
e6fa66 215     venusian = venusian # for testing injection
8ec8e2 216     def __init__(self, **settings):
CM 217         if 'for_' in settings:
218             if settings.get('context') is None:
219                 settings['context'] = settings['for_']
220         self.__dict__.update(settings)
5a7f9a 221
CM 222     def __call__(self, wrapped):
e6fa66 223         settings = self.__dict__.copy()
ed1419 224         depth = settings.pop('_depth', 0)
2c5f77 225         category = settings.pop('_category', 'pyramid')
e6fa66 226
CM 227         def callback(context, name, ob):
b2c4e0 228             config = context.config.with_package(info.module)
CM 229             config.add_view(view=ob, **settings)
e6fa66 230
2c5f77 231         info = self.venusian.attach(wrapped, callback, category=category,
ed1419 232                                     depth=depth + 1)
e6fa66 233
CM 234         if info.scope == 'class':
32418e 235             # if the decorator was attached to a method in a class, or
CM 236             # otherwise executed at class scope, we need to set an
237             # 'attr' into the settings if one isn't already in there
4c29ef 238             if settings.get('attr') is None:
e6fa66 239                 settings['attr'] = wrapped.__name__
89968d 240
b2c4e0 241         settings['_info'] = info.codeinfo # fbo "action_method"
c89bcb 242         return wrapped
5a7f9a 243
33516a 244 bfg_view = view_config # bw compat (forever)
5f4780 245
914abe 246 class view_defaults(view_config):
4375cf 247     """ A class :term:`decorator` which, when applied to a class, will
CM 248     provide defaults for all view configurations that use the class.  This
249     decorator accepts all the arguments accepted by
aaedf5 250     :meth:`pyramid.view.view_config`, and each has the same meaning.
4375cf 251
CM 252     See :ref:`view_defaults` for more information.
253     """
a8f669 254
914abe 255     def __call__(self, wrapped):
CM 256         wrapped.__view_defaults__ = self.__dict__.copy()
257         return wrapped
258
d96ff9 259 class AppendSlashNotFoundViewFactory(object):
CM 260     """ There can only be one :term:`Not Found view` in any
fd5ae9 261     :app:`Pyramid` application.  Even if you use
c81aad 262     :func:`pyramid.view.append_slash_notfound_view` as the Not
fd5ae9 263     Found view, :app:`Pyramid` still must generate a ``404 Not
d96ff9 264     Found`` response when it cannot redirect to a slash-appended URL;
CM 265     this not found response will be visible to site users.
266
267     If you don't care what this 404 response looks like, and you only
268     need redirections to slash-appended route URLs, you may use the
c81aad 269     :func:`pyramid.view.append_slash_notfound_view` object as the
d96ff9 270     Not Found view.  However, if you wish to use a *custom* notfound
CM 271     view callable when a URL cannot be redirected to a slash-appended
272     URL, you may wish to use an instance of this class as the Not
273     Found view, supplying a :term:`view callable` to be used as the
274     custom notfound view as the first argument to its constructor.
275     For instance:
276
277     .. code-block:: python
278
99edc5 279        from pyramid.httpexceptions import HTTPNotFound
c81aad 280        from pyramid.view import AppendSlashNotFoundViewFactory
d96ff9 281
1b4360 282        def notfound_view(context, request): return HTTPNotFound('nope')
d96ff9 283
CM 284        custom_append_slash = AppendSlashNotFoundViewFactory(notfound_view)
a7e625 285        config.add_view(custom_append_slash, context=HTTPNotFound)
d96ff9 286
CM 287     The ``notfound_view`` supplied must adhere to the two-argument
288     view callable calling convention of ``(context, request)``
289     (``context`` will be the exception object).
290
2033ee 291     .. deprecated:: 1.3
SP 292
d96ff9 293     """
12b6f5 294     def __init__(self, notfound_view=None, redirect_class=HTTPFound):
d96ff9 295         if notfound_view is None:
CM 296             notfound_view = default_exceptionresponse_view
297         self.notfound_view = notfound_view
12b6f5 298         self.redirect_class = redirect_class
d96ff9 299
CM 300     def __call__(self, context, request):
0db4a1 301         path = decode_path_info(request.environ['PATH_INFO'] or '/')
30e64f 302         registry = request.registry
d96ff9 303         mapper = registry.queryUtility(IRoutesMapper)
CM 304         if mapper is not None and not path.endswith('/'):
305             slashpath = path + '/'
306             for route in mapper.get_routes():
307                 if route.match(slashpath) is not None:
b596e1 308                     qs = request.query_string
CM 309                     if qs:
0db4a1 310                         qs = '?' + qs
17279b 311                     return self.redirect_class(location=request.path + '/' + qs)
d96ff9 312         return self.notfound_view(context, request)
a9454c 313
d96ff9 314 append_slash_notfound_view = AppendSlashNotFoundViewFactory()
CM 315 append_slash_notfound_view.__doc__ = """\
316 For behavior like Django's ``APPEND_SLASH=True``, use this view as the
317 :term:`Not Found view` in your application.
a9454c 318
87a85f 319 When this view is the Not Found view (indicating that no view was found), and
CM 320 any routes have been defined in the configuration of your application, if the
321 value of the ``PATH_INFO`` WSGI environment variable does not already end in
322 a slash, and if the value of ``PATH_INFO`` *plus* a slash matches any route's
323 path, do an HTTP redirect to the slash-appended PATH_INFO.  Note that this
324 will *lose* ``POST`` data information (turning it into a GET), so you
325 shouldn't rely on this to redirect POST requests.  Note also that static
326 routes are not considered when attempting to find a matching route.
d66bfb 327
c1eb0c 328 Use the :meth:`pyramid.config.Configurator.add_view` method to configure this
CM 329 view as the Not Found view::
8b1f6e 330
99edc5 331   from pyramid.httpexceptions import HTTPNotFound
c81aad 332   from pyramid.view import append_slash_notfound_view
a7e625 333   config.add_view(append_slash_notfound_view, context=HTTPNotFound)
8b1f6e 334
2033ee 335 .. deprecated:: 1.3
d66bfb 336
d96ff9 337 """
d66bfb 338
0db4a1 339 class notfound_view_config(object):
CM 340     """
05e928 341     .. versionadded:: 1.3
0db4a1 342
CM 343     An analogue of :class:`pyramid.view.view_config` which registers a
e8c66a 344     :term:`Not Found View` using
MM 345     :meth:`pyramid.config.Configurator.add_notfound_view`.
0db4a1 346
e450ca 347     The ``notfound_view_config`` constructor accepts most of the same arguments
0db4a1 348     as the constructor of :class:`pyramid.view.view_config`.  It can be used
CM 349     in the same places, and behaves in largely the same way, except it always
8ec8e2 350     registers a not found exception view instead of a 'normal' view.
0db4a1 351
CM 352     Example:
353
354     .. code-block:: python
355
356         from pyramid.view import notfound_view_config
357         from pyramid.response import Response
a8f669 358
15e3b1 359         @notfound_view_config()
0db4a1 360         def notfound(request):
c898dd 361             return Response('Not found!', status='404 Not Found')
0db4a1 362
CM 363     All arguments except ``append_slash`` have the same meaning as
364     :meth:`pyramid.view.view_config` and each predicate
365     argument restricts the set of circumstances under which this notfound
366     view will be invoked.
367
cec2b0 368     If ``append_slash`` is ``True``, when the Not Found View is invoked, and
0db4a1 369     the current path info does not end in a slash, the notfound logic will
CM 370     attempt to find a :term:`route` that matches the request's path info
371     suffixed with a slash.  If such a route exists, Pyramid will issue a
372     redirect to the URL implied by the route; if it does not, Pyramid will
373     return the result of the view callable provided as ``view``, as normal.
374
24358c 375     If the argument provided as ``append_slash`` is not a boolean but
CM 376     instead implements :class:`~pyramid.interfaces.IResponse`, the
377     append_slash logic will behave as if ``append_slash=True`` was passed,
378     but the provided class will be used as the response class instead of
379     the default :class:`~pyramid.httpexceptions.HTTPFound` response class
380     when a redirect is performed.  For example:
381
382       .. code-block:: python
383
384         from pyramid.httpexceptions import (
385             HTTPMovedPermanently,
386             HTTPNotFound
387             )
388
389         @notfound_view_config(append_slash=HTTPMovedPermanently)
390         def aview(request):
391             return HTTPNotFound('not found')
392
393     The above means that a redirect to a slash-appended route will be
394     attempted, but instead of :class:`~pyramid.httpexceptions.HTTPFound`
395     being used, :class:`~pyramid.httpexceptions.HTTPMovedPermanently will
396     be used` for the redirect response if a slash-appended route is found.
397
398     .. versionchanged:: 1.6
399
0db4a1 400     See :ref:`changing_the_notfound_view` for detailed usage information.
CM 401
402     """
403
404     venusian = venusian
405
8ec8e2 406     def __init__(self, **settings):
CM 407         self.__dict__.update(settings)
0db4a1 408
CM 409     def __call__(self, wrapped):
410         settings = self.__dict__.copy()
411
412         def callback(context, name, ob):
413             config = context.config.with_package(info.module)
414             config.add_notfound_view(view=ob, **settings)
415
416         info = self.venusian.attach(wrapped, callback, category='pyramid')
417
418         if info.scope == 'class':
419             # if the decorator was attached to a method in a class, or
420             # otherwise executed at class scope, we need to set an
421             # 'attr' into the settings if one isn't already in there
422             if settings.get('attr') is None:
423                 settings['attr'] = wrapped.__name__
424
425         settings['_info'] = info.codeinfo # fbo "action_method"
426         return wrapped
427
a7fe30 428 class forbidden_view_config(object):
CM 429     """
05e928 430     .. versionadded:: 1.3
a7fe30 431
CM 432     An analogue of :class:`pyramid.view.view_config` which registers a
e8c66a 433     :term:`forbidden view` using
MM 434     :meth:`pyramid.config.Configurator.add_forbidden_view`.
a7fe30 435
CM 436     The forbidden_view_config constructor accepts most of the same arguments
437     as the constructor of :class:`pyramid.view.view_config`.  It can be used
438     in the same places, and behaves in largely the same way, except it always
8ec8e2 439     registers a forbidden exception view instead of a 'normal' view.
a7fe30 440
CM 441     Example:
442
443     .. code-block:: python
444
445         from pyramid.view import forbidden_view_config
446         from pyramid.response import Response
a8f669 447
15e3b1 448         @forbidden_view_config()
bbd3ab 449         def forbidden(request):
59e7cc 450             return Response('You are not allowed', status='403 Forbidden')
a7fe30 451
8ec8e2 452     All arguments passed to this function have the same meaning as
CM 453     :meth:`pyramid.view.view_config` and each predicate argument restricts
454     the set of circumstances under which this notfound view will be invoked.
a7fe30 455
CM 456     See :ref:`changing_the_forbidden_view` for detailed usage information.
457
458     """
459
460     venusian = venusian
461
8ec8e2 462     def __init__(self, **settings):
CM 463         self.__dict__.update(settings)
a7fe30 464
CM 465     def __call__(self, wrapped):
466         settings = self.__dict__.copy()
467
468         def callback(context, name, ob):
469             config = context.config.with_package(info.module)
470             config.add_forbidden_view(view=ob, **settings)
471
472         info = self.venusian.attach(wrapped, callback, category='pyramid')
473
474         if info.scope == 'class':
475             # if the decorator was attached to a method in a class, or
476             # otherwise executed at class scope, we need to set an
477             # 'attr' into the settings if one isn't already in there
478             if settings.get('attr') is None:
479                 settings['attr'] = wrapped.__name__
480
481         settings['_info'] = info.codeinfo # fbo "action_method"
482         return wrapped
a8f669 483
93c94b 484 class exception_view_config(object):
AL 485     """
486     .. versionadded:: 1.8
487
488     An analogue of :class:`pyramid.view.view_config` which registers an
e8c66a 489     :term:`exception view` using
MM 490     :meth:`pyramid.config.Configurator.add_exception_view`.
93c94b 491
e8c66a 492     The ``exception_view_config`` constructor requires an exception context,
MM 493     and additionally accepts most of the same arguments as the constructor of
93c94b 494     :class:`pyramid.view.view_config`.  It can be used in the same places,
e8c66a 495     and behaves in largely the same way, except it always registers an
160aab 496     exception view instead of a "normal" view that dispatches on the request
e8c66a 497     :term:`context`.
93c94b 498
AL 499     Example:
500
501     .. code-block:: python
502
503         from pyramid.view import exception_view_config
504         from pyramid.response import Response
505
e8c66a 506         @exception_view_config(ValueError, renderer='json')
MM 507         def error_view(request):
508             return {'error': str(request.exception)}
93c94b 509
AL 510     All arguments passed to this function have the same meaning as
160aab 511     :meth:`pyramid.view.view_config`, and each predicate argument restricts
93c94b 512     the set of circumstances under which this exception view will be invoked.
e8c66a 513
93c94b 514     """
74842a 515     venusian = venusian
93c94b 516
e8c66a 517     def __init__(self, *args, **settings):
MM 518         if 'context' not in settings and len(args) > 0:
519             exception, args = args[0], args[1:]
520             settings['context'] = exception
521         if len(args) > 0:
522             raise ConfigurationError('unknown positional arguments')
93c94b 523         self.__dict__.update(settings)
AL 524
525     def __call__(self, wrapped):
526         settings = self.__dict__.copy()
527
528         def callback(context, name, ob):
529             config = context.config.with_package(info.module)
530             config.add_exception_view(view=ob, **settings)
531
532         info = self.venusian.attach(wrapped, callback, category='pyramid')
533
534         if info.scope == 'class':
535             # if the decorator was attached to a method in a class, or
536             # otherwise executed at class scope, we need to set an
160aab 537             # 'attr' in the settings if one isn't already in there
93c94b 538             if settings.get('attr') is None:
AL 539                 settings['attr'] = wrapped.__name__
540
541         settings['_info'] = info.codeinfo # fbo "action_method"
542         return wrapped
543
17c7f4 544 def _find_views(
CM 545     registry,
546     request_iface,
547     context_iface,
548     view_name,
549     view_types=None,
550     view_classifier=None,
551     ):
03c11e 552     if view_types is None:
17c7f4 553         view_types = (IView, ISecuredView, IMultiView)
03c11e 554     if view_classifier is None:
17c7f4 555         view_classifier = IViewClassifier
fb2824 556     registered = registry.adapters.registered
c15cbc 557     cache = registry._view_lookup_cache
CM 558     views = cache.get((request_iface, context_iface, view_name))
559     if views is None:
560         views = []
561         for req_type, ctx_type in itertools.product(
562             request_iface.__sro__, context_iface.__sro__
563         ):
17c7f4 564             source_ifaces = (view_classifier, req_type, ctx_type)
c15cbc 565             for view_type in view_types:
CM 566                 view_callable = registered(
567                     source_ifaces,
568                     view_type,
569                     name=view_name,
570                 )
571                 if view_callable is not None:
572                     views.append(view_callable)
573         if views:
574             # do not cache view lookup misses.  rationale: dont allow cache to
575             # grow without bound if somebody tries to hit the site with many
576             # missing URLs.  we could use an LRU cache instead, but then
577             # purposeful misses by an attacker would just blow out the cache
578             # anyway. downside: misses will almost always consume more CPU than
579             # hits in steady state.
580             with registry._lock:
581                 cache[(request_iface, context_iface, view_name)] = views
849196 582
99bc0c 583     return views
eb3ac8 584
849196 585 def _call_view(
CM 586     registry,
587     request,
588     context,
589     context_iface,
590     view_name,
17c7f4 591     view_types=None,
CM 592     view_classifier=None,
849196 593     secure=True,
17c7f4 594     request_iface=None,
849196 595     ):
17c7f4 596     if request_iface is None:
CM 597         request_iface = getattr(request, 'request_iface', IRequest)
eb3ac8 598     view_callables = _find_views(
CM 599         registry,
17c7f4 600         request_iface,
eb3ac8 601         context_iface,
CM 602         view_name,
17c7f4 603         view_types=view_types,
CM 604         view_classifier=view_classifier,
eb3ac8 605         )
CM 606
607     pme = None
608     response = None
609
610     for view_callable in view_callables:
611         # look for views that meet the predicate criteria
612         try:
849196 613             if not secure:
CM 614                 # the view will have a __call_permissive__ attribute if it's
615                 # secured; otherwise it won't.
616                 view_callable = getattr(
617                     view_callable,
618                     '__call_permissive__',
619                     view_callable
620                     )
621
622             # if this view is secured, it will raise a Forbidden
623             # appropriately if the executing user does not have the proper
624             # permission
eb3ac8 625             response = view_callable(context, request)
CM 626             return response
627         except PredicateMismatch as _pme:
628             pme = _pme
629
630     if pme is not None:
631         raise pme
632
633     return response
531428 634
CM 635 class ViewMethodsMixin(object):
636     """ Request methods mixin for BaseRequest having to do with executing
637     views """
638     def invoke_exception_view(
639         self,
e40ef2 640         exc_info=None,
531428 641         request=None,
6f2f04 642         secure=True,
MM 643         reraise=False,
644     ):
e40ef2 645         """ Executes an exception view related to the request it's called upon.
CM 646         The arguments it takes are these:
647
648         ``exc_info``
649
650             If provided, should be a 3-tuple in the form provided by
252fa5 651             ``sys.exc_info()``.  If not provided,
CM 652             ``sys.exc_info()`` will be called to obtain the current
e40ef2 653             interpreter exception information.  Default: ``None``.
CM 654
655         ``request``
656
657             If the request to be used is not the same one as the instance that
658             this method is called upon, it may be passed here.  Default:
659             ``None``.
660
661         ``secure``
662
663             If the exception view should not be rendered if the current user
664             does not have the appropriate permission, this should be ``True``.
665             Default: ``True``.
666
6f2f04 667         ``reraise``
e40ef2 668
6f2f04 669             A boolean indicating whether the original error should be reraised
MM 670             if a :term:`response` object could not be created. If ``False``
671             then an :class:`pyramid.httpexceptions.HTTPNotFound`` exception
672             will be raised. Default: ``False``.
e40ef2 673
3b886e 674         If a response is generated then ``request.exception`` and
MM 675         ``request.exc_info`` will be left at the values used to render the
676         response. Otherwise the previous values for ``request.exception`` and
677         ``request.exc_info`` will be restored.
678
daf06d 679         .. versionadded:: 1.7
MM 680
e2e51b 681         .. versionchanged:: 1.9
MM 682            The ``request.exception`` and ``request.exc_info`` properties will
683            reflect the exception used to render the response where previously
684            they were reset to the values prior to invoking the method.
6f2f04 685
MM 686            Also added the ``reraise`` argument.
e2e51b 687
3b886e 688         """
531428 689         if request is None:
CM 690             request = self
691         registry = getattr(request, 'registry', None)
692         if registry is None:
693             registry = get_current_registry()
dda9fa 694
BJR 695         if registry is None:
696             raise RuntimeError("Unable to retrieve registry")
697
e40ef2 698         if exc_info is None:
252fa5 699             exc_info = sys.exc_info()
dda9fa 700
d52257 701         exc = exc_info[1]
ca529f 702         attrs = request.__dict__
d52257 703         context_iface = providedBy(exc)
19016b 704
MM 705         # clear old generated request.response, if any; it may
706         # have been mutated by the view, and its state is not
707         # sane (e.g. caching headers)
3b886e 708         with hide_attrs(request, 'response', 'exc_info', 'exception'):
d52257 709             attrs['exception'] = exc
68b303 710             attrs['exc_info'] = exc_info
19016b 711             # we use .get instead of .__getitem__ below due to
MM 712             # https://github.com/Pylons/pyramid/issues/700
713             request_iface = attrs.get('request_iface', IRequest)
dda9fa 714
4f6635 715             manager.push({'request': request, 'registry': registry})
dda9fa 716
4f6635 717             try:
dda9fa 718                 response = _call_view(
BJR 719                     registry,
720                     request,
721                     exc,
722                     context_iface,
723                     '',
724                     view_types=None,
725                     view_classifier=IExceptionViewClassifier,
726                     secure=secure,
727                     request_iface=request_iface.combined,
728                     )
6f2f04 729             except:
MM 730                 if reraise:
731                     reraise_(*exc_info)
732                 raise
dda9fa 733             finally:
BJR 734                 manager.pop()
3b886e 735
MM 736         if response is None:
6f2f04 737             if reraise:
MM 738                 reraise_(*exc_info)
3b886e 739             raise HTTPNotFound
MM 740
741         # successful response, overwrite exception/exc_info
742         attrs['exception'] = exc
743         attrs['exc_info'] = exc_info
744         return response