From d817d4b4b875069928a38d16ac6bf1a1030c5df4 Mon Sep 17 00:00:00 2001 From: Ian Stapleton Cordasco Date: Thu, 9 Apr 2020 08:17:47 -0500 Subject: [PATCH] Use black to format the codebase and fix docs Enforce that black requires no changes in Travis as well. Furthermore, re-enable our docs building and validation on Travis by fixing our doctests. For some reason, using the `.. doctest::` directive and tracebacks in Sphinx causes problems with pygments' pycon3 lexer and so we have to use the less explicit doctest format in our plain-rST docs. --- .travis.yml | 11 +- docs/source/_static/.keep | 0 docs/source/user/validating.rst | 202 +++++++++---------- src/rfc3986/__init__.py | 40 ++-- src/rfc3986/_mixin.py | 122 ++++++----- src/rfc3986/abnf_regexp.py | 220 ++++++++++---------- src/rfc3986/api.py | 10 +- src/rfc3986/builder.py | 43 ++-- src/rfc3986/compat.py | 16 +- src/rfc3986/exceptions.py | 24 ++- src/rfc3986/iri.py | 65 +++--- src/rfc3986/misc.py | 93 +++++---- src/rfc3986/normalizers.py | 53 ++--- src/rfc3986/parseresult.py | 346 ++++++++++++++++++++------------ src/rfc3986/uri.py | 44 ++-- src/rfc3986/validators.py | 83 ++++---- tests/base.py | 63 +++--- tests/conftest.py | 128 ++++++------ tests/test_api.py | 13 +- tests/test_builder.py | 184 +++++++++-------- tests/test_iri.py | 66 +++--- tests/test_misc.py | 60 +++--- tests/test_normalizers.py | 115 ++++++----- tests/test_parseresult.py | 105 +++++----- tests/test_unicode_support.py | 34 ++-- tests/test_uri.py | 68 ++++--- tests/test_validators.py | 259 +++++++++++++----------- tox.ini | 15 +- 28 files changed, 1366 insertions(+), 1116 deletions(-) create mode 100644 docs/source/_static/.keep diff --git a/.travis.yml b/.travis.yml index 25317ae..f0f7551 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,4 @@ language: python -sudo: false before_script: - pip install tox codecov @@ -17,13 +16,15 @@ matrix: env: TOXENV=py36 - python: 3.7 env: TOXENV=py37 - dist: xenial - sudo: true + - python: 3.8 + env: TOXENV=py38 - python: pypy env: TOXENV=pypy - python: 3.6 - env: TOXENV=flake8 - #- env: TOXENV=docs + env: TOXENV=lint + BLACK_ARGS='--check' + - python: 3.7 + env: TOXENV=docs notifications: on_success: change diff --git a/docs/source/_static/.keep b/docs/source/_static/.keep new file mode 100644 index 0000000..e69de29 diff --git a/docs/source/user/validating.rst b/docs/source/user/validating.rst index 39e900c..d4638ae 100644 --- a/docs/source/user/validating.rst +++ b/docs/source/user/validating.rst @@ -24,27 +24,25 @@ Let's assume that we're building something that takes user input for a URL and we want to ensure that URL is only ever using a specific domain with https. In that case, our code would look like this: -.. doctest:: - - >>> from rfc3986 import validators, uri_reference - >>> user_url = 'https://github.com/sigmavirus24/rfc3986' - >>> validator = validators.Validator().allow_schemes( - ... 'https', - ... ).allow_hosts( - ... 'github.com', - ... ) - >>> validator.validate(uri_reference( - ... 'https://github.com/sigmavirus24/rfc3986' - ... )) - >>> validator.validate(uri_reference( - ... 'https://github.com/' - ... )) - >>> validator.validate(uri_reference( - ... 'http://example.com' - ... )) - Traceback (most recent call last): - ... - rfc3986.exceptions.UnpermittedComponentError +>>> from rfc3986 import validators, uri_reference +>>> user_url = 'https://github.com/sigmavirus24/rfc3986' +>>> validator = validators.Validator().allow_schemes( +... 'https', +... ).allow_hosts( +... 'github.com', +... ) +>>> validator.validate(uri_reference( +... 'https://github.com/sigmavirus24/rfc3986' +... )) +>>> validator.validate(uri_reference( +... 'https://github.com/' +... )) +>>> validator.validate(uri_reference( +... 'http://example.com' +... )) +Traceback (most recent call last): + ... +rfc3986.exceptions.UnpermittedComponentError First notice that we can easily reuse our validator object for each URL. This allows users to not have to constantly reconstruct Validators for each @@ -65,36 +63,34 @@ Next, let's imagine that we want to prevent leaking user credentials. In that case, we want to ensure that there is no password in the user information portion of the authority. In that case, our new validator would look like this: -.. doctest:: - - >>> from rfc3986 import validators, uri_reference - >>> user_url = 'https://github.com/sigmavirus24/rfc3986' - >>> validator = validators.Validator().allow_schemes( - ... 'https', - ... ).allow_hosts( - ... 'github.com', - ... ).forbid_use_of_password() - >>> validator.validate(uri_reference( - ... 'https://github.com/sigmavirus24/rfc3986' - ... )) - >>> validator.validate(uri_reference( - ... 'https://github.com/' - ... )) - >>> validator.validate(uri_reference( - ... 'http://example.com' - ... )) - Traceback (most recent call last): - ... - rfc3986.exceptions.UnpermittedComponentError - >>> validator.validate(uri_reference( - ... 'https://sigmavirus24@github.com' - ... )) - >>> validator.validate(uri_reference( - ... 'https://sigmavirus24:not-my-real-password@github.com' - ... )) - Traceback (most recent call last): - ... - rfc3986.exceptions.PasswordForbidden +>>> from rfc3986 import validators, uri_reference +>>> user_url = 'https://github.com/sigmavirus24/rfc3986' +>>> validator = validators.Validator().allow_schemes( +... 'https', +... ).allow_hosts( +... 'github.com', +... ).forbid_use_of_password() +>>> validator.validate(uri_reference( +... 'https://github.com/sigmavirus24/rfc3986' +... )) +>>> validator.validate(uri_reference( +... 'https://github.com/' +... )) +>>> validator.validate(uri_reference( +... 'http://example.com' +... )) +Traceback (most recent call last): + ... +rfc3986.exceptions.UnpermittedComponentError +>>> validator.validate(uri_reference( +... 'https://sigmavirus24@github.com' +... )) +>>> validator.validate(uri_reference( +... 'https://sigmavirus24:not-my-real-password@github.com' +... )) +Traceback (most recent call last): + ... +rfc3986.exceptions.PasswordForbidden Requiring the Presence of Components ------------------------------------ @@ -104,47 +100,43 @@ components for validation. For example, we assume that we will have a URL that has a scheme and hostname. However, our current validation doesn't require those items exist. -.. doctest:: - - >>> from rfc3986 import validators, uri_reference - >>> user_url = 'https://github.com/sigmavirus24/rfc3986' - >>> validator = validators.Validator().allow_schemes( - ... 'https', - ... ).allow_hosts( - ... 'github.com', - ... ).forbid_use_of_password() - >>> validator.validate(uri_reference('//github.com')) - >>> validator.validate(uri_reference('https:/')) +>>> from rfc3986 import validators, uri_reference +>>> user_url = 'https://github.com/sigmavirus24/rfc3986' +>>> validator = validators.Validator().allow_schemes( +... 'https', +... ).allow_hosts( +... 'github.com', +... ).forbid_use_of_password() +>>> validator.validate(uri_reference('//github.com')) +>>> validator.validate(uri_reference('https:/')) In the first case, we have a host name but no scheme and in the second we have a scheme and a path but no host. If we want to ensure that those components are there and that they are *always* what we allow, then we must add one last item to our validator: -.. doctest:: - - >>> from rfc3986 import validators, uri_reference - >>> user_url = 'https://github.com/sigmavirus24/rfc3986' - >>> validator = validators.Validator().allow_schemes( - ... 'https', - ... ).allow_hosts( - ... 'github.com', - ... ).forbid_use_of_password( - ... ).require_presence_of( - ... 'scheme', 'host', - ... ) - >>> validator.validate(uri_reference('//github.com')) - Traceback (most recent call last): - ... - rfc3986.exceptions.MissingComponentError - >>> validator.validate(uri_reference('https:/')) - Traceback (most recent call last): - ... - rfc3986.exceptions.MissingComponentError - >>> validator.validate(uri_reference('https://github.com')) - >>> validator.validate(uri_reference( - ... 'https://github.com/sigmavirus24/rfc3986' - ... )) +>>> from rfc3986 import validators, uri_reference +>>> user_url = 'https://github.com/sigmavirus24/rfc3986' +>>> validator = validators.Validator().allow_schemes( +... 'https', +... ).allow_hosts( +... 'github.com', +... ).forbid_use_of_password( +... ).require_presence_of( +... 'scheme', 'host', +... ) +>>> validator.validate(uri_reference('//github.com')) +Traceback (most recent call last): + ... +rfc3986.exceptions.MissingComponentError +>>> validator.validate(uri_reference('https:/')) +Traceback (most recent call last): + ... +rfc3986.exceptions.MissingComponentError +>>> validator.validate(uri_reference('https://github.com')) +>>> validator.validate(uri_reference( +... 'https://github.com/sigmavirus24/rfc3986' +... )) Checking the Validity of Components @@ -156,26 +148,24 @@ examples we can also check that a URI is valid per :rfc:`3986`. The validation of the components is pre-determined so all we need to do is specify which components we want to validate: -.. doctest:: - - >>> from rfc3986 import validators, uri_reference - >>> valid_uri = uri_reference('https://github.com/') - >>> validator = validators.Validator().allow_schemes( - ... 'https', - ... ).allow_hosts( - ... 'github.com', - ... ).forbid_use_of_password( - ... ).require_presence_of( - ... 'scheme', 'host', - ... ).check_validity_of( - ... 'scheme', 'host', 'path', - ... ) - >>> validator.validate(valid_uri) - >>> invalid_uri = valid_uri.copy_with(path='/#invalid/path') - >>> validator.validate(invalid_uri) - Traceback (most recent call last): - ... - rfc3986.exceptions.InvalidComponentsError +>>> from rfc3986 import validators, uri_reference +>>> valid_uri = uri_reference('https://github.com/') +>>> validator = validators.Validator().allow_schemes( +... 'https', +... ).allow_hosts( +... 'github.com', +... ).forbid_use_of_password( +... ).require_presence_of( +... 'scheme', 'host', +... ).check_validity_of( +... 'scheme', 'host', 'path', +... ) +>>> validator.validate(valid_uri) +>>> invalid_uri = valid_uri.copy_with(path='/#invalid/path') +>>> validator.validate(invalid_uri) +Traceback (most recent call last): + ... +rfc3986.exceptions.InvalidComponentsError Paths are not allowed to contain a ``#`` character unless it's percent-encoded. This is why our ``invalid_uri`` raises an exception when we diff --git a/src/rfc3986/__init__.py b/src/rfc3986/__init__.py index 02a8034..955f531 100644 --- a/src/rfc3986/__init__.py +++ b/src/rfc3986/__init__.py @@ -31,26 +31,26 @@ from .api import urlparse from .parseresult import ParseResult -__title__ = 'rfc3986' -__author__ = 'Ian Stapleton Cordasco' -__author_email__ = 'graffatcolmingov@gmail.com' -__license__ = 'Apache v2.0' -__copyright__ = 'Copyright 2014 Rackspace; 2016 Ian Stapleton Cordasco' -__version__ = '1.4.0' +__title__ = "rfc3986" +__author__ = "Ian Stapleton Cordasco" +__author_email__ = "graffatcolmingov@gmail.com" +__license__ = "Apache v2.0" +__copyright__ = "Copyright 2014 Rackspace; 2016 Ian Stapleton Cordasco" +__version__ = "1.4.0" __all__ = ( - 'ParseResult', - 'URIReference', - 'IRIReference', - 'is_valid_uri', - 'normalize_uri', - 'uri_reference', - 'iri_reference', - 'urlparse', - '__title__', - '__author__', - '__author_email__', - '__license__', - '__copyright__', - '__version__', + "ParseResult", + "URIReference", + "IRIReference", + "is_valid_uri", + "normalize_uri", + "uri_reference", + "iri_reference", + "urlparse", + "__title__", + "__author__", + "__author_email__", + "__license__", + "__copyright__", + "__version__", ) diff --git a/src/rfc3986/_mixin.py b/src/rfc3986/_mixin.py index 543925c..fc6bd40 100644 --- a/src/rfc3986/_mixin.py +++ b/src/rfc3986/_mixin.py @@ -26,7 +26,7 @@ def authority_info(self): If the authority is not ``None`` and can not be parsed. """ if not self.authority: - return {'userinfo': None, 'host': None, 'port': None} + return {"userinfo": None, "host": None, "port": None} match = self._match_subauthority() @@ -40,10 +40,13 @@ def authority_info(self): # We had a match, now let's ensure that it is actually a valid host # address if it is IPv4 matches = match.groupdict() - host = matches.get('host') + host = matches.get("host") - if (host and misc.IPv4_MATCHER.match(host) and not - validators.valid_ipv4_host_address(host)): + if ( + host + and misc.IPv4_MATCHER.match(host) + and not validators.valid_ipv4_host_address(host) + ): # If we have a host, it appears to be IPv4 and it does not have # valid bytes, it is an InvalidAuthority. raise exc.InvalidAuthority(self.authority.encode(self.encoding)) @@ -60,7 +63,7 @@ def host(self): authority = self.authority_info() except exc.InvalidAuthority: return None - return authority['host'] + return authority["host"] @property def port(self): @@ -69,7 +72,7 @@ def port(self): authority = self.authority_info() except exc.InvalidAuthority: return None - return authority['port'] + return authority["port"] @property def userinfo(self): @@ -78,7 +81,7 @@ def userinfo(self): authority = self.authority_info() except exc.InvalidAuthority: return None - return authority['userinfo'] + return authority["userinfo"] def is_absolute(self): """Determine if this URI Reference is an absolute URI. @@ -110,16 +113,18 @@ def is_valid(self, **kwargs): :returns: ``True`` if the URI is valid. ``False`` otherwise. :rtype: bool """ - warnings.warn("Please use rfc3986.validators.Validator instead. " - "This method will be eventually removed.", - DeprecationWarning) + warnings.warn( + "Please use rfc3986.validators.Validator instead. " + "This method will be eventually removed.", + DeprecationWarning, + ) validators = [ - (self.scheme_is_valid, kwargs.get('require_scheme', False)), - (self.authority_is_valid, kwargs.get('require_authority', False)), - (self.path_is_valid, kwargs.get('require_path', False)), - (self.query_is_valid, kwargs.get('require_query', False)), - (self.fragment_is_valid, kwargs.get('require_fragment', False)), - ] + (self.scheme_is_valid, kwargs.get("require_scheme", False)), + (self.authority_is_valid, kwargs.get("require_authority", False)), + (self.path_is_valid, kwargs.get("require_path", False)), + (self.query_is_valid, kwargs.get("require_query", False)), + (self.fragment_is_valid, kwargs.get("require_fragment", False)), + ] return all(v(r) for v, r in validators) def authority_is_valid(self, require=False): @@ -136,18 +141,18 @@ def authority_is_valid(self, require=False): :rtype: bool """ - warnings.warn("Please use rfc3986.validators.Validator instead. " - "This method will be eventually removed.", - DeprecationWarning) + warnings.warn( + "Please use rfc3986.validators.Validator instead. " + "This method will be eventually removed.", + DeprecationWarning, + ) try: self.authority_info() except exc.InvalidAuthority: return False return validators.authority_is_valid( - self.authority, - host=self.host, - require=require, + self.authority, host=self.host, require=require, ) def scheme_is_valid(self, require=False): @@ -162,9 +167,11 @@ def scheme_is_valid(self, require=False): :returns: ``True`` if the scheme is valid. ``False`` otherwise. :rtype: bool """ - warnings.warn("Please use rfc3986.validators.Validator instead. " - "This method will be eventually removed.", - DeprecationWarning) + warnings.warn( + "Please use rfc3986.validators.Validator instead. " + "This method will be eventually removed.", + DeprecationWarning, + ) return validators.scheme_is_valid(self.scheme, require) def path_is_valid(self, require=False): @@ -179,9 +186,11 @@ def path_is_valid(self, require=False): :returns: ``True`` if the path is valid. ``False`` otherwise. :rtype: bool """ - warnings.warn("Please use rfc3986.validators.Validator instead. " - "This method will be eventually removed.", - DeprecationWarning) + warnings.warn( + "Please use rfc3986.validators.Validator instead. " + "This method will be eventually removed.", + DeprecationWarning, + ) return validators.path_is_valid(self.path, require) def query_is_valid(self, require=False): @@ -196,9 +205,11 @@ def query_is_valid(self, require=False): :returns: ``True`` if the query is valid. ``False`` otherwise. :rtype: bool """ - warnings.warn("Please use rfc3986.validators.Validator instead. " - "This method will be eventually removed.", - DeprecationWarning) + warnings.warn( + "Please use rfc3986.validators.Validator instead. " + "This method will be eventually removed.", + DeprecationWarning, + ) return validators.query_is_valid(self.query, require) def fragment_is_valid(self, require=False): @@ -213,9 +224,11 @@ def fragment_is_valid(self, require=False): :returns: ``True`` if the fragment is valid. ``False`` otherwise. :rtype: bool """ - warnings.warn("Please use rfc3986.validators.Validator instead. " - "This method will be eventually removed.", - DeprecationWarning) + warnings.warn( + "Please use rfc3986.validators.Validator instead. " + "This method will be eventually removed.", + DeprecationWarning, + ) return validators.fragment_is_valid(self.fragment, require) def normalized_equality(self, other_ref): @@ -269,7 +282,7 @@ def resolve_with(self, base_uri, strict=False): if resolving.authority is not None: target = resolving.copy_with( scheme=base_uri.scheme, - path=normalizers.normalize_path(resolving.path) + path=normalizers.normalize_path(resolving.path), ) else: if resolving.path is None: @@ -281,10 +294,10 @@ def resolve_with(self, base_uri, strict=False): scheme=base_uri.scheme, authority=base_uri.authority, path=base_uri.path, - query=query + query=query, ) else: - if resolving.path.startswith('/'): + if resolving.path.startswith("/"): path = normalizers.normalize_path(resolving.path) else: path = normalizers.normalize_path( @@ -294,7 +307,7 @@ def resolve_with(self, base_uri, strict=False): scheme=base_uri.scheme, authority=base_uri.authority, path=path, - query=resolving.query + query=resolving.query, ) return target @@ -307,20 +320,25 @@ def unsplit(self): # See http://tools.ietf.org/html/rfc3986#section-5.3 result_list = [] if self.scheme: - result_list.extend([self.scheme, ':']) + result_list.extend([self.scheme, ":"]) if self.authority: - result_list.extend(['//', self.authority]) + result_list.extend(["//", self.authority]) if self.path: result_list.append(self.path) if self.query is not None: - result_list.extend(['?', self.query]) + result_list.extend(["?", self.query]) if self.fragment is not None: - result_list.extend(['#', self.fragment]) - return ''.join(result_list) - - def copy_with(self, scheme=misc.UseExisting, authority=misc.UseExisting, - path=misc.UseExisting, query=misc.UseExisting, - fragment=misc.UseExisting): + result_list.extend(["#", self.fragment]) + return "".join(result_list) + + def copy_with( + self, + scheme=misc.UseExisting, + authority=misc.UseExisting, + path=misc.UseExisting, + query=misc.UseExisting, + fragment=misc.UseExisting, + ): """Create a copy of this reference with the new components. :param str scheme: @@ -339,11 +357,11 @@ def copy_with(self, scheme=misc.UseExisting, authority=misc.UseExisting, URIReference """ attributes = { - 'scheme': scheme, - 'authority': authority, - 'path': path, - 'query': query, - 'fragment': fragment, + "scheme": scheme, + "authority": authority, + "path": path, + "query": query, + "fragment": fragment, } for key, value in list(attributes.items()): if value is misc.UseExisting: diff --git a/src/rfc3986/abnf_regexp.py b/src/rfc3986/abnf_regexp.py index 08a5255..5852e4d 100644 --- a/src/rfc3986/abnf_regexp.py +++ b/src/rfc3986/abnf_regexp.py @@ -24,35 +24,35 @@ # Escape the '*' for use in regular expressions SUB_DELIMITERS_RE = r"!$&'()\*+,;=" RESERVED_CHARS_SET = GENERIC_DELIMITERS_SET.union(SUB_DELIMITERS_SET) -ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' -DIGIT = '0123456789' +ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +DIGIT = "0123456789" # https://tools.ietf.org/html/rfc3986#section-2.3 -UNRESERVED = UNRESERVED_CHARS = ALPHA + DIGIT + r'._!-' +UNRESERVED = UNRESERVED_CHARS = ALPHA + DIGIT + r"._!-" UNRESERVED_CHARS_SET = set(UNRESERVED_CHARS) NON_PCT_ENCODED_SET = RESERVED_CHARS_SET.union(UNRESERVED_CHARS_SET) # We need to escape the '-' in this case: -UNRESERVED_RE = r'A-Za-z0-9._~\-' +UNRESERVED_RE = r"A-Za-z0-9._~\-" # Percent encoded character values -PERCENT_ENCODED = PCT_ENCODED = '%[A-Fa-f0-9]{2}' -PCHAR = '([' + UNRESERVED_RE + SUB_DELIMITERS_RE + ':@]|%s)' % PCT_ENCODED +PERCENT_ENCODED = PCT_ENCODED = "%[A-Fa-f0-9]{2}" +PCHAR = "([" + UNRESERVED_RE + SUB_DELIMITERS_RE + ":@]|%s)" % PCT_ENCODED # NOTE(sigmavirus24): We're going to use more strict regular expressions # than appear in Appendix B for scheme. This will prevent over-eager # consuming of items that aren't schemes. -SCHEME_RE = '[a-zA-Z][a-zA-Z0-9+.-]*' -_AUTHORITY_RE = '[^\\\\/?#]*' -_PATH_RE = '[^?#]*' -_QUERY_RE = '[^#]*' -_FRAGMENT_RE = '.*' +SCHEME_RE = "[a-zA-Z][a-zA-Z0-9+.-]*" +_AUTHORITY_RE = "[^\\\\/?#]*" +_PATH_RE = "[^?#]*" +_QUERY_RE = "[^#]*" +_FRAGMENT_RE = ".*" # Extracted from http://tools.ietf.org/html/rfc3986#appendix-B COMPONENT_PATTERN_DICT = { - 'scheme': SCHEME_RE, - 'authority': _AUTHORITY_RE, - 'path': _PATH_RE, - 'query': _QUERY_RE, - 'fragment': _FRAGMENT_RE, + "scheme": SCHEME_RE, + "authority": _AUTHORITY_RE, + "path": _PATH_RE, + "query": _QUERY_RE, + "fragment": _FRAGMENT_RE, } # See http://tools.ietf.org/html/rfc3986#appendix-B @@ -61,9 +61,9 @@ # modified to ignore other matches that are not important to the parsing of # the reference so we can also simply use SRE_Match#groups. URL_PARSING_RE = ( - r'(?:(?P{scheme}):)?(?://(?P{authority}))?' - r'(?P{path})(?:\?(?P{query}))?' - r'(?:#(?P{fragment}))?' + r"(?:(?P{scheme}):)?(?://(?P{authority}))?" + r"(?P{path})(?:\?(?P{query}))?" + r"(?:#(?P{fragment}))?" ).format(**COMPONENT_PATTERN_DICT) @@ -73,71 +73,66 @@ # Host patterns, see: http://tools.ietf.org/html/rfc3986#section-3.2.2 # The pattern for a regular name, e.g., www.google.com, api.github.com -REGULAR_NAME_RE = REG_NAME = '((?:{0}|[{1}])*)'.format( - '%[0-9A-Fa-f]{2}', SUB_DELIMITERS_RE + UNRESERVED_RE +REGULAR_NAME_RE = REG_NAME = "((?:{0}|[{1}])*)".format( + "%[0-9A-Fa-f]{2}", SUB_DELIMITERS_RE + UNRESERVED_RE ) # The pattern for an IPv4 address, e.g., 192.168.255.255, 127.0.0.1, -IPv4_RE = r'([0-9]{1,3}\.){3}[0-9]{1,3}' +IPv4_RE = r"([0-9]{1,3}\.){3}[0-9]{1,3}" # Hexadecimal characters used in each piece of an IPv6 address -HEXDIG_RE = '[0-9A-Fa-f]{1,4}' +HEXDIG_RE = "[0-9A-Fa-f]{1,4}" # Least-significant 32 bits of an IPv6 address -LS32_RE = '({hex}:{hex}|{ipv4})'.format(hex=HEXDIG_RE, ipv4=IPv4_RE) +LS32_RE = "({hex}:{hex}|{ipv4})".format(hex=HEXDIG_RE, ipv4=IPv4_RE) # Substitutions into the following patterns for IPv6 patterns defined # http://tools.ietf.org/html/rfc3986#page-20 -_subs = {'hex': HEXDIG_RE, 'ls32': LS32_RE} +_subs = {"hex": HEXDIG_RE, "ls32": LS32_RE} # Below: h16 = hexdig, see: https://tools.ietf.org/html/rfc5234 for details # about ABNF (Augmented Backus-Naur Form) use in the comments variations = [ # 6( h16 ":" ) ls32 - '(%(hex)s:){6}%(ls32)s' % _subs, + "(%(hex)s:){6}%(ls32)s" % _subs, # "::" 5( h16 ":" ) ls32 - '::(%(hex)s:){5}%(ls32)s' % _subs, + "::(%(hex)s:){5}%(ls32)s" % _subs, # [ h16 ] "::" 4( h16 ":" ) ls32 - '(%(hex)s)?::(%(hex)s:){4}%(ls32)s' % _subs, + "(%(hex)s)?::(%(hex)s:){4}%(ls32)s" % _subs, # [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 - '((%(hex)s:)?%(hex)s)?::(%(hex)s:){3}%(ls32)s' % _subs, + "((%(hex)s:)?%(hex)s)?::(%(hex)s:){3}%(ls32)s" % _subs, # [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 - '((%(hex)s:){0,2}%(hex)s)?::(%(hex)s:){2}%(ls32)s' % _subs, + "((%(hex)s:){0,2}%(hex)s)?::(%(hex)s:){2}%(ls32)s" % _subs, # [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 - '((%(hex)s:){0,3}%(hex)s)?::%(hex)s:%(ls32)s' % _subs, + "((%(hex)s:){0,3}%(hex)s)?::%(hex)s:%(ls32)s" % _subs, # [ *4( h16 ":" ) h16 ] "::" ls32 - '((%(hex)s:){0,4}%(hex)s)?::%(ls32)s' % _subs, + "((%(hex)s:){0,4}%(hex)s)?::%(ls32)s" % _subs, # [ *5( h16 ":" ) h16 ] "::" h16 - '((%(hex)s:){0,5}%(hex)s)?::%(hex)s' % _subs, + "((%(hex)s:){0,5}%(hex)s)?::%(hex)s" % _subs, # [ *6( h16 ":" ) h16 ] "::" - '((%(hex)s:){0,6}%(hex)s)?::' % _subs, + "((%(hex)s:){0,6}%(hex)s)?::" % _subs, ] -IPv6_RE = '(({0})|({1})|({2})|({3})|({4})|({5})|({6})|({7})|({8}))'.format( +IPv6_RE = "(({0})|({1})|({2})|({3})|({4})|({5})|({6})|({7})|({8}))".format( *variations ) -IPv_FUTURE_RE = r'v[0-9A-Fa-f]+\.[%s]+' % ( - UNRESERVED_RE + SUB_DELIMITERS_RE + ':' +IPv_FUTURE_RE = r"v[0-9A-Fa-f]+\.[%s]+" % ( + UNRESERVED_RE + SUB_DELIMITERS_RE + ":" ) # RFC 6874 Zone ID ABNF -ZONE_ID = '(?:[' + UNRESERVED_RE + ']|' + PCT_ENCODED + ')+' +ZONE_ID = "(?:[" + UNRESERVED_RE + "]|" + PCT_ENCODED + ")+" -IPv6_ADDRZ_RFC4007_RE = IPv6_RE + '(?:(?:%25|%)' + ZONE_ID + ')?' -IPv6_ADDRZ_RE = IPv6_RE + '(?:%25' + ZONE_ID + ')?' +IPv6_ADDRZ_RFC4007_RE = IPv6_RE + "(?:(?:%25|%)" + ZONE_ID + ")?" +IPv6_ADDRZ_RE = IPv6_RE + "(?:%25" + ZONE_ID + ")?" -IP_LITERAL_RE = r'\[({0}|{1})\]'.format( - IPv6_ADDRZ_RFC4007_RE, - IPv_FUTURE_RE, -) +IP_LITERAL_RE = r"\[({0}|{1})\]".format(IPv6_ADDRZ_RFC4007_RE, IPv_FUTURE_RE,) # Pattern for matching the host piece of the authority -HOST_RE = HOST_PATTERN = '({0}|{1}|{2})'.format( - REG_NAME, - IPv4_RE, - IP_LITERAL_RE, +HOST_RE = HOST_PATTERN = "({0}|{1}|{2})".format( + REG_NAME, IPv4_RE, IP_LITERAL_RE, ) -USERINFO_RE = '^([' + UNRESERVED_RE + SUB_DELIMITERS_RE + ':]|%s)+' % ( - PCT_ENCODED +USERINFO_RE = ( + "^([" + UNRESERVED_RE + SUB_DELIMITERS_RE + ":]|%s)+" % (PCT_ENCODED) ) -PORT_RE = '[0-9]{1,5}' +PORT_RE = "[0-9]{1,5}" # #################### # Path Matcher Section @@ -146,25 +141,29 @@ # See http://tools.ietf.org/html/rfc3986#section-3.3 for more information # about the path patterns defined below. segments = { - 'segment': PCHAR + '*', + "segment": PCHAR + "*", # Non-zero length segment - 'segment-nz': PCHAR + '+', + "segment-nz": PCHAR + "+", # Non-zero length segment without ":" - 'segment-nz-nc': PCHAR.replace(':', '') + '+' + "segment-nz-nc": PCHAR.replace(":", "") + "+", } # Path types taken from Section 3.3 (linked above) -PATH_EMPTY = '^$' -PATH_ROOTLESS = '%(segment-nz)s(/%(segment)s)*' % segments -PATH_NOSCHEME = '%(segment-nz-nc)s(/%(segment)s)*' % segments -PATH_ABSOLUTE = '/(%s)?' % PATH_ROOTLESS -PATH_ABEMPTY = '(/%(segment)s)*' % segments -PATH_RE = '^(%s|%s|%s|%s|%s)$' % ( - PATH_ABEMPTY, PATH_ABSOLUTE, PATH_NOSCHEME, PATH_ROOTLESS, PATH_EMPTY +PATH_EMPTY = "^$" +PATH_ROOTLESS = "%(segment-nz)s(/%(segment)s)*" % segments +PATH_NOSCHEME = "%(segment-nz-nc)s(/%(segment)s)*" % segments +PATH_ABSOLUTE = "/(%s)?" % PATH_ROOTLESS +PATH_ABEMPTY = "(/%(segment)s)*" % segments +PATH_RE = "^(%s|%s|%s|%s|%s)$" % ( + PATH_ABEMPTY, + PATH_ABSOLUTE, + PATH_NOSCHEME, + PATH_ROOTLESS, + PATH_EMPTY, ) FRAGMENT_RE = QUERY_RE = ( - '^([/?:@' + UNRESERVED_RE + SUB_DELIMITERS_RE + ']|%s)*$' % PCT_ENCODED + "^([/?:@" + UNRESERVED_RE + SUB_DELIMITERS_RE + "]|%s)*$" % PCT_ENCODED ) # ########################## @@ -172,8 +171,8 @@ # ########################## # See http://tools.ietf.org/html/rfc3986#section-4.2 for details -RELATIVE_PART_RE = '(//%s%s|%s|%s|%s)' % ( - COMPONENT_PATTERN_DICT['authority'], +RELATIVE_PART_RE = "(//%s%s|%s|%s|%s)" % ( + COMPONENT_PATTERN_DICT["authority"], PATH_ABEMPTY, PATH_ABSOLUTE, PATH_NOSCHEME, @@ -181,8 +180,8 @@ ) # See http://tools.ietf.org/html/rfc3986#section-3 for definition -HIER_PART_RE = '(//%s%s|%s|%s|%s)' % ( - COMPONENT_PATTERN_DICT['authority'], +HIER_PART_RE = "(//%s%s|%s|%s|%s)" % ( + COMPONENT_PATTERN_DICT["authority"], PATH_ABEMPTY, PATH_ABSOLUTE, PATH_ROOTLESS, @@ -195,71 +194,80 @@ # Only wide-unicode gets the high-ranges of UCSCHAR if sys.maxunicode > 0xFFFF: # pragma: no cover - IPRIVATE = u'\uE000-\uF8FF\U000F0000-\U000FFFFD\U00100000-\U0010FFFD' + IPRIVATE = u"\uE000-\uF8FF\U000F0000-\U000FFFFD\U00100000-\U0010FFFD" UCSCHAR_RE = ( - u'\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF' - u'\U00010000-\U0001FFFD\U00020000-\U0002FFFD' - u'\U00030000-\U0003FFFD\U00040000-\U0004FFFD' - u'\U00050000-\U0005FFFD\U00060000-\U0006FFFD' - u'\U00070000-\U0007FFFD\U00080000-\U0008FFFD' - u'\U00090000-\U0009FFFD\U000A0000-\U000AFFFD' - u'\U000B0000-\U000BFFFD\U000C0000-\U000CFFFD' - u'\U000D0000-\U000DFFFD\U000E1000-\U000EFFFD' + u"\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF" + u"\U00010000-\U0001FFFD\U00020000-\U0002FFFD" + u"\U00030000-\U0003FFFD\U00040000-\U0004FFFD" + u"\U00050000-\U0005FFFD\U00060000-\U0006FFFD" + u"\U00070000-\U0007FFFD\U00080000-\U0008FFFD" + u"\U00090000-\U0009FFFD\U000A0000-\U000AFFFD" + u"\U000B0000-\U000BFFFD\U000C0000-\U000CFFFD" + u"\U000D0000-\U000DFFFD\U000E1000-\U000EFFFD" ) else: # pragma: no cover - IPRIVATE = u'\uE000-\uF8FF' - UCSCHAR_RE = ( - u'\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF' - ) + IPRIVATE = u"\uE000-\uF8FF" + UCSCHAR_RE = u"\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF" -IUNRESERVED_RE = u'A-Za-z0-9\\._~\\-' + UCSCHAR_RE -IPCHAR = u'([' + IUNRESERVED_RE + SUB_DELIMITERS_RE + u':@]|%s)' % PCT_ENCODED +IUNRESERVED_RE = u"A-Za-z0-9\\._~\\-" + UCSCHAR_RE +IPCHAR = u"([" + IUNRESERVED_RE + SUB_DELIMITERS_RE + u":@]|%s)" % PCT_ENCODED isegments = { - 'isegment': IPCHAR + u'*', + "isegment": IPCHAR + u"*", # Non-zero length segment - 'isegment-nz': IPCHAR + u'+', + "isegment-nz": IPCHAR + u"+", # Non-zero length segment without ":" - 'isegment-nz-nc': IPCHAR.replace(':', '') + u'+' + "isegment-nz-nc": IPCHAR.replace(":", "") + u"+", } -IPATH_ROOTLESS = u'%(isegment-nz)s(/%(isegment)s)*' % isegments -IPATH_NOSCHEME = u'%(isegment-nz-nc)s(/%(isegment)s)*' % isegments -IPATH_ABSOLUTE = u'/(?:%s)?' % IPATH_ROOTLESS -IPATH_ABEMPTY = u'(?:/%(isegment)s)*' % isegments -IPATH_RE = u'^(?:%s|%s|%s|%s|%s)$' % ( - IPATH_ABEMPTY, IPATH_ABSOLUTE, IPATH_NOSCHEME, IPATH_ROOTLESS, PATH_EMPTY +IPATH_ROOTLESS = u"%(isegment-nz)s(/%(isegment)s)*" % isegments +IPATH_NOSCHEME = u"%(isegment-nz-nc)s(/%(isegment)s)*" % isegments +IPATH_ABSOLUTE = u"/(?:%s)?" % IPATH_ROOTLESS +IPATH_ABEMPTY = u"(?:/%(isegment)s)*" % isegments +IPATH_RE = u"^(?:%s|%s|%s|%s|%s)$" % ( + IPATH_ABEMPTY, + IPATH_ABSOLUTE, + IPATH_NOSCHEME, + IPATH_ROOTLESS, + PATH_EMPTY, ) -IREGULAR_NAME_RE = IREG_NAME = u'(?:{0}|[{1}])*'.format( - u'%[0-9A-Fa-f]{2}', SUB_DELIMITERS_RE + IUNRESERVED_RE +IREGULAR_NAME_RE = IREG_NAME = u"(?:{0}|[{1}])*".format( + u"%[0-9A-Fa-f]{2}", SUB_DELIMITERS_RE + IUNRESERVED_RE ) -IHOST_RE = IHOST_PATTERN = u'({0}|{1}|{2})'.format( - IREG_NAME, - IPv4_RE, - IP_LITERAL_RE, +IHOST_RE = IHOST_PATTERN = u"({0}|{1}|{2})".format( + IREG_NAME, IPv4_RE, IP_LITERAL_RE, ) -IUSERINFO_RE = u'^(?:[' + IUNRESERVED_RE + SUB_DELIMITERS_RE + u':]|%s)+' % ( - PCT_ENCODED +IUSERINFO_RE = ( + u"^(?:[" + IUNRESERVED_RE + SUB_DELIMITERS_RE + u":]|%s)+" % (PCT_ENCODED) ) -IFRAGMENT_RE = (u'^(?:[/?:@' + IUNRESERVED_RE + SUB_DELIMITERS_RE - + u']|%s)*$' % PCT_ENCODED) -IQUERY_RE = (u'^(?:[/?:@' + IUNRESERVED_RE + SUB_DELIMITERS_RE - + IPRIVATE + u']|%s)*$' % PCT_ENCODED) +IFRAGMENT_RE = ( + u"^(?:[/?:@" + + IUNRESERVED_RE + + SUB_DELIMITERS_RE + + u"]|%s)*$" % PCT_ENCODED +) +IQUERY_RE = ( + u"^(?:[/?:@" + + IUNRESERVED_RE + + SUB_DELIMITERS_RE + + IPRIVATE + + u"]|%s)*$" % PCT_ENCODED +) -IRELATIVE_PART_RE = u'(//%s%s|%s|%s|%s)' % ( - COMPONENT_PATTERN_DICT['authority'], +IRELATIVE_PART_RE = u"(//%s%s|%s|%s|%s)" % ( + COMPONENT_PATTERN_DICT["authority"], IPATH_ABEMPTY, IPATH_ABSOLUTE, IPATH_NOSCHEME, PATH_EMPTY, ) -IHIER_PART_RE = u'(//%s%s|%s|%s|%s)' % ( - COMPONENT_PATTERN_DICT['authority'], +IHIER_PART_RE = u"(//%s%s|%s|%s|%s)" % ( + COMPONENT_PATTERN_DICT["authority"], IPATH_ABEMPTY, IPATH_ABSOLUTE, IPATH_ROOTLESS, diff --git a/src/rfc3986/api.py b/src/rfc3986/api.py index ddc4a1c..1e098b3 100644 --- a/src/rfc3986/api.py +++ b/src/rfc3986/api.py @@ -24,7 +24,7 @@ from .uri import URIReference -def uri_reference(uri, encoding='utf-8'): +def uri_reference(uri, encoding="utf-8"): """Parse a URI string into a URIReference. This is a convenience function. You could achieve the same end by using @@ -38,7 +38,7 @@ def uri_reference(uri, encoding='utf-8'): return URIReference.from_string(uri, encoding) -def iri_reference(iri, encoding='utf-8'): +def iri_reference(iri, encoding="utf-8"): """Parse a IRI string into an IRIReference. This is a convenience function. You could achieve the same end by using @@ -52,7 +52,7 @@ def iri_reference(iri, encoding='utf-8'): return IRIReference.from_string(iri, encoding) -def is_valid_uri(uri, encoding='utf-8', **kwargs): +def is_valid_uri(uri, encoding="utf-8", **kwargs): """Determine if the URI given is valid. This is a convenience function. You could use either @@ -77,7 +77,7 @@ def is_valid_uri(uri, encoding='utf-8', **kwargs): return URIReference.from_string(uri, encoding).is_valid(**kwargs) -def normalize_uri(uri, encoding='utf-8'): +def normalize_uri(uri, encoding="utf-8"): """Normalize the given URI. This is a convenience function. You could use either @@ -93,7 +93,7 @@ def normalize_uri(uri, encoding='utf-8'): return normalized_reference.unsplit() -def urlparse(uri, encoding='utf-8'): +def urlparse(uri, encoding="utf-8"): """Parse a given URI and return a ParseResult. This is a partial replacement of the standard library's urlparse function. diff --git a/src/rfc3986/builder.py b/src/rfc3986/builder.py index bd807e3..1c47c37 100644 --- a/src/rfc3986/builder.py +++ b/src/rfc3986/builder.py @@ -30,8 +30,16 @@ class URIBuilder(object): """ - def __init__(self, scheme=None, userinfo=None, host=None, port=None, - path=None, query=None, fragment=None): + def __init__( + self, + scheme=None, + userinfo=None, + host=None, + port=None, + path=None, + query=None, + fragment=None, + ): """Initialize our URI builder. :param str scheme: @@ -59,9 +67,11 @@ def __init__(self, scheme=None, userinfo=None, host=None, port=None, 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})') + 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 @@ -81,7 +91,7 @@ def from_uri(cls, reference): port=reference.port, path=reference.path, query=reference.query, - fragment=reference.fragment + fragment=reference.fragment, ) def add_scheme(self, scheme): @@ -122,13 +132,12 @@ def add_credentials(self, username, password): port=None, path=None, query=None, fragment=None) """ if username is None: - raise ValueError('Username cannot be None') + raise ValueError("Username cannot be None") userinfo = normalizers.normalize_username(username) if password is not None: - userinfo = '{}:{}'.format( - userinfo, - normalizers.normalize_password(password), + userinfo = "{}:{}".format( + userinfo, normalizers.normalize_password(password), ) return URIBuilder( @@ -178,23 +187,21 @@ def add_port(self, port): port_int = int(port) if port_int < 0: raise ValueError( - 'ports are not allowed to be negative. You provided {}'.format( + "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, - ) + "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), + port="{}".format(port_int), path=self.path, query=self.query, fragment=self.fragment, @@ -214,8 +221,8 @@ def add_path(self, path): path='/checkout.php', query=None, fragment=None) """ - if not path.startswith('/'): - path = '/{}'.format(path) + if not path.startswith("/"): + path = "/{}".format(path) return URIBuilder( scheme=self.scheme, diff --git a/src/rfc3986/compat.py b/src/rfc3986/compat.py index 8968c38..3e84bae 100644 --- a/src/rfc3986/compat.py +++ b/src/rfc3986/compat.py @@ -26,10 +26,10 @@ from urllib import urlencode __all__ = ( - 'to_bytes', - 'to_str', - 'urlquote', - 'urlencode', + "to_bytes", + "to_str", + "urlquote", + "urlencode", ) PY3 = (3, 0) <= sys.version_info < (4, 0) @@ -40,15 +40,15 @@ unicode = str # Python 3.x -def to_str(b, encoding='utf-8'): +def to_str(b, encoding="utf-8"): """Ensure that b is text in the specified encoding.""" - if hasattr(b, 'decode') and not isinstance(b, unicode): + if hasattr(b, "decode") and not isinstance(b, unicode): b = b.decode(encoding) return b -def to_bytes(s, encoding='utf-8'): +def to_bytes(s, encoding="utf-8"): """Ensure that s is converted to bytes from the encoding.""" - if hasattr(s, 'encode') and not isinstance(s, bytes): + if hasattr(s, "encode") and not isinstance(s, bytes): s = s.encode(encoding) return s diff --git a/src/rfc3986/exceptions.py b/src/rfc3986/exceptions.py index da8ca7c..acc331d 100644 --- a/src/rfc3986/exceptions.py +++ b/src/rfc3986/exceptions.py @@ -17,7 +17,9 @@ def __init__(self, authority): """Initialize the exception with the invalid authority.""" super(InvalidAuthority, self).__init__( u"The authority ({0}) is not valid.".format( - compat.to_str(authority))) + compat.to_str(authority) + ) + ) class InvalidPort(RFC3986Exception): @@ -26,7 +28,8 @@ class InvalidPort(RFC3986Exception): def __init__(self, port): """Initialize the exception with the invalid port.""" super(InvalidPort, self).__init__( - 'The port ("{0}") is not valid.'.format(port)) + 'The port ("{0}") is not valid.'.format(port) + ) class ResolutionError(RFC3986Exception): @@ -35,7 +38,8 @@ class ResolutionError(RFC3986Exception): def __init__(self, uri): """Initialize the error with the failed URI.""" super(ResolutionError, self).__init__( - "{0} is not an absolute URI.".format(uri.unsplit())) + "{0} is not an absolute URI.".format(uri.unsplit()) + ) class ValidationError(RFC3986Exception): @@ -49,13 +53,13 @@ class MissingComponentError(ValidationError): def __init__(self, uri, *component_names): """Initialize the error with the missing component name.""" - verb = 'was' + verb = "was" if len(component_names) > 1: - verb = 'were' + verb = "were" self.uri = uri self.components = sorted(component_names) - components = ', '.join(self.components) + components = ", ".join(self.components) super(MissingComponentError, self).__init__( "{} {} required but missing".format(components, verb), uri, @@ -86,7 +90,7 @@ class PasswordForbidden(ValidationError): def __init__(self, uri): """Initialize the error with the URI that failed validation.""" - unsplit = getattr(uri, 'unsplit', lambda: uri) + unsplit = getattr(uri, "unsplit", lambda: uri) super(PasswordForbidden, self).__init__( '"{}" contained a password when validation forbade it'.format( unsplit() @@ -100,13 +104,13 @@ class InvalidComponentsError(ValidationError): def __init__(self, uri, *component_names): """Initialize the error with the invalid component name(s).""" - verb = 'was' + verb = "was" if len(component_names) > 1: - verb = 'were' + verb = "were" self.uri = uri self.components = sorted(component_names) - components = ', '.join(self.components) + components = ", ".join(self.components) super(InvalidComponentsError, self).__init__( "{} {} found to be invalid".format(components, verb), uri, diff --git a/src/rfc3986/iri.py b/src/rfc3986/iri.py index 9c01fe1..540aa7b 100644 --- a/src/rfc3986/iri.py +++ b/src/rfc3986/iri.py @@ -29,8 +29,9 @@ idna = None -class IRIReference(namedtuple('IRIReference', misc.URI_COMPONENTS), - uri.URIMixin): +class IRIReference( + namedtuple("IRIReference", misc.URI_COMPONENTS), uri.URIMixin +): """Immutable object representing a parsed IRI Reference. Can be encoded into an URIReference object via the procedure @@ -43,8 +44,9 @@ class IRIReference(namedtuple('IRIReference', misc.URI_COMPONENTS), slots = () - def __new__(cls, scheme, authority, path, query, fragment, - encoding='utf-8'): + def __new__( + cls, scheme, authority, path, query, fragment, encoding="utf-8" + ): """Create a new IRIReference.""" ref = super(IRIReference, cls).__new__( cls, @@ -52,7 +54,8 @@ def __new__(cls, scheme, authority, path, query, fragment, authority or None, path or None, query, - fragment) + fragment, + ) ref.encoding = encoding return ref @@ -66,8 +69,10 @@ def __eq__(self, other): other_ref = self.__class__.from_string(other) except TypeError: raise TypeError( - 'Unable to compare {0}() to {1}()'.format( - type(self).__name__, type(other).__name__)) + "Unable to compare {0}() to {1}()".format( + type(self).__name__, type(other).__name__ + ) + ) # See http://tools.ietf.org/html/rfc3986#section-6.2 return tuple(self) == tuple(other_ref) @@ -76,7 +81,7 @@ def _match_subauthority(self): return misc.ISUBAUTHORITY_MATCHER.match(self.authority) @classmethod - def from_string(cls, iri_string, encoding='utf-8'): + def from_string(cls, iri_string, encoding="utf-8"): """Parse a IRI reference from the given unicode IRI string. :param str iri_string: Unicode IRI to be parsed into a reference. @@ -87,10 +92,11 @@ def from_string(cls, iri_string, encoding='utf-8'): split_iri = misc.IRI_MATCHER.match(iri_string).groupdict() return cls( - split_iri['scheme'], split_iri['authority'], - normalizers.encode_component(split_iri['path'], encoding), - normalizers.encode_component(split_iri['query'], encoding), - normalizers.encode_component(split_iri['fragment'], encoding), + split_iri["scheme"], + split_iri["authority"], + normalizers.encode_component(split_iri["path"], encoding), + normalizers.encode_component(split_iri["query"], encoding), + normalizers.encode_component(split_iri["fragment"], encoding), encoding, ) @@ -120,28 +126,37 @@ def encode(self, idna_encoder=None): # noqa: C901 def idna_encoder(name): if any(ord(c) > 128 for c in name): try: - return idna.encode(name.lower(), - strict=True, - std3_rules=True) + return idna.encode( + name.lower(), strict=True, std3_rules=True + ) except idna.IDNAError: raise exceptions.InvalidAuthority(self.authority) return name authority = "" if self.host: - authority = ".".join([compat.to_str(idna_encoder(part)) - for part in self.host.split(".")]) + authority = ".".join( + [ + compat.to_str(idna_encoder(part)) + for part in self.host.split(".") + ] + ) if self.userinfo is not None: - authority = (normalizers.encode_component( - self.userinfo, self.encoding) + '@' + authority) + authority = ( + normalizers.encode_component(self.userinfo, self.encoding) + + "@" + + authority + ) if self.port is not None: authority += ":" + str(self.port) - return uri.URIReference(self.scheme, - authority, - path=self.path, - query=self.query, - fragment=self.fragment, - encoding=self.encoding) + return uri.URIReference( + self.scheme, + authority, + path=self.path, + query=self.query, + fragment=self.fragment, + encoding=self.encoding, + ) diff --git a/src/rfc3986/misc.py b/src/rfc3986/misc.py index b735e04..338b187 100644 --- a/src/rfc3986/misc.py +++ b/src/rfc3986/misc.py @@ -25,16 +25,16 @@ # These are enumerated for the named tuple used as a superclass of # URIReference -URI_COMPONENTS = ['scheme', 'authority', 'path', 'query', 'fragment'] +URI_COMPONENTS = ["scheme", "authority", "path", "query", "fragment"] important_characters = { - 'generic_delimiters': abnf_regexp.GENERIC_DELIMITERS, - 'sub_delimiters': abnf_regexp.SUB_DELIMITERS, + "generic_delimiters": abnf_regexp.GENERIC_DELIMITERS, + "sub_delimiters": abnf_regexp.SUB_DELIMITERS, # We need to escape the '*' in this case - 're_sub_delimiters': abnf_regexp.SUB_DELIMITERS_RE, - 'unreserved_chars': abnf_regexp.UNRESERVED_CHARS, + "re_sub_delimiters": abnf_regexp.SUB_DELIMITERS_RE, + "unreserved_chars": abnf_regexp.UNRESERVED_CHARS, # We need to escape the '-' in this case: - 're_unreserved': abnf_regexp.UNRESERVED_RE, + "re_unreserved": abnf_regexp.UNRESERVED_RE, } # For details about delimiters and reserved characters, see: @@ -49,23 +49,25 @@ URI_MATCHER = re.compile(abnf_regexp.URL_PARSING_RE) -SUBAUTHORITY_MATCHER = re.compile(( - '^(?:(?P{0})@)?' # userinfo - '(?P{1})' # host - ':?(?P{2})?$' # port - ).format(abnf_regexp.USERINFO_RE, - abnf_regexp.HOST_PATTERN, - abnf_regexp.PORT_RE)) +SUBAUTHORITY_MATCHER = re.compile( + ( + "^(?:(?P{0})@)?" # userinfo + "(?P{1})" # host + ":?(?P{2})?$" # port + ).format( + abnf_regexp.USERINFO_RE, abnf_regexp.HOST_PATTERN, abnf_regexp.PORT_RE + ) +) -HOST_MATCHER = re.compile('^' + abnf_regexp.HOST_RE + '$') -IPv4_MATCHER = re.compile('^' + abnf_regexp.IPv4_RE + '$') -IPv6_MATCHER = re.compile(r'^\[' + abnf_regexp.IPv6_ADDRZ_RFC4007_RE + r'\]$') +HOST_MATCHER = re.compile("^" + abnf_regexp.HOST_RE + "$") +IPv4_MATCHER = re.compile("^" + abnf_regexp.IPv4_RE + "$") +IPv6_MATCHER = re.compile(r"^\[" + abnf_regexp.IPv6_ADDRZ_RFC4007_RE + r"\]$") # Used by host validator -IPv6_NO_RFC4007_MATCHER = re.compile(r'^\[%s\]$' % ( - abnf_regexp.IPv6_ADDRZ_RE -)) +IPv6_NO_RFC4007_MATCHER = re.compile( + r"^\[%s\]$" % (abnf_regexp.IPv6_ADDRZ_RE) +) # Matcher used to validate path components PATH_MATCHER = re.compile(abnf_regexp.PATH_RE) @@ -80,20 +82,26 @@ FRAGMENT_MATCHER = QUERY_MATCHER # Scheme validation, see: http://tools.ietf.org/html/rfc3986#section-3.1 -SCHEME_MATCHER = re.compile('^{0}$'.format(abnf_regexp.SCHEME_RE)) +SCHEME_MATCHER = re.compile("^{0}$".format(abnf_regexp.SCHEME_RE)) -RELATIVE_REF_MATCHER = re.compile(r'^%s(\?%s)?(#%s)?$' % ( - abnf_regexp.RELATIVE_PART_RE, - abnf_regexp.QUERY_RE, - abnf_regexp.FRAGMENT_RE, -)) +RELATIVE_REF_MATCHER = re.compile( + r"^%s(\?%s)?(#%s)?$" + % ( + abnf_regexp.RELATIVE_PART_RE, + abnf_regexp.QUERY_RE, + abnf_regexp.FRAGMENT_RE, + ) +) # See http://tools.ietf.org/html/rfc3986#section-4.3 -ABSOLUTE_URI_MATCHER = re.compile(r'^%s:%s(\?%s)?$' % ( - abnf_regexp.COMPONENT_PATTERN_DICT['scheme'], - abnf_regexp.HIER_PART_RE, - abnf_regexp.QUERY_RE[1:-1], -)) +ABSOLUTE_URI_MATCHER = re.compile( + r"^%s:%s(\?%s)?$" + % ( + abnf_regexp.COMPONENT_PATTERN_DICT["scheme"], + abnf_regexp.HIER_PART_RE, + abnf_regexp.QUERY_RE[1:-1], + ) +) # ############### # IRIs / RFC 3987 @@ -101,24 +109,27 @@ IRI_MATCHER = re.compile(abnf_regexp.URL_PARSING_RE, re.UNICODE) -ISUBAUTHORITY_MATCHER = re.compile(( - u'^(?:(?P{0})@)?' # iuserinfo - u'(?P{1})' # ihost - u':?(?P{2})?$' # port - ).format(abnf_regexp.IUSERINFO_RE, - abnf_regexp.IHOST_RE, - abnf_regexp.PORT_RE), re.UNICODE) +ISUBAUTHORITY_MATCHER = re.compile( + ( + u"^(?:(?P{0})@)?" # iuserinfo + u"(?P{1})" # ihost + u":?(?P{2})?$" # port + ).format( + abnf_regexp.IUSERINFO_RE, abnf_regexp.IHOST_RE, abnf_regexp.PORT_RE + ), + re.UNICODE, +) # Path merger as defined in http://tools.ietf.org/html/rfc3986#section-5.2.3 def merge_paths(base_uri, relative_path): """Merge a base URI's path with a relative URI's path.""" if base_uri.path is None and base_uri.authority is not None: - return '/' + relative_path + return "/" + relative_path else: - path = base_uri.path or '' - index = path.rfind('/') - return path[:index] + '/' + relative_path + path = base_uri.path or "" + index = path.rfind("/") + return path[:index] + "/" + relative_path UseExisting = object() diff --git a/src/rfc3986/normalizers.py b/src/rfc3986/normalizers.py index 2eb1bb3..0d702b6 100644 --- a/src/rfc3986/normalizers.py +++ b/src/rfc3986/normalizers.py @@ -27,13 +27,13 @@ def normalize_scheme(scheme): def normalize_authority(authority): """Normalize an authority tuple to a string.""" userinfo, host, port = authority - result = '' + result = "" if userinfo: - result += normalize_percent_characters(userinfo) + '@' + result += normalize_percent_characters(userinfo) + "@" if host: result += normalize_host(host) if port: - result += ':' + port + result += ":" + port return result @@ -50,16 +50,19 @@ def normalize_password(password): def normalize_host(host): """Normalize a host string.""" if misc.IPv6_MATCHER.match(host): - percent = host.find('%') + percent = host.find("%") if percent != -1: - percent_25 = host.find('%25') + percent_25 = host.find("%25") # Replace RFC 4007 IPv6 Zone ID delimiter '%' with '%25' # from RFC 6874. If the host is '[%25]' then we # assume RFC 4007 and normalize to '[%2525]' - if percent_25 == -1 or percent < percent_25 or \ - (percent == percent_25 and percent_25 == len(host) - 4): - host = host.replace('%', '%25', 1) + if ( + percent_25 == -1 + or percent < percent_25 + or (percent == percent_25 and percent_25 == len(host) - 4) + ): + host = host.replace("%", "%25", 1) # Don't normalize the casing of the Zone ID return host[:percent].lower() + host[percent:] @@ -90,7 +93,7 @@ def normalize_fragment(fragment): return normalize_percent_characters(fragment) -PERCENT_MATCHER = re.compile('%[A-Fa-f0-9]{2}') +PERCENT_MATCHER = re.compile("%[A-Fa-f0-9]{2}") def normalize_percent_characters(s): @@ -111,15 +114,15 @@ def remove_dot_segments(s): See also Section 5.2.4 of :rfc:`3986`. """ # See http://tools.ietf.org/html/rfc3986#section-5.2.4 for pseudo-code - segments = s.split('/') # Turn the path into a list of segments + segments = s.split("/") # Turn the path into a list of segments output = [] # Initialize the variable to use to store output for segment in segments: # '.' is the current directory, so ignore it, it is superfluous - if segment == '.': + if segment == ".": continue # Anything other than '..', should be appended to the output - elif segment != '..': + elif segment != "..": output.append(segment) # In this case segment == '..', if we can, we should pop the last # element @@ -128,15 +131,15 @@ def remove_dot_segments(s): # If the path starts with '/' and the output is empty or the first string # is non-empty - if s.startswith('/') and (not output or output[0]): - output.insert(0, '') + if s.startswith("/") and (not output or output[0]): + output.insert(0, "") # If the path starts with '/.' or '/..' ensure we add one more empty # string to add a trailing '/' - if s.endswith(('/.', '/..')): - output.append('') + if s.endswith(("/.", "/..")): + output.append("") - return '/'.join(output) + return "/".join(output) def encode_component(uri_component, encoding): @@ -146,22 +149,24 @@ def encode_component(uri_component, encoding): # Try to see if the component we're encoding is already percent-encoded # so we can skip all '%' characters but still encode all others. - percent_encodings = len(PERCENT_MATCHER.findall( - compat.to_str(uri_component, encoding))) + percent_encodings = len( + PERCENT_MATCHER.findall(compat.to_str(uri_component, encoding)) + ) uri_bytes = compat.to_bytes(uri_component, encoding) - is_percent_encoded = percent_encodings == uri_bytes.count(b'%') + is_percent_encoded = percent_encodings == uri_bytes.count(b"%") encoded_uri = bytearray() for i in range(0, len(uri_bytes)): # Will return a single character bytestring on both Python 2 & 3 - byte = uri_bytes[i:i+1] + byte = uri_bytes[i : i + 1] byte_ord = ord(byte) - if ((is_percent_encoded and byte == b'%') - or (byte_ord < 128 and byte.decode() in misc.NON_PCT_ENCODED)): + if (is_percent_encoded and byte == b"%") or ( + byte_ord < 128 and byte.decode() in misc.NON_PCT_ENCODED + ): encoded_uri.extend(byte) continue - encoded_uri.extend('%{0:02x}'.format(byte_ord).encode().upper()) + encoded_uri.extend("%{0:02x}".format(byte_ord).encode().upper()) return encoded_uri.decode(encoding) diff --git a/src/rfc3986/parseresult.py b/src/rfc3986/parseresult.py index ed5b723..8887e8f 100644 --- a/src/rfc3986/parseresult.py +++ b/src/rfc3986/parseresult.py @@ -21,30 +21,42 @@ from . import normalizers from . import uri -__all__ = ('ParseResult', 'ParseResultBytes') +__all__ = ("ParseResult", "ParseResultBytes") -PARSED_COMPONENTS = ('scheme', 'userinfo', 'host', 'port', 'path', 'query', - 'fragment') +PARSED_COMPONENTS = ( + "scheme", + "userinfo", + "host", + "port", + "path", + "query", + "fragment", +) class ParseResultMixin(object): def _generate_authority(self, attributes): # I swear I did not align the comparisons below. That's just how they # happened to align based on pep8 and attribute lengths. - userinfo, host, port = (attributes[p] - for p in ('userinfo', 'host', 'port')) - if (self.userinfo != userinfo or - self.host != host or - self.port != port): + userinfo, host, port = ( + attributes[p] for p in ("userinfo", "host", "port") + ) + if ( + self.userinfo != userinfo + or self.host != host + or self.port != port + ): if port: - port = '{0}'.format(port) + port = "{0}".format(port) return normalizers.normalize_authority( - (compat.to_str(userinfo, self.encoding), - compat.to_str(host, self.encoding), - port) + ( + compat.to_str(userinfo, self.encoding), + compat.to_str(host, self.encoding), + port, + ) ) if isinstance(self.authority, bytes): - return self.authority.decode('utf-8') + return self.authority.decode("utf-8") return self.authority def geturl(self): @@ -67,8 +79,9 @@ def params(self): return self.query -class ParseResult(namedtuple('ParseResult', PARSED_COMPONENTS), - ParseResultMixin): +class ParseResult( + namedtuple("ParseResult", PARSED_COMPONENTS), ParseResultMixin +): """Implementation of urlparse compatibility class. This uses the URIReference logic to handle compatibility with the @@ -77,8 +90,18 @@ class ParseResult(namedtuple('ParseResult', PARSED_COMPONENTS), slots = () - def __new__(cls, scheme, userinfo, host, port, path, query, fragment, - uri_ref, encoding='utf-8'): + def __new__( + cls, + scheme, + userinfo, + host, + port, + path, + query, + fragment, + uri_ref, + encoding="utf-8", + ): """Create a new ParseResult.""" parse_result = super(ParseResult, cls).__new__( cls, @@ -88,42 +111,57 @@ def __new__(cls, scheme, userinfo, host, port, path, query, fragment, port or None, path or None, query, - fragment) + fragment, + ) parse_result.encoding = encoding parse_result.reference = uri_ref return parse_result @classmethod - def from_parts(cls, scheme=None, userinfo=None, host=None, port=None, - path=None, query=None, fragment=None, encoding='utf-8'): + def from_parts( + cls, + scheme=None, + userinfo=None, + host=None, + port=None, + path=None, + query=None, + fragment=None, + encoding="utf-8", + ): """Create a ParseResult instance from its parts.""" - authority = '' + authority = "" if userinfo is not None: - authority += userinfo + '@' + authority += userinfo + "@" if host is not None: authority += host if port is not None: - authority += ':{0}'.format(port) - uri_ref = uri.URIReference(scheme=scheme, - authority=authority, - path=path, - query=query, - fragment=fragment, - encoding=encoding).normalize() + authority += ":{0}".format(port) + uri_ref = uri.URIReference( + scheme=scheme, + authority=authority, + path=path, + query=query, + fragment=fragment, + encoding=encoding, + ).normalize() userinfo, host, port = authority_from(uri_ref, strict=True) - return cls(scheme=uri_ref.scheme, - userinfo=userinfo, - host=host, - port=port, - path=uri_ref.path, - query=uri_ref.query, - fragment=uri_ref.fragment, - uri_ref=uri_ref, - encoding=encoding) + return cls( + scheme=uri_ref.scheme, + userinfo=userinfo, + host=host, + port=port, + path=uri_ref.path, + query=uri_ref.query, + fragment=uri_ref.fragment, + uri_ref=uri_ref, + encoding=encoding, + ) @classmethod - def from_string(cls, uri_string, encoding='utf-8', strict=True, - lazy_normalize=True): + def from_string( + cls, uri_string, encoding="utf-8", strict=True, lazy_normalize=True + ): """Parse a URI from the given unicode URI string. :param str uri_string: Unicode URI to be parsed into a reference. @@ -138,52 +176,67 @@ def from_string(cls, uri_string, encoding='utf-8', strict=True, reference = reference.normalize() userinfo, host, port = authority_from(reference, strict) - return cls(scheme=reference.scheme, - userinfo=userinfo, - host=host, - port=port, - path=reference.path, - query=reference.query, - fragment=reference.fragment, - uri_ref=reference, - encoding=encoding) + return cls( + scheme=reference.scheme, + userinfo=userinfo, + host=host, + port=port, + path=reference.path, + query=reference.query, + fragment=reference.fragment, + uri_ref=reference, + encoding=encoding, + ) @property def authority(self): """Return the normalized authority.""" return self.reference.authority - def copy_with(self, scheme=misc.UseExisting, userinfo=misc.UseExisting, - host=misc.UseExisting, port=misc.UseExisting, - path=misc.UseExisting, query=misc.UseExisting, - fragment=misc.UseExisting): + def copy_with( + self, + scheme=misc.UseExisting, + userinfo=misc.UseExisting, + host=misc.UseExisting, + port=misc.UseExisting, + path=misc.UseExisting, + query=misc.UseExisting, + fragment=misc.UseExisting, + ): """Create a copy of this instance replacing with specified parts.""" - attributes = zip(PARSED_COMPONENTS, - (scheme, userinfo, host, port, path, query, fragment)) + attributes = zip( + PARSED_COMPONENTS, + (scheme, userinfo, host, port, path, query, fragment), + ) attrs_dict = {} for name, value in attributes: if value is misc.UseExisting: value = getattr(self, name) attrs_dict[name] = value authority = self._generate_authority(attrs_dict) - ref = self.reference.copy_with(scheme=attrs_dict['scheme'], - authority=authority, - path=attrs_dict['path'], - query=attrs_dict['query'], - fragment=attrs_dict['fragment']) + ref = self.reference.copy_with( + scheme=attrs_dict["scheme"], + authority=authority, + path=attrs_dict["path"], + query=attrs_dict["query"], + fragment=attrs_dict["fragment"], + ) return ParseResult(uri_ref=ref, encoding=self.encoding, **attrs_dict) def encode(self, encoding=None): """Convert to an instance of ParseResultBytes.""" encoding = encoding or self.encoding attrs = dict( - zip(PARSED_COMPONENTS, - (attr.encode(encoding) if hasattr(attr, 'encode') else attr - for attr in self))) + zip( + PARSED_COMPONENTS, + ( + attr.encode(encoding) if hasattr(attr, "encode") else attr + for attr in self + ), + ) + ) return ParseResultBytes( - uri_ref=self.reference, - encoding=encoding, - **attrs + uri_ref=self.reference, encoding=encoding, **attrs ) def unsplit(self, use_idna=False): @@ -194,18 +247,30 @@ def unsplit(self, use_idna=False): """ parse_result = self if use_idna and self.host: - hostbytes = self.host.encode('idna') + hostbytes = self.host.encode("idna") host = hostbytes.decode(self.encoding) parse_result = self.copy_with(host=host) return parse_result.reference.unsplit() -class ParseResultBytes(namedtuple('ParseResultBytes', PARSED_COMPONENTS), - ParseResultMixin): +class ParseResultBytes( + namedtuple("ParseResultBytes", PARSED_COMPONENTS), ParseResultMixin +): """Compatibility shim for the urlparse.ParseResultBytes object.""" - def __new__(cls, scheme, userinfo, host, port, path, query, fragment, - uri_ref, encoding='utf-8', lazy_normalize=True): + def __new__( + cls, + scheme, + userinfo, + host, + port, + path, + query, + fragment, + uri_ref, + encoding="utf-8", + lazy_normalize=True, + ): """Create a new ParseResultBytes instance.""" parse_result = super(ParseResultBytes, cls).__new__( cls, @@ -215,48 +280,63 @@ def __new__(cls, scheme, userinfo, host, port, path, query, fragment, port or None, path or None, query or None, - fragment or None) + fragment or None, + ) parse_result.encoding = encoding parse_result.reference = uri_ref parse_result.lazy_normalize = lazy_normalize return parse_result @classmethod - def from_parts(cls, scheme=None, userinfo=None, host=None, port=None, - path=None, query=None, fragment=None, encoding='utf-8', - lazy_normalize=True): + def from_parts( + cls, + scheme=None, + userinfo=None, + host=None, + port=None, + path=None, + query=None, + fragment=None, + encoding="utf-8", + lazy_normalize=True, + ): """Create a ParseResult instance from its parts.""" - authority = '' + authority = "" if userinfo is not None: - authority += userinfo + '@' + authority += userinfo + "@" if host is not None: authority += host if port is not None: - authority += ':{0}'.format(int(port)) - uri_ref = uri.URIReference(scheme=scheme, - authority=authority, - path=path, - query=query, - fragment=fragment, - encoding=encoding) + authority += ":{0}".format(int(port)) + uri_ref = uri.URIReference( + scheme=scheme, + authority=authority, + path=path, + query=query, + fragment=fragment, + encoding=encoding, + ) if not lazy_normalize: uri_ref = uri_ref.normalize() to_bytes = compat.to_bytes userinfo, host, port = authority_from(uri_ref, strict=True) - return cls(scheme=to_bytes(scheme, encoding), - userinfo=to_bytes(userinfo, encoding), - host=to_bytes(host, encoding), - port=port, - path=to_bytes(path, encoding), - query=to_bytes(query, encoding), - fragment=to_bytes(fragment, encoding), - uri_ref=uri_ref, - encoding=encoding, - lazy_normalize=lazy_normalize) + return cls( + scheme=to_bytes(scheme, encoding), + userinfo=to_bytes(userinfo, encoding), + host=to_bytes(host, encoding), + port=port, + path=to_bytes(path, encoding), + query=to_bytes(query, encoding), + fragment=to_bytes(fragment, encoding), + uri_ref=uri_ref, + encoding=encoding, + lazy_normalize=lazy_normalize, + ) @classmethod - def from_string(cls, uri_string, encoding='utf-8', strict=True, - lazy_normalize=True): + def from_string( + cls, uri_string, encoding="utf-8", strict=True, lazy_normalize=True + ): """Parse a URI from the given unicode URI string. :param str uri_string: Unicode URI to be parsed into a reference. @@ -272,44 +352,55 @@ def from_string(cls, uri_string, encoding='utf-8', strict=True, userinfo, host, port = authority_from(reference, strict) to_bytes = compat.to_bytes - return cls(scheme=to_bytes(reference.scheme, encoding), - userinfo=to_bytes(userinfo, encoding), - host=to_bytes(host, encoding), - port=port, - path=to_bytes(reference.path, encoding), - query=to_bytes(reference.query, encoding), - fragment=to_bytes(reference.fragment, encoding), - uri_ref=reference, - encoding=encoding, - lazy_normalize=lazy_normalize) + return cls( + scheme=to_bytes(reference.scheme, encoding), + userinfo=to_bytes(userinfo, encoding), + host=to_bytes(host, encoding), + port=port, + path=to_bytes(reference.path, encoding), + query=to_bytes(reference.query, encoding), + fragment=to_bytes(reference.fragment, encoding), + uri_ref=reference, + encoding=encoding, + lazy_normalize=lazy_normalize, + ) @property def authority(self): """Return the normalized authority.""" return self.reference.authority.encode(self.encoding) - def copy_with(self, scheme=misc.UseExisting, userinfo=misc.UseExisting, - host=misc.UseExisting, port=misc.UseExisting, - path=misc.UseExisting, query=misc.UseExisting, - fragment=misc.UseExisting, lazy_normalize=True): + def copy_with( + self, + scheme=misc.UseExisting, + userinfo=misc.UseExisting, + host=misc.UseExisting, + port=misc.UseExisting, + path=misc.UseExisting, + query=misc.UseExisting, + fragment=misc.UseExisting, + lazy_normalize=True, + ): """Create a copy of this instance replacing with specified parts.""" - attributes = zip(PARSED_COMPONENTS, - (scheme, userinfo, host, port, path, query, fragment)) + attributes = zip( + PARSED_COMPONENTS, + (scheme, userinfo, host, port, path, query, fragment), + ) attrs_dict = {} for name, value in attributes: if value is misc.UseExisting: value = getattr(self, name) - if not isinstance(value, bytes) and hasattr(value, 'encode'): + if not isinstance(value, bytes) and hasattr(value, "encode"): value = value.encode(self.encoding) attrs_dict[name] = value authority = self._generate_authority(attrs_dict) to_str = compat.to_str ref = self.reference.copy_with( - scheme=to_str(attrs_dict['scheme'], self.encoding), + scheme=to_str(attrs_dict["scheme"], self.encoding), authority=to_str(authority, self.encoding), - path=to_str(attrs_dict['path'], self.encoding), - query=to_str(attrs_dict['query'], self.encoding), - fragment=to_str(attrs_dict['fragment'], self.encoding) + path=to_str(attrs_dict["path"], self.encoding), + query=to_str(attrs_dict["query"], self.encoding), + fragment=to_str(attrs_dict["fragment"], self.encoding), ) if not lazy_normalize: ref = ref.normalize() @@ -331,7 +422,7 @@ def unsplit(self, use_idna=False): # self.host is bytes, to encode to idna, we need to decode it # first host = self.host.decode(self.encoding) - hostbytes = host.encode('idna') + hostbytes = host.encode("idna") parse_result = self.copy_with(host=hostbytes) if self.lazy_normalize: parse_result = parse_result.copy_with(lazy_normalize=False) @@ -347,16 +438,16 @@ def split_authority(authority): # Set-up rest in case there is no userinfo portion rest = authority - if '@' in authority: - userinfo, rest = authority.rsplit('@', 1) + if "@" in authority: + userinfo, rest = authority.rsplit("@", 1) # Handle IPv6 host addresses - if rest.startswith('['): - host, rest = rest.split(']', 1) - host += ']' + if rest.startswith("["): + host, rest = rest.split("]", 1) + host += "]" - if ':' in rest: - extra_host, port = rest.split(':', 1) + if ":" in rest: + extra_host, port = rest.split(":", 1) elif not host and rest: host = rest @@ -376,8 +467,9 @@ def authority_from(reference, strict): else: # Thanks to Richard Barrell for this idea: # https://twitter.com/0x2ba22e11/status/617338811975139328 - userinfo, host, port = (subauthority.get(p) - for p in ('userinfo', 'host', 'port')) + userinfo, host, port = ( + subauthority.get(p) for p in ("userinfo", "host", "port") + ) if port: try: diff --git a/src/rfc3986/uri.py b/src/rfc3986/uri.py index bb04033..75c617d 100644 --- a/src/rfc3986/uri.py +++ b/src/rfc3986/uri.py @@ -22,7 +22,7 @@ from ._mixin import URIMixin -class URIReference(namedtuple('URIReference', misc.URI_COMPONENTS), URIMixin): +class URIReference(namedtuple("URIReference", misc.URI_COMPONENTS), URIMixin): """Immutable object representing a parsed URI Reference. .. note:: @@ -82,8 +82,9 @@ class URIReference(namedtuple('URIReference', misc.URI_COMPONENTS), URIMixin): slots = () - def __new__(cls, scheme, authority, path, query, fragment, - encoding='utf-8'): + def __new__( + cls, scheme, authority, path, query, fragment, encoding="utf-8" + ): """Create a new URIReference.""" ref = super(URIReference, cls).__new__( cls, @@ -91,7 +92,8 @@ def __new__(cls, scheme, authority, path, query, fragment, authority or None, path or None, query, - fragment) + fragment, + ) ref.encoding = encoding return ref @@ -107,8 +109,10 @@ def __eq__(self, other): other_ref = URIReference.from_string(other) except TypeError: raise TypeError( - 'Unable to compare URIReference() to {0}()'.format( - type(other).__name__)) + "Unable to compare URIReference() to {0}()".format( + type(other).__name__ + ) + ) # See http://tools.ietf.org/html/rfc3986#section-6.2 naive_equality = tuple(self) == tuple(other_ref) @@ -125,16 +129,19 @@ def normalize(self): """ # See http://tools.ietf.org/html/rfc3986#section-6.2.2 for logic in # this method. - return URIReference(normalizers.normalize_scheme(self.scheme or ''), - normalizers.normalize_authority( - (self.userinfo, self.host, self.port)), - normalizers.normalize_path(self.path or ''), - normalizers.normalize_query(self.query), - normalizers.normalize_fragment(self.fragment), - self.encoding) + return URIReference( + normalizers.normalize_scheme(self.scheme or ""), + normalizers.normalize_authority( + (self.userinfo, self.host, self.port) + ), + normalizers.normalize_path(self.path or ""), + normalizers.normalize_query(self.query), + normalizers.normalize_fragment(self.fragment), + self.encoding, + ) @classmethod - def from_string(cls, uri_string, encoding='utf-8'): + def from_string(cls, uri_string, encoding="utf-8"): """Parse a URI reference from the given unicode URI string. :param str uri_string: Unicode URI to be parsed into a reference. @@ -145,9 +152,10 @@ def from_string(cls, uri_string, encoding='utf-8'): split_uri = misc.URI_MATCHER.match(uri_string).groupdict() return cls( - split_uri['scheme'], split_uri['authority'], - normalizers.encode_component(split_uri['path'], encoding), - normalizers.encode_component(split_uri['query'], encoding), - normalizers.encode_component(split_uri['fragment'], encoding), + split_uri["scheme"], + split_uri["authority"], + normalizers.encode_component(split_uri["path"], encoding), + normalizers.encode_component(split_uri["query"], encoding), + normalizers.encode_component(split_uri["fragment"], encoding), encoding, ) diff --git a/src/rfc3986/validators.py b/src/rfc3986/validators.py index 7fc9721..c58c29c 100644 --- a/src/rfc3986/validators.py +++ b/src/rfc3986/validators.py @@ -45,15 +45,9 @@ class Validator(object): """ - COMPONENT_NAMES = frozenset([ - 'scheme', - 'userinfo', - 'host', - 'port', - 'path', - 'query', - 'fragment', - ]) + COMPONENT_NAMES = frozenset( + ["scheme", "userinfo", "host", "port", "path", "query", "fragment"] + ) def __init__(self): """Initialize our default validations.""" @@ -62,13 +56,13 @@ def __init__(self): self.allowed_ports = set() self.allow_password = True self.required_components = { - 'scheme': False, - 'userinfo': False, - 'host': False, - 'port': False, - 'path': False, - 'query': False, - 'fragment': False, + "scheme": False, + "userinfo": False, + "host": False, + "port": False, + "path": False, + "query": False, + "fragment": False, } self.validated_components = self.required_components.copy() @@ -168,9 +162,9 @@ def check_validity_of(self, *components): raise ValueError( '"{}" is not a valid component'.format(component) ) - self.validated_components.update({ - component: True for component in components - }) + self.validated_components.update( + {component: True for component in components} + ) return self def require_presence_of(self, *components): @@ -193,9 +187,9 @@ def require_presence_of(self, *components): raise ValueError( '"{}" is not a valid component'.format(component) ) - self.required_components.update({ - component: True for component in components - }) + self.required_components.update( + {component: True for component in components} + ) return self def validate(self, uri): @@ -235,9 +229,9 @@ def validate(self, uri): if validated_components: ensure_components_are_valid(uri, validated_components) - ensure_one_of(self.allowed_schemes, uri, 'scheme') - ensure_one_of(self.allowed_hosts, uri, 'host') - ensure_one_of(self.allowed_ports, uri, 'port') + ensure_one_of(self.allowed_schemes, uri, "scheme") + ensure_one_of(self.allowed_hosts, uri, "host") + ensure_one_of(self.allowed_ports, uri, "port") def check_password(uri): @@ -245,7 +239,7 @@ def check_password(uri): userinfo = uri.userinfo if not userinfo: return - credentials = userinfo.split(':', 1) + credentials = userinfo.split(":", 1) if len(credentials) <= 1: return raise exceptions.PasswordForbidden(uri) @@ -262,11 +256,13 @@ def ensure_one_of(allowed_values, uri, attribute): def ensure_required_components_exist(uri, required_components): """Assert that all required components are present in the URI.""" - missing_components = sorted([ - component - for component in required_components - if getattr(uri, component) is None - ]) + missing_components = sorted( + [ + component + for component in required_components + if getattr(uri, component) is None + ] + ) if missing_components: raise exceptions.MissingComponentError(uri, *missing_components) @@ -282,8 +278,7 @@ def is_valid(value, matcher, require): Whether or not the value is required. """ if require: - return (value is not None - and matcher.match(value)) + return value is not None and matcher.match(value) # require is False and value is not None return value is None or matcher.match(value) @@ -393,17 +388,17 @@ def valid_ipv4_host_address(host): """Determine if the given host is a valid IPv4 address.""" # If the host exists, and it might be IPv4, check each byte in the # address. - return all([0 <= int(byte, base=10) <= 255 for byte in host.split('.')]) + return all([0 <= int(byte, base=10) <= 255 for byte in host.split(".")]) _COMPONENT_VALIDATORS = { - 'scheme': scheme_is_valid, - 'path': path_is_valid, - 'query': query_is_valid, - 'fragment': fragment_is_valid, + "scheme": scheme_is_valid, + "path": path_is_valid, + "query": query_is_valid, + "fragment": fragment_is_valid, } -_SUBAUTHORITY_VALIDATORS = set(['userinfo', 'host', 'port']) +_SUBAUTHORITY_VALIDATORS = set(["userinfo", "host", "port"]) def subauthority_component_is_valid(uri, component): @@ -415,19 +410,19 @@ def subauthority_component_is_valid(uri, component): # If we can parse the authority into sub-components and we're not # validating the port, we can assume it's valid. - if component == 'host': - return host_is_valid(subauthority_dict['host']) - elif component != 'port': + if component == "host": + return host_is_valid(subauthority_dict["host"]) + elif component != "port": return True try: - port = int(subauthority_dict['port']) + port = int(subauthority_dict["port"]) except TypeError: # If the port wasn't provided it'll be None and int(None) raises a # TypeError return True - return (0 <= port <= 65535) + return 0 <= port <= 65535 def ensure_components_are_valid(uri, validated_components): diff --git a/tests/base.py b/tests/base.py index b74cd55..d825b0e 100644 --- a/tests/base.py +++ b/tests/base.py @@ -18,10 +18,11 @@ class BaseTestParsesURIs: test_class = None """Tests for self.test_class handling of URIs.""" + def test_handles_basic_uri(self, basic_uri): """Test that self.test_class can handle a simple URI.""" uri = self.test_class.from_string(basic_uri) - assert uri.scheme == 'http' + assert uri.scheme == "http" assert uri.authority == basic_uri[7:] # len('http://') assert uri.host == uri.authority assert uri.path is None @@ -33,56 +34,57 @@ def test_handles_basic_uri(self, basic_uri): def test_handles_basic_uri_with_port(self, basic_uri_with_port): """Test that self.test_class can handle a simple URI with a port.""" uri = self.test_class.from_string(basic_uri_with_port) - assert uri.scheme == 'ftp' + assert uri.scheme == "ftp" assert uri.authority == basic_uri_with_port[6:] assert uri.host != uri.authority - assert str(uri.port) == '21' + assert str(uri.port) == "21" assert uri.path is None assert uri.query is None assert uri.fragment is None assert uri.userinfo is None def test_handles_uri_with_port_and_userinfo( - self, uri_with_port_and_userinfo): + self, uri_with_port_and_userinfo + ): """ Test that self.test_class can handle a URI with a port and userinfo. """ uri = self.test_class.from_string(uri_with_port_and_userinfo) - assert uri.scheme == 'ssh' + assert uri.scheme == "ssh" # 6 == len('ftp://') assert uri.authority == uri_with_port_and_userinfo[6:] assert uri.host != uri.authority - assert str(uri.port) == '22' + assert str(uri.port) == "22" assert uri.path is None assert uri.query is None assert uri.fragment is None - assert uri.userinfo == 'user:pass' + assert uri.userinfo == "user:pass" - def test_handles_tricky_userinfo( - self, uri_with_port_and_tricky_userinfo): + def test_handles_tricky_userinfo(self, uri_with_port_and_tricky_userinfo): """ Test that self.test_class can handle a URI with unusual (non a-z) chars in userinfo. """ uri = self.test_class.from_string(uri_with_port_and_tricky_userinfo) - assert uri.scheme == 'ssh' + assert uri.scheme == "ssh" # 6 == len('ftp://') assert uri.authority == uri_with_port_and_tricky_userinfo[6:] assert uri.host != uri.authority - assert str(uri.port) == '22' + assert str(uri.port) == "22" assert uri.path is None assert uri.query is None assert uri.fragment is None - assert uri.userinfo == 'user%20!=:pass' + assert uri.userinfo == "user%20!=:pass" def test_handles_basic_uri_with_path(self, basic_uri_with_path): """Test that self.test_class can handle a URI with a path.""" uri = self.test_class.from_string(basic_uri_with_path) - assert uri.scheme == 'http' - assert basic_uri_with_path == (uri.scheme + '://' + uri.authority - + uri.path) + assert uri.scheme == "http" + assert basic_uri_with_path == ( + uri.scheme + "://" + uri.authority + uri.path + ) assert uri.host == uri.authority - assert uri.path == '/path/to/resource' + assert uri.path == "/path/to/resource" assert uri.query is None assert uri.fragment is None assert uri.userinfo is None @@ -93,10 +95,10 @@ def test_handles_uri_with_path_and_query(self, uri_with_path_and_query): Test that self.test_class can handle a URI with a path and query. """ uri = self.test_class.from_string(uri_with_path_and_query) - assert uri.scheme == 'http' + assert uri.scheme == "http" assert uri.host == uri.authority - assert uri.path == '/path/to/resource' - assert uri.query == 'key=value' + assert uri.path == "/path/to/resource" + assert uri.query == "key=value" assert uri.fragment is None assert uri.userinfo is None assert uri.port is None @@ -106,12 +108,12 @@ def test_handles_uri_with_everything(self, uri_with_everything): Test that self.test_class can handle and with everything in it. """ uri = self.test_class.from_string(uri_with_everything) - assert uri.scheme == 'https' - assert uri.path == '/path/to/resource' - assert uri.query == 'key=value' - assert uri.fragment == 'fragment' - assert uri.userinfo == 'user:pass' - assert str(uri.port) == '443' + assert uri.scheme == "https" + assert uri.path == "/path/to/resource" + assert uri.query == "key=value" + assert uri.fragment == "fragment" + assert uri.userinfo == "user:pass" + assert str(uri.port) == "443" def test_handles_relative_uri(self, relative_uri): """Test that self.test_class can handle a relative URI.""" @@ -123,15 +125,15 @@ def test_handles_percent_in_path(self, uri_path_with_percent): """Test that self.test_class encodes the % character properly.""" uri = self.test_class.from_string(uri_path_with_percent) print(uri.path) - assert uri.path == '/%25%20' + assert uri.path == "/%25%20" def test_handles_percent_in_query(self, uri_query_with_percent): uri = self.test_class.from_string(uri_query_with_percent) - assert uri.query == 'a=%25' + assert uri.query == "a=%25" def test_handles_percent_in_fragment(self, uri_fragment_with_percent): uri = self.test_class.from_string(uri_fragment_with_percent) - assert uri.fragment == 'perc%25ent' + assert uri.fragment == "perc%25ent" class BaseTestUnsplits: @@ -145,8 +147,9 @@ def test_basic_uri_with_port_unsplits(self, basic_uri_with_port): uri = self.test_class.from_string(basic_uri_with_port) assert uri.unsplit() == basic_uri_with_port - def test_uri_with_port_and_userinfo_unsplits(self, - uri_with_port_and_userinfo): + def test_uri_with_port_and_userinfo_unsplits( + self, uri_with_port_and_userinfo + ): uri = self.test_class.from_string(uri_with_port_and_userinfo) assert uri.unsplit() == uri_with_port_and_userinfo diff --git a/tests/conftest.py b/tests/conftest.py index 48190ab..db43961 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,134 +4,134 @@ import pytest -SNOWMAN = b'\xe2\x98\x83' +SNOWMAN = b"\xe2\x98\x83" valid_hosts = [ - '[21DA:00D3:0000:2F3B:02AA:00FF:FE28:9C5A]', - '[::1]', - '[::1%25lo]', # With ZoneID - '[FF02:0:0:0:0:0:0:2%25en01]', # With ZoneID - '[FF02:30:0:0:0:0:0:5%25en1]', # With ZoneID - '[FF02:30:0:0:0:0:0:5%25%26]', # With ZoneID - '[FF02:30:0:0:0:0:0:5%2525]', # With ZoneID - '[21DA:D3:0:2F3B:2AA:FF:FE28:9C5A]', - '[FE80::2AA:FF:FE9A:4CA2]', - '[FF02::2]', - '[FFFF::]', - '[FF02:3::5]', - '[FF02:0:0:0:0:0:0:2]', - '[FF02:30:0:0:0:0:0:5]', - '127.0.0.1', - 'www.example.com', - 'localhost', - 'http-bin.org', - '%2Fvar%2Frun%2Fsocket', - '6g9m8V6', # Issue #48 - ] + "[21DA:00D3:0000:2F3B:02AA:00FF:FE28:9C5A]", + "[::1]", + "[::1%25lo]", # With ZoneID + "[FF02:0:0:0:0:0:0:2%25en01]", # With ZoneID + "[FF02:30:0:0:0:0:0:5%25en1]", # With ZoneID + "[FF02:30:0:0:0:0:0:5%25%26]", # With ZoneID + "[FF02:30:0:0:0:0:0:5%2525]", # With ZoneID + "[21DA:D3:0:2F3B:2AA:FF:FE28:9C5A]", + "[FE80::2AA:FF:FE9A:4CA2]", + "[FF02::2]", + "[FFFF::]", + "[FF02:3::5]", + "[FF02:0:0:0:0:0:0:2]", + "[FF02:30:0:0:0:0:0:5]", + "127.0.0.1", + "www.example.com", + "localhost", + "http-bin.org", + "%2Fvar%2Frun%2Fsocket", + "6g9m8V6", # Issue #48 +] invalid_hosts = [ - '[FF02::3::5]', # IPv6 can only have one :: - '[FADF:01]', # Not properly compacted (missing a :) - '[FADF:01%en0]', # Not properly compacted (missing a :), Invalid ZoneID - '[FADF::01%]', # Empty Zone ID - 'localhost:80:80:80', # Too many ports - '256.256.256.256', # Invalid IPv4 Address - SNOWMAN.decode('utf-8') - ] + "[FF02::3::5]", # IPv6 can only have one :: + "[FADF:01]", # Not properly compacted (missing a :) + "[FADF:01%en0]", # Not properly compacted (missing a :), Invalid ZoneID + "[FADF::01%]", # Empty Zone ID + "localhost:80:80:80", # Too many ports + "256.256.256.256", # Invalid IPv4 Address + SNOWMAN.decode("utf-8"), +] equivalent_hostnames = [ - 'example.com', - 'eXample.com', - 'example.COM', - 'EXAMPLE.com', - 'ExAMPLE.com', - 'eXample.COM', - 'example.COM', - 'EXAMPLE.COM', - 'ExAMPLE.COM', + "example.com", + "eXample.com", + "example.COM", + "EXAMPLE.com", + "ExAMPLE.com", + "eXample.COM", + "example.COM", + "EXAMPLE.COM", + "ExAMPLE.COM", ] equivalent_schemes = [ - 'https', - 'HTTPS', - 'HttPs', - 'hTTpS', - 'HtTpS', + "https", + "HTTPS", + "HttPs", + "hTTpS", + "HtTpS", ] -equivalent_schemes_and_hostnames = list(itertools.product( - equivalent_schemes, - equivalent_hostnames, -)) +equivalent_schemes_and_hostnames = list( + itertools.product(equivalent_schemes, equivalent_hostnames,) +) @pytest.fixture(params=valid_hosts) def basic_uri(request): - return 'http://%s' % request.param + return "http://%s" % request.param @pytest.fixture(params=equivalent_schemes_and_hostnames) def uri_to_normalize(request): - return '%s://%s' % request.param + return "%s://%s" % request.param @pytest.fixture(params=valid_hosts) def basic_uri_with_port(request): - return 'ftp://%s:21' % request.param + return "ftp://%s:21" % request.param @pytest.fixture(params=valid_hosts) def uri_with_port_and_userinfo(request): - return 'ssh://user:pass@%s:22' % request.param + return "ssh://user:pass@%s:22" % request.param @pytest.fixture(params=valid_hosts) def uri_with_port_and_tricky_userinfo(request): - return 'ssh://%s@%s:22' % ('user%20!=:pass', request.param) + return "ssh://%s@%s:22" % ("user%20!=:pass", request.param) @pytest.fixture(params=valid_hosts) def basic_uri_with_path(request): - return 'http://%s/path/to/resource' % request.param + return "http://%s/path/to/resource" % request.param @pytest.fixture(params=valid_hosts) def uri_with_path_and_query(request): - return 'http://%s/path/to/resource?key=value' % request.param + return "http://%s/path/to/resource?key=value" % request.param @pytest.fixture(params=valid_hosts) def uri_with_everything(request): - return 'https://user:pass@%s:443/path/to/resource?key=value#fragment' % ( - request.param) + return "https://user:pass@%s:443/path/to/resource?key=value#fragment" % ( + request.param + ) @pytest.fixture(params=valid_hosts) def relative_uri(request): - return '//%s' % request.param + return "//%s" % request.param @pytest.fixture def absolute_path_uri(): - return '/path/to/file' + return "/path/to/file" @pytest.fixture(params=invalid_hosts) def invalid_uri(request): - return 'https://%s' % request.param + return "https://%s" % request.param @pytest.fixture(params=valid_hosts) def uri_path_with_percent(request): - return 'https://%s/%% ' % request.param + return "https://%s/%% " % request.param @pytest.fixture(params=valid_hosts) def uri_query_with_percent(request): - return 'https://%s?a=%%' % request.param + return "https://%s?a=%%" % request.param @pytest.fixture(params=valid_hosts) def uri_fragment_with_percent(request): - return 'https://%s#perc%%ent' % request.param + return "https://%s#perc%%ent" % request.param -sys.path.insert(0, '.') +sys.path.insert(0, ".") diff --git a/tests/test_api.py b/tests/test_api.py index 9e9189a..587d184 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,16 +1,19 @@ # -*- coding: utf-8 -*- from rfc3986.api import ( - uri_reference, is_valid_uri, normalize_uri, URIReference - ) + uri_reference, + is_valid_uri, + normalize_uri, + URIReference, +) def test_uri_reference(): - assert isinstance(uri_reference('http://example.com'), URIReference) + assert isinstance(uri_reference("http://example.com"), URIReference) def test_is_valid_uri(): - assert is_valid_uri('http://example.com') is True + assert is_valid_uri("http://example.com") is True def test_normalize_uri(): - assert normalize_uri('HTTP://EXAMPLE.COM') == 'http://example.com' + assert normalize_uri("HTTP://EXAMPLE.COM") == "http://example.com" diff --git a/tests/test_builder.py b/tests/test_builder.py index de907b4..bc23128 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -33,22 +33,22 @@ def test_builder_default(): def test_from_uri_reference(): uri = uri_reference("http://foo.bar:1234/baz") uribuilder = builder.URIBuilder().from_uri(uri) - assert uribuilder.scheme == 'http' + assert uribuilder.scheme == "http" assert uribuilder.userinfo is None - assert uribuilder.host == 'foo.bar' - assert uribuilder.port == '1234' - assert uribuilder.path == '/baz' + assert uribuilder.host == "foo.bar" + assert uribuilder.port == "1234" + assert uribuilder.path == "/baz" assert uribuilder.query is None assert uribuilder.fragment is None def test_from_uri_string(): uribuilder = builder.URIBuilder().from_uri("https://bar.foo:4321/boom") - assert uribuilder.scheme == 'https' + assert uribuilder.scheme == "https" assert uribuilder.userinfo is None - assert uribuilder.host == 'bar.foo' - assert uribuilder.port == '4321' - assert uribuilder.path == '/boom' + assert uribuilder.host == "bar.foo" + assert uribuilder.port == "4321" + assert uribuilder.path == "/boom" assert uribuilder.query is None assert uribuilder.fragment is None @@ -56,28 +56,27 @@ def test_from_uri_string(): def test_repr(): """Verify our repr looks like our class.""" uribuilder = builder.URIBuilder() - assert repr(uribuilder).startswith('URIBuilder(scheme=None') + assert repr(uribuilder).startswith("URIBuilder(scheme=None") -@pytest.mark.parametrize('scheme', [ - 'https', - 'hTTps', - 'Https', - 'HtTpS', - 'HTTPS', -]) +@pytest.mark.parametrize( + "scheme", ["https", "hTTps", "Https", "HtTpS", "HTTPS",] +) def test_add_scheme(scheme): """Verify schemes are normalized when added.""" uribuilder = builder.URIBuilder().add_scheme(scheme) - assert uribuilder.scheme == 'https' + assert uribuilder.scheme == "https" -@pytest.mark.parametrize('username, password, userinfo', [ - ('user', 'pass', 'user:pass'), - ('user', None, 'user'), - ('user@domain.com', 'password', 'user%40domain.com:password'), - ('user', 'pass:word', 'user:pass%3Aword'), -]) +@pytest.mark.parametrize( + "username, password, userinfo", + [ + ("user", "pass", "user:pass"), + ("user", None, "user"), + ("user@domain.com", "password", "user%40domain.com:password"), + ("user", "pass:word", "user:pass%3Aword"), + ], +) def test_add_credentials(username, password, userinfo): """Verify we normalize usernames and passwords.""" uribuilder = builder.URIBuilder().add_credentials(username, password) @@ -91,16 +90,16 @@ def test_add_credentials_requires_username(): @pytest.mark.parametrize( - ['hostname', 'expected_hostname'], + ["hostname", "expected_hostname"], [ - ('google.com', 'google.com'), - ('GOOGLE.COM', 'google.com'), - ('gOOgLe.COM', 'google.com'), - ('goOgLE.com', 'google.com'), - ('[::ff%etH0]', '[::ff%25etH0]'), - ('[::ff%25etH0]', '[::ff%25etH0]'), - ('[::FF%etH0]', '[::ff%25etH0]'), - ] + ("google.com", "google.com"), + ("GOOGLE.COM", "google.com"), + ("gOOgLe.COM", "google.com"), + ("goOgLE.com", "google.com"), + ("[::ff%etH0]", "[::ff%25etH0]"), + ("[::ff%25etH0]", "[::ff%25etH0]"), + ("[::FF%etH0]", "[::ff%25etH0]"), + ], ) def test_add_host(hostname, expected_hostname): """Verify we normalize hostnames in add_host.""" @@ -108,64 +107,72 @@ def test_add_host(hostname, expected_hostname): assert uribuilder.host == expected_hostname -@pytest.mark.parametrize('port', [ - -100, - '-100', - -1, - '-1', - 65536, - '65536', - 1000000, - '1000000', - '', - 'abc', - '0b10', -]) +@pytest.mark.parametrize( + "port", + [ + -100, + "-100", + -1, + "-1", + 65536, + "65536", + 1000000, + "1000000", + "", + "abc", + "0b10", + ], +) def test_add_invalid_port(port): """Verify we raise a ValueError for invalid ports.""" with pytest.raises(ValueError): builder.URIBuilder().add_port(port) -@pytest.mark.parametrize('port, expected', [ - (0, '0'), - ('0', '0'), - (1, '1'), - ('1', '1'), - (22, '22'), - ('22', '22'), - (80, '80'), - ('80', '80'), - (443, '443'), - ('443', '443'), - (65535, '65535'), - ('65535', '65535'), -]) +@pytest.mark.parametrize( + "port, expected", + [ + (0, "0"), + ("0", "0"), + (1, "1"), + ("1", "1"), + (22, "22"), + ("22", "22"), + (80, "80"), + ("80", "80"), + (443, "443"), + ("443", "443"), + (65535, "65535"), + ("65535", "65535"), + ], +) def test_add_port(port, expected): """Verify we normalize our port.""" uribuilder = builder.URIBuilder().add_port(port) assert uribuilder.port == expected -@pytest.mark.parametrize('path', [ - 'sigmavirus24/rfc3986', - '/sigmavirus24/rfc3986', -]) +@pytest.mark.parametrize( + "path", ["sigmavirus24/rfc3986", "/sigmavirus24/rfc3986",] +) def test_add_path(path): """Verify we normalize our path value.""" uribuilder = builder.URIBuilder().add_path(path) - assert uribuilder.path == '/sigmavirus24/rfc3986' - - -@pytest.mark.parametrize('query_items, expected', [ - ({'a': 'b c'}, 'a=b+c'), - ({'a': 'b+c'}, 'a=b%2Bc'), - ([('a', 'b c')], 'a=b+c'), - ([('a', 'b+c')], 'a=b%2Bc'), - ([('a', 'b'), ('c', 'd')], 'a=b&c=d'), - ([('a', 'b'), ('username', '@d')], 'a=b&username=%40d'), - ([('percent', '%')], 'percent=%25'), -]) + assert uribuilder.path == "/sigmavirus24/rfc3986" + + +@pytest.mark.parametrize( + "query_items, expected", + [ + ({"a": "b c"}, "a=b+c"), + ({"a": "b+c"}, "a=b%2Bc"), + ([("a", "b c")], "a=b+c"), + ([("a", "b+c")], "a=b%2Bc"), + ([("a", "b"), ("c", "d")], "a=b&c=d"), + ([("a", "b"), ("username", "@d")], "a=b&username=%40d"), + ([("percent", "%")], "percent=%25"), + ], +) def test_add_query_from(query_items, expected): """Verify the behaviour of add_query_from.""" uribuilder = builder.URIBuilder().add_query_from(query_items) @@ -174,22 +181,29 @@ def test_add_query_from(query_items, expected): def test_add_query(): """Verify we do not modify the provided query string.""" - uribuilder = builder.URIBuilder().add_query('username=@foo') - assert uribuilder.query == 'username=@foo' + uribuilder = builder.URIBuilder().add_query("username=@foo") + assert uribuilder.query == "username=@foo" def test_add_fragment(): """Verify our handling of fragments.""" - uribuilder = builder.URIBuilder().add_fragment('section-2.5.1') - assert uribuilder.fragment == 'section-2.5.1' + uribuilder = builder.URIBuilder().add_fragment("section-2.5.1") + assert uribuilder.fragment == "section-2.5.1" def test_finalize(): """Verify the whole thing.""" - uri = builder.URIBuilder().add_scheme('https').add_credentials( - 'sigmavirus24', 'not-my-re@l-password' - ).add_host('github.com').add_path('sigmavirus24/rfc3986').finalize( - ).unsplit() - expected = ('https://sigmavirus24:not-my-re%40l-password@github.com/' - 'sigmavirus24/rfc3986') + uri = ( + builder.URIBuilder() + .add_scheme("https") + .add_credentials("sigmavirus24", "not-my-re@l-password") + .add_host("github.com") + .add_path("sigmavirus24/rfc3986") + .finalize() + .unsplit() + ) + expected = ( + "https://sigmavirus24:not-my-re%40l-password@github.com/" + "sigmavirus24/rfc3986" + ) assert expected == uri diff --git a/tests/test_iri.py b/tests/test_iri.py index 59decac..fc0c581 100644 --- a/tests/test_iri.py +++ b/tests/test_iri.py @@ -11,21 +11,29 @@ idna = None -requires_idna = pytest.mark.skipif(idna is None, reason="This test requires the 'idna' module") +requires_idna = pytest.mark.skipif( + idna is None, reason="This test requires the 'idna' module" +) iri_to_uri = pytest.mark.parametrize( ["iri", "uri"], [ - (u'http://Bücher.de', u'http://xn--bcher-kva.de'), - (u'http://faß.de', u'http://xn--fa-hia.de'), - (u'http://βόλος.com/β/ό?λ#ος', u'http://xn--nxasmm1c.com/%CE%B2/%CF%8C?%CE%BB#%CE%BF%CF%82'), - (u'http://ශ්\u200dරී.com', u'http://xn--10cl1a0b660p.com'), - (u'http://نامه\u200cای.com', u'http://xn--mgba3gch31f060k.com'), - (u'http://Bü:ẞ@gOoGle.com', u'http://B%C3%BC:%E1%BA%9E@gOoGle.com'), - (u'http://ẞ.com:443', u'http://xn--zca.com:443'), - (u'http://ẞ.foo.com', u'http://xn--zca.foo.com'), - (u'http://Bẞ.com', u'http://xn--b-qfa.com'), - (u'http+unix://%2Ftmp%2FTEST.sock/get', 'http+unix://%2Ftmp%2FTEST.sock/get'), - ] + (u"http://Bücher.de", u"http://xn--bcher-kva.de"), + (u"http://faß.de", u"http://xn--fa-hia.de"), + ( + u"http://βόλος.com/β/ό?λ#ος", + u"http://xn--nxasmm1c.com/%CE%B2/%CF%8C?%CE%BB#%CE%BF%CF%82", + ), + (u"http://ශ්\u200dරී.com", u"http://xn--10cl1a0b660p.com"), + (u"http://نامه\u200cای.com", u"http://xn--mgba3gch31f060k.com"), + (u"http://Bü:ẞ@gOoGle.com", u"http://B%C3%BC:%E1%BA%9E@gOoGle.com"), + (u"http://ẞ.com:443", u"http://xn--zca.com:443"), + (u"http://ẞ.foo.com", u"http://xn--zca.foo.com"), + (u"http://Bẞ.com", u"http://xn--b-qfa.com"), + ( + u"http+unix://%2Ftmp%2FTEST.sock/get", + "http+unix://%2Ftmp%2FTEST.sock/get", + ), + ], ) @@ -41,25 +49,33 @@ def test_iri_equality(iri, uri): def test_iri_equality_special_cases(): - assert rfc3986.iri_reference(u"http://Bü:ẞ@βόλος.com/β/ό?λ#ος") == \ - (u"http", u"Bü:ẞ@βόλος.com", u"/%CE%B2/%CF%8C", u"%CE%BB", u"%CE%BF%CF%82") + assert rfc3986.iri_reference(u"http://Bü:ẞ@βόλος.com/β/ό?λ#ος") == ( + u"http", + u"Bü:ẞ@βόλος.com", + u"/%CE%B2/%CF%8C", + u"%CE%BB", + u"%CE%BF%CF%82", + ) with pytest.raises(TypeError): rfc3986.iri_reference(u"http://ẞ.com") == 1 @requires_idna -@pytest.mark.parametrize("iri", [ - u'http://♥.net', - u'http://\u0378.net', - pytest.param( - u'http://㛼.com', - marks=pytest.mark.skipif( - sys.version_info < (3, 3) and sys.maxunicode <= 0xFFFF, - reason="Python configured without UCS-4 support" - ) - ), -]) +@pytest.mark.parametrize( + "iri", + [ + u"http://♥.net", + u"http://\u0378.net", + pytest.param( + u"http://㛼.com", + marks=pytest.mark.skipif( + sys.version_info < (3, 3) and sys.maxunicode <= 0xFFFF, + reason="Python configured without UCS-4 support", + ), + ), + ], +) def test_encode_invalid_iri(iri): iri_ref = rfc3986.iri_reference(iri) with pytest.raises(InvalidAuthority): diff --git a/tests/test_misc.py b/tests/test_misc.py index eced326..4854379 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -5,43 +5,47 @@ def test_merge_paths_with_base_path_without_base_authority(): """Demonstrate merging with a base URI without an authority.""" - base = URIReference(scheme=None, - authority=None, - path='/foo/bar/bogus', - query=None, - fragment=None) - expected = '/foo/bar/relative' - assert merge_paths(base, 'relative') == expected + base = URIReference( + scheme=None, + authority=None, + path="/foo/bar/bogus", + query=None, + fragment=None, + ) + expected = "/foo/bar/relative" + assert merge_paths(base, "relative") == expected def test_merge_paths_with_base_authority_and_path(): """Demonstrate merging with a base URI with an authority and path.""" - base = URIReference(scheme=None, - authority='authority', - path='/foo/bar/bogus', - query=None, - fragment=None) - expected = '/foo/bar/relative' - assert merge_paths(base, 'relative') == expected + base = URIReference( + scheme=None, + authority="authority", + path="/foo/bar/bogus", + query=None, + fragment=None, + ) + expected = "/foo/bar/relative" + assert merge_paths(base, "relative") == expected def test_merge_paths_without_base_authority_or_path(): """Demonstrate merging with a base URI without an authority or path.""" - base = URIReference(scheme=None, - authority=None, - path=None, - query=None, - fragment=None) - expected = '/relative' - assert merge_paths(base, 'relative') == expected + base = URIReference( + scheme=None, authority=None, path=None, query=None, fragment=None + ) + expected = "/relative" + assert merge_paths(base, "relative") == expected def test_merge_paths_with_base_authority_without_path(): """Demonstrate merging with a base URI without an authority or path.""" - base = URIReference(scheme=None, - authority='authority', - path=None, - query=None, - fragment=None) - expected = '/relative' - assert merge_paths(base, 'relative') == expected + base = URIReference( + scheme=None, + authority="authority", + path=None, + query=None, + fragment=None, + ) + expected = "/relative" + assert merge_paths(base, "relative") == expected diff --git a/tests/test_normalizers.py b/tests/test_normalizers.py index 1fe02b2..1dbd6fa 100644 --- a/tests/test_normalizers.py +++ b/tests/test_normalizers.py @@ -3,42 +3,48 @@ from rfc3986.uri import URIReference from rfc3986.normalizers import ( - normalize_scheme, normalize_percent_characters, - remove_dot_segments, encode_component, normalize_host - ) + normalize_scheme, + normalize_percent_characters, + remove_dot_segments, + encode_component, + normalize_host, +) def test_normalize_scheme(): - assert 'http' == normalize_scheme('htTp') - assert 'http' == normalize_scheme('http') - assert 'http' == normalize_scheme('HTTP') + assert "http" == normalize_scheme("htTp") + assert "http" == normalize_scheme("http") + assert "http" == normalize_scheme("HTTP") def test_normalize_percent_characters(): - expected = '%3Athis_should_be_lowercase%DF%AB%4C' + expected = "%3Athis_should_be_lowercase%DF%AB%4C" assert expected == normalize_percent_characters( - '%3athis_should_be_lowercase%DF%ab%4c') + "%3athis_should_be_lowercase%DF%ab%4c" + ) assert expected == normalize_percent_characters( - '%3Athis_should_be_lowercase%DF%AB%4C') + "%3Athis_should_be_lowercase%DF%AB%4C" + ) assert expected == normalize_percent_characters( - '%3Athis_should_be_lowercase%DF%aB%4C') + "%3Athis_should_be_lowercase%DF%aB%4C" + ) paths = [ # (Input, expected output) - ('/foo/bar/.', '/foo/bar/'), - ('/foo/bar/', '/foo/bar/'), - ('/foo/bar', '/foo/bar'), - ('./foo/bar', 'foo/bar'), - ('/./foo/bar', '/foo/bar'), - ('/foo%20bar/biz%2Abaz', '/foo%20bar/biz%2Abaz'), - ('../foo/bar', 'foo/bar'), - ('/../foo/bar', '/foo/bar'), - ('a/./b/../b/%63/%7Bfoo%7D', 'a/b/%63/%7Bfoo%7D'), - ('//a/./b/../b/%63/%7Bfoo%7D', '//a/b/%63/%7Bfoo%7D'), - ('mid/content=5/../6', 'mid/6'), - ('/a/b/c/./../../g', '/a/g'), - ] + ("/foo/bar/.", "/foo/bar/"), + ("/foo/bar/", "/foo/bar/"), + ("/foo/bar", "/foo/bar"), + ("./foo/bar", "foo/bar"), + ("/./foo/bar", "/foo/bar"), + ("/foo%20bar/biz%2Abaz", "/foo%20bar/biz%2Abaz"), + ("../foo/bar", "foo/bar"), + ("/../foo/bar", "/foo/bar"), + ("a/./b/../b/%63/%7Bfoo%7D", "a/b/%63/%7Bfoo%7D"), + ("//a/./b/../b/%63/%7Bfoo%7D", "//a/b/%63/%7Bfoo%7D"), + ("mid/content=5/../6", "mid/6"), + ("/a/b/c/./../../g", "/a/g"), +] @pytest.fixture(params=paths) @@ -49,8 +55,10 @@ def path_fixture(request): @pytest.fixture(params=paths) def uris(request): to_norm, normalized = request.param - return (URIReference(None, None, to_norm, None, None), - URIReference(None, None, normalized, None, None)) + return ( + URIReference(None, None, to_norm, None, None), + URIReference(None, None, normalized, None, None), + ) def test_remove_dot_segments(path_fixture): @@ -63,57 +71,56 @@ def test_normalized_equality(uris): def test_hostname_normalization(): - assert (URIReference(None, 'EXAMPLE.COM', None, None, None) == - URIReference(None, 'example.com', None, None, None)) + assert URIReference( + None, "EXAMPLE.COM", None, None, None + ) == URIReference(None, "example.com", None, None, None) @pytest.mark.parametrize( - ['authority', 'expected_authority'], + ["authority", "expected_authority"], [ - ('user%2aName@EXAMPLE.COM', 'user%2AName@example.com'), - ('[::1%eth0]', '[::1%25eth0]') - ] + ("user%2aName@EXAMPLE.COM", "user%2AName@example.com"), + ("[::1%eth0]", "[::1%25eth0]"), + ], ) def test_authority_normalization(authority, expected_authority): - uri = URIReference( - None, authority, None, None, None).normalize() + uri = URIReference(None, authority, None, None, None).normalize() assert uri.authority == expected_authority def test_fragment_normalization(): - uri = URIReference( - None, 'example.com', None, None, 'fiz%DF').normalize() - assert uri.fragment == 'fiz%DF' + uri = URIReference(None, "example.com", None, None, "fiz%DF").normalize() + assert uri.fragment == "fiz%DF" @pytest.mark.parametrize( ["component", "encoded_component"], [ - ('/%', '/%25'), - ('/%a', '/%25a'), - ('/%ag', '/%25ag'), - ('/%af', '/%af'), - ('/%20/%', '/%2520/%25'), - ('/%20%25', '/%20%25'), - ('/%21%22%23%ah%12%ff', '/%2521%2522%2523%25ah%2512%25ff'), - ] + ("/%", "/%25"), + ("/%a", "/%25a"), + ("/%ag", "/%25ag"), + ("/%af", "/%af"), + ("/%20/%", "/%2520/%25"), + ("/%20%25", "/%20%25"), + ("/%21%22%23%ah%12%ff", "/%2521%2522%2523%25ah%2512%25ff"), + ], ) def test_detect_percent_encoded_component(component, encoded_component): - assert encode_component(component, 'utf-8') == encoded_component + assert encode_component(component, "utf-8") == encoded_component @pytest.mark.parametrize( ["host", "normalized_host"], [ - ('LOCALHOST', 'localhost'), - ('[::1%eth0]', '[::1%25eth0]'), - ('[::1%25]', '[::1%2525]'), - ('[::1%%25]', '[::1%25%25]'), - ('[::1%25%25]', '[::1%25%25]'), - ('[::Af%Ff]', '[::af%25Ff]'), - ('[::Af%%Ff]', '[::af%25%Ff]'), - ('[::Af%25Ff]', '[::af%25Ff]'), - ] + ("LOCALHOST", "localhost"), + ("[::1%eth0]", "[::1%25eth0]"), + ("[::1%25]", "[::1%2525]"), + ("[::1%%25]", "[::1%25%25]"), + ("[::1%25%25]", "[::1%25%25]"), + ("[::Af%Ff]", "[::af%25Ff]"), + ("[::Af%%Ff]", "[::af%25%Ff]"), + ("[::Af%25Ff]", "[::af%25Ff]"), + ], ) def test_normalize_host(host, normalized_host): assert normalize_host(host) == normalized_host diff --git a/tests/test_parseresult.py b/tests/test_parseresult.py index a1d12cc..6c1e147 100644 --- a/tests/test_parseresult.py +++ b/tests/test_parseresult.py @@ -20,37 +20,47 @@ from . import base -INVALID_PORTS = ['443:80', '443:80:443', 'abcdef', 'port', '43port'] +INVALID_PORTS = ["443:80", "443:80:443", "abcdef", "port", "43port"] -SNOWMAN = b'\xe2\x98\x83' -SNOWMAN_IDNA_HOST = 'http://xn--n3h.com' +SNOWMAN = b"\xe2\x98\x83" +SNOWMAN_IDNA_HOST = "http://xn--n3h.com" -@pytest.mark.parametrize('port', INVALID_PORTS) +@pytest.mark.parametrize("port", INVALID_PORTS) def test_port_parsing(port): with pytest.raises(exceptions.InvalidPort): - rfc3986.urlparse('https://httpbin.org:{0}/get'.format(port)) - - -@pytest.mark.parametrize('parts, unsplit', [ - (('https', None, 'httpbin.org'), u'https://httpbin.org'), - (('https', 'user', 'httpbin.org'), u'https://user@httpbin.org'), - (('https', None, 'httpbin.org', 443, '/get'), - u'https://httpbin.org:443/get'), - (('HTTPS', None, 'HTTPBIN.ORG'), u'https://httpbin.org'), -]) + rfc3986.urlparse("https://httpbin.org:{0}/get".format(port)) + + +@pytest.mark.parametrize( + "parts, unsplit", + [ + (("https", None, "httpbin.org"), u"https://httpbin.org"), + (("https", "user", "httpbin.org"), u"https://user@httpbin.org"), + ( + ("https", None, "httpbin.org", 443, "/get"), + u"https://httpbin.org:443/get", + ), + (("HTTPS", None, "HTTPBIN.ORG"), u"https://httpbin.org"), + ], +) def test_from_parts(parts, unsplit): uri = pr.ParseResult.from_parts(*parts) assert uri.unsplit() == unsplit -@pytest.mark.parametrize('parts, unsplit', [ - (('https', None, 'httpbin.org'), b'https://httpbin.org'), - (('https', 'user', 'httpbin.org'), b'https://user@httpbin.org'), - (('https', None, 'httpbin.org', 443, '/get'), - b'https://httpbin.org:443/get'), - (('HTTPS', None, 'HTTPBIN.ORG'), b'https://httpbin.org'), -]) +@pytest.mark.parametrize( + "parts, unsplit", + [ + (("https", None, "httpbin.org"), b"https://httpbin.org"), + (("https", "user", "httpbin.org"), b"https://user@httpbin.org"), + ( + ("https", None, "httpbin.org", 443, "/get"), + b"https://httpbin.org:443/get", + ), + (("HTTPS", None, "HTTPBIN.ORG"), b"https://httpbin.org"), + ], +) def test_bytes_from_parts(parts, unsplit): uri = pr.ParseResultBytes.from_parts(*parts) assert uri.unsplit() == unsplit @@ -66,10 +76,11 @@ class TestParseResultUnsplits(base.BaseTestUnsplits): def test_normalizes_uris_when_using_from_string(uri_to_normalize): """Verify we always get the same thing out as we expect.""" - result = pr.ParseResult.from_string(uri_to_normalize, - lazy_normalize=False) - assert result.scheme == 'https' - assert result.host == 'example.com' + result = pr.ParseResult.from_string( + uri_to_normalize, lazy_normalize=False + ) + assert result.scheme == "https" + assert result.host == "example.com" class TestStdlibShims: @@ -83,8 +94,8 @@ def test_uri_with_everything(self, uri_with_everything): def test_creates_a_copy_with_a_new_path(uri_with_everything): uri = pr.ParseResult.from_string(uri_with_everything) - new_uri = uri.copy_with(path='/parse/result/tests/are/fun') - assert new_uri.path == '/parse/result/tests/are/fun' + new_uri = uri.copy_with(path="/parse/result/tests/are/fun") + assert new_uri.path == "/parse/result/tests/are/fun" def test_creates_a_copy_with_a_new_port(basic_uri): @@ -109,11 +120,11 @@ def test_parse_result_encodes_itself(uri_with_everything): class TestParseResultBytes: def test_handles_uri_with_everything(self, uri_with_everything): uri = pr.ParseResultBytes.from_string(uri_with_everything) - assert uri.scheme == b'https' - assert uri.path == b'/path/to/resource' - assert uri.query == b'key=value' - assert uri.fragment == b'fragment' - assert uri.userinfo == b'user:pass' + assert uri.scheme == b"https" + assert uri.path == b"/path/to/resource" + assert uri.query == b"key=value" + assert uri.fragment == b"fragment" + assert uri.userinfo == b"user:pass" assert uri.port == 443 assert isinstance(uri.authority, bytes) is True @@ -121,44 +132,44 @@ def test_raises_invalid_authority_for_invalid_uris(self, invalid_uri): with pytest.raises(exceptions.InvalidAuthority): pr.ParseResultBytes.from_string(invalid_uri) - @pytest.mark.parametrize('port', INVALID_PORTS) + @pytest.mark.parametrize("port", INVALID_PORTS) def test_raises_invalid_port_non_strict_parse(self, port): with pytest.raises(exceptions.InvalidPort): pr.ParseResultBytes.from_string( - 'https://httpbin.org:{0}/get'.format(port), - strict=False + "https://httpbin.org:{0}/get".format(port), strict=False ) def test_copy_with_a_new_path(self, uri_with_everything): uri = pr.ParseResultBytes.from_string(uri_with_everything) - new_uri = uri.copy_with(path=b'/parse/result/tests/are/fun') - assert new_uri.path == b'/parse/result/tests/are/fun' + new_uri = uri.copy_with(path=b"/parse/result/tests/are/fun") + assert new_uri.path == b"/parse/result/tests/are/fun" def test_copy_with_a_new_unicode_path(self, uri_with_everything): uri = pr.ParseResultBytes.from_string(uri_with_everything) - pathbytes = b'/parse/result/tests/are/fun' + SNOWMAN - new_uri = uri.copy_with(path=pathbytes.decode('utf-8')) - assert new_uri.path == (b'/parse/result/tests/are/fun' + SNOWMAN) + pathbytes = b"/parse/result/tests/are/fun" + SNOWMAN + new_uri = uri.copy_with(path=pathbytes.decode("utf-8")) + assert new_uri.path == (b"/parse/result/tests/are/fun" + SNOWMAN) def test_unsplit(self): uri = pr.ParseResultBytes.from_string( - b'http://' + SNOWMAN + b'.com/path', - strict=False + b"http://" + SNOWMAN + b".com/path", strict=False ) - idna_encoded = SNOWMAN_IDNA_HOST.encode('utf-8') + b'/path' + idna_encoded = SNOWMAN_IDNA_HOST.encode("utf-8") + b"/path" assert uri.unsplit(use_idna=True) == idna_encoded def test_eager_normalization_from_string(self): uri = pr.ParseResultBytes.from_string( - b'http://' + SNOWMAN + b'.com/path', + b"http://" + SNOWMAN + b".com/path", strict=False, lazy_normalize=False, ) - assert uri.unsplit() == b'http:/path' + assert uri.unsplit() == b"http:/path" def test_eager_normalization_from_parts(self): uri = pr.ParseResultBytes.from_parts( - scheme='http', host=SNOWMAN.decode('utf-8'), path='/path', + scheme="http", + host=SNOWMAN.decode("utf-8"), + path="/path", lazy_normalize=False, ) - assert uri.unsplit() == b'http:/path' + assert uri.unsplit() == b"http:/path" diff --git a/tests/test_unicode_support.py b/tests/test_unicode_support.py index bea2223..4ba3a6e 100644 --- a/tests/test_unicode_support.py +++ b/tests/test_unicode_support.py @@ -7,30 +7,30 @@ from rfc3986 import urlparse -SNOWMAN = b'\xe2\x98\x83' -SNOWMAN_PARAMS = b'http://example.com?utf8=' + SNOWMAN -SNOWMAN_HOST = b'http://' + SNOWMAN + b'.com' -SNOWMAN_IDNA_HOST = 'http://xn--n3h.com' +SNOWMAN = b"\xe2\x98\x83" +SNOWMAN_PARAMS = b"http://example.com?utf8=" + SNOWMAN +SNOWMAN_HOST = b"http://" + SNOWMAN + b".com" +SNOWMAN_IDNA_HOST = "http://xn--n3h.com" def test_unicode_uri(): url_bytestring = SNOWMAN_PARAMS - unicode_url = url_bytestring.decode('utf-8') + unicode_url = url_bytestring.decode("utf-8") uri = uri_reference(unicode_url) assert uri.is_valid() is True - assert uri == 'http://example.com?utf8=%E2%98%83' + assert uri == "http://example.com?utf8=%E2%98%83" def test_unicode_uri_passed_as_bytes(): url_bytestring = SNOWMAN_PARAMS uri = uri_reference(url_bytestring) assert uri.is_valid() is True - assert uri == 'http://example.com?utf8=%E2%98%83' + assert uri == "http://example.com?utf8=%E2%98%83" def test_unicode_authority(): url_bytestring = SNOWMAN_HOST - unicode_url = url_bytestring.decode('utf-8') + unicode_url = url_bytestring.decode("utf-8") uri = uri_reference(unicode_url) assert uri.is_valid() is False assert uri == unicode_url @@ -38,31 +38,31 @@ def test_unicode_authority(): def test_urlparse_a_unicode_hostname(): url_bytestring = SNOWMAN_HOST - unicode_url = url_bytestring.decode('utf-8') + unicode_url = url_bytestring.decode("utf-8") parsed = urlparse(url_bytestring) assert parsed.host == unicode_url[7:] def test_urlparse_a_unicode_hostname_with_auth(): - url = b'http://userinfo@' + SNOWMAN + b'.com' + url = b"http://userinfo@" + SNOWMAN + b".com" parsed = urlparse(url) - assert parsed.userinfo == 'userinfo' + assert parsed.userinfo == "userinfo" def test_urlparse_idna_encoding_with_geturl(): """https://github.com/python-hyper/rfc3986/issues/57""" parsed = urlparse("https://i❤.ws") - encoded = parsed.encode('idna') - assert encoded.encoding == 'idna' - assert encoded.geturl() == b'https://xn--i-7iq.ws' + encoded = parsed.encode("idna") + assert encoded.encoding == "idna" + assert encoded.geturl() == b"https://xn--i-7iq.ws" def test_urlparse_an_invalid_authority_parses_port(): - url = 'http://foo:b@r@[::1]:80/get' + url = "http://foo:b@r@[::1]:80/get" parsed = urlparse(url) assert parsed.port == 80 - assert parsed.userinfo == 'foo:b@r' - assert parsed.hostname == '[::1]' + assert parsed.userinfo == "foo:b@r" + assert parsed.hostname == "[::1]" def test_unsplit_idna_a_unicode_hostname(): diff --git a/tests/test_uri.py b/tests/test_uri.py index 2f912bd..d9ffb8b 100644 --- a/tests/test_uri.py +++ b/tests/test_uri.py @@ -10,11 +10,12 @@ @pytest.fixture def scheme_and_path_uri(): - return 'mailto:user@example.com' + return "mailto:user@example.com" class TestURIReferenceParsesURIs(base.BaseTestParsesURIs): """Tests for URIReference handling of URIs.""" + test_class = URIReference def test_authority_info_raises_InvalidAuthority(self, invalid_uri): @@ -35,10 +36,10 @@ def test_handles_absolute_path_uri(self, absolute_path_uri): uri = URIReference.from_string(absolute_path_uri) assert uri.path == absolute_path_uri assert uri.authority_info() == { - 'userinfo': None, - 'host': None, - 'port': None, - } + "userinfo": None, + "host": None, + "port": None, + } def test_scheme_and_path_uri_is_valid(self, scheme_and_path_uri): uri = self.test_class.from_string(scheme_and_path_uri) @@ -47,8 +48,8 @@ def test_scheme_and_path_uri_is_valid(self, scheme_and_path_uri): def test_handles_scheme_and_path_uri(self, scheme_and_path_uri): """Test that self.test_class can handle a `scheme:path` URI.""" uri = self.test_class.from_string(scheme_and_path_uri) - assert uri.path == 'user@example.com' - assert uri.scheme == 'mailto' + assert uri.path == "user@example.com" + assert uri.scheme == "mailto" assert uri.query is None assert uri.host is None assert uri.port is None @@ -57,10 +58,10 @@ def test_handles_scheme_and_path_uri(self, scheme_and_path_uri): def test_parses_ipv6_to_path(self): """Verify that we don't parse [ as a scheme.""" - uri = self.test_class.from_string('[::1]') + uri = self.test_class.from_string("[::1]") assert uri.scheme is None assert uri.authority is None - assert uri.path == '[::1]' + assert uri.path == "[::1]" class TestURIValidation: @@ -85,8 +86,9 @@ def test_uri_with_everything_requiring_query(self, uri_with_everything): uri = URIReference.from_string(uri_with_everything) assert uri.is_valid(require_query=True) is True - def test_uri_with_everything_requiring_fragment(self, - uri_with_everything): + def test_uri_with_everything_requiring_fragment( + self, uri_with_everything + ): uri = URIReference.from_string(uri_with_everything) assert uri.is_valid(require_fragment=True) is True @@ -94,8 +96,9 @@ def test_basic_uri_with_port_is_valid(self, basic_uri_with_port): uri = URIReference.from_string(basic_uri_with_port) assert uri.is_valid() is True - def test_uri_with_port_and_userinfo_is_valid(self, - uri_with_port_and_userinfo): + def test_uri_with_port_and_userinfo_is_valid( + self, uri_with_port_and_userinfo + ): uri = URIReference.from_string(uri_with_port_and_userinfo) assert uri.is_valid() is True @@ -129,19 +132,19 @@ def test_invalid_uri_is_not_valid(self, invalid_uri): assert uri.is_valid() is False def test_invalid_scheme(self): - uri = URIReference('123', None, None, None, None) + uri = URIReference("123", None, None, None, None) assert uri.is_valid() is False def test_invalid_path(self): - uri = URIReference(None, None, 'foo#bar', None, None) + uri = URIReference(None, None, "foo#bar", None, None) assert uri.is_valid() is False def test_invalid_query_component(self): - uri = URIReference(None, None, None, 'foo#bar', None) + uri = URIReference(None, None, None, "foo#bar", None) assert uri.is_valid() is False def test_invalid_fragment_component(self): - uri = URIReference(None, None, None, None, 'foo#bar') + uri = URIReference(None, None, None, None, "foo#bar") assert uri.is_valid() is False @@ -286,8 +289,9 @@ def test_with_basic_and_relative_uris(self, basic_uri, relative_uri): assert T.host == R.host assert T.path == R.path - def test_with_basic_and_absolute_path_uris(self, basic_uri, - absolute_path_uri): + def test_with_basic_and_absolute_path_uris( + self, basic_uri, absolute_path_uri + ): R = URIReference.from_string(absolute_path_uri) B = URIReference.from_string(basic_uri).normalize() T = R.resolve_with(B) @@ -296,25 +300,25 @@ def test_with_basic_and_absolute_path_uris(self, basic_uri, assert T.path == R.path def test_with_basic_uri_and_relative_path(self, basic_uri): - R = URIReference.from_string('foo/bar/bogus') + R = URIReference.from_string("foo/bar/bogus") B = URIReference.from_string(basic_uri).normalize() T = R.resolve_with(B) assert T.scheme == B.scheme assert T.host == B.host - assert T.path == '/' + R.path + assert T.path == "/" + R.path def test_basic_uri_with_path_and_relative_path(self, basic_uri_with_path): - R = URIReference.from_string('foo/bar/bogus') + R = URIReference.from_string("foo/bar/bogus") B = URIReference.from_string(basic_uri_with_path).normalize() T = R.resolve_with(B) assert T.scheme == B.scheme assert T.host == B.host - index = B.path.rfind('/') - assert T.path == B.path[:index] + '/' + R.path + index = B.path.rfind("/") + assert T.path == B.path[:index] + "/" + R.path def test_uri_with_everything_raises_exception(self, uri_with_everything): - R = URIReference.from_string('foo/bar/bogus') + R = URIReference.from_string("foo/bar/bogus") B = URIReference.from_string(uri_with_everything) with pytest.raises(ResolutionError): R.resolve_with(B) @@ -326,30 +330,30 @@ def test_basic_uri_resolves_itself(self, basic_uri): assert T == B def test_differing_schemes(self, basic_uri): - R = URIReference.from_string('https://example.com/path') + R = URIReference.from_string("https://example.com/path") B = URIReference.from_string(basic_uri) T = R.resolve_with(B) assert T.scheme == R.scheme def test_resolve_pathless_fragment(self, basic_uri): - R = URIReference.from_string('#fragment') + R = URIReference.from_string("#fragment") B = URIReference.from_string(basic_uri) T = R.resolve_with(B) assert T.path is None - assert T.fragment == 'fragment' + assert T.fragment == "fragment" def test_resolve_pathless_query(self, basic_uri): - R = URIReference.from_string('?query') + R = URIReference.from_string("?query") B = URIReference.from_string(basic_uri) T = R.resolve_with(B) assert T.path is None - assert T.query == 'query' + assert T.query == "query" def test_empty_querystrings_persist(): - url = 'https://httpbin.org/get?' + url = "https://httpbin.org/get?" ref = URIReference.from_string(url) - assert ref.query == '' + assert ref.query == "" assert ref.unsplit() == url diff --git a/tests/test_validators.py b/tests/test_validators.py index cd88332..d052229 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -22,40 +22,40 @@ def test_defaults(): def test_allowing_schemes(): """Verify the ability to select schemes to be allowed.""" - validator = validators.Validator().allow_schemes('http', 'https') + validator = validators.Validator().allow_schemes("http", "https") - assert 'http' in validator.allowed_schemes - assert 'https' in validator.allowed_schemes + assert "http" in validator.allowed_schemes + assert "https" in validator.allowed_schemes def test_allowing_hosts(): """Verify the ability to select hosts to be allowed.""" validator = validators.Validator().allow_hosts( - 'pypi.python.org', 'pypi.org', + "pypi.python.org", "pypi.org", ) - assert 'pypi.python.org' in validator.allowed_hosts - assert 'pypi.org' in validator.allowed_hosts + assert "pypi.python.org" in validator.allowed_hosts + assert "pypi.org" in validator.allowed_hosts def test_allowing_ports(): """Verify the ability select ports to be allowed.""" - validator = validators.Validator().allow_ports('80', '100') + validator = validators.Validator().allow_ports("80", "100") - assert '80' in validator.allowed_ports - assert '100' in validator.allowed_ports + assert "80" in validator.allowed_ports + assert "100" in validator.allowed_ports def test_requiring_invalid_component(): """Verify that we validate required component names.""" with pytest.raises(ValueError): - validators.Validator().require_presence_of('frob') + validators.Validator().require_presence_of("frob") def test_checking_validity_of_component(): """Verify that we validate components we're validating.""" with pytest.raises(ValueError): - validators.Validator().check_validity_of('frob') + validators.Validator().check_validity_of("frob") def test_use_of_password(): @@ -70,13 +70,18 @@ def test_use_of_password(): assert validator.allow_password is True -@pytest.mark.parametrize('uri', [ - rfc3986.uri_reference('https://user:password@github.com'), - rfc3986.uri_reference('https://user:password@github.com/path'), - rfc3986.uri_reference('https://user:password@github.com/path?query'), - rfc3986.uri_reference('https://user:password@github.com/path?query#frag'), - rfc3986.uri_reference('//user:password@github.com'), -]) +@pytest.mark.parametrize( + "uri", + [ + rfc3986.uri_reference("https://user:password@github.com"), + rfc3986.uri_reference("https://user:password@github.com/path"), + rfc3986.uri_reference("https://user:password@github.com/path?query"), + rfc3986.uri_reference( + "https://user:password@github.com/path?query#frag" + ), + rfc3986.uri_reference("//user:password@github.com"), + ], +) def test_forbidden_passwords(uri): """Verify that passwords are disallowed.""" validator = validators.Validator().forbid_use_of_password() @@ -84,87 +89,102 @@ def test_forbidden_passwords(uri): validator.validate(uri) -@pytest.mark.parametrize('uri', [ - rfc3986.uri_reference('https://user@github.com'), - rfc3986.uri_reference('https://user@github.com/path'), - rfc3986.uri_reference('https://user@github.com/path?query'), - rfc3986.uri_reference('https://user@github.com/path?query#frag'), - rfc3986.uri_reference('//user@github.com'), - rfc3986.uri_reference('//github.com'), - rfc3986.uri_reference('https://github.com'), -]) +@pytest.mark.parametrize( + "uri", + [ + rfc3986.uri_reference("https://user@github.com"), + rfc3986.uri_reference("https://user@github.com/path"), + rfc3986.uri_reference("https://user@github.com/path?query"), + rfc3986.uri_reference("https://user@github.com/path?query#frag"), + rfc3986.uri_reference("//user@github.com"), + rfc3986.uri_reference("//github.com"), + rfc3986.uri_reference("https://github.com"), + ], +) def test_passwordless_uris_pass_validation(uri): """Verify password-less URLs validate properly.""" validator = validators.Validator().forbid_use_of_password() validator.validate(uri) -@pytest.mark.parametrize('uri', [ - rfc3986.uri_reference('https://'), - rfc3986.uri_reference('/path/to/resource'), -]) +@pytest.mark.parametrize( + "uri", + [ + rfc3986.uri_reference("https://"), + rfc3986.uri_reference("/path/to/resource"), + ], +) def test_missing_host_component(uri): """Verify that missing host components cause errors.""" validators.Validator().validate(uri) - validator = validators.Validator().require_presence_of('host') + validator = validators.Validator().require_presence_of("host") with pytest.raises(exceptions.MissingComponentError): validator.validate(uri) -@pytest.mark.parametrize('uri', [ - rfc3986.uri_reference('https://'), - rfc3986.uri_reference('//google.com'), - rfc3986.uri_reference('//google.com?query=value'), - rfc3986.uri_reference('//google.com#fragment'), - rfc3986.uri_reference('https://google.com'), - rfc3986.uri_reference('https://google.com#fragment'), - rfc3986.uri_reference('https://google.com?query=value'), -]) +@pytest.mark.parametrize( + "uri", + [ + rfc3986.uri_reference("https://"), + rfc3986.uri_reference("//google.com"), + rfc3986.uri_reference("//google.com?query=value"), + rfc3986.uri_reference("//google.com#fragment"), + rfc3986.uri_reference("https://google.com"), + rfc3986.uri_reference("https://google.com#fragment"), + rfc3986.uri_reference("https://google.com?query=value"), + ], +) def test_missing_path_component(uri): """Verify that missing path components cause errors.""" - validator = validators.Validator().require_presence_of('path') + validator = validators.Validator().require_presence_of("path") with pytest.raises(exceptions.MissingComponentError): validator.validate(uri) -@pytest.mark.parametrize('uri', [ - rfc3986.uri_reference('//google.com'), - rfc3986.uri_reference('//google.com?query=value'), - rfc3986.uri_reference('//google.com#fragment'), -]) +@pytest.mark.parametrize( + "uri", + [ + rfc3986.uri_reference("//google.com"), + rfc3986.uri_reference("//google.com?query=value"), + rfc3986.uri_reference("//google.com#fragment"), + ], +) def test_multiple_missing_components(uri): """Verify that multiple missing components are caught.""" - validator = validators.Validator().require_presence_of('scheme', 'path') + validator = validators.Validator().require_presence_of("scheme", "path") with pytest.raises(exceptions.MissingComponentError) as captured_exc: validator.validate(uri) exception = captured_exc.value assert 2 == len(exception.args[-1]) -@pytest.mark.parametrize('uri', [ - rfc3986.uri_reference('smtp://'), - rfc3986.uri_reference('telnet://'), -]) +@pytest.mark.parametrize( + "uri", + [rfc3986.uri_reference("smtp://"), rfc3986.uri_reference("telnet://"),], +) def test_ensure_uri_has_a_scheme(uri): """Verify validation with allowed schemes.""" - validator = validators.Validator().allow_schemes('https', 'http') + validator = validators.Validator().allow_schemes("https", "http") with pytest.raises(exceptions.UnpermittedComponentError): validator.validate(uri) -@pytest.mark.parametrize('uri, failed_component', [ - (rfc3986.uri_reference('git://github.com'), 'scheme'), - (rfc3986.uri_reference('http://github.com'), 'scheme'), - (rfc3986.uri_reference('ssh://gitlab.com'), 'host'), - (rfc3986.uri_reference('https://gitlab.com'), 'host'), -]) +@pytest.mark.parametrize( + "uri, failed_component", + [ + (rfc3986.uri_reference("git://github.com"), "scheme"), + (rfc3986.uri_reference("http://github.com"), "scheme"), + (rfc3986.uri_reference("ssh://gitlab.com"), "host"), + (rfc3986.uri_reference("https://gitlab.com"), "host"), + ], +) def test_allowed_hosts_and_schemes(uri, failed_component): """Verify each of these fails.""" - validator = validators.Validator().allow_schemes( - 'https', 'ssh', - ).allow_hosts( - 'github.com', 'git.openstack.org', + validator = ( + validators.Validator() + .allow_schemes("https", "ssh",) + .allow_hosts("github.com", "git.openstack.org",) ) with pytest.raises(exceptions.UnpermittedComponentError) as caught_exc: validator.validate(uri) @@ -173,70 +193,75 @@ def test_allowed_hosts_and_schemes(uri, failed_component): assert exc.component_name == failed_component -@pytest.mark.parametrize('uri', [ - rfc3986.uri_reference('https://github.com/sigmavirus24'), - rfc3986.uri_reference('ssh://github.com/sigmavirus24'), - rfc3986.uri_reference('ssh://ssh@github.com:22/sigmavirus24'), - rfc3986.uri_reference('https://github.com:443/sigmavirus24'), - rfc3986.uri_reference('https://gitlab.com/sigmavirus24'), - rfc3986.uri_reference('ssh://gitlab.com/sigmavirus24'), - rfc3986.uri_reference('ssh://ssh@gitlab.com:22/sigmavirus24'), - rfc3986.uri_reference('https://gitlab.com:443/sigmavirus24'), - rfc3986.uri_reference('https://bitbucket.org/sigmavirus24'), - rfc3986.uri_reference('ssh://bitbucket.org/sigmavirus24'), - rfc3986.uri_reference('ssh://ssh@bitbucket.org:22/sigmavirus24'), - rfc3986.uri_reference('https://bitbucket.org:443/sigmavirus24'), - rfc3986.uri_reference('https://git.openstack.org/sigmavirus24'), - rfc3986.uri_reference('ssh://git.openstack.org/sigmavirus24'), - rfc3986.uri_reference('ssh://ssh@git.openstack.org:22/sigmavirus24'), - rfc3986.uri_reference('https://git.openstack.org:443/sigmavirus24'), - rfc3986.uri_reference( - 'ssh://ssh@git.openstack.org:22/sigmavirus24?foo=bar#fragment' - ), - rfc3986.uri_reference( - 'ssh://git.openstack.org:22/sigmavirus24?foo=bar#fragment' - ), - rfc3986.uri_reference('ssh://git.openstack.org:22/?foo=bar#fragment'), - rfc3986.uri_reference('ssh://git.openstack.org:22/sigmavirus24#fragment'), - rfc3986.uri_reference('ssh://git.openstack.org:22/#fragment'), - rfc3986.uri_reference('ssh://git.openstack.org:22/'), - rfc3986.uri_reference('ssh://ssh@git.openstack.org:22/?foo=bar#fragment'), - rfc3986.uri_reference( - 'ssh://ssh@git.openstack.org:22/sigmavirus24#fragment' - ), - rfc3986.uri_reference('ssh://ssh@git.openstack.org:22/#fragment'), - rfc3986.uri_reference('ssh://ssh@git.openstack.org:22/'), -]) +@pytest.mark.parametrize( + "uri", + [ + rfc3986.uri_reference("https://github.com/sigmavirus24"), + rfc3986.uri_reference("ssh://github.com/sigmavirus24"), + rfc3986.uri_reference("ssh://ssh@github.com:22/sigmavirus24"), + rfc3986.uri_reference("https://github.com:443/sigmavirus24"), + rfc3986.uri_reference("https://gitlab.com/sigmavirus24"), + rfc3986.uri_reference("ssh://gitlab.com/sigmavirus24"), + rfc3986.uri_reference("ssh://ssh@gitlab.com:22/sigmavirus24"), + rfc3986.uri_reference("https://gitlab.com:443/sigmavirus24"), + rfc3986.uri_reference("https://bitbucket.org/sigmavirus24"), + rfc3986.uri_reference("ssh://bitbucket.org/sigmavirus24"), + rfc3986.uri_reference("ssh://ssh@bitbucket.org:22/sigmavirus24"), + rfc3986.uri_reference("https://bitbucket.org:443/sigmavirus24"), + rfc3986.uri_reference("https://git.openstack.org/sigmavirus24"), + rfc3986.uri_reference("ssh://git.openstack.org/sigmavirus24"), + rfc3986.uri_reference("ssh://ssh@git.openstack.org:22/sigmavirus24"), + rfc3986.uri_reference("https://git.openstack.org:443/sigmavirus24"), + rfc3986.uri_reference( + "ssh://ssh@git.openstack.org:22/sigmavirus24?foo=bar#fragment" + ), + rfc3986.uri_reference( + "ssh://git.openstack.org:22/sigmavirus24?foo=bar#fragment" + ), + rfc3986.uri_reference("ssh://git.openstack.org:22/?foo=bar#fragment"), + rfc3986.uri_reference( + "ssh://git.openstack.org:22/sigmavirus24#fragment" + ), + rfc3986.uri_reference("ssh://git.openstack.org:22/#fragment"), + rfc3986.uri_reference("ssh://git.openstack.org:22/"), + rfc3986.uri_reference( + "ssh://ssh@git.openstack.org:22/?foo=bar#fragment" + ), + rfc3986.uri_reference( + "ssh://ssh@git.openstack.org:22/sigmavirus24#fragment" + ), + rfc3986.uri_reference("ssh://ssh@git.openstack.org:22/#fragment"), + rfc3986.uri_reference("ssh://ssh@git.openstack.org:22/"), + ], +) def test_successful_complex_validation(uri): """Verify we do not raise ValidationErrors for good URIs.""" - validators.Validator().allow_schemes( - 'https', 'ssh', - ).allow_hosts( - 'github.com', 'bitbucket.org', 'gitlab.com', 'git.openstack.org', - ).allow_ports( - '22', '443', - ).require_presence_of( - 'scheme', 'host', 'path', + validators.Validator().allow_schemes("https", "ssh",).allow_hosts( + "github.com", "bitbucket.org", "gitlab.com", "git.openstack.org", + ).allow_ports("22", "443",).require_presence_of( + "scheme", "host", "path", ).check_validity_of( - 'scheme', 'userinfo', 'host', 'port', 'path', 'query', 'fragment', - ).validate(uri) + "scheme", "userinfo", "host", "port", "path", "query", "fragment", + ).validate( + uri + ) def test_invalid_uri_generates_error(invalid_uri): """Verify we catch invalid URIs.""" uri = rfc3986.uri_reference(invalid_uri) with pytest.raises(exceptions.InvalidComponentsError): - validators.Validator().check_validity_of('host').validate(uri) + validators.Validator().check_validity_of("host").validate(uri) def test_invalid_uri_with_invalid_path(invalid_uri): """Verify we catch multiple invalid components.""" uri = rfc3986.uri_reference(invalid_uri) - uri = uri.copy_with(path='#foobar') + uri = uri.copy_with(path="#foobar") with pytest.raises(exceptions.InvalidComponentsError): - validators.Validator().check_validity_of( - 'host', 'path', - ).validate(uri) + validators.Validator().check_validity_of("host", "path",).validate( + uri + ) def test_validating_rfc_4007_ipv6_zone_ids(): @@ -245,13 +270,9 @@ def test_validating_rfc_4007_ipv6_zone_ids(): """ uri = rfc3986.uri_reference("http://[::1%eth0]") with pytest.raises(exceptions.InvalidComponentsError): - validators.Validator().check_validity_of( - 'host' - ).validate(uri) + validators.Validator().check_validity_of("host").validate(uri) uri = uri.normalize() - assert uri.host == '[::1%25eth0]' + assert uri.host == "[::1%25eth0]" - validators.Validator().check_validity_of( - 'host' - ).validate(uri) + validators.Validator().check_validity_of("host").validate(uri) diff --git a/tox.ini b/tox.ini index fc73244..b8195e4 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27,py33,py34,py35,py36,pypy,flake8 +envlist = py27,py33,py34,py35,py36,py37,py38,pypy,lint [testenv] pip_pre = False @@ -13,8 +13,19 @@ commands = deps = {[testenv]deps} commands = py.test {posargs} +[testenv:lint] +basepython = python3 +skip_install = true +deps = + {[testenv:flake8]deps} + black +commands = + black -l 78 {env:BLACK_ARGS:} -t py27 --safe src/rfc3986 tests/ + {[testenv:flake8]commands} + [testenv:flake8] basepython = python3 +skip_install = true deps = flake8 flake8-docstrings @@ -39,6 +50,7 @@ commands = twine upload {posargs:--skip-existing dist/*} [testenv:docs] +basepython = python3 deps = -rdocs/source/requirements.txt commands = @@ -56,6 +68,7 @@ addopts = -q norecursedirs = *.egg .git .* _* [flake8] +extend-ignore = D203, W503, E203 exclude = .tox, .git,