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