Michael Merickel
2017-06-15 2ea943e690d8f09d07c13ca4d513cfafbfba33f1
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
a8d71c 188     An additional keyword argument named ``_depth`` is provided for people who
ed1419 189     wish to reuse this class from another decorator.  The default value is
MM 190     ``0`` and should be specified relative to the ``view_config`` invocation.
191     It will be passed in to the :term:`venusian` ``attach`` function as the
192     depth of the callstack when Venusian checks if the decorator is being used
193     in a class or module context.  It's not often used, but it can be useful
194     in this circumstance.  See the ``attach`` function in Venusian for more
195     information.
2033ee 196     
SP 197     .. seealso::
198     
199         See also :ref:`mapping_views_using_a_decorator_section` for
200         details about using :class:`pyramid.view.view_config`.
a8d71c 201
2033ee 202     .. warning::
SP 203     
204         ``view_config`` will work ONLY on module top level members
205         because of the limitation of ``venusian.Scanner.scan``.
a8f669 206
5a7f9a 207     """
e6fa66 208     venusian = venusian # for testing injection
8ec8e2 209     def __init__(self, **settings):
CM 210         if 'for_' in settings:
211             if settings.get('context') is None:
212                 settings['context'] = settings['for_']
213         self.__dict__.update(settings)
5a7f9a 214
CM 215     def __call__(self, wrapped):
e6fa66 216         settings = self.__dict__.copy()
ed1419 217         depth = settings.pop('_depth', 0)
e6fa66 218
CM 219         def callback(context, name, ob):
b2c4e0 220             config = context.config.with_package(info.module)
CM 221             config.add_view(view=ob, **settings)
e6fa66 222
a8d71c 223         info = self.venusian.attach(wrapped, callback, category='pyramid',
ed1419 224                                     depth=depth + 1)
e6fa66 225
CM 226         if info.scope == 'class':
32418e 227             # if the decorator was attached to a method in a class, or
CM 228             # otherwise executed at class scope, we need to set an
229             # 'attr' into the settings if one isn't already in there
4c29ef 230             if settings.get('attr') is None:
e6fa66 231                 settings['attr'] = wrapped.__name__
89968d 232
b2c4e0 233         settings['_info'] = info.codeinfo # fbo "action_method"
c89bcb 234         return wrapped
5a7f9a 235
33516a 236 bfg_view = view_config # bw compat (forever)
5f4780 237
914abe 238 class view_defaults(view_config):
4375cf 239     """ A class :term:`decorator` which, when applied to a class, will
CM 240     provide defaults for all view configurations that use the class.  This
241     decorator accepts all the arguments accepted by
aaedf5 242     :meth:`pyramid.view.view_config`, and each has the same meaning.
4375cf 243
CM 244     See :ref:`view_defaults` for more information.
245     """
a8f669 246
914abe 247     def __call__(self, wrapped):
CM 248         wrapped.__view_defaults__ = self.__dict__.copy()
249         return wrapped
250
d96ff9 251 class AppendSlashNotFoundViewFactory(object):
CM 252     """ There can only be one :term:`Not Found view` in any
fd5ae9 253     :app:`Pyramid` application.  Even if you use
c81aad 254     :func:`pyramid.view.append_slash_notfound_view` as the Not
fd5ae9 255     Found view, :app:`Pyramid` still must generate a ``404 Not
d96ff9 256     Found`` response when it cannot redirect to a slash-appended URL;
CM 257     this not found response will be visible to site users.
258
259     If you don't care what this 404 response looks like, and you only
260     need redirections to slash-appended route URLs, you may use the
c81aad 261     :func:`pyramid.view.append_slash_notfound_view` object as the
d96ff9 262     Not Found view.  However, if you wish to use a *custom* notfound
CM 263     view callable when a URL cannot be redirected to a slash-appended
264     URL, you may wish to use an instance of this class as the Not
265     Found view, supplying a :term:`view callable` to be used as the
266     custom notfound view as the first argument to its constructor.
267     For instance:
268
269     .. code-block:: python
270
99edc5 271        from pyramid.httpexceptions import HTTPNotFound
c81aad 272        from pyramid.view import AppendSlashNotFoundViewFactory
d96ff9 273
1b4360 274        def notfound_view(context, request): return HTTPNotFound('nope')
d96ff9 275
CM 276        custom_append_slash = AppendSlashNotFoundViewFactory(notfound_view)
a7e625 277        config.add_view(custom_append_slash, context=HTTPNotFound)
d96ff9 278
CM 279     The ``notfound_view`` supplied must adhere to the two-argument
280     view callable calling convention of ``(context, request)``
281     (``context`` will be the exception object).
282
2033ee 283     .. deprecated:: 1.3
SP 284
d96ff9 285     """
12b6f5 286     def __init__(self, notfound_view=None, redirect_class=HTTPFound):
d96ff9 287         if notfound_view is None:
CM 288             notfound_view = default_exceptionresponse_view
289         self.notfound_view = notfound_view
12b6f5 290         self.redirect_class = redirect_class
d96ff9 291
CM 292     def __call__(self, context, request):
0db4a1 293         path = decode_path_info(request.environ['PATH_INFO'] or '/')
30e64f 294         registry = request.registry
d96ff9 295         mapper = registry.queryUtility(IRoutesMapper)
CM 296         if mapper is not None and not path.endswith('/'):
297             slashpath = path + '/'
298             for route in mapper.get_routes():
299                 if route.match(slashpath) is not None:
b596e1 300                     qs = request.query_string
CM 301                     if qs:
0db4a1 302                         qs = '?' + qs
17279b 303                     return self.redirect_class(location=request.path + '/' + qs)
d96ff9 304         return self.notfound_view(context, request)
a9454c 305
d96ff9 306 append_slash_notfound_view = AppendSlashNotFoundViewFactory()
CM 307 append_slash_notfound_view.__doc__ = """\
308 For behavior like Django's ``APPEND_SLASH=True``, use this view as the
309 :term:`Not Found view` in your application.
a9454c 310
87a85f 311 When this view is the Not Found view (indicating that no view was found), and
CM 312 any routes have been defined in the configuration of your application, if the
313 value of the ``PATH_INFO`` WSGI environment variable does not already end in
314 a slash, and if the value of ``PATH_INFO`` *plus* a slash matches any route's
315 path, do an HTTP redirect to the slash-appended PATH_INFO.  Note that this
316 will *lose* ``POST`` data information (turning it into a GET), so you
317 shouldn't rely on this to redirect POST requests.  Note also that static
318 routes are not considered when attempting to find a matching route.
d66bfb 319
c1eb0c 320 Use the :meth:`pyramid.config.Configurator.add_view` method to configure this
CM 321 view as the Not Found view::
8b1f6e 322
99edc5 323   from pyramid.httpexceptions import HTTPNotFound
c81aad 324   from pyramid.view import append_slash_notfound_view
a7e625 325   config.add_view(append_slash_notfound_view, context=HTTPNotFound)
8b1f6e 326
2033ee 327 .. deprecated:: 1.3
d66bfb 328
d96ff9 329 """
d66bfb 330
0db4a1 331 class notfound_view_config(object):
CM 332     """
05e928 333     .. versionadded:: 1.3
0db4a1 334
CM 335     An analogue of :class:`pyramid.view.view_config` which registers a
e8c66a 336     :term:`Not Found View` using
MM 337     :meth:`pyramid.config.Configurator.add_notfound_view`.
0db4a1 338
e450ca 339     The ``notfound_view_config`` constructor accepts most of the same arguments
0db4a1 340     as the constructor of :class:`pyramid.view.view_config`.  It can be used
CM 341     in the same places, and behaves in largely the same way, except it always
8ec8e2 342     registers a not found exception view instead of a 'normal' view.
0db4a1 343
CM 344     Example:
345
346     .. code-block:: python
347
348         from pyramid.view import notfound_view_config
349         from pyramid.response import Response
a8f669 350
15e3b1 351         @notfound_view_config()
0db4a1 352         def notfound(request):
c898dd 353             return Response('Not found!', status='404 Not Found')
0db4a1 354
CM 355     All arguments except ``append_slash`` have the same meaning as
356     :meth:`pyramid.view.view_config` and each predicate
357     argument restricts the set of circumstances under which this notfound
358     view will be invoked.
359
cec2b0 360     If ``append_slash`` is ``True``, when the Not Found View is invoked, and
0db4a1 361     the current path info does not end in a slash, the notfound logic will
CM 362     attempt to find a :term:`route` that matches the request's path info
363     suffixed with a slash.  If such a route exists, Pyramid will issue a
364     redirect to the URL implied by the route; if it does not, Pyramid will
365     return the result of the view callable provided as ``view``, as normal.
366
24358c 367     If the argument provided as ``append_slash`` is not a boolean but
CM 368     instead implements :class:`~pyramid.interfaces.IResponse`, the
369     append_slash logic will behave as if ``append_slash=True`` was passed,
370     but the provided class will be used as the response class instead of
371     the default :class:`~pyramid.httpexceptions.HTTPFound` response class
372     when a redirect is performed.  For example:
373
374       .. code-block:: python
375
376         from pyramid.httpexceptions import (
377             HTTPMovedPermanently,
378             HTTPNotFound
379             )
380
381         @notfound_view_config(append_slash=HTTPMovedPermanently)
382         def aview(request):
383             return HTTPNotFound('not found')
384
385     The above means that a redirect to a slash-appended route will be
386     attempted, but instead of :class:`~pyramid.httpexceptions.HTTPFound`
387     being used, :class:`~pyramid.httpexceptions.HTTPMovedPermanently will
388     be used` for the redirect response if a slash-appended route is found.
389
390     .. versionchanged:: 1.6
391
0db4a1 392     See :ref:`changing_the_notfound_view` for detailed usage information.
CM 393
394     """
395
396     venusian = venusian
397
8ec8e2 398     def __init__(self, **settings):
CM 399         self.__dict__.update(settings)
0db4a1 400
CM 401     def __call__(self, wrapped):
402         settings = self.__dict__.copy()
403
404         def callback(context, name, ob):
405             config = context.config.with_package(info.module)
406             config.add_notfound_view(view=ob, **settings)
407
408         info = self.venusian.attach(wrapped, callback, category='pyramid')
409
410         if info.scope == 'class':
411             # if the decorator was attached to a method in a class, or
412             # otherwise executed at class scope, we need to set an
413             # 'attr' into the settings if one isn't already in there
414             if settings.get('attr') is None:
415                 settings['attr'] = wrapped.__name__
416
417         settings['_info'] = info.codeinfo # fbo "action_method"
418         return wrapped
419
a7fe30 420 class forbidden_view_config(object):
CM 421     """
05e928 422     .. versionadded:: 1.3
a7fe30 423
CM 424     An analogue of :class:`pyramid.view.view_config` which registers a
e8c66a 425     :term:`forbidden view` using
MM 426     :meth:`pyramid.config.Configurator.add_forbidden_view`.
a7fe30 427
CM 428     The forbidden_view_config constructor accepts most of the same arguments
429     as the constructor of :class:`pyramid.view.view_config`.  It can be used
430     in the same places, and behaves in largely the same way, except it always
8ec8e2 431     registers a forbidden exception view instead of a 'normal' view.
a7fe30 432
CM 433     Example:
434
435     .. code-block:: python
436
437         from pyramid.view import forbidden_view_config
438         from pyramid.response import Response
a8f669 439
15e3b1 440         @forbidden_view_config()
bbd3ab 441         def forbidden(request):
59e7cc 442             return Response('You are not allowed', status='403 Forbidden')
a7fe30 443
8ec8e2 444     All arguments passed to this function have the same meaning as
CM 445     :meth:`pyramid.view.view_config` and each predicate argument restricts
446     the set of circumstances under which this notfound view will be invoked.
a7fe30 447
CM 448     See :ref:`changing_the_forbidden_view` for detailed usage information.
449
450     """
451
452     venusian = venusian
453
8ec8e2 454     def __init__(self, **settings):
CM 455         self.__dict__.update(settings)
a7fe30 456
CM 457     def __call__(self, wrapped):
458         settings = self.__dict__.copy()
459
460         def callback(context, name, ob):
461             config = context.config.with_package(info.module)
462             config.add_forbidden_view(view=ob, **settings)
463
464         info = self.venusian.attach(wrapped, callback, category='pyramid')
465
466         if info.scope == 'class':
467             # if the decorator was attached to a method in a class, or
468             # otherwise executed at class scope, we need to set an
469             # 'attr' into the settings if one isn't already in there
470             if settings.get('attr') is None:
471                 settings['attr'] = wrapped.__name__
472
473         settings['_info'] = info.codeinfo # fbo "action_method"
474         return wrapped
a8f669 475
93c94b 476 class exception_view_config(object):
AL 477     """
478     .. versionadded:: 1.8
479
480     An analogue of :class:`pyramid.view.view_config` which registers an
e8c66a 481     :term:`exception view` using
MM 482     :meth:`pyramid.config.Configurator.add_exception_view`.
93c94b 483
e8c66a 484     The ``exception_view_config`` constructor requires an exception context,
MM 485     and additionally accepts most of the same arguments as the constructor of
93c94b 486     :class:`pyramid.view.view_config`.  It can be used in the same places,
e8c66a 487     and behaves in largely the same way, except it always registers an
160aab 488     exception view instead of a "normal" view that dispatches on the request
e8c66a 489     :term:`context`.
93c94b 490
AL 491     Example:
492
493     .. code-block:: python
494
495         from pyramid.view import exception_view_config
496         from pyramid.response import Response
497
e8c66a 498         @exception_view_config(ValueError, renderer='json')
MM 499         def error_view(request):
500             return {'error': str(request.exception)}
93c94b 501
AL 502     All arguments passed to this function have the same meaning as
160aab 503     :meth:`pyramid.view.view_config`, and each predicate argument restricts
93c94b 504     the set of circumstances under which this exception view will be invoked.
e8c66a 505
93c94b 506     """
74842a 507     venusian = venusian
93c94b 508
e8c66a 509     def __init__(self, *args, **settings):
MM 510         if 'context' not in settings and len(args) > 0:
511             exception, args = args[0], args[1:]
512             settings['context'] = exception
513         if len(args) > 0:
514             raise ConfigurationError('unknown positional arguments')
93c94b 515         self.__dict__.update(settings)
AL 516
517     def __call__(self, wrapped):
518         settings = self.__dict__.copy()
519
520         def callback(context, name, ob):
521             config = context.config.with_package(info.module)
522             config.add_exception_view(view=ob, **settings)
523
524         info = self.venusian.attach(wrapped, callback, category='pyramid')
525
526         if info.scope == 'class':
527             # if the decorator was attached to a method in a class, or
528             # otherwise executed at class scope, we need to set an
160aab 529             # 'attr' in the settings if one isn't already in there
93c94b 530             if settings.get('attr') is None:
AL 531                 settings['attr'] = wrapped.__name__
532
533         settings['_info'] = info.codeinfo # fbo "action_method"
534         return wrapped
535
17c7f4 536 def _find_views(
CM 537     registry,
538     request_iface,
539     context_iface,
540     view_name,
541     view_types=None,
542     view_classifier=None,
543     ):
03c11e 544     if view_types is None:
17c7f4 545         view_types = (IView, ISecuredView, IMultiView)
03c11e 546     if view_classifier is None:
17c7f4 547         view_classifier = IViewClassifier
fb2824 548     registered = registry.adapters.registered
c15cbc 549     cache = registry._view_lookup_cache
CM 550     views = cache.get((request_iface, context_iface, view_name))
551     if views is None:
552         views = []
553         for req_type, ctx_type in itertools.product(
554             request_iface.__sro__, context_iface.__sro__
555         ):
17c7f4 556             source_ifaces = (view_classifier, req_type, ctx_type)
c15cbc 557             for view_type in view_types:
CM 558                 view_callable = registered(
559                     source_ifaces,
560                     view_type,
561                     name=view_name,
562                 )
563                 if view_callable is not None:
564                     views.append(view_callable)
565         if views:
566             # do not cache view lookup misses.  rationale: dont allow cache to
567             # grow without bound if somebody tries to hit the site with many
568             # missing URLs.  we could use an LRU cache instead, but then
569             # purposeful misses by an attacker would just blow out the cache
570             # anyway. downside: misses will almost always consume more CPU than
571             # hits in steady state.
572             with registry._lock:
573                 cache[(request_iface, context_iface, view_name)] = views
849196 574
99bc0c 575     return views
eb3ac8 576
849196 577 def _call_view(
CM 578     registry,
579     request,
580     context,
581     context_iface,
582     view_name,
17c7f4 583     view_types=None,
CM 584     view_classifier=None,
849196 585     secure=True,
17c7f4 586     request_iface=None,
849196 587     ):
17c7f4 588     if request_iface is None:
CM 589         request_iface = getattr(request, 'request_iface', IRequest)
eb3ac8 590     view_callables = _find_views(
CM 591         registry,
17c7f4 592         request_iface,
eb3ac8 593         context_iface,
CM 594         view_name,
17c7f4 595         view_types=view_types,
CM 596         view_classifier=view_classifier,
eb3ac8 597         )
CM 598
599     pme = None
600     response = None
601
602     for view_callable in view_callables:
603         # look for views that meet the predicate criteria
604         try:
849196 605             if not secure:
CM 606                 # the view will have a __call_permissive__ attribute if it's
607                 # secured; otherwise it won't.
608                 view_callable = getattr(
609                     view_callable,
610                     '__call_permissive__',
611                     view_callable
612                     )
613
614             # if this view is secured, it will raise a Forbidden
615             # appropriately if the executing user does not have the proper
616             # permission
eb3ac8 617             response = view_callable(context, request)
CM 618             return response
619         except PredicateMismatch as _pme:
620             pme = _pme
621
622     if pme is not None:
623         raise pme
624
625     return response
531428 626
CM 627 class ViewMethodsMixin(object):
628     """ Request methods mixin for BaseRequest having to do with executing
629     views """
630     def invoke_exception_view(
631         self,
e40ef2 632         exc_info=None,
531428 633         request=None,
6f2f04 634         secure=True,
MM 635         reraise=False,
636     ):
e40ef2 637         """ Executes an exception view related to the request it's called upon.
CM 638         The arguments it takes are these:
639
640         ``exc_info``
641
642             If provided, should be a 3-tuple in the form provided by
252fa5 643             ``sys.exc_info()``.  If not provided,
CM 644             ``sys.exc_info()`` will be called to obtain the current
e40ef2 645             interpreter exception information.  Default: ``None``.
CM 646
647         ``request``
648
649             If the request to be used is not the same one as the instance that
650             this method is called upon, it may be passed here.  Default:
651             ``None``.
652
653         ``secure``
654
655             If the exception view should not be rendered if the current user
656             does not have the appropriate permission, this should be ``True``.
657             Default: ``True``.
658
6f2f04 659         ``reraise``
e40ef2 660
6f2f04 661             A boolean indicating whether the original error should be reraised
MM 662             if a :term:`response` object could not be created. If ``False``
663             then an :class:`pyramid.httpexceptions.HTTPNotFound`` exception
664             will be raised. Default: ``False``.
e40ef2 665
3b886e 666         If a response is generated then ``request.exception`` and
MM 667         ``request.exc_info`` will be left at the values used to render the
668         response. Otherwise the previous values for ``request.exception`` and
669         ``request.exc_info`` will be restored.
670
daf06d 671         .. versionadded:: 1.7
MM 672
e2e51b 673         .. versionchanged:: 1.9
MM 674            The ``request.exception`` and ``request.exc_info`` properties will
675            reflect the exception used to render the response where previously
676            they were reset to the values prior to invoking the method.
6f2f04 677
MM 678            Also added the ``reraise`` argument.
e2e51b 679
3b886e 680         """
531428 681         if request is None:
CM 682             request = self
683         registry = getattr(request, 'registry', None)
684         if registry is None:
685             registry = get_current_registry()
dda9fa 686
BJR 687         if registry is None:
688             raise RuntimeError("Unable to retrieve registry")
689
e40ef2 690         if exc_info is None:
252fa5 691             exc_info = sys.exc_info()
dda9fa 692
d52257 693         exc = exc_info[1]
ca529f 694         attrs = request.__dict__
d52257 695         context_iface = providedBy(exc)
19016b 696
MM 697         # clear old generated request.response, if any; it may
698         # have been mutated by the view, and its state is not
699         # sane (e.g. caching headers)
3b886e 700         with hide_attrs(request, 'response', 'exc_info', 'exception'):
d52257 701             attrs['exception'] = exc
68b303 702             attrs['exc_info'] = exc_info
19016b 703             # we use .get instead of .__getitem__ below due to
MM 704             # https://github.com/Pylons/pyramid/issues/700
705             request_iface = attrs.get('request_iface', IRequest)
dda9fa 706
4f6635 707             manager.push({'request': request, 'registry': registry})
dda9fa 708
4f6635 709             try:
dda9fa 710                 response = _call_view(
BJR 711                     registry,
712                     request,
713                     exc,
714                     context_iface,
715                     '',
716                     view_types=None,
717                     view_classifier=IExceptionViewClassifier,
718                     secure=secure,
719                     request_iface=request_iface.combined,
720                     )
6f2f04 721             except:
MM 722                 if reraise:
723                     reraise_(*exc_info)
724                 raise
dda9fa 725             finally:
BJR 726                 manager.pop()
3b886e 727
MM 728         if response is None:
6f2f04 729             if reraise:
MM 730                 reraise_(*exc_info)
3b886e 731             raise HTTPNotFound
MM 732
733         # successful response, overwrite exception/exc_info
734         attrs['exception'] = exc
735         attrs['exc_info'] = exc_info
736         return response