Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/pyramid/config/views.py : 63%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import functools
2import inspect
3import posixpath
4import operator
5import os
6import warnings
8from webob.acceptparse import Accept
9from zope.interface import Interface, implementedBy, implementer
10from zope.interface.interfaces import IInterface
12from pyramid.interfaces import (
13 IAcceptOrder,
14 IExceptionViewClassifier,
15 IException,
16 IMultiView,
17 IPackageOverrides,
18 IRendererFactory,
19 IRequest,
20 IResponse,
21 IRouteRequest,
22 ISecuredView,
23 IStaticURLInfo,
24 IView,
25 IViewClassifier,
26 IViewDerivers,
27 IViewDeriverInfo,
28 IViewMapperFactory,
29 PHASE1_CONFIG,
30)
32from pyramid import renderers
34from pyramid.asset import resolve_asset_spec
35from pyramid.compat import (
36 string_types,
37 urlparse,
38 url_quote,
39 WIN,
40 is_nonstr_iter,
41)
43from pyramid.decorator import reify
45from pyramid.exceptions import ConfigurationError, PredicateMismatch
47from pyramid.httpexceptions import (
48 HTTPForbidden,
49 HTTPNotFound,
50 default_exceptionresponse_view,
51)
53from pyramid.registry import Deferred
55from pyramid.security import NO_PERMISSION_REQUIRED
56from pyramid.static import static_view
58from pyramid.url import parse_url_overrides
60from pyramid.view import AppendSlashNotFoundViewFactory
62from pyramid.util import as_sorted_tuple, TopologicalSorter
64import pyramid.predicates
65import pyramid.viewderivers
67from pyramid.viewderivers import (
68 INGRESS,
69 VIEW,
70 preserve_view_attrs,
71 view_description,
72 requestonly,
73 DefaultViewMapper,
74 wraps_view,
75)
77from pyramid.config.actions import action_method
78from pyramid.config.predicates import (
79 DEFAULT_PHASH,
80 MAX_ORDER,
81 normalize_accept_offer,
82 predvalseq,
83 sort_accept_offers,
84)
86urljoin = urlparse.urljoin
87url_parse = urlparse.urlparse
89DefaultViewMapper = DefaultViewMapper # bw-compat
90preserve_view_attrs = preserve_view_attrs # bw-compat
91requestonly = requestonly # bw-compat
92view_description = view_description # bw-compat
95@implementer(IMultiView)
96class MultiView(object):
97 def __init__(self, name):
98 self.name = name
99 self.media_views = {}
100 self.views = []
101 self.accepts = []
103 def __discriminator__(self, context, request):
104 # used by introspection systems like so:
105 # view = adapters.lookup(....)
106 # view.__discriminator__(context, request) -> view's discriminator
107 # so that superdynamic systems can feed the discriminator to
108 # the introspection system to get info about it
109 view = self.match(context, request)
110 return view.__discriminator__(context, request)
112 def add(self, view, order, phash=None, accept=None, accept_order=None):
113 if phash is not None:
114 for i, (s, v, h) in enumerate(list(self.views)):
115 if phash == h:
116 self.views[i] = (order, view, phash)
117 return
119 if accept is None or '*' in accept:
120 self.views.append((order, view, phash))
121 self.views.sort(key=operator.itemgetter(0))
122 else:
123 subset = self.media_views.setdefault(accept, [])
124 for i, (s, v, h) in enumerate(list(subset)):
125 if phash == h:
126 subset[i] = (order, view, phash)
127 return
128 else:
129 subset.append((order, view, phash))
130 subset.sort(key=operator.itemgetter(0))
131 # dedupe accepts and sort appropriately
132 accepts = set(self.accepts)
133 accepts.add(accept)
134 if accept_order:
135 accept_order = [v for _, v in accept_order.sorted()]
136 self.accepts = sort_accept_offers(accepts, accept_order)
138 def get_views(self, request):
139 if self.accepts and hasattr(request, 'accept'):
140 views = []
141 for offer, _ in request.accept.acceptable_offers(self.accepts):
142 views.extend(self.media_views[offer])
143 views.extend(self.views)
144 return views
145 return self.views
147 def match(self, context, request):
148 for order, view, phash in self.get_views(request):
149 if not hasattr(view, '__predicated__'):
150 return view
151 if view.__predicated__(context, request):
152 return view
153 raise PredicateMismatch(self.name)
155 def __permitted__(self, context, request):
156 view = self.match(context, request)
157 if hasattr(view, '__permitted__'):
158 return view.__permitted__(context, request)
159 return True
161 def __call_permissive__(self, context, request):
162 view = self.match(context, request)
163 view = getattr(view, '__call_permissive__', view)
164 return view(context, request)
166 def __call__(self, context, request):
167 for order, view, phash in self.get_views(request):
168 try:
169 return view(context, request)
170 except PredicateMismatch:
171 continue
172 raise PredicateMismatch(self.name)
175def attr_wrapped_view(view, info):
176 accept, order, phash = (
177 info.options.get('accept', None),
178 getattr(info, 'order', MAX_ORDER),
179 getattr(info, 'phash', DEFAULT_PHASH),
180 )
181 # this is a little silly but we don't want to decorate the original
182 # function with attributes that indicate accept, order, and phash,
183 # so we use a wrapper
184 if (accept is None) and (order == MAX_ORDER) and (phash == DEFAULT_PHASH):
185 return view # defaults
187 def attr_view(context, request):
188 return view(context, request)
190 attr_view.__accept__ = accept
191 attr_view.__order__ = order
192 attr_view.__phash__ = phash
193 attr_view.__view_attr__ = info.options.get('attr')
194 attr_view.__permission__ = info.options.get('permission')
195 return attr_view
198attr_wrapped_view.options = ('accept', 'attr', 'permission')
201def predicated_view(view, info):
202 preds = info.predicates
203 if not preds:
204 return view
206 def predicate_wrapper(context, request):
207 for predicate in preds:
208 if not predicate(context, request):
209 view_name = getattr(view, '__name__', view)
210 raise PredicateMismatch(
211 'predicate mismatch for view %s (%s)'
212 % (view_name, predicate.text())
213 )
214 return view(context, request)
216 def checker(context, request):
217 return all((predicate(context, request) for predicate in preds))
219 predicate_wrapper.__predicated__ = checker
220 predicate_wrapper.__predicates__ = preds
221 return predicate_wrapper
224def viewdefaults(wrapped):
225 """ Decorator for add_view-like methods which takes into account
226 __view_defaults__ attached to view it is passed. Not a documented API but
227 used by some external systems."""
229 def wrapper(self, *arg, **kw):
230 defaults = {}
231 if arg:
232 view = arg[0]
233 else:
234 view = kw.get('view')
235 view = self.maybe_dotted(view)
236 if inspect.isclass(view):
237 defaults = getattr(view, '__view_defaults__', {}).copy()
238 if '_backframes' not in kw:
239 kw['_backframes'] = 1 # for action_method
240 defaults.update(kw)
241 return wrapped(self, *arg, **defaults)
243 return functools.wraps(wrapped)(wrapper)
246def combine_decorators(*decorators):
247 def decorated(view_callable):
248 # reversed() allows a more natural ordering in the api
249 for decorator in reversed(decorators):
250 view_callable = decorator(view_callable)
251 return view_callable
253 return decorated
256class ViewsConfiguratorMixin(object):
257 @viewdefaults
258 @action_method
259 def add_view(
260 self,
261 view=None,
262 name="",
263 for_=None,
264 permission=None,
265 request_type=None,
266 route_name=None,
267 request_method=None,
268 request_param=None,
269 containment=None,
270 attr=None,
271 renderer=None,
272 wrapper=None,
273 xhr=None,
274 accept=None,
275 header=None,
276 path_info=None,
277 custom_predicates=(),
278 context=None,
279 decorator=None,
280 mapper=None,
281 http_cache=None,
282 match_param=None,
283 check_csrf=None,
284 require_csrf=None,
285 exception_only=False,
286 **view_options
287 ):
288 """ Add a :term:`view configuration` to the current
289 configuration state. Arguments to ``add_view`` are broken
290 down below into *predicate* arguments and *non-predicate*
291 arguments. Predicate arguments narrow the circumstances in
292 which the view callable will be invoked when a request is
293 presented to :app:`Pyramid`; non-predicate arguments are
294 informational.
296 Non-Predicate Arguments
298 view
300 A :term:`view callable` or a :term:`dotted Python name`
301 which refers to a view callable. This argument is required
302 unless a ``renderer`` argument also exists. If a
303 ``renderer`` argument is passed, and a ``view`` argument is
304 not provided, the view callable defaults to a callable that
305 returns an empty dictionary (see
306 :ref:`views_which_use_a_renderer`).
308 permission
310 A :term:`permission` that the user must possess in order to invoke
311 the :term:`view callable`. See :ref:`view_security_section` for
312 more information about view security and permissions. This is
313 often a string like ``view`` or ``edit``.
315 If ``permission`` is omitted, a *default* permission may be used
316 for this view registration if one was named as the
317 :class:`pyramid.config.Configurator` constructor's
318 ``default_permission`` argument, or if
319 :meth:`pyramid.config.Configurator.set_default_permission` was used
320 prior to this view registration. Pass the value
321 :data:`pyramid.security.NO_PERMISSION_REQUIRED` as the permission
322 argument to explicitly indicate that the view should always be
323 executable by entirely anonymous users, regardless of the default
324 permission, bypassing any :term:`authorization policy` that may be
325 in effect.
327 attr
329 This knob is most useful when the view definition is a class.
331 The view machinery defaults to using the ``__call__`` method
332 of the :term:`view callable` (or the function itself, if the
333 view callable is a function) to obtain a response. The
334 ``attr`` value allows you to vary the method attribute used
335 to obtain the response. For example, if your view was a
336 class, and the class has a method named ``index`` and you
337 wanted to use this method instead of the class' ``__call__``
338 method to return the response, you'd say ``attr="index"`` in the
339 view configuration for the view.
341 renderer
343 This is either a single string term (e.g. ``json``) or a
344 string implying a path or :term:`asset specification`
345 (e.g. ``templates/views.pt``) naming a :term:`renderer`
346 implementation. If the ``renderer`` value does not contain
347 a dot ``.``, the specified string will be used to look up a
348 renderer implementation, and that renderer implementation
349 will be used to construct a response from the view return
350 value. If the ``renderer`` value contains a dot (``.``),
351 the specified term will be treated as a path, and the
352 filename extension of the last element in the path will be
353 used to look up the renderer implementation, which will be
354 passed the full path. The renderer implementation will be
355 used to construct a :term:`response` from the view return
356 value.
358 Note that if the view itself returns a :term:`response` (see
359 :ref:`the_response`), the specified renderer implementation
360 is never called.
362 When the renderer is a path, although a path is usually just
363 a simple relative pathname (e.g. ``templates/foo.pt``,
364 implying that a template named "foo.pt" is in the
365 "templates" directory relative to the directory of the
366 current :term:`package` of the Configurator), a path can be
367 absolute, starting with a slash on UNIX or a drive letter
368 prefix on Windows. The path can alternately be a
369 :term:`asset specification` in the form
370 ``some.dotted.package_name:relative/path``, making it
371 possible to address template assets which live in a
372 separate package.
374 The ``renderer`` attribute is optional. If it is not
375 defined, the "null" renderer is assumed (no rendering is
376 performed and the value is passed back to the upstream
377 :app:`Pyramid` machinery unmodified).
379 http_cache
381 .. versionadded:: 1.1
383 When you supply an ``http_cache`` value to a view configuration,
384 the ``Expires`` and ``Cache-Control`` headers of a response
385 generated by the associated view callable are modified. The value
386 for ``http_cache`` may be one of the following:
388 - A nonzero integer. If it's a nonzero integer, it's treated as a
389 number of seconds. This number of seconds will be used to
390 compute the ``Expires`` header and the ``Cache-Control:
391 max-age`` parameter of responses to requests which call this view.
392 For example: ``http_cache=3600`` instructs the requesting browser
393 to 'cache this response for an hour, please'.
395 - A ``datetime.timedelta`` instance. If it's a
396 ``datetime.timedelta`` instance, it will be converted into a
397 number of seconds, and that number of seconds will be used to
398 compute the ``Expires`` header and the ``Cache-Control:
399 max-age`` parameter of responses to requests which call this view.
400 For example: ``http_cache=datetime.timedelta(days=1)`` instructs
401 the requesting browser to 'cache this response for a day, please'.
403 - Zero (``0``). If the value is zero, the ``Cache-Control`` and
404 ``Expires`` headers present in all responses from this view will
405 be composed such that client browser cache (and any intermediate
406 caches) are instructed to never cache the response.
408 - A two-tuple. If it's a two tuple (e.g. ``http_cache=(1,
409 {'public':True})``), the first value in the tuple may be a
410 nonzero integer or a ``datetime.timedelta`` instance; in either
411 case this value will be used as the number of seconds to cache
412 the response. The second value in the tuple must be a
413 dictionary. The values present in the dictionary will be used as
414 input to the ``Cache-Control`` response header. For example:
415 ``http_cache=(3600, {'public':True})`` means 'cache for an hour,
416 and add ``public`` to the Cache-Control header of the response'.
417 All keys and values supported by the
418 ``webob.cachecontrol.CacheControl`` interface may be added to the
419 dictionary. Supplying ``{'public':True}`` is equivalent to
420 calling ``response.cache_control.public = True``.
422 Providing a non-tuple value as ``http_cache`` is equivalent to
423 calling ``response.cache_expires(value)`` within your view's body.
425 Providing a two-tuple value as ``http_cache`` is equivalent to
426 calling ``response.cache_expires(value[0], **value[1])`` within your
427 view's body.
429 If you wish to avoid influencing, the ``Expires`` header, and
430 instead wish to only influence ``Cache-Control`` headers, pass a
431 tuple as ``http_cache`` with the first element of ``None``, e.g.:
432 ``(None, {'public':True})``.
434 If you wish to prevent a view that uses ``http_cache`` in its
435 configuration from having its caching response headers changed by
436 this machinery, set ``response.cache_control.prevent_auto = True``
437 before returning the response from the view. This effectively
438 disables any HTTP caching done by ``http_cache`` for that response.
440 require_csrf
442 .. versionadded:: 1.7
444 A boolean option or ``None``. Default: ``None``.
446 If this option is set to ``True`` then CSRF checks will be enabled
447 for requests to this view. The required token or header default to
448 ``csrf_token`` and ``X-CSRF-Token``, respectively.
450 CSRF checks only affect "unsafe" methods as defined by RFC2616. By
451 default, these methods are anything except
452 ``GET``, ``HEAD``, ``OPTIONS``, and ``TRACE``.
454 The defaults here may be overridden by
455 :meth:`pyramid.config.Configurator.set_default_csrf_options`.
457 This feature requires a configured :term:`session factory`.
459 If this option is set to ``False`` then CSRF checks will be disabled
460 regardless of the default ``require_csrf`` setting passed
461 to ``set_default_csrf_options``.
463 See :ref:`auto_csrf_checking` for more information.
465 wrapper
467 The :term:`view name` of a different :term:`view
468 configuration` which will receive the response body of this
469 view as the ``request.wrapped_body`` attribute of its own
470 :term:`request`, and the :term:`response` returned by this
471 view as the ``request.wrapped_response`` attribute of its
472 own request. Using a wrapper makes it possible to "chain"
473 views together to form a composite response. The response
474 of the outermost wrapper view will be returned to the user.
475 The wrapper view will be found as any view is found: see
476 :ref:`view_lookup`. The "best" wrapper view will be found
477 based on the lookup ordering: "under the hood" this wrapper
478 view is looked up via
479 ``pyramid.view.render_view_to_response(context, request,
480 'wrapper_viewname')``. The context and request of a wrapper
481 view is the same context and request of the inner view. If
482 this attribute is unspecified, no view wrapping is done.
484 decorator
486 A :term:`dotted Python name` to function (or the function itself,
487 or an iterable of the aforementioned) which will be used to
488 decorate the registered :term:`view callable`. The decorator
489 function(s) will be called with the view callable as a single
490 argument. The view callable it is passed will accept
491 ``(context, request)``. The decorator(s) must return a
492 replacement view callable which also accepts ``(context,
493 request)``.
495 If decorator is an iterable, the callables will be combined and
496 used in the order provided as a decorator.
497 For example::
499 @view_config(...,
500 decorator=(decorator2,
501 decorator1))
502 def myview(request):
503 ....
505 Is similar to doing::
507 @view_config(...)
508 @decorator2
509 @decorator1
510 def myview(request):
511 ...
513 Except with the existing benefits of ``decorator=`` (having a common
514 decorator syntax for all view calling conventions and not having to
515 think about preserving function attributes such as ``__name__`` and
516 ``__module__`` within decorator logic).
518 An important distinction is that each decorator will receive a
519 response object implementing :class:`pyramid.interfaces.IResponse`
520 instead of the raw value returned from the view callable. All
521 decorators in the chain must return a response object or raise an
522 exception:
524 .. code-block:: python
526 def log_timer(wrapped):
527 def wrapper(context, request):
528 start = time.time()
529 response = wrapped(context, request)
530 duration = time.time() - start
531 response.headers['X-View-Time'] = '%.3f' % (duration,)
532 log.info('view took %.3f seconds', duration)
533 return response
534 return wrapper
536 .. versionchanged:: 1.4a4
537 Passing an iterable.
539 mapper
541 A Python object or :term:`dotted Python name` which refers to a
542 :term:`view mapper`, or ``None``. By default it is ``None``, which
543 indicates that the view should use the default view mapper. This
544 plug-point is useful for Pyramid extension developers, but it's not
545 very useful for 'civilians' who are just developing stock Pyramid
546 applications. Pay no attention to the man behind the curtain.
548 accept
550 A :term:`media type` that will be matched against the ``Accept``
551 HTTP request header. If this value is specified, it must be a
552 specific media type such as ``text/html`` or ``text/html;level=1``.
553 If the media type is acceptable by the ``Accept`` header of the
554 request, or if the ``Accept`` header isn't set at all in the request,
555 this predicate will match. If this does not match the ``Accept``
556 header of the request, view matching continues.
558 If ``accept`` is not specified, the ``HTTP_ACCEPT`` HTTP header is
559 not taken into consideration when deciding whether or not to invoke
560 the associated view callable.
562 The ``accept`` argument is technically not a predicate and does
563 not support wrapping with :func:`pyramid.config.not_`.
565 See :ref:`accept_content_negotiation` for more information.
567 .. versionchanged:: 1.10
569 Specifying a media range is deprecated and will be removed in
570 :app:`Pyramid` 2.0. Use explicit media types to avoid any
571 ambiguities in content negotiation.
573 exception_only
575 .. versionadded:: 1.8
577 When this value is ``True``, the ``context`` argument must be
578 a subclass of ``Exception``. This flag indicates that only an
579 :term:`exception view` should be created, and that this view should
580 not match if the traversal :term:`context` matches the ``context``
581 argument. If the ``context`` is a subclass of ``Exception`` and
582 this value is ``False`` (the default), then a view will be
583 registered to match the traversal :term:`context` as well.
585 Predicate Arguments
587 name
589 The :term:`view name`. Read :ref:`traversal_chapter` to
590 understand the concept of a view name.
592 context
594 An object or a :term:`dotted Python name` referring to an
595 interface or class object that the :term:`context` must be
596 an instance of, *or* the :term:`interface` that the
597 :term:`context` must provide in order for this view to be
598 found and called. This predicate is true when the
599 :term:`context` is an instance of the represented class or
600 if the :term:`context` provides the represented interface;
601 it is otherwise false. This argument may also be provided
602 to ``add_view`` as ``for_`` (an older, still-supported
603 spelling). If the view should *only* match when handling
604 exceptions, then set the ``exception_only`` to ``True``.
606 route_name
608 This value must match the ``name`` of a :term:`route
609 configuration` declaration (see :ref:`urldispatch_chapter`)
610 that must match before this view will be called.
612 request_type
614 This value should be an :term:`interface` that the
615 :term:`request` must provide in order for this view to be
616 found and called. This value exists only for backwards
617 compatibility purposes.
619 request_method
621 This value can be either a string (such as ``"GET"``, ``"POST"``,
622 ``"PUT"``, ``"DELETE"``, ``"HEAD"`` or ``"OPTIONS"``) representing
623 an HTTP ``REQUEST_METHOD``, or a tuple containing one or more of
624 these strings. A view declaration with this argument ensures that
625 the view will only be called when the ``method`` attribute of the
626 request (aka the ``REQUEST_METHOD`` of the WSGI environment) matches
627 a supplied value. Note that use of ``GET`` also implies that the
628 view will respond to ``HEAD`` as of Pyramid 1.4.
630 .. versionchanged:: 1.2
631 The ability to pass a tuple of items as ``request_method``.
632 Previous versions allowed only a string.
634 request_param
636 This value can be any string or any sequence of strings. A view
637 declaration with this argument ensures that the view will only be
638 called when the :term:`request` has a key in the ``request.params``
639 dictionary (an HTTP ``GET`` or ``POST`` variable) that has a
640 name which matches the supplied value (if the value is a string)
641 or values (if the value is a tuple). If any value
642 supplied has a ``=`` sign in it,
643 e.g. ``request_param="foo=123"``, then the key (``foo``)
644 must both exist in the ``request.params`` dictionary, *and*
645 the value must match the right hand side of the expression
646 (``123``) for the view to "match" the current request.
648 match_param
650 .. versionadded:: 1.2
652 This value can be a string of the format "key=value" or a tuple
653 containing one or more of these strings.
655 A view declaration with this argument ensures that the view will
656 only be called when the :term:`request` has key/value pairs in its
657 :term:`matchdict` that equal those supplied in the predicate.
658 e.g. ``match_param="action=edit"`` would require the ``action``
659 parameter in the :term:`matchdict` match the right hand side of
660 the expression (``edit``) for the view to "match" the current
661 request.
663 If the ``match_param`` is a tuple, every key/value pair must match
664 for the predicate to pass.
666 containment
668 This value should be a Python class or :term:`interface` (or a
669 :term:`dotted Python name`) that an object in the
670 :term:`lineage` of the context must provide in order for this view
671 to be found and called. The nodes in your object graph must be
672 "location-aware" to use this feature. See
673 :ref:`location_aware` for more information about
674 location-awareness.
676 xhr
678 This value should be either ``True`` or ``False``. If this
679 value is specified and is ``True``, the :term:`request`
680 must possess an ``HTTP_X_REQUESTED_WITH`` (aka
681 ``X-Requested-With``) header that has the value
682 ``XMLHttpRequest`` for this view to be found and called.
683 This is useful for detecting AJAX requests issued from
684 jQuery, Prototype and other Javascript libraries.
686 header
688 This value represents an HTTP header name or a header
689 name/value pair. If the value contains a ``:`` (colon), it
690 will be considered a name/value pair
691 (e.g. ``User-Agent:Mozilla/.*`` or ``Host:localhost``). The
692 value portion should be a regular expression. If the value
693 does not contain a colon, the entire value will be
694 considered to be the header name
695 (e.g. ``If-Modified-Since``). If the value evaluates to a
696 header name only without a value, the header specified by
697 the name must be present in the request for this predicate
698 to be true. If the value evaluates to a header name/value
699 pair, the header specified by the name must be present in
700 the request *and* the regular expression specified as the
701 value must match the header value. Whether or not the value
702 represents a header name or a header name/value pair, the
703 case of the header name is not significant.
705 path_info
707 This value represents a regular expression pattern that will
708 be tested against the ``PATH_INFO`` WSGI environment
709 variable. If the regex matches, this predicate will be
710 ``True``.
712 check_csrf
714 .. deprecated:: 1.7
715 Use the ``require_csrf`` option or see :ref:`auto_csrf_checking`
716 instead to have :class:`pyramid.exceptions.BadCSRFToken`
717 exceptions raised.
719 If specified, this value should be one of ``None``, ``True``,
720 ``False``, or a string representing the 'check name'. If the value
721 is ``True`` or a string, CSRF checking will be performed. If the
722 value is ``False`` or ``None``, CSRF checking will not be performed.
724 If the value provided is a string, that string will be used as the
725 'check name'. If the value provided is ``True``, ``csrf_token`` will
726 be used as the check name.
728 If CSRF checking is performed, the checked value will be the value of
729 ``request.params[check_name]``. This value will be compared against
730 the value of ``policy.get_csrf_token()`` (where ``policy`` is an
731 implementation of :meth:`pyramid.interfaces.ICSRFStoragePolicy`), and
732 the check will pass if these two values are the same. If the check
733 passes, the associated view will be permitted to execute. If the
734 check fails, the associated view will not be permitted to execute.
736 .. versionadded:: 1.4a2
738 .. versionchanged:: 1.9
739 This feature requires either a :term:`session factory` to have been
740 configured, or a :term:`CSRF storage policy` other than the default
741 to be in use.
744 physical_path
746 If specified, this value should be a string or a tuple representing
747 the :term:`physical path` of the context found via traversal for this
748 predicate to match as true. For example: ``physical_path='/'`` or
749 ``physical_path='/a/b/c'`` or ``physical_path=('', 'a', 'b', 'c')``.
750 This is not a path prefix match or a regex, it's a whole-path match.
751 It's useful when you want to always potentially show a view when some
752 object is traversed to, but you can't be sure about what kind of
753 object it will be, so you can't use the ``context`` predicate. The
754 individual path elements inbetween slash characters or in tuple
755 elements should be the Unicode representation of the name of the
756 resource and should not be encoded in any way.
758 .. versionadded:: 1.4a3
760 effective_principals
762 If specified, this value should be a :term:`principal` identifier or
763 a sequence of principal identifiers. If the
764 :attr:`pyramid.request.Request.effective_principals` property
765 indicates that every principal named in the argument list is present
766 in the current request, this predicate will return True; otherwise it
767 will return False. For example:
768 ``effective_principals=pyramid.security.Authenticated`` or
769 ``effective_principals=('fred', 'group:admins')``.
771 .. versionadded:: 1.4a4
773 custom_predicates
775 .. deprecated:: 1.5
776 This value should be a sequence of references to custom
777 predicate callables. Use custom predicates when no set of
778 predefined predicates do what you need. Custom predicates
779 can be combined with predefined predicates as necessary.
780 Each custom predicate callable should accept two arguments:
781 ``context`` and ``request`` and should return either
782 ``True`` or ``False`` after doing arbitrary evaluation of
783 the context and/or the request. The ``predicates`` argument
784 to this method and the ability to register third-party view
785 predicates via
786 :meth:`pyramid.config.Configurator.add_view_predicate`
787 obsoletes this argument, but it is kept around for backwards
788 compatibility.
790 view_options
792 Pass a key/value pair here to use a third-party predicate or set a
793 value for a view deriver. See
794 :meth:`pyramid.config.Configurator.add_view_predicate` and
795 :meth:`pyramid.config.Configurator.add_view_deriver`. See
796 :ref:`view_and_route_predicates` for more information about
797 third-party predicates and :ref:`view_derivers` for information
798 about view derivers.
800 .. versionadded: 1.4a1
802 .. versionchanged: 1.7
804 Support setting view deriver options. Previously, only custom
805 view predicate values could be supplied.
807 """
808 if custom_predicates:
809 warnings.warn(
810 (
811 'The "custom_predicates" argument to '
812 'Configurator.add_view is deprecated as of Pyramid 1.5. '
813 'Use "config.add_view_predicate" and use the registered '
814 'view predicate as a predicate argument to add_view '
815 'instead. See "Adding A Third Party View, Route, or '
816 'Subscriber Predicate" in the "Hooks" chapter of the '
817 'documentation for more information.'
818 ),
819 DeprecationWarning,
820 stacklevel=4,
821 )
823 if check_csrf is not None:
824 warnings.warn(
825 (
826 'The "check_csrf" argument to Configurator.add_view is '
827 'deprecated as of Pyramid 1.7. Use the "require_csrf" '
828 'option instead or see "Checking CSRF Tokens '
829 'Automatically" in the "Sessions" chapter of the '
830 'documentation for more information.'
831 ),
832 DeprecationWarning,
833 stacklevel=4,
834 )
836 if accept is not None:
837 if is_nonstr_iter(accept):
838 raise ConfigurationError(
839 'A list is not supported in the "accept" view predicate.'
840 )
841 if '*' in accept:
842 warnings.warn(
843 (
844 'Passing a media range to the "accept" argument of '
845 'Configurator.add_view is deprecated as of '
846 'Pyramid 1.10. Use explicit media types to avoid '
847 'ambiguities in content negotiation that may impact '
848 'your users.'
849 ),
850 DeprecationWarning,
851 stacklevel=4,
852 )
853 # XXX when media ranges are gone, switch allow_range=False
854 accept = normalize_accept_offer(accept, allow_range=True)
856 view = self.maybe_dotted(view)
857 context = self.maybe_dotted(context)
858 for_ = self.maybe_dotted(for_)
859 containment = self.maybe_dotted(containment)
860 mapper = self.maybe_dotted(mapper)
862 if is_nonstr_iter(decorator):
863 decorator = combine_decorators(*map(self.maybe_dotted, decorator))
864 else:
865 decorator = self.maybe_dotted(decorator)
867 if not view:
868 if renderer:
870 def view(context, request):
871 return {}
873 else:
874 raise ConfigurationError(
875 '"view" was not specified and ' 'no "renderer" specified'
876 )
878 if request_type is not None:
879 request_type = self.maybe_dotted(request_type)
880 if not IInterface.providedBy(request_type):
881 raise ConfigurationError(
882 'request_type must be an interface, not %s' % request_type
883 )
885 if context is None:
886 context = for_
888 isexc = isexception(context)
889 if exception_only and not isexc:
890 raise ConfigurationError(
891 'view "context" must be an exception type when '
892 '"exception_only" is True'
893 )
895 r_context = context
896 if r_context is None:
897 r_context = Interface
898 if not IInterface.providedBy(r_context):
899 r_context = implementedBy(r_context)
901 if isinstance(renderer, string_types):
902 renderer = renderers.RendererHelper(
903 name=renderer, package=self.package, registry=self.registry
904 )
906 introspectables = []
907 ovals = view_options.copy()
908 ovals.update(
909 dict(
910 xhr=xhr,
911 request_method=request_method,
912 path_info=path_info,
913 request_param=request_param,
914 header=header,
915 accept=accept,
916 containment=containment,
917 request_type=request_type,
918 match_param=match_param,
919 check_csrf=check_csrf,
920 custom=predvalseq(custom_predicates),
921 )
922 )
924 def discrim_func():
925 # We need to defer the discriminator until we know what the phash
926 # is. It can't be computed any sooner because thirdparty
927 # predicates/view derivers may not yet exist when add_view is
928 # called.
929 predlist = self.get_predlist('view')
930 valid_predicates = predlist.names()
931 pvals = {}
932 dvals = {}
934 for (k, v) in ovals.items():
935 if k in valid_predicates:
936 pvals[k] = v
937 else:
938 dvals[k] = v
940 self._check_view_options(**dvals)
942 order, preds, phash = predlist.make(self, **pvals)
944 view_intr.update(
945 {'phash': phash, 'order': order, 'predicates': preds}
946 )
947 return ('view', context, name, route_name, phash)
949 discriminator = Deferred(discrim_func)
951 if inspect.isclass(view) and attr:
952 view_desc = 'method %r of %s' % (
953 attr,
954 self.object_description(view),
955 )
956 else:
957 view_desc = self.object_description(view)
959 tmpl_intr = None
961 view_intr = self.introspectable(
962 'views', discriminator, view_desc, 'view'
963 )
964 view_intr.update(
965 dict(
966 name=name,
967 context=context,
968 exception_only=exception_only,
969 containment=containment,
970 request_param=request_param,
971 request_methods=request_method,
972 route_name=route_name,
973 attr=attr,
974 xhr=xhr,
975 accept=accept,
976 header=header,
977 path_info=path_info,
978 match_param=match_param,
979 check_csrf=check_csrf,
980 http_cache=http_cache,
981 require_csrf=require_csrf,
982 callable=view,
983 mapper=mapper,
984 decorator=decorator,
985 )
986 )
987 view_intr.update(view_options)
988 introspectables.append(view_intr)
990 def register(permission=permission, renderer=renderer):
991 request_iface = IRequest
992 if route_name is not None:
993 request_iface = self.registry.queryUtility(
994 IRouteRequest, name=route_name
995 )
996 if request_iface is None:
997 # route configuration should have already happened in
998 # phase 2
999 raise ConfigurationError(
1000 'No route named %s found for view registration'
1001 % route_name
1002 )
1004 if renderer is None:
1005 # use default renderer if one exists (reg'd in phase 1)
1006 if self.registry.queryUtility(IRendererFactory) is not None:
1007 renderer = renderers.RendererHelper(
1008 name=None, package=self.package, registry=self.registry
1009 )
1011 renderer_type = getattr(renderer, 'type', None)
1012 intrspc = self.introspector
1013 if (
1014 renderer_type is not None
1015 and tmpl_intr is not None
1016 and intrspc is not None
1017 and intrspc.get('renderer factories', renderer_type)
1018 is not None
1019 ):
1020 # allow failure of registered template factories to be deferred
1021 # until view execution, like other bad renderer factories; if
1022 # we tried to relate this to an existing renderer factory
1023 # without checking if the factory actually existed, we'd end
1024 # up with a KeyError at startup time, which is inconsistent
1025 # with how other bad renderer registrations behave (they throw
1026 # a ValueError at view execution time)
1027 tmpl_intr.relate('renderer factories', renderer.type)
1029 # make a new view separately for normal and exception paths
1030 if not exception_only:
1031 derived_view = derive_view(False, renderer)
1032 register_view(IViewClassifier, request_iface, derived_view)
1033 if isexc:
1034 derived_exc_view = derive_view(True, renderer)
1035 register_view(
1036 IExceptionViewClassifier, request_iface, derived_exc_view
1037 )
1039 if exception_only:
1040 derived_view = derived_exc_view
1042 # if there are two derived views, combine them into one for
1043 # introspection purposes
1044 if not exception_only and isexc:
1045 derived_view = runtime_exc_view(derived_view, derived_exc_view)
1047 derived_view.__discriminator__ = lambda *arg: discriminator
1048 # __discriminator__ is used by superdynamic systems
1049 # that require it for introspection after manual view lookup;
1050 # see also MultiView.__discriminator__
1051 view_intr['derived_callable'] = derived_view
1053 self.registry._clear_view_lookup_cache()
1055 def derive_view(isexc_only, renderer):
1056 # added by discrim_func above during conflict resolving
1057 preds = view_intr['predicates']
1058 order = view_intr['order']
1059 phash = view_intr['phash']
1061 derived_view = self._derive_view(
1062 view,
1063 route_name=route_name,
1064 permission=permission,
1065 predicates=preds,
1066 attr=attr,
1067 context=context,
1068 exception_only=isexc_only,
1069 renderer=renderer,
1070 wrapper_viewname=wrapper,
1071 viewname=name,
1072 accept=accept,
1073 order=order,
1074 phash=phash,
1075 decorator=decorator,
1076 mapper=mapper,
1077 http_cache=http_cache,
1078 require_csrf=require_csrf,
1079 extra_options=ovals,
1080 )
1081 return derived_view
1083 def register_view(classifier, request_iface, derived_view):
1084 # A multiviews is a set of views which are registered for
1085 # exactly the same context type/request type/name triad. Each
1086 # constituent view in a multiview differs only by the
1087 # predicates which it possesses.
1089 # To find a previously registered view for a context
1090 # type/request type/name triad, we need to use the
1091 # ``registered`` method of the adapter registry rather than
1092 # ``lookup``. ``registered`` ignores interface inheritance
1093 # for the required and provided arguments, returning only a
1094 # view registered previously with the *exact* triad we pass
1095 # in.
1097 # We need to do this three times, because we use three
1098 # different interfaces as the ``provided`` interface while
1099 # doing registrations, and ``registered`` performs exact
1100 # matches on all the arguments it receives.
1102 old_view = None
1103 order, phash = view_intr['order'], view_intr['phash']
1104 registered = self.registry.adapters.registered
1106 for view_type in (IView, ISecuredView, IMultiView):
1107 old_view = registered(
1108 (classifier, request_iface, r_context), view_type, name
1109 )
1110 if old_view is not None:
1111 break
1113 old_phash = getattr(old_view, '__phash__', DEFAULT_PHASH)
1114 is_multiview = IMultiView.providedBy(old_view)
1115 want_multiview = (
1116 is_multiview
1117 # no component was yet registered for exactly this triad
1118 # or only one was registered but with the same phash, meaning
1119 # that this view is an override
1120 or (old_view is not None and old_phash != phash)
1121 )
1123 if not want_multiview:
1124 if hasattr(derived_view, '__call_permissive__'):
1125 view_iface = ISecuredView
1126 else:
1127 view_iface = IView
1128 self.registry.registerAdapter(
1129 derived_view,
1130 (classifier, request_iface, context),
1131 view_iface,
1132 name,
1133 )
1135 else:
1136 # - A view or multiview was already registered for this
1137 # triad, and the new view is not an override.
1139 # XXX we could try to be more efficient here and register
1140 # a non-secured view for a multiview if none of the
1141 # multiview's constituent views have a permission
1142 # associated with them, but this code is getting pretty
1143 # rough already
1144 if is_multiview:
1145 multiview = old_view
1146 else:
1147 multiview = MultiView(name)
1148 old_accept = getattr(old_view, '__accept__', None)
1149 old_order = getattr(old_view, '__order__', MAX_ORDER)
1150 # don't bother passing accept_order here as we know we're
1151 # adding another one right after which will re-sort
1152 multiview.add(old_view, old_order, old_phash, old_accept)
1153 accept_order = self.registry.queryUtility(IAcceptOrder)
1154 multiview.add(derived_view, order, phash, accept, accept_order)
1155 for view_type in (IView, ISecuredView):
1156 # unregister any existing views
1157 self.registry.adapters.unregister(
1158 (classifier, request_iface, r_context),
1159 view_type,
1160 name=name,
1161 )
1162 self.registry.registerAdapter(
1163 multiview,
1164 (classifier, request_iface, context),
1165 IMultiView,
1166 name=name,
1167 )
1169 if mapper:
1170 mapper_intr = self.introspectable(
1171 'view mappers',
1172 discriminator,
1173 'view mapper for %s' % view_desc,
1174 'view mapper',
1175 )
1176 mapper_intr['mapper'] = mapper
1177 mapper_intr.relate('views', discriminator)
1178 introspectables.append(mapper_intr)
1179 if route_name:
1180 view_intr.relate('routes', route_name) # see add_route
1181 if renderer is not None and renderer.name and '.' in renderer.name:
1182 # the renderer is a template
1183 tmpl_intr = self.introspectable(
1184 'templates', discriminator, renderer.name, 'template'
1185 )
1186 tmpl_intr.relate('views', discriminator)
1187 tmpl_intr['name'] = renderer.name
1188 tmpl_intr['type'] = renderer.type
1189 tmpl_intr['renderer'] = renderer
1190 introspectables.append(tmpl_intr)
1191 if permission is not None:
1192 # if a permission exists, register a permission introspectable
1193 perm_intr = self.introspectable(
1194 'permissions', permission, permission, 'permission'
1195 )
1196 perm_intr['value'] = permission
1197 perm_intr.relate('views', discriminator)
1198 introspectables.append(perm_intr)
1199 self.action(discriminator, register, introspectables=introspectables)
1201 def _check_view_options(self, **kw):
1202 # we only need to validate deriver options because the predicates
1203 # were checked by the predlist
1204 derivers = self.registry.getUtility(IViewDerivers)
1205 for deriver in derivers.values():
1206 for opt in getattr(deriver, 'options', []):
1207 kw.pop(opt, None)
1208 if kw:
1209 raise ConfigurationError('Unknown view options: %s' % (kw,))
1211 def _apply_view_derivers(self, info):
1212 # These derivers are not really derivers and so have fixed order
1213 outer_derivers = [
1214 ('attr_wrapped_view', attr_wrapped_view),
1215 ('predicated_view', predicated_view),
1216 ]
1218 view = info.original_view
1219 derivers = self.registry.getUtility(IViewDerivers)
1220 for name, deriver in reversed(outer_derivers + derivers.sorted()):
1221 view = wraps_view(deriver)(view, info)
1222 return view
1224 @action_method
1225 def add_view_predicate(
1226 self, name, factory, weighs_more_than=None, weighs_less_than=None
1227 ):
1228 """
1229 .. versionadded:: 1.4
1231 Adds a view predicate factory. The associated view predicate can
1232 later be named as a keyword argument to
1233 :meth:`pyramid.config.Configurator.add_view` in the
1234 ``predicates`` anonyous keyword argument dictionary.
1236 ``name`` should be the name of the predicate. It must be a valid
1237 Python identifier (it will be used as a keyword argument to
1238 ``add_view`` by others).
1240 ``factory`` should be a :term:`predicate factory` or :term:`dotted
1241 Python name` which refers to a predicate factory.
1243 See :ref:`view_and_route_predicates` for more information.
1244 """
1245 self._add_predicate(
1246 'view',
1247 name,
1248 factory,
1249 weighs_more_than=weighs_more_than,
1250 weighs_less_than=weighs_less_than,
1251 )
1253 def add_default_view_predicates(self):
1254 p = pyramid.predicates
1255 for (name, factory) in (
1256 ('xhr', p.XHRPredicate),
1257 ('request_method', p.RequestMethodPredicate),
1258 ('path_info', p.PathInfoPredicate),
1259 ('request_param', p.RequestParamPredicate),
1260 ('header', p.HeaderPredicate),
1261 ('accept', p.AcceptPredicate),
1262 ('containment', p.ContainmentPredicate),
1263 ('request_type', p.RequestTypePredicate),
1264 ('match_param', p.MatchParamPredicate),
1265 ('check_csrf', p.CheckCSRFTokenPredicate),
1266 ('physical_path', p.PhysicalPathPredicate),
1267 ('effective_principals', p.EffectivePrincipalsPredicate),
1268 ('custom', p.CustomPredicate),
1269 ):
1270 self.add_view_predicate(name, factory)
1272 def add_default_accept_view_order(self):
1273 for accept in (
1274 'text/html',
1275 'application/xhtml+xml',
1276 'application/xml',
1277 'text/xml',
1278 'text/plain',
1279 'application/json',
1280 ):
1281 self.add_accept_view_order(accept)
1283 @action_method
1284 def add_accept_view_order(
1285 self, value, weighs_more_than=None, weighs_less_than=None
1286 ):
1287 """
1288 Specify an ordering preference for the ``accept`` view option used
1289 during :term:`view lookup`.
1291 By default, if two views have different ``accept`` options and a
1292 request specifies ``Accept: */*`` or omits the header entirely then
1293 it is random which view will be selected. This method provides a way
1294 to specify a server-side, relative ordering between accept media types.
1296 ``value`` should be a :term:`media type` as specified by
1297 :rfc:`7231#section-5.3.2`. For example, ``text/plain;charset=utf8``,
1298 ``application/json`` or ``text/html``.
1300 ``weighs_more_than`` and ``weighs_less_than`` control the ordering
1301 of media types. Each value may be a string or a list of strings. If
1302 all options for ``weighs_more_than`` (or ``weighs_less_than``) cannot
1303 be found, it is an error.
1305 Earlier calls to ``add_accept_view_order`` are given higher priority
1306 over later calls, assuming similar constraints but standard conflict
1307 resolution mechanisms can be used to override constraints.
1309 See :ref:`accept_content_negotiation` for more information.
1311 .. versionadded:: 1.10
1313 """
1315 def check_type(than):
1316 than_type, than_subtype, than_params = Accept.parse_offer(than)
1317 # text/plain vs text/html;charset=utf8
1318 if bool(offer_params) ^ bool(than_params):
1319 raise ConfigurationError(
1320 'cannot compare a media type with params to one without '
1321 'params'
1322 )
1323 # text/plain;charset=utf8 vs text/html;charset=utf8
1324 if offer_params and (
1325 offer_subtype != than_subtype or offer_type != than_type
1326 ):
1327 raise ConfigurationError(
1328 'cannot compare params across different media types'
1329 )
1331 def normalize_types(thans):
1332 thans = [normalize_accept_offer(than) for than in thans]
1333 for than in thans:
1334 check_type(than)
1335 return thans
1337 value = normalize_accept_offer(value)
1338 offer_type, offer_subtype, offer_params = Accept.parse_offer(value)
1340 if weighs_more_than:
1341 if not is_nonstr_iter(weighs_more_than):
1342 weighs_more_than = [weighs_more_than]
1343 weighs_more_than = normalize_types(weighs_more_than)
1345 if weighs_less_than:
1346 if not is_nonstr_iter(weighs_less_than):
1347 weighs_less_than = [weighs_less_than]
1348 weighs_less_than = normalize_types(weighs_less_than)
1350 discriminator = ('accept view order', value)
1351 intr = self.introspectable(
1352 'accept view order', value, value, 'accept view order'
1353 )
1354 intr['value'] = value
1355 intr['weighs_more_than'] = weighs_more_than
1356 intr['weighs_less_than'] = weighs_less_than
1358 def register():
1359 sorter = self.registry.queryUtility(IAcceptOrder)
1360 if sorter is None:
1361 sorter = TopologicalSorter()
1362 self.registry.registerUtility(sorter, IAcceptOrder)
1363 sorter.add(
1364 value, value, before=weighs_more_than, after=weighs_less_than
1365 )
1367 self.action(
1368 discriminator,
1369 register,
1370 introspectables=(intr,),
1371 order=PHASE1_CONFIG,
1372 ) # must be registered before add_view
1374 @action_method
1375 def add_view_deriver(self, deriver, name=None, under=None, over=None):
1376 """
1377 .. versionadded:: 1.7
1379 Add a :term:`view deriver` to the view pipeline. View derivers are
1380 a feature used by extension authors to wrap views in custom code
1381 controllable by view-specific options.
1383 ``deriver`` should be a callable conforming to the
1384 :class:`pyramid.interfaces.IViewDeriver` interface.
1386 ``name`` should be the name of the view deriver. There are no
1387 restrictions on the name of a view deriver. If left unspecified, the
1388 name will be constructed from the name of the ``deriver``.
1390 The ``under`` and ``over`` options can be used to control the ordering
1391 of view derivers by providing hints about where in the view pipeline
1392 the deriver is used. Each option may be a string or a list of strings.
1393 At least one view deriver in each, the over and under directions, must
1394 exist to fully satisfy the constraints.
1396 ``under`` means closer to the user-defined :term:`view callable`,
1397 and ``over`` means closer to view pipeline ingress.
1399 The default value for ``over`` is ``rendered_view`` and ``under`` is
1400 ``decorated_view``. This places the deriver somewhere between the two
1401 in the view pipeline. If the deriver should be placed elsewhere in the
1402 pipeline, such as above ``decorated_view``, then you MUST also specify
1403 ``under`` to something earlier in the order, or a
1404 ``CyclicDependencyError`` will be raised when trying to sort the
1405 derivers.
1407 See :ref:`view_derivers` for more information.
1409 """
1410 deriver = self.maybe_dotted(deriver)
1412 if name is None:
1413 name = deriver.__name__
1415 if name in (INGRESS, VIEW):
1416 raise ConfigurationError(
1417 '%s is a reserved view deriver name' % name
1418 )
1420 if under is None:
1421 under = 'decorated_view'
1423 if over is None:
1424 over = 'rendered_view'
1426 over = as_sorted_tuple(over)
1427 under = as_sorted_tuple(under)
1429 if INGRESS in over:
1430 raise ConfigurationError('%s cannot be over INGRESS' % name)
1432 # ensure everything is always over mapped_view
1433 if VIEW in over and name != 'mapped_view':
1434 over = as_sorted_tuple(over + ('mapped_view',))
1436 if VIEW in under:
1437 raise ConfigurationError('%s cannot be under VIEW' % name)
1438 if 'mapped_view' in under:
1439 raise ConfigurationError('%s cannot be under "mapped_view"' % name)
1441 discriminator = ('view deriver', name)
1442 intr = self.introspectable('view derivers', name, name, 'view deriver')
1443 intr['name'] = name
1444 intr['deriver'] = deriver
1445 intr['under'] = under
1446 intr['over'] = over
1448 def register():
1449 derivers = self.registry.queryUtility(IViewDerivers)
1450 if derivers is None:
1451 derivers = TopologicalSorter(
1452 default_before=None,
1453 default_after=INGRESS,
1454 first=INGRESS,
1455 last=VIEW,
1456 )
1457 self.registry.registerUtility(derivers, IViewDerivers)
1458 derivers.add(name, deriver, before=over, after=under)
1460 self.action(
1461 discriminator,
1462 register,
1463 introspectables=(intr,),
1464 order=PHASE1_CONFIG,
1465 ) # must be registered before add_view
1467 def add_default_view_derivers(self):
1468 d = pyramid.viewderivers
1469 derivers = [
1470 ('secured_view', d.secured_view),
1471 ('owrapped_view', d.owrapped_view),
1472 ('http_cached_view', d.http_cached_view),
1473 ('decorated_view', d.decorated_view),
1474 ('rendered_view', d.rendered_view),
1475 ('mapped_view', d.mapped_view),
1476 ]
1477 last = INGRESS
1478 for name, deriver in derivers:
1479 self.add_view_deriver(deriver, name=name, under=last, over=VIEW)
1480 last = name
1482 # leave the csrf_view loosely coupled to the rest of the pipeline
1483 # by ensuring nothing in the default pipeline depends on the order
1484 # of the csrf_view
1485 self.add_view_deriver(
1486 d.csrf_view,
1487 'csrf_view',
1488 under='secured_view',
1489 over='owrapped_view',
1490 )
1492 def derive_view(self, view, attr=None, renderer=None):
1493 """
1494 Create a :term:`view callable` using the function, instance,
1495 or class (or :term:`dotted Python name` referring to the same)
1496 provided as ``view`` object.
1498 .. warning::
1500 This method is typically only used by :app:`Pyramid` framework
1501 extension authors, not by :app:`Pyramid` application developers.
1503 This is API is useful to framework extenders who create
1504 pluggable systems which need to register 'proxy' view
1505 callables for functions, instances, or classes which meet the
1506 requirements of being a :app:`Pyramid` view callable. For
1507 example, a ``some_other_framework`` function in another
1508 framework may want to allow a user to supply a view callable,
1509 but he may want to wrap the view callable in his own before
1510 registering the wrapper as a :app:`Pyramid` view callable.
1511 Because a :app:`Pyramid` view callable can be any of a
1512 number of valid objects, the framework extender will not know
1513 how to call the user-supplied object. Running it through
1514 ``derive_view`` normalizes it to a callable which accepts two
1515 arguments: ``context`` and ``request``.
1517 For example:
1519 .. code-block:: python
1521 def some_other_framework(user_supplied_view):
1522 config = Configurator(reg)
1523 proxy_view = config.derive_view(user_supplied_view)
1524 def my_wrapper(context, request):
1525 do_something_that_mutates(request)
1526 return proxy_view(context, request)
1527 config.add_view(my_wrapper)
1529 The ``view`` object provided should be one of the following:
1531 - A function or another non-class callable object that accepts
1532 a :term:`request` as a single positional argument and which
1533 returns a :term:`response` object.
1535 - A function or other non-class callable object that accepts
1536 two positional arguments, ``context, request`` and which
1537 returns a :term:`response` object.
1539 - A class which accepts a single positional argument in its
1540 constructor named ``request``, and which has a ``__call__``
1541 method that accepts no arguments that returns a
1542 :term:`response` object.
1544 - A class which accepts two positional arguments named
1545 ``context, request``, and which has a ``__call__`` method
1546 that accepts no arguments that returns a :term:`response`
1547 object.
1549 - A :term:`dotted Python name` which refers to any of the
1550 kinds of objects above.
1552 This API returns a callable which accepts the arguments
1553 ``context, request`` and which returns the result of calling
1554 the provided ``view`` object.
1556 The ``attr`` keyword argument is most useful when the view
1557 object is a class. It names the method that should be used as
1558 the callable. If ``attr`` is not provided, the attribute
1559 effectively defaults to ``__call__``. See
1560 :ref:`class_as_view` for more information.
1562 The ``renderer`` keyword argument should be a renderer
1563 name. If supplied, it will cause the returned callable to use
1564 a :term:`renderer` to convert the user-supplied view result to
1565 a :term:`response` object. If a ``renderer`` argument is not
1566 supplied, the user-supplied view must itself return a
1567 :term:`response` object. """
1568 return self._derive_view(view, attr=attr, renderer=renderer)
1570 # b/w compat
1571 def _derive_view(
1572 self,
1573 view,
1574 permission=None,
1575 predicates=(),
1576 attr=None,
1577 renderer=None,
1578 wrapper_viewname=None,
1579 viewname=None,
1580 accept=None,
1581 order=MAX_ORDER,
1582 phash=DEFAULT_PHASH,
1583 decorator=None,
1584 route_name=None,
1585 mapper=None,
1586 http_cache=None,
1587 context=None,
1588 require_csrf=None,
1589 exception_only=False,
1590 extra_options=None,
1591 ):
1592 view = self.maybe_dotted(view)
1593 mapper = self.maybe_dotted(mapper)
1594 if isinstance(renderer, string_types):
1595 renderer = renderers.RendererHelper(
1596 name=renderer, package=self.package, registry=self.registry
1597 )
1598 if renderer is None:
1599 # use default renderer if one exists
1600 if self.registry.queryUtility(IRendererFactory) is not None:
1601 renderer = renderers.RendererHelper(
1602 name=None, package=self.package, registry=self.registry
1603 )
1605 options = dict(
1606 view=view,
1607 context=context,
1608 permission=permission,
1609 attr=attr,
1610 renderer=renderer,
1611 wrapper=wrapper_viewname,
1612 name=viewname,
1613 accept=accept,
1614 mapper=mapper,
1615 decorator=decorator,
1616 http_cache=http_cache,
1617 require_csrf=require_csrf,
1618 route_name=route_name,
1619 )
1620 if extra_options:
1621 options.update(extra_options)
1623 info = ViewDeriverInfo(
1624 view=view,
1625 registry=self.registry,
1626 package=self.package,
1627 predicates=predicates,
1628 exception_only=exception_only,
1629 options=options,
1630 )
1632 # order and phash are only necessary for the predicated view and
1633 # are not really view deriver options
1634 info.order = order
1635 info.phash = phash
1637 return self._apply_view_derivers(info)
1639 @viewdefaults
1640 @action_method
1641 def add_forbidden_view(
1642 self,
1643 view=None,
1644 attr=None,
1645 renderer=None,
1646 wrapper=None,
1647 route_name=None,
1648 request_type=None,
1649 request_method=None,
1650 request_param=None,
1651 containment=None,
1652 xhr=None,
1653 accept=None,
1654 header=None,
1655 path_info=None,
1656 custom_predicates=(),
1657 decorator=None,
1658 mapper=None,
1659 match_param=None,
1660 **view_options
1661 ):
1662 """ Add a forbidden view to the current configuration state. The
1663 view will be called when Pyramid or application code raises a
1664 :exc:`pyramid.httpexceptions.HTTPForbidden` exception and the set of
1665 circumstances implied by the predicates provided are matched. The
1666 simplest example is:
1668 .. code-block:: python
1670 def forbidden(request):
1671 return Response('Forbidden', status='403 Forbidden')
1673 config.add_forbidden_view(forbidden)
1675 If ``view`` argument is not provided, the view callable defaults to
1676 :func:`~pyramid.httpexceptions.default_exceptionresponse_view`.
1678 All arguments have the same meaning as
1679 :meth:`pyramid.config.Configurator.add_view` and each predicate
1680 argument restricts the set of circumstances under which this forbidden
1681 view will be invoked. Unlike
1682 :meth:`pyramid.config.Configurator.add_view`, this method will raise
1683 an exception if passed ``name``, ``permission``, ``require_csrf``,
1684 ``context``, ``for_``, or ``exception_only`` keyword arguments. These
1685 argument values make no sense in the context of a forbidden
1686 :term:`exception view`.
1688 .. versionadded:: 1.3
1690 .. versionchanged:: 1.8
1692 The view is created using ``exception_only=True``.
1693 """
1694 for arg in (
1695 'name',
1696 'permission',
1697 'context',
1698 'for_',
1699 'require_csrf',
1700 'exception_only',
1701 ):
1702 if arg in view_options:
1703 raise ConfigurationError(
1704 '%s may not be used as an argument to add_forbidden_view'
1705 % (arg,)
1706 )
1708 if view is None:
1709 view = default_exceptionresponse_view
1711 settings = dict(
1712 view=view,
1713 context=HTTPForbidden,
1714 exception_only=True,
1715 wrapper=wrapper,
1716 request_type=request_type,
1717 request_method=request_method,
1718 request_param=request_param,
1719 containment=containment,
1720 xhr=xhr,
1721 accept=accept,
1722 header=header,
1723 path_info=path_info,
1724 custom_predicates=custom_predicates,
1725 decorator=decorator,
1726 mapper=mapper,
1727 match_param=match_param,
1728 route_name=route_name,
1729 permission=NO_PERMISSION_REQUIRED,
1730 require_csrf=False,
1731 attr=attr,
1732 renderer=renderer,
1733 )
1734 settings.update(view_options)
1735 return self.add_view(**settings)
1737 set_forbidden_view = add_forbidden_view # deprecated sorta-bw-compat alias
1739 @viewdefaults
1740 @action_method
1741 def add_notfound_view(
1742 self,
1743 view=None,
1744 attr=None,
1745 renderer=None,
1746 wrapper=None,
1747 route_name=None,
1748 request_type=None,
1749 request_method=None,
1750 request_param=None,
1751 containment=None,
1752 xhr=None,
1753 accept=None,
1754 header=None,
1755 path_info=None,
1756 custom_predicates=(),
1757 decorator=None,
1758 mapper=None,
1759 match_param=None,
1760 append_slash=False,
1761 **view_options
1762 ):
1763 """ Add a default :term:`Not Found View` to the current configuration
1764 state. The view will be called when Pyramid or application code raises
1765 an :exc:`pyramid.httpexceptions.HTTPNotFound` exception (e.g., when a
1766 view cannot be found for the request). The simplest example is:
1768 .. code-block:: python
1770 def notfound(request):
1771 return Response('Not Found', status='404 Not Found')
1773 config.add_notfound_view(notfound)
1775 If ``view`` argument is not provided, the view callable defaults to
1776 :func:`~pyramid.httpexceptions.default_exceptionresponse_view`.
1778 All arguments except ``append_slash`` have the same meaning as
1779 :meth:`pyramid.config.Configurator.add_view` and each predicate
1780 argument restricts the set of circumstances under which this notfound
1781 view will be invoked. Unlike
1782 :meth:`pyramid.config.Configurator.add_view`, this method will raise
1783 an exception if passed ``name``, ``permission``, ``require_csrf``,
1784 ``context``, ``for_``, or ``exception_only`` keyword arguments. These
1785 argument values make no sense in the context of a Not Found View.
1787 If ``append_slash`` is ``True``, when this Not Found View is invoked,
1788 and the current path info does not end in a slash, the notfound logic
1789 will attempt to find a :term:`route` that matches the request's path
1790 info suffixed with a slash. If such a route exists, Pyramid will
1791 issue a redirect to the URL implied by the route; if it does not,
1792 Pyramid will return the result of the view callable provided as
1793 ``view``, as normal.
1795 If the argument provided as ``append_slash`` is not a boolean but
1796 instead implements :class:`~pyramid.interfaces.IResponse`, the
1797 append_slash logic will behave as if ``append_slash=True`` was passed,
1798 but the provided class will be used as the response class instead of
1799 the default :class:`~pyramid.httpexceptions.HTTPTemporaryRedirect`
1800 response class when a redirect is performed. For example:
1802 .. code-block:: python
1804 from pyramid.httpexceptions import HTTPMovedPermanently
1805 config.add_notfound_view(append_slash=HTTPMovedPermanently)
1807 The above means that a redirect to a slash-appended route will be
1808 attempted, but instead of
1809 :class:`~pyramid.httpexceptions.HTTPTemporaryRedirect`
1810 being used, :class:`~pyramid.httpexceptions.HTTPMovedPermanently will
1811 be used` for the redirect response if a slash-appended route is found.
1813 :class:`~pyramid.httpexceptions.HTTPTemporaryRedirect` class is used
1814 as default response, which is equivalent to
1815 :class:`~pyramid.httpexceptions.HTTPFound` with addition of redirecting
1816 with the same HTTP method (useful when doing POST requests).
1818 .. versionadded:: 1.3
1820 .. versionchanged:: 1.6
1822 The ``append_slash`` argument was modified to allow any object that
1823 implements the ``IResponse`` interface to specify the response class
1824 used when a redirect is performed.
1826 .. versionchanged:: 1.8
1828 The view is created using ``exception_only=True``.
1830 .. versionchanged: 1.10
1832 Default response was changed from
1833 :class:`~pyramid.httpexceptions.HTTPFound`
1834 to :class:`~pyramid.httpexceptions.HTTPTemporaryRedirect`.
1836 """
1837 for arg in (
1838 'name',
1839 'permission',
1840 'context',
1841 'for_',
1842 'require_csrf',
1843 'exception_only',
1844 ):
1845 if arg in view_options:
1846 raise ConfigurationError(
1847 '%s may not be used as an argument to add_notfound_view'
1848 % (arg,)
1849 )
1851 if view is None:
1852 view = default_exceptionresponse_view
1854 settings = dict(
1855 view=view,
1856 context=HTTPNotFound,
1857 exception_only=True,
1858 wrapper=wrapper,
1859 request_type=request_type,
1860 request_method=request_method,
1861 request_param=request_param,
1862 containment=containment,
1863 xhr=xhr,
1864 accept=accept,
1865 header=header,
1866 path_info=path_info,
1867 custom_predicates=custom_predicates,
1868 decorator=decorator,
1869 mapper=mapper,
1870 match_param=match_param,
1871 route_name=route_name,
1872 permission=NO_PERMISSION_REQUIRED,
1873 require_csrf=False,
1874 )
1875 settings.update(view_options)
1876 if append_slash:
1877 view = self._derive_view(view, attr=attr, renderer=renderer)
1878 if IResponse.implementedBy(append_slash):
1879 view = AppendSlashNotFoundViewFactory(
1880 view, redirect_class=append_slash
1881 )
1882 else:
1883 view = AppendSlashNotFoundViewFactory(view)
1884 settings['view'] = view
1885 else:
1886 settings['attr'] = attr
1887 settings['renderer'] = renderer
1888 return self.add_view(**settings)
1890 set_notfound_view = add_notfound_view # deprecated sorta-bw-compat alias
1892 @viewdefaults
1893 @action_method
1894 def add_exception_view(
1895 self,
1896 view=None,
1897 context=None,
1898 # force all other arguments to be specified as key=value
1899 **view_options
1900 ):
1901 """ Add an :term:`exception view` for the specified ``exception`` to
1902 the current configuration state. The view will be called when Pyramid
1903 or application code raises the given exception.
1905 This method accepts almost all of the same arguments as
1906 :meth:`pyramid.config.Configurator.add_view` except for ``name``,
1907 ``permission``, ``for_``, ``require_csrf``, and ``exception_only``.
1909 By default, this method will set ``context=Exception``, thus
1910 registering for most default Python exceptions. Any subclass of
1911 ``Exception`` may be specified.
1913 .. versionadded:: 1.8
1914 """
1915 for arg in (
1916 'name',
1917 'for_',
1918 'exception_only',
1919 'require_csrf',
1920 'permission',
1921 ):
1922 if arg in view_options:
1923 raise ConfigurationError(
1924 '%s may not be used as an argument to add_exception_view'
1925 % (arg,)
1926 )
1927 if context is None:
1928 context = Exception
1929 view_options.update(
1930 dict(
1931 view=view,
1932 context=context,
1933 exception_only=True,
1934 permission=NO_PERMISSION_REQUIRED,
1935 require_csrf=False,
1936 )
1937 )
1938 return self.add_view(**view_options)
1940 @action_method
1941 def set_view_mapper(self, mapper):
1942 """
1943 Setting a :term:`view mapper` makes it possible to make use of
1944 :term:`view callable` objects which implement different call
1945 signatures than the ones supported by :app:`Pyramid` as described in
1946 its narrative documentation.
1948 The ``mapper`` argument should be an object implementing
1949 :class:`pyramid.interfaces.IViewMapperFactory` or a :term:`dotted
1950 Python name` to such an object. The provided ``mapper`` will become
1951 the default view mapper to be used by all subsequent :term:`view
1952 configuration` registrations.
1954 .. seealso::
1956 See also :ref:`using_a_view_mapper`.
1958 .. note::
1960 Using the ``default_view_mapper`` argument to the
1961 :class:`pyramid.config.Configurator` constructor
1962 can be used to achieve the same purpose.
1963 """
1964 mapper = self.maybe_dotted(mapper)
1966 def register():
1967 self.registry.registerUtility(mapper, IViewMapperFactory)
1969 # IViewMapperFactory is looked up as the result of view config
1970 # in phase 3
1971 intr = self.introspectable(
1972 'view mappers',
1973 IViewMapperFactory,
1974 self.object_description(mapper),
1975 'default view mapper',
1976 )
1977 intr['mapper'] = mapper
1978 self.action(
1979 IViewMapperFactory,
1980 register,
1981 order=PHASE1_CONFIG,
1982 introspectables=(intr,),
1983 )
1985 @action_method
1986 def add_static_view(self, name, path, **kw):
1987 """ Add a view used to render static assets such as images
1988 and CSS files.
1990 The ``name`` argument is a string representing an
1991 application-relative local URL prefix. It may alternately be a full
1992 URL.
1994 The ``path`` argument is the path on disk where the static files
1995 reside. This can be an absolute path, a package-relative path, or a
1996 :term:`asset specification`.
1998 The ``cache_max_age`` keyword argument is input to set the
1999 ``Expires`` and ``Cache-Control`` headers for static assets served.
2000 Note that this argument has no effect when the ``name`` is a *url
2001 prefix*. By default, this argument is ``None``, meaning that no
2002 particular Expires or Cache-Control headers are set in the response.
2004 The ``permission`` keyword argument is used to specify the
2005 :term:`permission` required by a user to execute the static view. By
2006 default, it is the string
2007 :data:`pyramid.security.NO_PERMISSION_REQUIRED`, a special sentinel
2008 which indicates that, even if a :term:`default permission` exists for
2009 the current application, the static view should be renderered to
2010 completely anonymous users. This default value is permissive
2011 because, in most web apps, static assets seldom need protection from
2012 viewing. If ``permission`` is specified, the security checking will
2013 be performed against the default root factory ACL.
2015 Any other keyword arguments sent to ``add_static_view`` are passed on
2016 to :meth:`pyramid.config.Configurator.add_route` (e.g. ``factory``,
2017 perhaps to define a custom factory with a custom ACL for this static
2018 view).
2020 *Usage*
2022 The ``add_static_view`` function is typically used in conjunction
2023 with the :meth:`pyramid.request.Request.static_url` method.
2024 ``add_static_view`` adds a view which renders a static asset when
2025 some URL is visited; :meth:`pyramid.request.Request.static_url`
2026 generates a URL to that asset.
2028 The ``name`` argument to ``add_static_view`` is usually a simple URL
2029 prefix (e.g. ``'images'``). When this is the case, the
2030 :meth:`pyramid.request.Request.static_url` API will generate a URL
2031 which points to a Pyramid view, which will serve up a set of assets
2032 that live in the package itself. For example:
2034 .. code-block:: python
2036 add_static_view('images', 'mypackage:images/')
2038 Code that registers such a view can generate URLs to the view via
2039 :meth:`pyramid.request.Request.static_url`:
2041 .. code-block:: python
2043 request.static_url('mypackage:images/logo.png')
2045 When ``add_static_view`` is called with a ``name`` argument that
2046 represents a URL prefix, as it is above, subsequent calls to
2047 :meth:`pyramid.request.Request.static_url` with paths that start with
2048 the ``path`` argument passed to ``add_static_view`` will generate a
2049 URL something like ``http://<Pyramid app URL>/images/logo.png``,
2050 which will cause the ``logo.png`` file in the ``images`` subdirectory
2051 of the ``mypackage`` package to be served.
2053 ``add_static_view`` can alternately be used with a ``name`` argument
2054 which is a *URL*, causing static assets to be served from an external
2055 webserver. This happens when the ``name`` argument is a fully
2056 qualified URL (e.g. starts with ``http://`` or similar). In this
2057 mode, the ``name`` is used as the prefix of the full URL when
2058 generating a URL using :meth:`pyramid.request.Request.static_url`.
2059 Furthermore, if a protocol-relative URL (e.g. ``//example.com/images``)
2060 is used as the ``name`` argument, the generated URL will use the
2061 protocol of the request (http or https, respectively).
2063 For example, if ``add_static_view`` is called like so:
2065 .. code-block:: python
2067 add_static_view('http://example.com/images', 'mypackage:images/')
2069 Subsequently, the URLs generated by
2070 :meth:`pyramid.request.Request.static_url` for that static view will
2071 be prefixed with ``http://example.com/images`` (the external webserver
2072 listening on ``example.com`` must be itself configured to respond
2073 properly to such a request.):
2075 .. code-block:: python
2077 static_url('mypackage:images/logo.png', request)
2079 See :ref:`static_assets_section` for more information.
2080 """
2081 spec = self._make_spec(path)
2082 info = self._get_static_info()
2083 info.add(self, name, spec, **kw)
2085 def add_cache_buster(self, path, cachebust, explicit=False):
2086 """
2087 Add a cache buster to a set of files on disk.
2089 The ``path`` should be the path on disk where the static files
2090 reside. This can be an absolute path, a package-relative path, or a
2091 :term:`asset specification`.
2093 The ``cachebust`` argument may be set to cause
2094 :meth:`~pyramid.request.Request.static_url` to use cache busting when
2095 generating URLs. See :ref:`cache_busting` for general information
2096 about cache busting. The value of the ``cachebust`` argument must
2097 be an object which implements
2098 :class:`~pyramid.interfaces.ICacheBuster`.
2100 If ``explicit`` is set to ``True`` then the ``path`` for the cache
2101 buster will be matched based on the ``rawspec`` instead of the
2102 ``pathspec`` as defined in the
2103 :class:`~pyramid.interfaces.ICacheBuster` interface.
2104 Default: ``False``.
2106 """
2107 spec = self._make_spec(path)
2108 info = self._get_static_info()
2109 info.add_cache_buster(self, spec, cachebust, explicit=explicit)
2111 def _get_static_info(self):
2112 info = self.registry.queryUtility(IStaticURLInfo)
2113 if info is None:
2114 info = StaticURLInfo()
2115 self.registry.registerUtility(info, IStaticURLInfo)
2116 return info
2119def isexception(o):
2120 if IInterface.providedBy(o):
2121 if IException.isEqualOrExtendedBy(o):
2122 return True
2123 return isinstance(o, Exception) or (
2124 inspect.isclass(o) and (issubclass(o, Exception))
2125 )
2128def runtime_exc_view(view, excview):
2129 # create a view callable which can pretend to be both a normal view
2130 # and an exception view, dispatching to the appropriate one based
2131 # on the state of request.exception
2132 def wrapper_view(context, request):
2133 if getattr(request, 'exception', None):
2134 return excview(context, request)
2135 return view(context, request)
2137 # these constants are the same between the two views
2138 wrapper_view.__wraps__ = wrapper_view
2139 wrapper_view.__original_view__ = getattr(view, '__original_view__', view)
2140 wrapper_view.__module__ = view.__module__
2141 wrapper_view.__doc__ = view.__doc__
2142 wrapper_view.__name__ = view.__name__
2144 wrapper_view.__accept__ = getattr(view, '__accept__', None)
2145 wrapper_view.__order__ = getattr(view, '__order__', MAX_ORDER)
2146 wrapper_view.__phash__ = getattr(view, '__phash__', DEFAULT_PHASH)
2147 wrapper_view.__view_attr__ = getattr(view, '__view_attr__', None)
2148 wrapper_view.__permission__ = getattr(view, '__permission__', None)
2150 def wrap_fn(attr):
2151 def wrapper(context, request):
2152 if getattr(request, 'exception', None):
2153 selected_view = excview
2154 else:
2155 selected_view = view
2156 fn = getattr(selected_view, attr, None)
2157 if fn is not None:
2158 return fn(context, request)
2160 return wrapper
2162 # these methods are dynamic per-request and should dispatch to their
2163 # respective views based on whether it's an exception or not
2164 wrapper_view.__call_permissive__ = wrap_fn('__call_permissive__')
2165 wrapper_view.__permitted__ = wrap_fn('__permitted__')
2166 wrapper_view.__predicated__ = wrap_fn('__predicated__')
2167 wrapper_view.__predicates__ = wrap_fn('__predicates__')
2168 return wrapper_view
2171@implementer(IViewDeriverInfo)
2172class ViewDeriverInfo(object):
2173 def __init__(
2174 self, view, registry, package, predicates, exception_only, options
2175 ):
2176 self.original_view = view
2177 self.registry = registry
2178 self.package = package
2179 self.predicates = predicates or []
2180 self.options = options or {}
2181 self.exception_only = exception_only
2183 @reify
2184 def settings(self):
2185 return self.registry.settings
2188@implementer(IStaticURLInfo)
2189class StaticURLInfo(object):
2190 def __init__(self):
2191 self.registrations = []
2192 self.cache_busters = []
2194 def generate(self, path, request, **kw):
2195 for (url, spec, route_name) in self.registrations:
2196 if path.startswith(spec):
2197 subpath = path[len(spec) :]
2198 if WIN: # pragma: no cover
2199 subpath = subpath.replace('\\', '/') # windows
2200 if self.cache_busters:
2201 subpath, kw = self._bust_asset_path(
2202 request, spec, subpath, kw
2203 )
2204 if url is None:
2205 kw['subpath'] = subpath
2206 return request.route_url(route_name, **kw)
2207 else:
2208 app_url, qs, anchor = parse_url_overrides(request, kw)
2209 parsed = url_parse(url)
2210 if not parsed.scheme:
2211 url = urlparse.urlunparse(
2212 parsed._replace(
2213 scheme=request.environ['wsgi.url_scheme']
2214 )
2215 )
2216 subpath = url_quote(subpath)
2217 result = urljoin(url, subpath)
2218 return result + qs + anchor
2220 raise ValueError('No static URL definition matching %s' % path)
2222 def add(self, config, name, spec, **extra):
2223 # This feature only allows for the serving of a directory and
2224 # the files contained within, not of a single asset;
2225 # appending a slash here if the spec doesn't have one is
2226 # required for proper prefix matching done in ``generate``
2227 # (``subpath = path[len(spec):]``).
2228 if os.path.isabs(spec): # FBO windows
2229 sep = os.sep
2230 else:
2231 sep = '/'
2232 if not spec.endswith(sep) and not spec.endswith(':'):
2233 spec = spec + sep
2235 # we also make sure the name ends with a slash, purely as a
2236 # convenience: a name that is a url is required to end in a
2237 # slash, so that ``urljoin(name, subpath))`` will work above
2238 # when the name is a URL, and it doesn't hurt things for it to
2239 # have a name that ends in a slash if it's used as a route
2240 # name instead of a URL.
2241 if not name.endswith('/'):
2242 # make sure it ends with a slash
2243 name = name + '/'
2245 if url_parse(name).netloc:
2246 # it's a URL
2247 # url, spec, route_name
2248 url = name
2249 route_name = None
2250 else:
2251 # it's a view name
2252 url = None
2253 cache_max_age = extra.pop('cache_max_age', None)
2255 # create a view
2256 view = static_view(
2257 spec, cache_max_age=cache_max_age, use_subpath=True
2258 )
2260 # Mutate extra to allow factory, etc to be passed through here.
2261 # Treat permission specially because we'd like to default to
2262 # permissiveness (see docs of config.add_static_view).
2263 permission = extra.pop('permission', None)
2264 if permission is None:
2265 permission = NO_PERMISSION_REQUIRED
2267 context = extra.pop('context', None)
2268 if context is None:
2269 context = extra.pop('for_', None)
2271 renderer = extra.pop('renderer', None)
2273 # register a route using the computed view, permission, and
2274 # pattern, plus any extras passed to us via add_static_view
2275 pattern = "%s*subpath" % name # name already ends with slash
2276 if config.route_prefix:
2277 route_name = '__%s/%s' % (config.route_prefix, name)
2278 else:
2279 route_name = '__%s' % name
2280 config.add_route(route_name, pattern, **extra)
2281 config.add_view(
2282 route_name=route_name,
2283 view=view,
2284 permission=permission,
2285 context=context,
2286 renderer=renderer,
2287 )
2289 def register():
2290 registrations = self.registrations
2292 names = [t[0] for t in registrations]
2294 if name in names:
2295 idx = names.index(name)
2296 registrations.pop(idx)
2298 # url, spec, route_name
2299 registrations.append((url, spec, route_name))
2301 intr = config.introspectable(
2302 'static views', name, 'static view for %r' % name, 'static view'
2303 )
2304 intr['name'] = name
2305 intr['spec'] = spec
2307 config.action(None, callable=register, introspectables=(intr,))
2309 def add_cache_buster(self, config, spec, cachebust, explicit=False):
2310 # ensure the spec always has a trailing slash as we only support
2311 # adding cache busters to folders, not files
2312 if os.path.isabs(spec): # FBO windows
2313 sep = os.sep
2314 else:
2315 sep = '/'
2316 if not spec.endswith(sep) and not spec.endswith(':'):
2317 spec = spec + sep
2319 def register():
2320 if config.registry.settings.get('pyramid.prevent_cachebust'):
2321 return
2323 cache_busters = self.cache_busters
2325 # find duplicate cache buster (old_idx)
2326 # and insertion location (new_idx)
2327 new_idx, old_idx = len(cache_busters), None
2328 for idx, (spec_, cb_, explicit_) in enumerate(cache_busters):
2329 # if we find an identical (spec, explicit) then use it
2330 if spec == spec_ and explicit == explicit_:
2331 old_idx = new_idx = idx
2332 break
2334 # past all explicit==False specs then add to the end
2335 elif not explicit and explicit_:
2336 new_idx = idx
2337 break
2339 # explicit matches and spec is shorter
2340 elif explicit == explicit_ and len(spec) < len(spec_):
2341 new_idx = idx
2342 break
2344 if old_idx is not None:
2345 cache_busters.pop(old_idx)
2347 cache_busters.insert(new_idx, (spec, cachebust, explicit))
2349 intr = config.introspectable(
2350 'cache busters', spec, 'cache buster for %r' % spec, 'cache buster'
2351 )
2352 intr['cachebust'] = cachebust
2353 intr['path'] = spec
2354 intr['explicit'] = explicit
2356 config.action(None, callable=register, introspectables=(intr,))
2358 def _bust_asset_path(self, request, spec, subpath, kw):
2359 registry = request.registry
2360 pkg_name, pkg_subpath = resolve_asset_spec(spec)
2361 rawspec = None
2363 if pkg_name is not None:
2364 pathspec = '{0}:{1}{2}'.format(pkg_name, pkg_subpath, subpath)
2365 overrides = registry.queryUtility(IPackageOverrides, name=pkg_name)
2366 if overrides is not None:
2367 resource_name = posixpath.join(pkg_subpath, subpath)
2368 sources = overrides.filtered_sources(resource_name)
2369 for source, filtered_path in sources:
2370 rawspec = source.get_path(filtered_path)
2371 if hasattr(source, 'pkg_name'):
2372 rawspec = '{0}:{1}'.format(source.pkg_name, rawspec)
2373 break
2375 else:
2376 pathspec = pkg_subpath + subpath
2378 if rawspec is None:
2379 rawspec = pathspec
2381 kw['pathspec'] = pathspec
2382 kw['rawspec'] = rawspec
2383 for spec_, cachebust, explicit in reversed(self.cache_busters):
2384 if (explicit and rawspec.startswith(spec_)) or (
2385 not explicit and pathspec.startswith(spec_)
2386 ):
2387 subpath, kw = cachebust(request, subpath, kw)
2388 break
2389 return subpath, kw