389 lines
12 KiB
Python
389 lines
12 KiB
Python
|
# Copyright (c) 2017 Ian Stapleton Cordasco
|
||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
# you may not use this file except in compliance with the License.
|
||
|
# You may obtain a copy of the License at
|
||
|
#
|
||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||
|
#
|
||
|
# Unless required by applicable law or agreed to in writing, software
|
||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||
|
# implied.
|
||
|
# See the License for the specific language governing permissions and
|
||
|
# limitations under the License.
|
||
|
"""Module containing the logic for the URIBuilder object."""
|
||
|
from . import compat
|
||
|
from . import normalizers
|
||
|
from . import uri
|
||
|
from . import uri_reference
|
||
|
|
||
|
|
||
|
class URIBuilder:
|
||
|
"""Object to aid in building up a URI Reference from parts.
|
||
|
|
||
|
.. note::
|
||
|
|
||
|
This object should be instantiated by the user, but it's recommended
|
||
|
that it is not provided with arguments. Instead, use the available
|
||
|
method to populate the fields.
|
||
|
|
||
|
"""
|
||
|
|
||
|
def __init__(
|
||
|
self,
|
||
|
scheme=None,
|
||
|
userinfo=None,
|
||
|
host=None,
|
||
|
port=None,
|
||
|
path=None,
|
||
|
query=None,
|
||
|
fragment=None,
|
||
|
):
|
||
|
"""Initialize our URI builder.
|
||
|
|
||
|
:param str scheme:
|
||
|
(optional)
|
||
|
:param str userinfo:
|
||
|
(optional)
|
||
|
:param str host:
|
||
|
(optional)
|
||
|
:param int port:
|
||
|
(optional)
|
||
|
:param str path:
|
||
|
(optional)
|
||
|
:param str query:
|
||
|
(optional)
|
||
|
:param str fragment:
|
||
|
(optional)
|
||
|
"""
|
||
|
self.scheme = scheme
|
||
|
self.userinfo = userinfo
|
||
|
self.host = host
|
||
|
self.port = port
|
||
|
self.path = path
|
||
|
self.query = query
|
||
|
self.fragment = fragment
|
||
|
|
||
|
def __repr__(self):
|
||
|
"""Provide a convenient view of our builder object."""
|
||
|
formatstr = (
|
||
|
"URIBuilder(scheme={b.scheme}, userinfo={b.userinfo}, "
|
||
|
"host={b.host}, port={b.port}, path={b.path}, "
|
||
|
"query={b.query}, fragment={b.fragment})"
|
||
|
)
|
||
|
return formatstr.format(b=self)
|
||
|
|
||
|
@classmethod
|
||
|
def from_uri(cls, reference):
|
||
|
"""Initialize the URI builder from another URI.
|
||
|
|
||
|
Takes the given URI reference and creates a new URI builder instance
|
||
|
populated with the values from the reference. If given a string it will
|
||
|
try to convert it to a reference before constructing the builder.
|
||
|
"""
|
||
|
if not isinstance(reference, uri.URIReference):
|
||
|
reference = uri_reference(reference)
|
||
|
return cls(
|
||
|
scheme=reference.scheme,
|
||
|
userinfo=reference.userinfo,
|
||
|
host=reference.host,
|
||
|
port=reference.port,
|
||
|
path=reference.path,
|
||
|
query=reference.query,
|
||
|
fragment=reference.fragment,
|
||
|
)
|
||
|
|
||
|
def add_scheme(self, scheme):
|
||
|
"""Add a scheme to our builder object.
|
||
|
|
||
|
After normalizing, this will generate a new URIBuilder instance with
|
||
|
the specified scheme and all other attributes the same.
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
>>> URIBuilder().add_scheme('HTTPS')
|
||
|
URIBuilder(scheme='https', userinfo=None, host=None, port=None,
|
||
|
path=None, query=None, fragment=None)
|
||
|
|
||
|
"""
|
||
|
scheme = normalizers.normalize_scheme(scheme)
|
||
|
return URIBuilder(
|
||
|
scheme=scheme,
|
||
|
userinfo=self.userinfo,
|
||
|
host=self.host,
|
||
|
port=self.port,
|
||
|
path=self.path,
|
||
|
query=self.query,
|
||
|
fragment=self.fragment,
|
||
|
)
|
||
|
|
||
|
def add_credentials(self, username, password):
|
||
|
"""Add credentials as the userinfo portion of the URI.
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
>>> URIBuilder().add_credentials('root', 's3crete')
|
||
|
URIBuilder(scheme=None, userinfo='root:s3crete', host=None,
|
||
|
port=None, path=None, query=None, fragment=None)
|
||
|
|
||
|
>>> URIBuilder().add_credentials('root', None)
|
||
|
URIBuilder(scheme=None, userinfo='root', host=None,
|
||
|
port=None, path=None, query=None, fragment=None)
|
||
|
"""
|
||
|
if username is None:
|
||
|
raise ValueError("Username cannot be None")
|
||
|
userinfo = normalizers.normalize_username(username)
|
||
|
|
||
|
if password is not None:
|
||
|
userinfo = "{}:{}".format(
|
||
|
userinfo,
|
||
|
normalizers.normalize_password(password),
|
||
|
)
|
||
|
|
||
|
return URIBuilder(
|
||
|
scheme=self.scheme,
|
||
|
userinfo=userinfo,
|
||
|
host=self.host,
|
||
|
port=self.port,
|
||
|
path=self.path,
|
||
|
query=self.query,
|
||
|
fragment=self.fragment,
|
||
|
)
|
||
|
|
||
|
def add_host(self, host):
|
||
|
"""Add hostname to the URI.
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
>>> URIBuilder().add_host('google.com')
|
||
|
URIBuilder(scheme=None, userinfo=None, host='google.com',
|
||
|
port=None, path=None, query=None, fragment=None)
|
||
|
|
||
|
"""
|
||
|
return URIBuilder(
|
||
|
scheme=self.scheme,
|
||
|
userinfo=self.userinfo,
|
||
|
host=normalizers.normalize_host(host),
|
||
|
port=self.port,
|
||
|
path=self.path,
|
||
|
query=self.query,
|
||
|
fragment=self.fragment,
|
||
|
)
|
||
|
|
||
|
def add_port(self, port):
|
||
|
"""Add port to the URI.
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
>>> URIBuilder().add_port(80)
|
||
|
URIBuilder(scheme=None, userinfo=None, host=None, port='80',
|
||
|
path=None, query=None, fragment=None)
|
||
|
|
||
|
>>> URIBuilder().add_port(443)
|
||
|
URIBuilder(scheme=None, userinfo=None, host=None, port='443',
|
||
|
path=None, query=None, fragment=None)
|
||
|
|
||
|
"""
|
||
|
port_int = int(port)
|
||
|
if port_int < 0:
|
||
|
raise ValueError(
|
||
|
"ports are not allowed to be negative. You provided {}".format(
|
||
|
port_int,
|
||
|
)
|
||
|
)
|
||
|
if port_int > 65535:
|
||
|
raise ValueError(
|
||
|
"ports are not allowed to be larger than 65535. "
|
||
|
"You provided {}".format(
|
||
|
port_int,
|
||
|
)
|
||
|
)
|
||
|
|
||
|
return URIBuilder(
|
||
|
scheme=self.scheme,
|
||
|
userinfo=self.userinfo,
|
||
|
host=self.host,
|
||
|
port=f"{port_int}",
|
||
|
path=self.path,
|
||
|
query=self.query,
|
||
|
fragment=self.fragment,
|
||
|
)
|
||
|
|
||
|
def add_path(self, path):
|
||
|
"""Add a path to the URI.
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
>>> URIBuilder().add_path('sigmavirus24/rfc3985')
|
||
|
URIBuilder(scheme=None, userinfo=None, host=None, port=None,
|
||
|
path='/sigmavirus24/rfc3986', query=None, fragment=None)
|
||
|
|
||
|
>>> URIBuilder().add_path('/checkout.php')
|
||
|
URIBuilder(scheme=None, userinfo=None, host=None, port=None,
|
||
|
path='/checkout.php', query=None, fragment=None)
|
||
|
|
||
|
"""
|
||
|
if not path.startswith("/"):
|
||
|
path = f"/{path}"
|
||
|
|
||
|
return URIBuilder(
|
||
|
scheme=self.scheme,
|
||
|
userinfo=self.userinfo,
|
||
|
host=self.host,
|
||
|
port=self.port,
|
||
|
path=normalizers.normalize_path(path),
|
||
|
query=self.query,
|
||
|
fragment=self.fragment,
|
||
|
)
|
||
|
|
||
|
def extend_path(self, path):
|
||
|
"""Extend the existing path value with the provided value.
|
||
|
|
||
|
.. versionadded:: 1.5.0
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
>>> URIBuilder(path="/users").extend_path("/sigmavirus24")
|
||
|
URIBuilder(scheme=None, userinfo=None, host=None, port=None,
|
||
|
path='/users/sigmavirus24', query=None, fragment=None)
|
||
|
|
||
|
>>> URIBuilder(path="/users/").extend_path("/sigmavirus24")
|
||
|
URIBuilder(scheme=None, userinfo=None, host=None, port=None,
|
||
|
path='/users/sigmavirus24', query=None, fragment=None)
|
||
|
|
||
|
>>> URIBuilder(path="/users/").extend_path("sigmavirus24")
|
||
|
URIBuilder(scheme=None, userinfo=None, host=None, port=None,
|
||
|
path='/users/sigmavirus24', query=None, fragment=None)
|
||
|
|
||
|
>>> URIBuilder(path="/users").extend_path("sigmavirus24")
|
||
|
URIBuilder(scheme=None, userinfo=None, host=None, port=None,
|
||
|
path='/users/sigmavirus24', query=None, fragment=None)
|
||
|
|
||
|
"""
|
||
|
existing_path = self.path or ""
|
||
|
path = "{}/{}".format(existing_path.rstrip("/"), path.lstrip("/"))
|
||
|
|
||
|
return self.add_path(path)
|
||
|
|
||
|
def add_query_from(self, query_items):
|
||
|
"""Generate and add a query a dictionary or list of tuples.
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
>>> URIBuilder().add_query_from({'a': 'b c'})
|
||
|
URIBuilder(scheme=None, userinfo=None, host=None, port=None,
|
||
|
path=None, query='a=b+c', fragment=None)
|
||
|
|
||
|
>>> URIBuilder().add_query_from([('a', 'b c')])
|
||
|
URIBuilder(scheme=None, userinfo=None, host=None, port=None,
|
||
|
path=None, query='a=b+c', fragment=None)
|
||
|
|
||
|
"""
|
||
|
query = normalizers.normalize_query(compat.urlencode(query_items))
|
||
|
|
||
|
return URIBuilder(
|
||
|
scheme=self.scheme,
|
||
|
userinfo=self.userinfo,
|
||
|
host=self.host,
|
||
|
port=self.port,
|
||
|
path=self.path,
|
||
|
query=query,
|
||
|
fragment=self.fragment,
|
||
|
)
|
||
|
|
||
|
def extend_query_with(self, query_items):
|
||
|
"""Extend the existing query string with the new query items.
|
||
|
|
||
|
.. versionadded:: 1.5.0
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
>>> URIBuilder(query='a=b+c').extend_query_with({'a': 'b c'})
|
||
|
URIBuilder(scheme=None, userinfo=None, host=None, port=None,
|
||
|
path=None, query='a=b+c&a=b+c', fragment=None)
|
||
|
|
||
|
>>> URIBuilder(query='a=b+c').extend_query_with([('a', 'b c')])
|
||
|
URIBuilder(scheme=None, userinfo=None, host=None, port=None,
|
||
|
path=None, query='a=b+c&a=b+c', fragment=None)
|
||
|
"""
|
||
|
original_query_items = compat.parse_qsl(self.query or "")
|
||
|
if not isinstance(query_items, list):
|
||
|
query_items = list(query_items.items())
|
||
|
|
||
|
return self.add_query_from(original_query_items + query_items)
|
||
|
|
||
|
def add_query(self, query):
|
||
|
"""Add a pre-formated query string to the URI.
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
>>> URIBuilder().add_query('a=b&c=d')
|
||
|
URIBuilder(scheme=None, userinfo=None, host=None, port=None,
|
||
|
path=None, query='a=b&c=d', fragment=None)
|
||
|
|
||
|
"""
|
||
|
return URIBuilder(
|
||
|
scheme=self.scheme,
|
||
|
userinfo=self.userinfo,
|
||
|
host=self.host,
|
||
|
port=self.port,
|
||
|
path=self.path,
|
||
|
query=normalizers.normalize_query(query),
|
||
|
fragment=self.fragment,
|
||
|
)
|
||
|
|
||
|
def add_fragment(self, fragment):
|
||
|
"""Add a fragment to the URI.
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
>>> URIBuilder().add_fragment('section-2.6.1')
|
||
|
URIBuilder(scheme=None, userinfo=None, host=None, port=None,
|
||
|
path=None, query=None, fragment='section-2.6.1')
|
||
|
|
||
|
"""
|
||
|
return URIBuilder(
|
||
|
scheme=self.scheme,
|
||
|
userinfo=self.userinfo,
|
||
|
host=self.host,
|
||
|
port=self.port,
|
||
|
path=self.path,
|
||
|
query=self.query,
|
||
|
fragment=normalizers.normalize_fragment(fragment),
|
||
|
)
|
||
|
|
||
|
def finalize(self):
|
||
|
"""Create a URIReference from our builder.
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
>>> URIBuilder().add_scheme('https').add_host('github.com'
|
||
|
... ).add_path('sigmavirus24/rfc3986').finalize().unsplit()
|
||
|
'https://github.com/sigmavirus24/rfc3986'
|
||
|
|
||
|
>>> URIBuilder().add_scheme('https').add_host('github.com'
|
||
|
... ).add_path('sigmavirus24/rfc3986').add_credentials(
|
||
|
... 'sigmavirus24', 'not-re@l').finalize().unsplit()
|
||
|
'https://sigmavirus24:not-re%40l@github.com/sigmavirus24/rfc3986'
|
||
|
|
||
|
"""
|
||
|
return uri.URIReference(
|
||
|
self.scheme,
|
||
|
normalizers.normalize_authority(
|
||
|
(self.userinfo, self.host, self.port)
|
||
|
),
|
||
|
self.path,
|
||
|
self.query,
|
||
|
self.fragment,
|
||
|
)
|
||
|
|
||
|
def geturl(self):
|
||
|
"""Generate the URL from this builder.
|
||
|
|
||
|
.. versionadded:: 1.5.0
|
||
|
|
||
|
This is an alternative to calling :meth:`finalize` and keeping the
|
||
|
:class:`rfc3986.uri.URIReference` around.
|
||
|
"""
|
||
|
return self.finalize().unsplit()
|