# -*- coding: utf-8 -*- # 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 class URIBuilder(object): """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) 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='{}'.format(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 = '/{}'.format(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 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 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, )