1 from __future__ import absolute_import
2 from collections import namedtuple
4 from ..exceptions import LocationParseError
7 url_attrs = ['scheme', 'auth', 'host', 'port', 'path', 'query', 'fragment']
10 class Url(namedtuple('Url', url_attrs)):
12 Datastructure for representing an HTTP URL. Used as a return value for
17 def __new__(cls, scheme=None, auth=None, host=None, port=None, path=None,
18 query=None, fragment=None):
19 if path and not path.startswith('/'):
21 return super(Url, cls).__new__(cls, scheme, auth, host, port, path,
26 """For backwards-compatibility with urlparse. We're nice like that."""
30 def request_uri(self):
31 """Absolute path including the query string."""
32 uri = self.path or '/'
34 if self.query is not None:
35 uri += '?' + self.query
41 """Network location including host and port"""
43 return '%s:%d' % (self.host, self.port)
49 Convert self into a url
51 This function should more or less round-trip with :func:`.parse_url`. The
52 returned url may not be exactly the same as the url inputted to
53 :func:`.parse_url`, but it should be equivalent by the RFC (e.g., urls
54 with a blank port will have : removed).
58 >>> U = parse_url('http://google.com/mail/')
60 'http://google.com/mail/'
61 >>> Url('http', 'username:password', 'host.com', 80,
62 ... '/path', 'query', 'fragment').url
63 'http://username:password@host.com:80/path?query#fragment'
65 scheme, auth, host, port, path, query, fragment = self
68 # We use "is not None" we want things to happen with empty strings (or 0 port)
69 if scheme is not None:
76 url += ':' + str(port)
81 if fragment is not None:
90 def split_first(s, delims):
92 Given a string and an iterable of delimiters, split on the first found
93 delimiter. Return two split parts and the matched delimiter.
95 If not found, then the first part is the full input string.
99 >>> split_first('foo/bar?baz', '?/=')
100 ('foo', 'bar?baz', '/')
101 >>> split_first('foo/bar?baz', '123')
102 ('foo/bar?baz', '', None)
104 Scales linearly with number of delims. Not ideal for large number of delims.
113 if min_idx is None or idx < min_idx:
117 if min_idx is None or min_idx < 0:
120 return s[:min_idx], s[min_idx + 1:], min_delim
125 Given a url, return a parsed :class:`.Url` namedtuple. Best-effort is
126 performed to parse incomplete urls. Fields not provided will be None.
128 Partly backwards-compatible with :mod:`urlparse`.
132 >>> parse_url('http://google.com/mail/')
133 Url(scheme='http', host='google.com', port=None, path='/mail/', ...)
134 >>> parse_url('google.com:80')
135 Url(scheme=None, host='google.com', port=80, path=None, ...)
136 >>> parse_url('/foo?bar')
137 Url(scheme=None, host=None, port=None, path='/foo', query='bar', ...)
140 # While this code has overlap with stdlib's urlparse, it is much
141 # simplified for our needs and less annoying.
142 # Additionally, this implementations does silly things to be optimal
159 scheme, url = url.split('://', 1)
161 # Find the earliest Authority Terminator
162 # (http://tools.ietf.org/html/rfc3986#section-3.2)
163 url, path_, delim = split_first(url, ['/', '?', '#'])
166 # Reassemble the path
171 # Last '@' denotes end of auth part
172 auth, url = url.rsplit('@', 1)
175 if url and url[0] == '[':
176 host, url = url.split(']', 1)
181 _host, port = url.split(':', 1)
187 # If given, ports must be integers.
188 if not port.isdigit():
189 raise LocationParseError(url)
192 # Blank ports are cool, too. (rfc3986#section-3.2.3)
195 elif not host and url:
199 return Url(scheme, auth, host, port, path, query, fragment)
203 path, fragment = path.split('#', 1)
207 path, query = path.split('?', 1)
209 return Url(scheme, auth, host, port, path, query, fragment)
214 Deprecated. Use :func:`.parse_url` instead.
217 return p.scheme or 'http', p.hostname, p.port