Michael Merickel
2018-10-26 035f6cf8238211d097c991677fde6b5bc046a57b
commit | author | age
c81aad 1 """ Utility functions for dealing with URLs in pyramid """
7ae0c2 2
750ce4 3 import os
7ae0c2 4
0c29cf 5 from pyramid.interfaces import IResourceURL, IRoutesMapper, IStaticURLInfo
a97a70 6
0c29cf 7 from pyramid.compat import bytes_, lru_cache, string_types
MM 8 from pyramid.encode import url_quote, urlencode
b60bdb 9 from pyramid.path import caller_package
CM 10 from pyramid.threadlocal import get_current_registry
0c1c39 11
CM 12 from pyramid.traversal import (
c51896 13     ResourceURL,
0c1c39 14     quote_path_segment,
f52759 15     PATH_SAFE,
MK 16     PATH_SEGMENT_SAFE,
0c29cf 17 )
c51896 18
0c29cf 19 QUERY_SAFE = "/?:@!$&'()*+,;="  # RFC 3986
af3134 20 ANCHOR_SAFE = QUERY_SAFE
0c29cf 21
af3134 22
498342 23 def parse_url_overrides(request, kw):
effe0e 24     """
MM 25     Parse special arguments passed when generating urls.
af3134 26
203101 27     The supplied dictionary is mutated when we pop arguments.
498342 28     Returns a 3-tuple of the format:
af3134 29
498342 30       ``(app_url, qs, anchor)``.
effe0e 31
203101 32     """
RB 33     app_url = kw.pop('_app_url', None)
34     scheme = kw.pop('_scheme', None)
35     host = kw.pop('_host', None)
36     port = kw.pop('_port', None)
707e22 37     query = kw.pop('_query', '')
203101 38     anchor = kw.pop('_anchor', '')
498342 39
MM 40     if app_url is None:
0c29cf 41         if scheme is not None or host is not None or port is not None:
498342 42             app_url = request._partial_application_url(scheme, host, port)
MM 43         else:
44             app_url = request.application_url
203101 45
707e22 46     qs = ''
203101 47     if query:
af3134 48         if isinstance(query, string_types):
ca419f 49             qs = '?' + url_quote(query, QUERY_SAFE)
707e22 50         else:
af3134 51             qs = '?' + urlencode(query, doseq=True)
MM 52
effe0e 53     frag = ''
203101 54     if anchor:
effe0e 55         frag = '#' + url_quote(anchor, ANCHOR_SAFE)
af3134 56
effe0e 57     return app_url, qs, frag
0c29cf 58
7ae0c2 59
fb90f0 60 class URLMethodsMixin(object):
CM 61     """ Request methods mixin for BaseRequest having to do with URL
62     generation """
c51896 63
5b1e2a 64     def _partial_application_url(self, scheme=None, host=None, port=None):
c51896 65         """
CM 66         Construct the URL defined by request.application_url, replacing any
67         of the default scheme, host, or port portions with user-supplied
68         variants.
69
70         If ``scheme`` is passed as ``https``, and the ``port`` is *not*
71         passed, the ``port`` value is assumed to ``443``.  Likewise, if
72         ``scheme`` is passed as ``http`` and ``port`` is not passed, the
73         ``port`` value is assumed to be ``80``.
effe0e 74
c51896 75         """
CM 76         e = self.environ
77         if scheme is None:
78             scheme = e['wsgi.url_scheme']
79         else:
80             if scheme == 'https':
81                 if port is None:
82                     port = '443'
83             if scheme == 'http':
84                 if port is None:
85                     port = '80'
86         if host is None:
87             host = e.get('HTTP_HOST')
b4fcff 88             if host is None:
SSU 89                 host = e['SERVER_NAME']
c51896 90         if port is None:
CM 91             if ':' in host:
92                 host, port = host.split(':', 1)
93             else:
94                 port = e['SERVER_PORT']
95         else:
55983f 96             port = str(port)
c51896 97             if ':' in host:
CM 98                 host, _ = host.split(':', 1)
99         if scheme == 'https':
100             if port == '443':
101                 port = None
102         elif scheme == 'http':
103             if port == '80':
104                 port = None
324140 105         url = scheme + '://' + host
c51896 106         if port:
CM 107             url += ':%s' % port
108
0c29cf 109         url_encoding = getattr(self, 'url_encoding', 'utf-8')  # webob 1.2b3+
c51896 110         bscript_name = bytes_(self.script_name, url_encoding)
CM 111         return url + url_quote(bscript_name, PATH_SAFE)
fb90f0 112
CM 113     def route_url(self, route_name, *elements, **kw):
114         """Generates a fully qualified URL for a named :app:`Pyramid`
115         :term:`route configuration`.
116
117         Use the route's ``name`` as the first positional argument.
118         Additional positional arguments (``*elements``) are appended to the
119         URL as path segments after it is generated.
120
121         Use keyword arguments to supply values which match any dynamic
122         path elements in the route definition.  Raises a :exc:`KeyError`
123         exception if the URL cannot be generated for any reason (not
124         enough arguments, for example).
125
126         For example, if you've defined a route named "foobar" with the path
127         ``{foo}/{bar}/*traverse``::
128
129             request.route_url('foobar',
130                                foo='1')             => <KeyError exception>
131             request.route_url('foobar',
132                                foo='1',
133                                bar='2')             => <KeyError exception>
134             request.route_url('foobar',
135                                foo='1',
136                                bar='2',
137                                traverse=('a','b'))  => http://e.com/1/2/a/b
138             request.route_url('foobar',
139                                foo='1',
140                                bar='2',
141                                traverse='/a/b')     => http://e.com/1/2/a/b
142
143         Values replacing ``:segment`` arguments can be passed as strings
144         or Unicode objects.  They will be encoded to UTF-8 and URL-quoted
145         before being placed into the generated URL.
146
147         Values replacing ``*remainder`` arguments can be passed as strings
148         *or* tuples of Unicode/string values.  If a tuple is passed as a
149         ``*remainder`` replacement value, its values are URL-quoted and
150         encoded to UTF-8.  The resulting strings are joined with slashes
151         and rendered into the URL.  If a string is passed as a
152         ``*remainder`` replacement value, it is tacked on to the URL
97bd71 153         after being URL-quoted-except-for-embedded-slashes.
fb90f0 154
d384ac 155         If ``_query`` is provided, it will be used to compose a query string
MM 156         that will be tacked on to the end of the URL.  The value of ``_query``
157         may be a sequence of two-tuples *or* a data structure with an
158         ``.items()`` method that returns a sequence of two-tuples
159         (presumably a dictionary). This data structure will be turned into
160         a query string per the documentation of the
161         :func:`pyramid.url.urlencode` function.  This will produce a query
162         string in the ``x-www-form-urlencoded`` format.  A
ca419f 163         non-``x-www-form-urlencoded`` query string may be used by passing a
CM 164         *string* value as ``_query`` in which case it will be URL-quoted
165         (e.g. query="foo bar" will become "foo%20bar").  However, the result
166         will not need to be in ``k=v`` form as required by
167         ``x-www-form-urlencoded``.  After the query data is turned into a query
168         string, a leading ``?`` is prepended, and the resulting string is
169         appended to the generated URL.
fb90f0 170
012b97 171         .. note::
M 172
173            Python data structures that are passed as ``_query`` which are
174            sequences or dictionaries are turned into a string under the same
a54bc1 175            rules as when run through :func:`urllib.urlencode` with the
MM 176            ``doseq`` argument equal to ``True``.  This means that sequences can
177            be passed as values, and a k=v pair will be placed into the query
178            string for each value.
fb90f0 179
CM 180         If a keyword argument ``_anchor`` is present, its string
a3654e 181         representation will be quoted per :rfc:`3986#section-3.5` and used as
MM 182         a named anchor in the generated URL
fb90f0 183         (e.g. if ``_anchor`` is passed as ``foo`` and the route URL is
CM 184         ``http://example.com/route/url``, the resulting generated URL will
185         be ``http://example.com/route/url#foo``).
186
012b97 187         .. note::
M 188
189            If ``_anchor`` is passed as a string, it should be UTF-8 encoded. If
190            ``_anchor`` is passed as a Unicode object, it will be converted to
a3654e 191            UTF-8 before being appended to the URL.
fb90f0 192
CM 193         If both ``_anchor`` and ``_query`` are specified, the anchor
194         element will always follow the query element,
195         e.g. ``http://example.com?foo=1#bar``.
196
0a6a26 197         If any of the keyword arguments ``_scheme``, ``_host``, or ``_port``
CM 198         is passed and is non-``None``, the provided value will replace the
199         named portion in the generated URL.  For example, if you pass
200         ``_host='foo.com'``, and the URL that would have been generated
201         without the host replacement is ``http://example.com/a``, the result
168a31 202         will be ``http://foo.com/a``.
a54bc1 203
0a6a26 204         Note that if ``_scheme`` is passed as ``https``, and ``_port`` is not
CM 205         passed, the ``_port`` value is assumed to have been passed as
206         ``443``.  Likewise, if ``_scheme`` is passed as ``http`` and
c51896 207         ``_port`` is not passed, the ``_port`` value is assumed to have been
0a6a26 208         passed as ``80``. To avoid this behavior, always explicitly pass
c51896 209         ``_port`` whenever you pass ``_scheme``.
CM 210
fb90f0 211         If a keyword ``_app_url`` is present, it will be used as the
CM 212         protocol/hostname/port/leading path prefix of the generated URL.
213         For example, using an ``_app_url`` of
214         ``http://example.com:8080/foo`` would cause the URL
215         ``http://example.com:8080/foo/fleeb/flub`` to be returned from
216         this function if the expansion of the route pattern associated
217         with the ``route_name`` expanded to ``/fleeb/flub``.  If
218         ``_app_url`` is not specified, the result of
219         ``request.application_url`` will be used as the prefix (the
220         default).
c51896 221
CM 222         If both ``_app_url`` and any of ``_scheme``, ``_host``, or ``_port``
223         are passed, ``_app_url`` takes precedence and any values passed for
224         ``_scheme``, ``_host``, and ``_port`` will be ignored.
fb90f0 225
CM 226         This function raises a :exc:`KeyError` if the URL cannot be
227         generated due to missing replacement names.  Extra replacement
228         names are ignored.
229
230         If the route object which matches the ``route_name`` argument has
043ccd 231         a :term:`pregenerator`, the ``*elements`` and ``**kw``
fb90f0 232         arguments passed to this function might be augmented or changed.
effe0e 233
MM 234         .. versionchanged:: 1.5
235            Allow the ``_query`` option to be a string to enable alternative
236            encodings.
237
238            The ``_anchor`` option will be escaped instead of using
239            its raw string representation.
240
241         .. versionchanged:: 1.9
242            If ``_query`` or ``_anchor`` are falsey (such as ``None`` or an
243            empty string) they will not be included in the generated url.
244
fb90f0 245         """
CM 246         try:
247             reg = self.registry
248         except AttributeError:
0c29cf 249             reg = get_current_registry()  # b/c
fb90f0 250         mapper = reg.getUtility(IRoutesMapper)
CM 251         route = mapper.get_route(route_name)
252
253         if route is None:
254             raise KeyError('No such route named %s' % route_name)
255
256         if route.pregenerator is not None:
257             elements, kw = route.pregenerator(self, elements, kw)
258
498342 259         app_url, qs, anchor = parse_url_overrides(self, kw)
c51896 260
0c29cf 261         path = route.generate(kw)  # raises KeyError if generate fails
fb90f0 262
CM 263         if elements:
264             suffix = _join_elements(elements)
265             if not path.endswith('/'):
266                 suffix = '/' + suffix
267         else:
268             suffix = ''
269
270         return app_url + path + suffix + qs + anchor
271
272     def route_path(self, route_name, *elements, **kw):
273         """
274         Generates a path (aka a 'relative URL', a URL minus the host, scheme,
275         and port) for a named :app:`Pyramid` :term:`route configuration`.
276
277         This function accepts the same argument as
278         :meth:`pyramid.request.Request.route_url` and performs the same duty.
279         It just omits the host, port, and scheme information in the return
280         value; only the script_name, path, query parameters, and anchor data
281         are present in the returned string.
282
283         For example, if you've defined a route named 'foobar' with the path
284         ``/{foo}/{bar}``, this call to ``route_path``::
285
286             request.route_path('foobar', foo='1', bar='2')
287
288         Will return the string ``/1/2``.
289
012b97 290         .. note::
M 291
292            Calling ``request.route_path('route')`` is the same as calling
293            ``request.route_url('route', _app_url=request.script_name)``.
d587b4 294            :meth:`pyramid.request.Request.route_path` is, in fact,
CM 295            implemented in terms of :meth:`pyramid.request.Request.route_url`
296            in just this way. As a result, any ``_app_url`` passed within the
297            ``**kw`` values to ``route_path`` will be ignored.
effe0e 298
fb90f0 299         """
CM 300         kw['_app_url'] = self.script_name
301         return self.route_url(route_name, *elements, **kw)
302
303     def resource_url(self, resource, *elements, **kw):
304         """
305         Generate a string representing the absolute URL of the
306         :term:`resource` object based on the ``wsgi.url_scheme``,
307         ``HTTP_HOST`` or ``SERVER_NAME`` in the request, plus any
308         ``SCRIPT_NAME``.  The overall result of this method is always a
c51896 309         UTF-8 encoded string.
fb90f0 310
CM 311         Examples::
312
313             request.resource_url(resource) =>
314
315                                        http://example.com/
316
317             request.resource_url(resource, 'a.html') =>
318
319                                        http://example.com/a.html
320
321             request.resource_url(resource, 'a.html', query={'q':'1'}) =>
322
323                                        http://example.com/a.html?q=1
324
325             request.resource_url(resource, 'a.html', anchor='abc') =>
326
327                                        http://example.com/a.html#abc
c51896 328
CM 329             request.resource_url(resource, app_url='') =>
330
331                                        /
fb90f0 332
CM 333         Any positional arguments passed in as ``elements`` must be strings
334         Unicode objects, or integer objects.  These will be joined by slashes
335         and appended to the generated resource URL.  Each of the elements
336         passed in is URL-quoted before being appended; if any element is
337         Unicode, it will converted to a UTF-8 bytestring before being
338         URL-quoted. If any element is an integer, it will be converted to its
339         string representation before being URL-quoted.
340
341         .. warning:: if no ``elements`` arguments are specified, the resource
342                      URL will end with a trailing slash.  If any
343                      ``elements`` are used, the generated URL will *not*
8856e2 344                      end in a trailing slash.
fb90f0 345
d384ac 346         If ``query`` is provided, it will be used to compose a query string
MM 347         that will be tacked on to the end of the URL.  The value of ``query``
348         may be a sequence of two-tuples *or* a data structure with an
349         ``.items()`` method that returns a sequence of two-tuples
350         (presumably a dictionary). This data structure will be turned into
351         a query string per the documentation of the
352         :func:`pyramid.url.urlencode` function.  This will produce a query
353         string in the ``x-www-form-urlencoded`` format.  A
354         non-``x-www-form-urlencoded`` query string may be used by passing a
355         *string* value as ``query`` in which case it will be URL-quoted
356         (e.g. query="foo bar" will become "foo%20bar").  However, the result
357         will not need to be in ``k=v`` form as required by
358         ``x-www-form-urlencoded``.  After the query data is turned into a query
359         string, a leading ``?`` is prepended, and the resulting string is
360         appended to the generated URL.
fb90f0 361
012b97 362         .. note::
M 363
364            Python data structures that are passed as ``query`` which are
365            sequences or dictionaries are turned into a string under the same
a54bc1 366            rules as when run through :func:`urllib.urlencode` with the
MM 367            ``doseq`` argument equal to ``True``.  This means that sequences can
368            be passed as values, and a k=v pair will be placed into the query
369            string for each value.
a3654e 370
fb90f0 371         If a keyword argument ``anchor`` is present, its string
CM 372         representation will be used as a named anchor in the generated URL
373         (e.g. if ``anchor`` is passed as ``foo`` and the resource URL is
374         ``http://example.com/resource/url``, the resulting generated URL will
375         be ``http://example.com/resource/url#foo``).
376
012b97 377         .. note::
M 378
379            If ``anchor`` is passed as a string, it should be UTF-8 encoded. If
380            ``anchor`` is passed as a Unicode object, it will be converted to
a3654e 381            UTF-8 before being appended to the URL.
fb90f0 382
CM 383         If both ``anchor`` and ``query`` are specified, the anchor element
384         will always follow the query element,
385         e.g. ``http://example.com?foo=1#bar``.
386
c51896 387         If any of the keyword arguments ``scheme``, ``host``, or ``port`` is
CM 388         passed and is non-``None``, the provided value will replace the named
389         portion in the generated URL.  For example, if you pass
0a6a26 390         ``host='foo.com'``, and the URL that would have been generated
CM 391         without the host replacement is ``http://example.com/a``, the result
168a31 392         will be ``http://foo.com/a``.
a54bc1 393
c51896 394         If ``scheme`` is passed as ``https``, and an explicit ``port`` is not
CM 395         passed, the ``port`` value is assumed to have been passed as ``443``.
396         Likewise, if ``scheme`` is passed as ``http`` and ``port`` is not
397         passed, the ``port`` value is assumed to have been passed as
398         ``80``. To avoid this behavior, always explicitly pass ``port``
399         whenever you pass ``scheme``.
400
401         If a keyword argument ``app_url`` is passed and is not ``None``, it
402         should be a string that will be used as the port/hostname/initial
403         path portion of the generated URL instead of the default request
404         application URL.  For example, if ``app_url='http://foo'``, then the
405         resulting url of a resource that has a path of ``/baz/bar`` will be
406         ``http://foo/baz/bar``.  If you want to generate completely relative
407         URLs with no leading scheme, host, port, or initial path, you can
3cdae9 408         pass ``app_url=''``.  Passing ``app_url=''`` when the resource path is
c51896 409         ``/baz/bar`` will return ``/baz/bar``.
CM 410
411         If ``app_url`` is passed and any of ``scheme``, ``port``, or ``host``
412         are also passed, ``app_url`` will take precedence and the values
413         passed for ``scheme``, ``host``, and/or ``port`` will be ignored.
414
0a4aed 415         If the ``resource`` passed in has a ``__resource_url__`` method, it
CM 416         will be used to generate the URL (scheme, host, port, path) for the
2033ee 417         base resource which is operated upon by this function.
a54bc1 418
2033ee 419         .. seealso::
SP 420
421             See also :ref:`overriding_resource_url_generation`.
a54bc1 422
db0185 423         If ``route_name`` is passed, this function will delegate its URL
CM 424         production to the ``route_url`` function.  Calling
425         ``resource_url(someresource, 'element1', 'element2', query={'a':1},
426         route_name='blogentry')`` is roughly equivalent to doing::
427
7764d4 428            traversal_path = request.resource_path(someobject)
db0185 429            url = request.route_url(
CM 430                      'blogentry',
431                      'element1',
432                      'element2',
433                      _query={'a':'1'},
c29603 434                      traverse=traversal_path,
db0185 435                      )
CM 436
437         It is only sensible to pass ``route_name`` if the route being named has
438         a ``*remainder`` stararg value such as ``*traverse``.  The remainder
0a4aed 439         value will be ignored in the output otherwise.
db0185 440
c29603 441         By default, the resource path value will be passed as the name
CM 442         ``traverse`` when ``route_url`` is called.  You can influence this by
443         passing a different ``route_remainder_name`` value if the route has a
444         different ``*stararg`` value at its end.  For example if the route
445         pattern you want to replace has a ``*subpath`` stararg ala
446         ``/foo*subpath``::
447
448            request.resource_url(
449                           resource,
450                           route_name='myroute',
451                           route_remainder_name='subpath'
452                           )
453
db0185 454         If ``route_name`` is passed, it is also permissible to pass
CM 455         ``route_kw``, which will passed as additional keyword arguments to
456         ``route_url``.  Saying ``resource_url(someresource, 'element1',
457         'element2', route_name='blogentry', route_kw={'id':'4'},
c29603 458         _query={'a':'1'})`` is roughly equivalent to::
db0185 459
7764d4 460            traversal_path = request.resource_path_tuple(someobject)
c29603 461            kw = {'id':'4', '_query':{'a':'1'}, 'traverse':traversal_path}
db0185 462            url = request.route_url(
CM 463                      'blogentry',
464                      'element1',
465                      'element2',
466                      **kw,
467                      )
468
c29603 469         If ``route_kw`` or ``route_remainder_name`` is passed, but
CM 470         ``route_name`` is not passed, both ``route_kw`` and
471         ``route_remainder_name`` will be ignored.  If ``route_name``
472         is passed, the ``__resource_url__`` method of the resource passed is
473         ignored unconditionally.  This feature is incompatible with
474         resources which generate their own URLs.
a54bc1 475
012b97 476         .. note::
fb90f0 477
a54bc1 478            If the :term:`resource` used is the result of a :term:`traversal`,
MM 479            it must be :term:`location`-aware.  The resource can also be the
480            context of a :term:`URL dispatch`; contexts found this way do not
481            need to be location-aware.
fb90f0 482
012b97 483         .. note::
M 484
485            If a 'virtual root path' is present in the request environment (the
486            value of the WSGI environ key ``HTTP_X_VHM_ROOT``), and the resource
a54bc1 487            was obtained via :term:`traversal`, the URL path will not include
MM 488            the virtual root prefix (it will be stripped off the left hand side
489            of the generated URL).
012b97 490
M 491         .. note::
492
493            For backwards compatibility purposes, this method is also
fb90f0 494            aliased as the ``model_url`` method of request.
effe0e 495
MM 496         .. versionchanged:: 1.3
497            Added the ``app_url`` keyword argument.
498
499         .. versionchanged:: 1.5
500            Allow the ``query`` option to be a string to enable alternative
501            encodings.
502
503            The ``anchor`` option will be escaped instead of using
504            its raw string representation.
505
506            Added the ``route_name``, ``route_kw``, and
507            ``route_remainder_name`` keyword arguments.
508
509         .. versionchanged:: 1.9
510            If ``query`` or ``anchor`` are falsey (such as ``None`` or an
511            empty string) they will not be included in the generated url.
fb90f0 512         """
CM 513         try:
514             reg = self.registry
515         except AttributeError:
0c29cf 516             reg = get_current_registry()  # b/c
fb90f0 517
c51896 518         url_adapter = reg.queryMultiAdapter((resource, self), IResourceURL)
CM 519         if url_adapter is None:
520             url_adapter = ResourceURL(resource, self)
521
522         virtual_path = getattr(url_adapter, 'virtual_path', None)
523
498342 524         urlkw = {}
0c29cf 525         for name in ('app_url', 'scheme', 'host', 'port', 'query', 'anchor'):
498342 526             val = kw.get(name, None)
MM 527             if val is not None:
528                 urlkw['_' + name] = val
c51896 529
7ba907 530         if 'route_name' in kw:
MM 531             route_name = kw['route_name']
532             remainder = getattr(url_adapter, 'virtual_path_tuple', None)
533             if remainder is None:
534                 # older user-supplied IResourceURL adapter without 1.5
535                 # virtual_path_tuple
536                 remainder = tuple(url_adapter.virtual_path.split('/'))
537             remainder_name = kw.get('route_remainder_name', 'traverse')
498342 538             urlkw[remainder_name] = remainder
c51896 539
7ba907 540             if 'route_kw' in kw:
MM 541                 route_kw = kw.get('route_kw')
542                 if route_kw is not None:
498342 543                     urlkw.update(route_kw)
c51896 544
498342 545             return self.route_url(route_name, *elements, **urlkw)
db0185 546
498342 547         app_url, qs, anchor = parse_url_overrides(self, urlkw)
c51896 548
7ba907 549         resource_url = None
MM 550         local_url = getattr(resource, '__resource_url__', None)
c51896 551
7ba907 552         if local_url is not None:
MM 553             # the resource handles its own url generation
554             d = dict(
555                 virtual_path=virtual_path,
556                 physical_path=url_adapter.physical_path,
557                 app_url=app_url,
558             )
c51896 559
7ba907 560             # allow __resource_url__ to punt by returning None
MM 561             resource_url = local_url(self, d)
c51896 562
7ba907 563         if resource_url is None:
MM 564             # the resource did not handle its own url generation or the
565             # __resource_url__ function returned None
566             resource_url = app_url + virtual_path
fb90f0 567
CM 568         if elements:
569             suffix = _join_elements(elements)
570         else:
571             suffix = ''
572
573         return resource_url + suffix + qs + anchor
012b97 574
0c29cf 575     model_url = resource_url  # b/w compat forever
fb90f0 576
c51896 577     def resource_path(self, resource, *elements, **kw):
CM 578         """
579         Generates a path (aka a 'relative URL', a URL minus the host, scheme,
580         and port) for a :term:`resource`.
581
582         This function accepts the same argument as
583         :meth:`pyramid.request.Request.resource_url` and performs the same
584         duty.  It just omits the host, port, and scheme information in the
585         return value; only the script_name, path, query parameters, and
586         anchor data are present in the returned string.
587
588         .. note::
589
590            Calling ``request.resource_path(resource)`` is the same as calling
591            ``request.resource_path(resource, app_url=request.script_name)``.
592            :meth:`pyramid.request.Request.resource_path` is, in fact,
593            implemented in terms of
594            :meth:`pyramid.request.Request.resource_url` in just this way. As
595            a result, any ``app_url`` passed within the ``**kw`` values to
596            ``route_path`` will be ignored.  ``scheme``, ``host``, and
597            ``port`` are also ignored.
598         """
599         kw['app_url'] = self.script_name
600         return self.resource_url(resource, *elements, **kw)
601
fb90f0 602     def static_url(self, path, **kw):
CM 603         """
604         Generates a fully qualified URL for a static :term:`asset`.
605         The asset must live within a location defined via the
606         :meth:`pyramid.config.Configurator.add_static_view`
607         :term:`configuration declaration` (see :ref:`static_assets_section`).
608
609         Example::
610
611             request.static_url('mypackage:static/foo.css') =>
612
613                                     http://example.com/static/foo.css
614
615
616         The ``path`` argument points at a file or directory on disk which
617         a URL should be generated for.  The ``path`` may be either a
49425b 618         relative path (e.g. ``static/foo.css``) or an absolute path (e.g.
BB 619         ``/abspath/to/static/foo.css``) or a :term:`asset specification`
620         (e.g. ``mypackage:static/foo.css``).
fb90f0 621
CM 622         The purpose of the ``**kw`` argument is the same as the purpose of
623         the :meth:`pyramid.request.Request.route_url` ``**kw`` argument.  See
624         the documentation for that function to understand the arguments which
625         you can provide to it.  However, typically, you don't need to pass
626         anything as ``*kw`` when generating a static asset URL.
627
628         This function raises a :exc:`ValueError` if a static view
629         definition cannot be found which matches the path specification.
630
631         """
b8c797 632         if not os.path.isabs(path):
25c64c 633             if ':' not in path:
b8c797 634                 # if it's not a package:relative/name and it's not an
CM 635                 # /absolute/path it's a relative/path; this means its relative
636                 # to the package in which the caller's module is defined.
637                 package = caller_package()
638                 path = '%s:%s' % (package.__name__, path)
fb90f0 639
CM 640         try:
641             reg = self.registry
642         except AttributeError:
0c29cf 643             reg = get_current_registry()  # b/c
fb90f0 644
CM 645         info = reg.queryUtility(IStaticURLInfo)
646         if info is None:
647             raise ValueError('No static URL definition matching %s' % path)
648
649         return info.generate(path, self, **kw)
650
5c6963 651     def static_path(self, path, **kw):
CM 652         """
653         Generates a path (aka a 'relative URL', a URL minus the host, scheme,
654         and port) for a static resource.
fb90f0 655
5c6963 656         This function accepts the same argument as
c83d57 657         :meth:`pyramid.request.Request.static_url` and performs the
5c6963 658         same duty.  It just omits the host, port, and scheme information in
CM 659         the return value; only the script_name, path, query parameters, and
660         anchor data are present in the returned string.
661
662         Example::
663
664             request.static_path('mypackage:static/foo.css') =>
665
666                                     /static/foo.css
667
012b97 668         .. note::
M 669
670            Calling ``request.static_path(apath)`` is the same as calling
671            ``request.static_url(apath, _app_url=request.script_name)``.
672            :meth:`pyramid.request.Request.static_path` is, in fact, implemented
c0fb3b 673            in terms of :meth:`pyramid.request.Request.static_url` in just this
012b97 674            way. As a result, any ``_app_url`` passed within the ``**kw`` values
M 675            to ``static_path`` will be ignored.
5c6963 676         """
b8c797 677         if not os.path.isabs(path):
25c64c 678             if ':' not in path:
b8c797 679                 # if it's not a package:relative/name and it's not an
CM 680                 # /absolute/path it's a relative/path; this means its relative
681                 # to the package in which the caller's module is defined.
682                 package = caller_package()
683                 path = '%s:%s' % (package.__name__, path)
5c6963 684
CM 685         kw['_app_url'] = self.script_name
686         return self.static_url(path, **kw)
687
688     def current_route_url(self, *elements, **kw):
fb90f0 689         """
CM 690         Generates a fully qualified URL for a named :app:`Pyramid`
691         :term:`route configuration` based on the 'current route'.
692
693         This function supplements
694         :meth:`pyramid.request.Request.route_url`. It presents an easy way to
695         generate a URL for the 'current route' (defined as the route which
696         matched when the request was generated).
697
698         The arguments to this method have the same meaning as those with the
699         same names passed to :meth:`pyramid.request.Request.route_url`.  It
700         also understands an extra argument which ``route_url`` does not named
701         ``_route_name``.
702
703         The route name used to generate a URL is taken from either the
704         ``_route_name`` keyword argument or the name of the route which is
705         currently associated with the request if ``_route_name`` was not
706         passed.  Keys and values from the current request :term:`matchdict`
707         are combined with the ``kw`` arguments to form a set of defaults
708         named ``newkw``.  Then ``request.route_url(route_name, *elements,
709         **newkw)`` is called, returning a URL.
710
711         Examples follow.
712
713         If the 'current route' has the route pattern ``/foo/{page}`` and the
714         current url path is ``/foo/1`` , the matchdict will be
715         ``{'page':'1'}``.  The result of ``request.current_route_url()`` in
716         this situation will be ``/foo/1``.
717
718         If the 'current route' has the route pattern ``/foo/{page}`` and the
719         current url path is ``/foo/1``, the matchdict will be
720         ``{'page':'1'}``.  The result of
721         ``request.current_route_url(page='2')`` in this situation will be
722         ``/foo/2``.
723
724         Usage of the ``_route_name`` keyword argument: if our routing table
725         defines routes ``/foo/{action}`` named 'foo' and
726         ``/foo/{action}/{page}`` named ``fooaction``, and the current url
727         pattern is ``/foo/view`` (which has matched the ``/foo/{action}``
728         route), we may want to use the matchdict args to generate a URL to
729         the ``fooaction`` route.  In this scenario,
730         ``request.current_route_url(_route_name='fooaction', page='5')``
731         Will return string like: ``/foo/view/5``.
732
733         """
734         if '_route_name' in kw:
735             route_name = kw.pop('_route_name')
736         else:
737             route = getattr(self, 'matched_route', None)
738             route_name = getattr(route, 'name', None)
739             if route_name is None:
740                 raise ValueError('Current request matches no route')
741
a54d7d 742         if '_query' not in kw:
MM 743             kw['_query'] = self.GET
744
fb90f0 745         newkw = {}
CM 746         newkw.update(self.matchdict)
747         newkw.update(kw)
748         return self.route_url(route_name, *elements, **newkw)
12cef0 749
CM 750     def current_route_path(self, *elements, **kw):
751         """
752         Generates a path (aka a 'relative URL', a URL minus the host, scheme,
753         and port) for the :app:`Pyramid` :term:`route configuration` matched
754         by the current request.
755
756         This function accepts the same argument as
757         :meth:`pyramid.request.Request.current_route_url` and performs the
758         same duty.  It just omits the host, port, and scheme information in
759         the return value; only the script_name, path, query parameters, and
760         anchor data are present in the returned string.
761
287109 762         For example, if the route matched by the current request has the
CM 763         pattern ``/{foo}/{bar}``, this call to ``current_route_path``::
12cef0 764
287109 765             request.current_route_path(foo='1', bar='2')
12cef0 766
CM 767         Will return the string ``/1/2``.
768
012b97 769         .. note::
M 770
771            Calling ``request.current_route_path('route')`` is the same
12cef0 772            as calling ``request.current_route_url('route',
CM 773            _app_url=request.script_name)``.
774            :meth:`pyramid.request.Request.current_route_path` is, in fact,
775            implemented in terms of
dcab61 776            :meth:`pyramid.request.Request.current_route_url` in just this
12cef0 777            way. As a result, any ``_app_url`` passed within the ``**kw``
CM 778            values to ``current_route_path`` will be ignored.
779         """
5c6963 780         kw['_app_url'] = self.script_name
12cef0 781         return self.current_route_url(*elements, **kw)
012b97 782
M 783
05c023 784 def route_url(route_name, request, *elements, **kw):
dc405b 785     """
fb90f0 786     This is a backwards compatibility function.  Its result is the same as
CM 787     calling::
70f1cd 788
fb90f0 789         request.route_url(route_name, *elements, **kw)
70f1cd 790
fb90f0 791     See :meth:`pyramid.request.Request.route_url` for more information.
CM 792     """
793     return request.route_url(route_name, *elements, **kw)
dc405b 794
0c29cf 795
2c9d14 796 def route_path(route_name, request, *elements, **kw):
CM 797     """
fb90f0 798     This is a backwards compatibility function.  Its result is the same as
CM 799     calling::
800
801         request.route_path(route_name, *elements, **kw)
802
803     See :meth:`pyramid.request.Request.route_path` for more information.
804     """
805     return request.route_path(route_name, *elements, **kw)
0c29cf 806
2c9d14 807
fb6a5c 808 def resource_url(resource, request, *elements, **kw):
7ae0c2 809     """
fb90f0 810     This is a backwards compatibility function.  Its result is the same as
CM 811     calling::
36c159 812
fb90f0 813         request.resource_url(resource, *elements, **kw)
7ae0c2 814
fb90f0 815     See :meth:`pyramid.request.Request.resource_url` for more information.
003908 816     """
fb90f0 817     return request.resource_url(resource, *elements, **kw)
fb6a5c 818
0c29cf 819
MM 820 model_url = resource_url  # b/w compat (forever)
206188 821
25c64c 822
750ce4 823 def static_url(path, request, **kw):
CM 824     """
fb90f0 825     This is a backwards compatibility function.  Its result is the same as
CM 826     calling::
750ce4 827
fb90f0 828         request.static_url(path, **kw)
36c159 829
fb90f0 830     See :meth:`pyramid.request.Request.static_url` for more information.
750ce4 831     """
b5c0cb 832     if not os.path.isabs(path):
25c64c 833         if ':' not in path:
b5c0cb 834             # if it's not a package:relative/name and it's not an
CM 835             # /absolute/path it's a relative/path; this means its relative
836             # to the package in which the caller's module is defined.
837             package = caller_package()
838             path = '%s:%s' % (package.__name__, path)
fb90f0 839     return request.static_url(path, **kw)
25c64c 840
7ae0c2 841
5c6963 842 def static_path(path, request, **kw):
CM 843     """
844     This is a backwards compatibility function.  Its result is the same as
845     calling::
846
847         request.static_path(path, **kw)
848
849     See :meth:`pyramid.request.Request.static_path` for more information.
850     """
b5c0cb 851     if not os.path.isabs(path):
25c64c 852         if ':' not in path:
b5c0cb 853             # if it's not a package:relative/name and it's not an
CM 854             # /absolute/path it's a relative/path; this means its relative
855             # to the package in which the caller's module is defined.
856             package = caller_package()
857             path = '%s:%s' % (package.__name__, path)
5c6963 858     return request.static_path(path, **kw)
CM 859
0c29cf 860
5653d1 861 def current_route_url(request, *elements, **kw):
b23e6e 862     """
fb90f0 863     This is a backwards compatibility function.  Its result is the same as
CM 864     calling::
5653d1 865
fb90f0 866         request.current_route_url(*elements, **kw)
5653d1 867
fb90f0 868     See :meth:`pyramid.request.Request.current_route_url` for more
CM 869     information.
870     """
871     return request.current_route_url(*elements, **kw)
0c29cf 872
b23e6e 873
12cef0 874 def current_route_path(request, *elements, **kw):
CM 875     """
876     This is a backwards compatibility function.  Its result is the same as
877     calling::
878
879         request.current_route_path(*elements, **kw)
880
881     See :meth:`pyramid.request.Request.current_route_path` for more
882     information.
883     """
884     return request.current_route_path(*elements, **kw)
885
0c29cf 886
afc4bb 887 @lru_cache(1000)
CM 888 def _join_elements(elements):
0c29cf 889     return '/'.join(
MM 890         [quote_path_segment(s, safe=PATH_SEGMENT_SAFE) for s in elements]
891     )