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