[230] | 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 |
|
---|
[264] | 17 | from rfc3986 import compat
|
---|
| 18 | from rfc3986 import normalizers
|
---|
| 19 | from rfc3986 import uri
|
---|
| 20 | from rfc3986.api import uri_reference
|
---|
[230] | 21 |
|
---|
[264] | 22 |
|
---|
[230] | 23 | class 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()
|
---|