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

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 contextlib
2import warnings
4from pyramid.compat import urlparse
5from pyramid.interfaces import (
6 IRequest,
7 IRouteRequest,
8 IRoutesMapper,
9 PHASE2_CONFIG,
10)
12from pyramid.exceptions import ConfigurationError
13import pyramid.predicates
14from pyramid.request import route_request_iface
15from pyramid.urldispatch import RoutesMapper
17from pyramid.util import as_sorted_tuple, is_nonstr_iter
19from pyramid.config.actions import action_method
20from pyramid.config.predicates import normalize_accept_offer, predvalseq
23class RoutesConfiguratorMixin(object):
24 @action_method
25 def add_route(
26 self,
27 name,
28 pattern=None,
29 factory=None,
30 for_=None,
31 header=None,
32 xhr=None,
33 accept=None,
34 path_info=None,
35 request_method=None,
36 request_param=None,
37 traverse=None,
38 custom_predicates=(),
39 use_global_views=False,
40 path=None,
41 pregenerator=None,
42 static=False,
43 **predicates
44 ):
45 """ Add a :term:`route configuration` to the current
46 configuration state, as well as possibly a :term:`view
47 configuration` to be used to specify a :term:`view callable`
48 that will be invoked when this route matches. The arguments
49 to this method are divided into *predicate*, *non-predicate*,
50 and *view-related* types. :term:`Route predicate` arguments
51 narrow the circumstances in which a route will be match a
52 request; non-predicate arguments are informational.
54 Non-Predicate Arguments
56 name
58 The name of the route, e.g. ``myroute``. This attribute is
59 required. It must be unique among all defined routes in a given
60 application.
62 factory
64 A Python object (often a function or a class) or a :term:`dotted
65 Python name` which refers to the same object that will generate a
66 :app:`Pyramid` root resource object when this route matches. For
67 example, ``mypackage.resources.MyFactory``. If this argument is
68 not specified, a default root factory will be used. See
69 :ref:`the_resource_tree` for more information about root factories.
71 traverse
73 If you would like to cause the :term:`context` to be
74 something other than the :term:`root` object when this route
75 matches, you can spell a traversal pattern as the
76 ``traverse`` argument. This traversal pattern will be used
77 as the traversal path: traversal will begin at the root
78 object implied by this route (either the global root, or the
79 object returned by the ``factory`` associated with this
80 route).
82 The syntax of the ``traverse`` argument is the same as it is
83 for ``pattern``. For example, if the ``pattern`` provided to
84 ``add_route`` is ``articles/{article}/edit``, and the
85 ``traverse`` argument provided to ``add_route`` is
86 ``/{article}``, when a request comes in that causes the route
87 to match in such a way that the ``article`` match value is
88 ``'1'`` (when the request URI is ``/articles/1/edit``), the
89 traversal path will be generated as ``/1``. This means that
90 the root object's ``__getitem__`` will be called with the
91 name ``'1'`` during the traversal phase. If the ``'1'`` object
92 exists, it will become the :term:`context` of the request.
93 :ref:`traversal_chapter` has more information about
94 traversal.
96 If the traversal path contains segment marker names which
97 are not present in the ``pattern`` argument, a runtime error
98 will occur. The ``traverse`` pattern should not contain
99 segment markers that do not exist in the ``pattern``
100 argument.
102 A similar combining of routing and traversal is available
103 when a route is matched which contains a ``*traverse``
104 remainder marker in its pattern (see
105 :ref:`using_traverse_in_a_route_pattern`). The ``traverse``
106 argument to add_route allows you to associate route patterns
107 with an arbitrary traversal path without using a
108 ``*traverse`` remainder marker; instead you can use other
109 match information.
111 Note that the ``traverse`` argument to ``add_route`` is
112 ignored when attached to a route that has a ``*traverse``
113 remainder marker in its pattern.
115 pregenerator
117 This option should be a callable object that implements the
118 :class:`pyramid.interfaces.IRoutePregenerator` interface. A
119 :term:`pregenerator` is a callable called by the
120 :meth:`pyramid.request.Request.route_url` function to augment or
121 replace the arguments it is passed when generating a URL for the
122 route. This is a feature not often used directly by applications,
123 it is meant to be hooked by frameworks that use :app:`Pyramid` as
124 a base.
126 use_global_views
128 When a request matches this route, and view lookup cannot
129 find a view which has a ``route_name`` predicate argument
130 that matches the route, try to fall back to using a view
131 that otherwise matches the context, request, and view name
132 (but which does not match the route_name predicate).
134 static
136 If ``static`` is ``True``, this route will never match an incoming
137 request; it will only be useful for URL generation. By default,
138 ``static`` is ``False``. See :ref:`static_route_narr`.
140 .. versionadded:: 1.1
142 Predicate Arguments
144 pattern
146 The pattern of the route e.g. ``ideas/{idea}``. This
147 argument is required. See :ref:`route_pattern_syntax`
148 for information about the syntax of route patterns. If the
149 pattern doesn't match the current URL, route matching
150 continues.
152 .. note::
154 For backwards compatibility purposes (as of :app:`Pyramid` 1.0), a
155 ``path`` keyword argument passed to this function will be used to
156 represent the pattern value if the ``pattern`` argument is
157 ``None``. If both ``path`` and ``pattern`` are passed,
158 ``pattern`` wins.
160 xhr
162 This value should be either ``True`` or ``False``. If this
163 value is specified and is ``True``, the :term:`request` must
164 possess an ``HTTP_X_REQUESTED_WITH`` (aka
165 ``X-Requested-With``) header for this route to match. This
166 is useful for detecting AJAX requests issued from jQuery,
167 Prototype and other Javascript libraries. If this predicate
168 returns ``False``, route matching continues.
170 request_method
172 A string representing an HTTP method name, e.g. ``GET``, ``POST``,
173 ``HEAD``, ``DELETE``, ``PUT`` or a tuple of elements containing
174 HTTP method names. If this argument is not specified, this route
175 will match if the request has *any* request method. If this
176 predicate returns ``False``, route matching continues.
178 .. versionchanged:: 1.2
179 The ability to pass a tuple of items as ``request_method``.
180 Previous versions allowed only a string.
182 path_info
184 This value represents a regular expression pattern that will
185 be tested against the ``PATH_INFO`` WSGI environment
186 variable. If the regex matches, this predicate will return
187 ``True``. If this predicate returns ``False``, route
188 matching continues.
190 request_param
192 This value can be any string or an iterable of strings. A view
193 declaration with this argument ensures that the associated route will
194 only match when the request has a key in the ``request.params``
195 dictionary (an HTTP ``GET`` or ``POST`` variable) that has a
196 name which matches the supplied value. If the value
197 supplied as the argument has a ``=`` sign in it,
198 e.g. ``request_param="foo=123"``, then the key
199 (``foo``) must both exist in the ``request.params`` dictionary, and
200 the value must match the right hand side of the expression (``123``)
201 for the route to "match" the current request. If this predicate
202 returns ``False``, route matching continues.
204 header
206 This argument represents an HTTP header name or a header
207 name/value pair. If the argument contains a ``:`` (colon),
208 it will be considered a name/value pair
209 (e.g. ``User-Agent:Mozilla/.*`` or ``Host:localhost``). If
210 the value contains a colon, the value portion should be a
211 regular expression. If the value does not contain a colon,
212 the entire value will be considered to be the header name
213 (e.g. ``If-Modified-Since``). If the value evaluates to a
214 header name only without a value, the header specified by
215 the name must be present in the request for this predicate
216 to be true. If the value evaluates to a header name/value
217 pair, the header specified by the name must be present in
218 the request *and* the regular expression specified as the
219 value must match the header value. Whether or not the value
220 represents a header name or a header name/value pair, the
221 case of the header name is not significant. If this
222 predicate returns ``False``, route matching continues.
224 accept
226 A :term:`media type` that will be matched against the ``Accept``
227 HTTP request header. If this value is specified, it may be a
228 specific media type such as ``text/html``, or a list of the same.
229 If the media type is acceptable by the ``Accept`` header of the
230 request, or if the ``Accept`` header isn't set at all in the request,
231 this predicate will match. If this does not match the ``Accept``
232 header of the request, route matching continues.
234 If ``accept`` is not specified, the ``HTTP_ACCEPT`` HTTP header is
235 not taken into consideration when deciding whether or not to select
236 the route.
238 Unlike the ``accept`` argument to
239 :meth:`pyramid.config.Configurator.add_view`, this value is
240 strictly a predicate and supports :func:`pyramid.config.not_`.
242 .. versionchanged:: 1.10
244 Specifying a media range is deprecated due to changes in WebOb
245 and ambiguities that occur when trying to match ranges against
246 ranges in the ``Accept`` header. Support will be removed in
247 :app:`Pyramid` 2.0. Use a list of specific media types to match
248 more than one type.
250 effective_principals
252 If specified, this value should be a :term:`principal` identifier or
253 a sequence of principal identifiers. If the
254 :attr:`pyramid.request.Request.effective_principals` property
255 indicates that every principal named in the argument list is present
256 in the current request, this predicate will return True; otherwise it
257 will return False. For example:
258 ``effective_principals=pyramid.security.Authenticated`` or
259 ``effective_principals=('fred', 'group:admins')``.
261 .. versionadded:: 1.4a4
263 custom_predicates
265 .. deprecated:: 1.5
266 This value should be a sequence of references to custom
267 predicate callables. Use custom predicates when no set of
268 predefined predicates does what you need. Custom predicates
269 can be combined with predefined predicates as necessary.
270 Each custom predicate callable should accept two arguments:
271 ``info`` and ``request`` and should return either ``True``
272 or ``False`` after doing arbitrary evaluation of the info
273 and/or the request. If all custom and non-custom predicate
274 callables return ``True`` the associated route will be
275 considered viable for a given request. If any predicate
276 callable returns ``False``, route matching continues. Note
277 that the value ``info`` passed to a custom route predicate
278 is a dictionary containing matching information; see
279 :ref:`custom_route_predicates` for more information about
280 ``info``.
282 predicates
284 Pass a key/value pair here to use a third-party predicate
285 registered via
286 :meth:`pyramid.config.Configurator.add_route_predicate`. More than
287 one key/value pair can be used at the same time. See
288 :ref:`view_and_route_predicates` for more information about
289 third-party predicates.
291 .. versionadded:: 1.4
293 """
294 if custom_predicates:
295 warnings.warn(
296 (
297 'The "custom_predicates" argument to '
298 'Configurator.add_route is deprecated as of Pyramid 1.5. '
299 'Use "config.add_route_predicate" and use the registered '
300 'route predicate as a predicate argument to add_route '
301 'instead. See "Adding A Third Party View, Route, or '
302 'Subscriber Predicate" in the "Hooks" chapter of the '
303 'documentation for more information.'
304 ),
305 DeprecationWarning,
306 stacklevel=3,
307 )
309 if accept is not None:
310 if not is_nonstr_iter(accept):
311 if '*' in accept:
312 warnings.warn(
313 (
314 'Passing a media range to the "accept" argument '
315 'of Configurator.add_route is deprecated as of '
316 'Pyramid 1.10. Use a list of explicit media types.'
317 ),
318 DeprecationWarning,
319 stacklevel=3,
320 )
321 # XXX switch this to False when range support is dropped
322 accept = [normalize_accept_offer(accept, allow_range=True)]
324 else:
325 accept = [
326 normalize_accept_offer(accept_option)
327 for accept_option in accept
328 ]
330 # these are route predicates; if they do not match, the next route
331 # in the routelist will be tried
332 if request_method is not None:
333 request_method = as_sorted_tuple(request_method)
335 factory = self.maybe_dotted(factory)
336 if pattern is None:
337 pattern = path
338 if pattern is None:
339 raise ConfigurationError('"pattern" argument may not be None')
341 # check for an external route; an external route is one which is
342 # is a full url (e.g. 'http://example.com/{id}')
343 parsed = urlparse.urlparse(pattern)
344 external_url = pattern
346 if parsed.hostname:
347 pattern = parsed.path
349 original_pregenerator = pregenerator
351 def external_url_pregenerator(request, elements, kw):
352 if '_app_url' in kw:
353 raise ValueError(
354 'You cannot generate a path to an external route '
355 'pattern via request.route_path nor pass an _app_url '
356 'to request.route_url when generating a URL for an '
357 'external route pattern (pattern was "%s") '
358 % (pattern,)
359 )
360 if '_scheme' in kw:
361 scheme = kw['_scheme']
362 elif parsed.scheme:
363 scheme = parsed.scheme
364 else:
365 scheme = request.scheme
366 kw['_app_url'] = '{0}://{1}'.format(scheme, parsed.netloc)
368 if original_pregenerator:
369 elements, kw = original_pregenerator(request, elements, kw)
370 return elements, kw
372 pregenerator = external_url_pregenerator
373 static = True
375 elif self.route_prefix:
376 pattern = self.route_prefix.rstrip('/') + '/' + pattern.lstrip('/')
378 mapper = self.get_routes_mapper()
380 introspectables = []
382 intr = self.introspectable(
383 'routes', name, '%s (pattern: %r)' % (name, pattern), 'route'
384 )
385 intr['name'] = name
386 intr['pattern'] = pattern
387 intr['factory'] = factory
388 intr['xhr'] = xhr
389 intr['request_methods'] = request_method
390 intr['path_info'] = path_info
391 intr['request_param'] = request_param
392 intr['header'] = header
393 intr['accept'] = accept
394 intr['traverse'] = traverse
395 intr['custom_predicates'] = custom_predicates
396 intr['pregenerator'] = pregenerator
397 intr['static'] = static
398 intr['use_global_views'] = use_global_views
400 if static is True:
401 intr['external_url'] = external_url
403 introspectables.append(intr)
405 if factory:
406 factory_intr = self.introspectable(
407 'root factories',
408 name,
409 self.object_description(factory),
410 'root factory',
411 )
412 factory_intr['factory'] = factory
413 factory_intr['route_name'] = name
414 factory_intr.relate('routes', name)
415 introspectables.append(factory_intr)
417 def register_route_request_iface():
418 request_iface = self.registry.queryUtility(
419 IRouteRequest, name=name
420 )
421 if request_iface is None:
422 if use_global_views:
423 bases = (IRequest,)
424 else:
425 bases = ()
426 request_iface = route_request_iface(name, bases)
427 self.registry.registerUtility(
428 request_iface, IRouteRequest, name=name
429 )
431 def register_connect():
432 pvals = predicates.copy()
433 pvals.update(
434 dict(
435 xhr=xhr,
436 request_method=request_method,
437 path_info=path_info,
438 request_param=request_param,
439 header=header,
440 accept=accept,
441 traverse=traverse,
442 custom=predvalseq(custom_predicates),
443 )
444 )
446 predlist = self.get_predlist('route')
447 _, preds, _ = predlist.make(self, **pvals)
448 route = mapper.connect(
449 name,
450 pattern,
451 factory,
452 predicates=preds,
453 pregenerator=pregenerator,
454 static=static,
455 )
456 intr['object'] = route
457 return route
459 # We have to connect routes in the order they were provided;
460 # we can't use a phase to do that, because when the actions are
461 # sorted, actions in the same phase lose relative ordering
462 self.action(('route-connect', name), register_connect)
464 # But IRouteRequest interfaces must be registered before we begin to
465 # process view registrations (in phase 3)
466 self.action(
467 ('route', name),
468 register_route_request_iface,
469 order=PHASE2_CONFIG,
470 introspectables=introspectables,
471 )
473 @action_method
474 def add_route_predicate(
475 self, name, factory, weighs_more_than=None, weighs_less_than=None
476 ):
477 """ Adds a route predicate factory. The view predicate can later be
478 named as a keyword argument to
479 :meth:`pyramid.config.Configurator.add_route`.
481 ``name`` should be the name of the predicate. It must be a valid
482 Python identifier (it will be used as a keyword argument to
483 ``add_route``).
485 ``factory`` should be a :term:`predicate factory` or :term:`dotted
486 Python name` which refers to a predicate factory.
488 See :ref:`view_and_route_predicates` for more information.
490 .. versionadded:: 1.4
491 """
492 self._add_predicate(
493 'route',
494 name,
495 factory,
496 weighs_more_than=weighs_more_than,
497 weighs_less_than=weighs_less_than,
498 )
500 def add_default_route_predicates(self):
501 p = pyramid.predicates
502 for (name, factory) in (
503 ('xhr', p.XHRPredicate),
504 ('request_method', p.RequestMethodPredicate),
505 ('path_info', p.PathInfoPredicate),
506 ('request_param', p.RequestParamPredicate),
507 ('header', p.HeaderPredicate),
508 ('accept', p.AcceptPredicate),
509 ('effective_principals', p.EffectivePrincipalsPredicate),
510 ('custom', p.CustomPredicate),
511 ('traverse', p.TraversePredicate),
512 ):
513 self.add_route_predicate(name, factory)
515 def get_routes_mapper(self):
516 """ Return the :term:`routes mapper` object associated with
517 this configurator's :term:`registry`."""
518 mapper = self.registry.queryUtility(IRoutesMapper)
519 if mapper is None:
520 mapper = RoutesMapper()
521 self.registry.registerUtility(mapper, IRoutesMapper)
522 return mapper
524 @contextlib.contextmanager
525 def route_prefix_context(self, route_prefix):
526 """ Return this configurator with the
527 :attr:`pyramid.config.Configurator.route_prefix` attribute mutated to
528 include the new ``route_prefix``.
530 When the context exits, the ``route_prefix`` is reset to the original.
532 ``route_prefix`` is a string suitable to be used as a route prefix,
533 or ``None``.
535 Example Usage:
537 .. code-block:: python
539 config = Configurator()
540 with config.route_prefix_context('foo'):
541 config.add_route('bar', '/bar')
543 .. versionadded:: 1.10
545 """
546 original_route_prefix = self.route_prefix
548 if route_prefix is None:
549 route_prefix = ''
551 old_route_prefix = self.route_prefix
552 if old_route_prefix is None:
553 old_route_prefix = ''
555 route_prefix = '{}/{}'.format(
556 old_route_prefix.rstrip('/'), route_prefix.lstrip('/')
557 )
559 route_prefix = route_prefix.strip('/')
561 if not route_prefix:
562 route_prefix = None
564 self.begin()
565 try:
566 self.route_prefix = route_prefix
567 yield
569 finally:
570 self.route_prefix = original_route_prefix
571 self.end()