source: uri/rfc3986/builder.py@ 241

Last change on this file since 241 was 230, checked in by wouter, 4 years ago

#91 clone https://pypi.org/project/rfc3986/

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