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