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