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