source: uri/rfc3986/builder.py@ 1355

Last change on this file since 1355 was 264, checked in by wouter, 3 years ago

#94 fix URI test code

File size: 12.5 KB
Line 
1# -*- coding: utf-8 -*-
2# Copyright (c) 2017 Ian Stapleton Cordasco
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12# implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15"""Module containing the logic for the URIBuilder object."""
16
17from rfc3986 import compat
18from rfc3986 import normalizers
19from rfc3986 import uri
20from rfc3986.api import uri_reference
21
22
23class URIBuilder(object):
24 """Object to aid in building up a URI Reference from parts.
25
26 .. note::
27
28 This object should be instantiated by the user, but it's recommended
29 that it is not provided with arguments. Instead, use the available
30 method to populate the fields.
31
32 """
33
34 def __init__(
35 self,
36 scheme=None,
37 userinfo=None,
38 host=None,
39 port=None,
40 path=None,
41 query=None,
42 fragment=None,
43 ):
44 """Initialize our URI builder.
45
46 :param str scheme:
47 (optional)
48 :param str userinfo:
49 (optional)
50 :param str host:
51 (optional)
52 :param int port:
53 (optional)
54 :param str path:
55 (optional)
56 :param str query:
57 (optional)
58 :param str fragment:
59 (optional)
60 """
61 self.scheme = scheme
62 self.userinfo = userinfo
63 self.host = host
64 self.port = port
65 self.path = path
66 self.query = query
67 self.fragment = fragment
68
69 def __repr__(self):
70 """Provide a convenient view of our builder object."""
71 formatstr = (
72 "URIBuilder(scheme={b.scheme}, userinfo={b.userinfo}, "
73 "host={b.host}, port={b.port}, path={b.path}, "
74 "query={b.query}, fragment={b.fragment})"
75 )
76 return formatstr.format(b=self)
77
78 @classmethod
79 def from_uri(cls, reference):
80 """Initialize the URI builder from another URI.
81
82 Takes the given URI reference and creates a new URI builder instance
83 populated with the values from the reference. If given a string it will
84 try to convert it to a reference before constructing the builder.
85 """
86 if not isinstance(reference, uri.URIReference):
87 reference = uri_reference(reference)
88 return cls(
89 scheme=reference.scheme,
90 userinfo=reference.userinfo,
91 host=reference.host,
92 port=reference.port,
93 path=reference.path,
94 query=reference.query,
95 fragment=reference.fragment,
96 )
97
98 def add_scheme(self, scheme):
99 """Add a scheme to our builder object.
100
101 After normalizing, this will generate a new URIBuilder instance with
102 the specified scheme and all other attributes the same.
103
104 .. code-block:: python
105
106 >>> URIBuilder().add_scheme('HTTPS')
107 URIBuilder(scheme='https', userinfo=None, host=None, port=None,
108 path=None, query=None, fragment=None)
109
110 """
111 scheme = normalizers.normalize_scheme(scheme)
112 return URIBuilder(
113 scheme=scheme,
114 userinfo=self.userinfo,
115 host=self.host,
116 port=self.port,
117 path=self.path,
118 query=self.query,
119 fragment=self.fragment,
120 )
121
122 def add_credentials(self, username, password):
123 """Add credentials as the userinfo portion of the URI.
124
125 .. code-block:: python
126
127 >>> URIBuilder().add_credentials('root', 's3crete')
128 URIBuilder(scheme=None, userinfo='root:s3crete', host=None,
129 port=None, path=None, query=None, fragment=None)
130
131 >>> URIBuilder().add_credentials('root', None)
132 URIBuilder(scheme=None, userinfo='root', host=None,
133 port=None, path=None, query=None, fragment=None)
134 """
135 if username is None:
136 raise ValueError("Username cannot be None")
137 userinfo = normalizers.normalize_username(username)
138
139 if password is not None:
140 userinfo = "{}:{}".format(
141 userinfo,
142 normalizers.normalize_password(password),
143 )
144
145 return URIBuilder(
146 scheme=self.scheme,
147 userinfo=userinfo,
148 host=self.host,
149 port=self.port,
150 path=self.path,
151 query=self.query,
152 fragment=self.fragment,
153 )
154
155 def add_host(self, host):
156 """Add hostname to the URI.
157
158 .. code-block:: python
159
160 >>> URIBuilder().add_host('google.com')
161 URIBuilder(scheme=None, userinfo=None, host='google.com',
162 port=None, path=None, query=None, fragment=None)
163
164 """
165 return URIBuilder(
166 scheme=self.scheme,
167 userinfo=self.userinfo,
168 host=normalizers.normalize_host(host),
169 port=self.port,
170 path=self.path,
171 query=self.query,
172 fragment=self.fragment,
173 )
174
175 def add_port(self, port):
176 """Add port to the URI.
177
178 .. code-block:: python
179
180 >>> URIBuilder().add_port(80)
181 URIBuilder(scheme=None, userinfo=None, host=None, port='80',
182 path=None, query=None, fragment=None)
183
184 >>> URIBuilder().add_port(443)
185 URIBuilder(scheme=None, userinfo=None, host=None, port='443',
186 path=None, query=None, fragment=None)
187
188 """
189 port_int = int(port)
190 if port_int < 0:
191 raise ValueError(
192 "ports are not allowed to be negative. You provided {}".format(
193 port_int,
194 )
195 )
196 if port_int > 65535:
197 raise ValueError(
198 "ports are not allowed to be larger than 65535. "
199 "You provided {}".format(
200 port_int,
201 )
202 )
203
204 return URIBuilder(
205 scheme=self.scheme,
206 userinfo=self.userinfo,
207 host=self.host,
208 port="{}".format(port_int),
209 path=self.path,
210 query=self.query,
211 fragment=self.fragment,
212 )
213
214 def add_path(self, path):
215 """Add a path to the URI.
216
217 .. code-block:: python
218
219 >>> URIBuilder().add_path('sigmavirus24/rfc3985')
220 URIBuilder(scheme=None, userinfo=None, host=None, port=None,
221 path='/sigmavirus24/rfc3986', query=None, fragment=None)
222
223 >>> URIBuilder().add_path('/checkout.php')
224 URIBuilder(scheme=None, userinfo=None, host=None, port=None,
225 path='/checkout.php', query=None, fragment=None)
226
227 """
228 if not path.startswith("/"):
229 path = "/{}".format(path)
230
231 return URIBuilder(
232 scheme=self.scheme,
233 userinfo=self.userinfo,
234 host=self.host,
235 port=self.port,
236 path=normalizers.normalize_path(path),
237 query=self.query,
238 fragment=self.fragment,
239 )
240
241 def extend_path(self, path):
242 """Extend the existing path value with the provided value.
243
244 .. versionadded:: 1.5.0
245
246 .. code-block:: python
247
248 >>> URIBuilder(path="/users").extend_path("/sigmavirus24")
249 URIBuilder(scheme=None, userinfo=None, host=None, port=None,
250 path='/users/sigmavirus24', query=None, fragment=None)
251
252 >>> URIBuilder(path="/users/").extend_path("/sigmavirus24")
253 URIBuilder(scheme=None, userinfo=None, host=None, port=None,
254 path='/users/sigmavirus24', query=None, fragment=None)
255
256 >>> URIBuilder(path="/users/").extend_path("sigmavirus24")
257 URIBuilder(scheme=None, userinfo=None, host=None, port=None,
258 path='/users/sigmavirus24', query=None, fragment=None)
259
260 >>> URIBuilder(path="/users").extend_path("sigmavirus24")
261 URIBuilder(scheme=None, userinfo=None, host=None, port=None,
262 path='/users/sigmavirus24', query=None, fragment=None)
263
264 """
265 existing_path = self.path or ""
266 path = "{}/{}".format(existing_path.rstrip("/"), path.lstrip("/"))
267
268 return self.add_path(path)
269
270 def add_query_from(self, query_items):
271 """Generate and add a query a dictionary or list of tuples.
272
273 .. code-block:: python
274
275 >>> URIBuilder().add_query_from({'a': 'b c'})
276 URIBuilder(scheme=None, userinfo=None, host=None, port=None,
277 path=None, query='a=b+c', fragment=None)
278
279 >>> URIBuilder().add_query_from([('a', 'b c')])
280 URIBuilder(scheme=None, userinfo=None, host=None, port=None,
281 path=None, query='a=b+c', fragment=None)
282
283 """
284 query = normalizers.normalize_query(compat.urlencode(query_items))
285
286 return URIBuilder(
287 scheme=self.scheme,
288 userinfo=self.userinfo,
289 host=self.host,
290 port=self.port,
291 path=self.path,
292 query=query,
293 fragment=self.fragment,
294 )
295
296 def extend_query_with(self, query_items):
297 """Extend the existing query string with the new query items.
298
299 .. versionadded:: 1.5.0
300
301 .. code-block:: python
302
303 >>> URIBuilder(query='a=b+c').extend_query_with({'a': 'b c'})
304 URIBuilder(scheme=None, userinfo=None, host=None, port=None,
305 path=None, query='a=b+c&a=b+c', fragment=None)
306
307 >>> URIBuilder(query='a=b+c').extend_query_with([('a', 'b c')])
308 URIBuilder(scheme=None, userinfo=None, host=None, port=None,
309 path=None, query='a=b+c&a=b+c', fragment=None)
310 """
311 original_query_items = compat.parse_qsl(self.query or "")
312 if not isinstance(query_items, list):
313 query_items = list(query_items.items())
314
315 return self.add_query_from(original_query_items + query_items)
316
317 def add_query(self, query):
318 """Add a pre-formated query string to the URI.
319
320 .. code-block:: python
321
322 >>> URIBuilder().add_query('a=b&c=d')
323 URIBuilder(scheme=None, userinfo=None, host=None, port=None,
324 path=None, query='a=b&c=d', fragment=None)
325
326 """
327 return URIBuilder(
328 scheme=self.scheme,
329 userinfo=self.userinfo,
330 host=self.host,
331 port=self.port,
332 path=self.path,
333 query=normalizers.normalize_query(query),
334 fragment=self.fragment,
335 )
336
337 def add_fragment(self, fragment):
338 """Add a fragment to the URI.
339
340 .. code-block:: python
341
342 >>> URIBuilder().add_fragment('section-2.6.1')
343 URIBuilder(scheme=None, userinfo=None, host=None, port=None,
344 path=None, query=None, fragment='section-2.6.1')
345
346 """
347 return URIBuilder(
348 scheme=self.scheme,
349 userinfo=self.userinfo,
350 host=self.host,
351 port=self.port,
352 path=self.path,
353 query=self.query,
354 fragment=normalizers.normalize_fragment(fragment),
355 )
356
357 def finalize(self):
358 """Create a URIReference from our builder.
359
360 .. code-block:: python
361
362 >>> URIBuilder().add_scheme('https').add_host('github.com'
363 ... ).add_path('sigmavirus24/rfc3986').finalize().unsplit()
364 'https://github.com/sigmavirus24/rfc3986'
365
366 >>> URIBuilder().add_scheme('https').add_host('github.com'
367 ... ).add_path('sigmavirus24/rfc3986').add_credentials(
368 ... 'sigmavirus24', 'not-re@l').finalize().unsplit()
369 'https://sigmavirus24:not-re%40l@github.com/sigmavirus24/rfc3986'
370
371 """
372 return uri.URIReference(
373 self.scheme,
374 normalizers.normalize_authority(
375 (self.userinfo, self.host, self.port)
376 ),
377 self.path,
378 self.query,
379 self.fragment,
380 )
381
382 def geturl(self):
383 """Generate the URL from this builder.
384
385 .. versionadded:: 1.5.0
386
387 This is an alternative to calling :meth:`finalize` and keeping the
388 :class:`rfc3986.uri.URIReference` around.
389 """
390 return self.finalize().unsplit()
Note: See TracBrowser for help on using the repository browser.