Michael Merickel
2018-10-15 0c29cf2df41600d3906d521c72991c7686018b71
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.
2033ee 201     
SP 202     .. seealso::
203     
204         See also :ref:`mapping_views_using_a_decorator_section` for
205         details about using :class:`pyramid.view.view_config`.
a8d71c 206
2033ee 207     .. warning::
SP 208     
209         ``view_config`` will work ONLY on module top level members
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
b5422e 405     attempted, but instead of :class:`~pyramid.httpexceptions.HTTPTemporaryRedirect`
24358c 406     being used, :class:`~pyramid.httpexceptions.HTTPMovedPermanently will
CM 407     be used` for the redirect response if a slash-appended route is found.
408
0db4a1 409     See :ref:`changing_the_notfound_view` for detailed usage information.
498158 410
MM 411     .. versionchanged:: 1.9.1
412        Added the ``_depth`` and ``_category`` arguments.
0db4a1 413
CM 414     """
415
416     venusian = venusian
417
8ec8e2 418     def __init__(self, **settings):
CM 419         self.__dict__.update(settings)
0db4a1 420
CM 421     def __call__(self, wrapped):
422         settings = self.__dict__.copy()
498158 423         depth = settings.pop('_depth', 0)
MM 424         category = settings.pop('_category', 'pyramid')
0db4a1 425
CM 426         def callback(context, name, ob):
427             config = context.config.with_package(info.module)
428             config.add_notfound_view(view=ob, **settings)
429
0c29cf 430         info = self.venusian.attach(
MM 431             wrapped, callback, category=category, depth=depth + 1
432         )
0db4a1 433
CM 434         if info.scope == 'class':
435             # if the decorator was attached to a method in a class, or
436             # otherwise executed at class scope, we need to set an
437             # 'attr' into the settings if one isn't already in there
438             if settings.get('attr') is None:
439                 settings['attr'] = wrapped.__name__
440
0c29cf 441         settings['_info'] = info.codeinfo  # fbo "action_method"
0db4a1 442         return wrapped
0c29cf 443
0db4a1 444
a7fe30 445 class forbidden_view_config(object):
CM 446     """
05e928 447     .. versionadded:: 1.3
a7fe30 448
CM 449     An analogue of :class:`pyramid.view.view_config` which registers a
e8c66a 450     :term:`forbidden view` using
MM 451     :meth:`pyramid.config.Configurator.add_forbidden_view`.
a7fe30 452
CM 453     The forbidden_view_config constructor accepts most of the same arguments
454     as the constructor of :class:`pyramid.view.view_config`.  It can be used
455     in the same places, and behaves in largely the same way, except it always
8ec8e2 456     registers a forbidden exception view instead of a 'normal' view.
a7fe30 457
CM 458     Example:
459
460     .. code-block:: python
461
462         from pyramid.view import forbidden_view_config
463         from pyramid.response import Response
a8f669 464
15e3b1 465         @forbidden_view_config()
bbd3ab 466         def forbidden(request):
59e7cc 467             return Response('You are not allowed', status='403 Forbidden')
a7fe30 468
8ec8e2 469     All arguments passed to this function have the same meaning as
CM 470     :meth:`pyramid.view.view_config` and each predicate argument restricts
471     the set of circumstances under which this notfound view will be invoked.
a7fe30 472
CM 473     See :ref:`changing_the_forbidden_view` for detailed usage information.
474
498158 475     .. versionchanged:: 1.9.1
MM 476        Added the ``_depth`` and ``_category`` arguments.
477
a7fe30 478     """
CM 479
480     venusian = venusian
481
8ec8e2 482     def __init__(self, **settings):
CM 483         self.__dict__.update(settings)
a7fe30 484
CM 485     def __call__(self, wrapped):
486         settings = self.__dict__.copy()
498158 487         depth = settings.pop('_depth', 0)
MM 488         category = settings.pop('_category', 'pyramid')
a7fe30 489
CM 490         def callback(context, name, ob):
491             config = context.config.with_package(info.module)
492             config.add_forbidden_view(view=ob, **settings)
493
0c29cf 494         info = self.venusian.attach(
MM 495             wrapped, callback, category=category, depth=depth + 1
496         )
a7fe30 497
CM 498         if info.scope == 'class':
499             # if the decorator was attached to a method in a class, or
500             # otherwise executed at class scope, we need to set an
501             # 'attr' into the settings if one isn't already in there
502             if settings.get('attr') is None:
503                 settings['attr'] = wrapped.__name__
504
0c29cf 505         settings['_info'] = info.codeinfo  # fbo "action_method"
a7fe30 506         return wrapped
0c29cf 507
a8f669 508
93c94b 509 class exception_view_config(object):
AL 510     """
511     .. versionadded:: 1.8
512
513     An analogue of :class:`pyramid.view.view_config` which registers an
e8c66a 514     :term:`exception view` using
MM 515     :meth:`pyramid.config.Configurator.add_exception_view`.
93c94b 516
e8c66a 517     The ``exception_view_config`` constructor requires an exception context,
MM 518     and additionally accepts most of the same arguments as the constructor of
93c94b 519     :class:`pyramid.view.view_config`.  It can be used in the same places,
e8c66a 520     and behaves in largely the same way, except it always registers an
160aab 521     exception view instead of a "normal" view that dispatches on the request
e8c66a 522     :term:`context`.
93c94b 523
AL 524     Example:
525
526     .. code-block:: python
527
528         from pyramid.view import exception_view_config
529         from pyramid.response import Response
530
e8c66a 531         @exception_view_config(ValueError, renderer='json')
MM 532         def error_view(request):
533             return {'error': str(request.exception)}
93c94b 534
AL 535     All arguments passed to this function have the same meaning as
160aab 536     :meth:`pyramid.view.view_config`, and each predicate argument restricts
93c94b 537     the set of circumstances under which this exception view will be invoked.
e8c66a 538
498158 539     .. versionchanged:: 1.9.1
MM 540        Added the ``_depth`` and ``_category`` arguments.
541
93c94b 542     """
0c29cf 543
74842a 544     venusian = venusian
93c94b 545
e8c66a 546     def __init__(self, *args, **settings):
MM 547         if 'context' not in settings and len(args) > 0:
548             exception, args = args[0], args[1:]
549             settings['context'] = exception
550         if len(args) > 0:
551             raise ConfigurationError('unknown positional arguments')
93c94b 552         self.__dict__.update(settings)
AL 553
554     def __call__(self, wrapped):
555         settings = self.__dict__.copy()
498158 556         depth = settings.pop('_depth', 0)
MM 557         category = settings.pop('_category', 'pyramid')
93c94b 558
AL 559         def callback(context, name, ob):
560             config = context.config.with_package(info.module)
561             config.add_exception_view(view=ob, **settings)
562
0c29cf 563         info = self.venusian.attach(
MM 564             wrapped, callback, category=category, depth=depth + 1
565         )
93c94b 566
AL 567         if info.scope == 'class':
568             # if the decorator was attached to a method in a class, or
569             # otherwise executed at class scope, we need to set an
160aab 570             # 'attr' in the settings if one isn't already in there
93c94b 571             if settings.get('attr') is None:
AL 572                 settings['attr'] = wrapped.__name__
573
0c29cf 574         settings['_info'] = info.codeinfo  # fbo "action_method"
93c94b 575         return wrapped
0c29cf 576
93c94b 577
17c7f4 578 def _find_views(
CM 579     registry,
580     request_iface,
581     context_iface,
582     view_name,
583     view_types=None,
584     view_classifier=None,
0c29cf 585 ):
03c11e 586     if view_types is None:
17c7f4 587         view_types = (IView, ISecuredView, IMultiView)
03c11e 588     if view_classifier is None:
17c7f4 589         view_classifier = IViewClassifier
fb2824 590     registered = registry.adapters.registered
c15cbc 591     cache = registry._view_lookup_cache
CM 592     views = cache.get((request_iface, context_iface, view_name))
593     if views is None:
594         views = []
595         for req_type, ctx_type in itertools.product(
596             request_iface.__sro__, context_iface.__sro__
597         ):
17c7f4 598             source_ifaces = (view_classifier, req_type, ctx_type)
c15cbc 599             for view_type in view_types:
CM 600                 view_callable = registered(
0c29cf 601                     source_ifaces, view_type, name=view_name
c15cbc 602                 )
CM 603                 if view_callable is not None:
604                     views.append(view_callable)
605         if views:
606             # do not cache view lookup misses.  rationale: dont allow cache to
607             # grow without bound if somebody tries to hit the site with many
608             # missing URLs.  we could use an LRU cache instead, but then
609             # purposeful misses by an attacker would just blow out the cache
610             # anyway. downside: misses will almost always consume more CPU than
611             # hits in steady state.
612             with registry._lock:
613                 cache[(request_iface, context_iface, view_name)] = views
849196 614
99bc0c 615     return views
eb3ac8 616
0c29cf 617
849196 618 def _call_view(
CM 619     registry,
620     request,
621     context,
622     context_iface,
623     view_name,
17c7f4 624     view_types=None,
CM 625     view_classifier=None,
849196 626     secure=True,
17c7f4 627     request_iface=None,
0c29cf 628 ):
17c7f4 629     if request_iface is None:
CM 630         request_iface = getattr(request, 'request_iface', IRequest)
eb3ac8 631     view_callables = _find_views(
CM 632         registry,
17c7f4 633         request_iface,
eb3ac8 634         context_iface,
CM 635         view_name,
17c7f4 636         view_types=view_types,
CM 637         view_classifier=view_classifier,
0c29cf 638     )
eb3ac8 639
CM 640     pme = None
641     response = None
642
643     for view_callable in view_callables:
644         # look for views that meet the predicate criteria
645         try:
849196 646             if not secure:
CM 647                 # the view will have a __call_permissive__ attribute if it's
648                 # secured; otherwise it won't.
649                 view_callable = getattr(
0c29cf 650                     view_callable, '__call_permissive__', view_callable
MM 651                 )
849196 652
CM 653             # if this view is secured, it will raise a Forbidden
654             # appropriately if the executing user does not have the proper
655             # permission
eb3ac8 656             response = view_callable(context, request)
CM 657             return response
658         except PredicateMismatch as _pme:
659             pme = _pme
660
661     if pme is not None:
662         raise pme
663
664     return response
531428 665
0c29cf 666
531428 667 class ViewMethodsMixin(object):
CM 668     """ Request methods mixin for BaseRequest having to do with executing
669     views """
0c29cf 670
531428 671     def invoke_exception_view(
0c29cf 672         self, exc_info=None, request=None, secure=True, reraise=False
6f2f04 673     ):
e40ef2 674         """ Executes an exception view related to the request it's called upon.
CM 675         The arguments it takes are these:
676
677         ``exc_info``
678
679             If provided, should be a 3-tuple in the form provided by
252fa5 680             ``sys.exc_info()``.  If not provided,
CM 681             ``sys.exc_info()`` will be called to obtain the current
e40ef2 682             interpreter exception information.  Default: ``None``.
CM 683
684         ``request``
685
686             If the request to be used is not the same one as the instance that
687             this method is called upon, it may be passed here.  Default:
688             ``None``.
689
690         ``secure``
691
692             If the exception view should not be rendered if the current user
693             does not have the appropriate permission, this should be ``True``.
694             Default: ``True``.
695
6f2f04 696         ``reraise``
e40ef2 697
6f2f04 698             A boolean indicating whether the original error should be reraised
MM 699             if a :term:`response` object could not be created. If ``False``
700             then an :class:`pyramid.httpexceptions.HTTPNotFound`` exception
701             will be raised. Default: ``False``.
e40ef2 702
3b886e 703         If a response is generated then ``request.exception`` and
MM 704         ``request.exc_info`` will be left at the values used to render the
705         response. Otherwise the previous values for ``request.exception`` and
706         ``request.exc_info`` will be restored.
707
daf06d 708         .. versionadded:: 1.7
MM 709
e2e51b 710         .. versionchanged:: 1.9
MM 711            The ``request.exception`` and ``request.exc_info`` properties will
712            reflect the exception used to render the response where previously
713            they were reset to the values prior to invoking the method.
6f2f04 714
MM 715            Also added the ``reraise`` argument.
e2e51b 716
3b886e 717         """
531428 718         if request is None:
CM 719             request = self
720         registry = getattr(request, 'registry', None)
721         if registry is None:
722             registry = get_current_registry()
dda9fa 723
BJR 724         if registry is None:
725             raise RuntimeError("Unable to retrieve registry")
726
e40ef2 727         if exc_info is None:
252fa5 728             exc_info = sys.exc_info()
dda9fa 729
d52257 730         exc = exc_info[1]
ca529f 731         attrs = request.__dict__
d52257 732         context_iface = providedBy(exc)
19016b 733
MM 734         # clear old generated request.response, if any; it may
735         # have been mutated by the view, and its state is not
736         # sane (e.g. caching headers)
3b886e 737         with hide_attrs(request, 'response', 'exc_info', 'exception'):
d52257 738             attrs['exception'] = exc
68b303 739             attrs['exc_info'] = exc_info
19016b 740             # we use .get instead of .__getitem__ below due to
MM 741             # https://github.com/Pylons/pyramid/issues/700
742             request_iface = attrs.get('request_iface', IRequest)
dda9fa 743
4f6635 744             manager.push({'request': request, 'registry': registry})
dda9fa 745
4f6635 746             try:
dda9fa 747                 response = _call_view(
BJR 748                     registry,
749                     request,
750                     exc,
751                     context_iface,
752                     '',
753                     view_types=None,
754                     view_classifier=IExceptionViewClassifier,
755                     secure=secure,
756                     request_iface=request_iface.combined,
0c29cf 757                 )
87b7b7 758             except Exception:
6f2f04 759                 if reraise:
MM 760                     reraise_(*exc_info)
761                 raise
dda9fa 762             finally:
BJR 763                 manager.pop()
3b886e 764
MM 765         if response is None:
6f2f04 766             if reraise:
MM 767                 reraise_(*exc_info)
3b886e 768             raise HTTPNotFound
MM 769
770         # successful response, overwrite exception/exc_info
771         attrs['exception'] = exc
772         attrs['exc_info'] = exc_info
773         return response