Skip to content

Commit

Permalink
Merge pull request #118 from Sachaa-Thanasius/feature/just-annotations
Browse files Browse the repository at this point in the history
Add type annotations
  • Loading branch information
sigmavirus24 authored Jul 6, 2024
2 parents b8ef055 + be1a4e8 commit 75e77ba
Show file tree
Hide file tree
Showing 27 changed files with 627 additions and 248 deletions.
2 changes: 2 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ exclude_lines =
.* # Python \d.*
.* # nocov: Python \d.*
.* # pragma: no cover.*
^\s*(?:el)?if t\.TYPE_CHECKING:$
^ +\.\.\.$
fail_under = 100
4 changes: 4 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ jobs:
- os: windows-latest
python: '3.12'
toxenv: py
# typing
- os: ubuntu-latest
python: '3.8'
toxenv: typing
# misc
- os: ubuntu-latest
python: '3.12'
Expand Down
19 changes: 8 additions & 11 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,34 +1,31 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
rev: v4.6.0
hooks:
- id: check-yaml
- id: debug-statements
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/asottile/reorder-python-imports
rev: v3.12.0
- repo: https://github.com/pycqa/isort
rev: 5.13.2
hooks:
- id: reorder-python-imports
args: [--application-directories, '.:src', --py37-plus]
- id: isort
- repo: https://github.com/psf/black
rev: 23.12.1
rev: 24.4.2
hooks:
- id: black
args: [--line-length=79]
- repo: https://github.com/asottile/pyupgrade
rev: v3.15.0
rev: v3.16.0
hooks:
- id: pyupgrade
args: [--py37-plus]
args: [--py38-plus]
- repo: https://github.com/pycqa/flake8
rev: 7.0.0
rev: 7.1.0
hooks:
- id: flake8
exclude: ^(tests/|docs/|setup.py)
additional_dependencies:
- flake8-docstrings
- flake8-import-order
- repo: https://github.com/asottile/setup-cfg-fmt
rev: v2.5.0
hooks:
Expand Down
1 change: 0 additions & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
# sys.path.insert(0, os.path.abspath('.'))
import rfc3986


# -- General configuration ------------------------------------------------

# If your documentation needs a minimal Sphinx version, state it here.
Expand Down
19 changes: 19 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[tool.black]
line-length = 79
target-version = ["py38"]

[tool.isort]
profile = "black"
line_length = 79
force_single_line = true

[tool.pyright]
include = ["src/rfc3986"]
ignore = ["tests"]
pythonVersion = "3.8"
typeCheckingMode = "strict"

reportPrivateUsage = "none"
reportImportCycles = "warning"
reportPropertyTypeMismatch = "warning"
reportUnnecessaryTypeIgnoreComment = "warning"
4 changes: 2 additions & 2 deletions src/rfc3986/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@
:copyright: (c) 2014 Rackspace
:license: Apache v2.0, see LICENSE for details
"""
from .api import iri_reference
from .api import IRIReference
from .api import URIReference
from .api import iri_reference
from .api import is_valid_uri
from .api import normalize_uri
from .api import uri_reference
from .api import URIReference
from .api import urlparse
from .parseresult import ParseResult

Expand Down
75 changes: 50 additions & 25 deletions src/rfc3986/_mixin.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,36 @@
"""Module containing the implementation of the URIMixin class."""

import typing as t
import warnings

from . import exceptions as exc
from . import misc
from . import normalizers
from . import uri
from . import validators
from ._typing_compat import Self as _Self


class _AuthorityInfo(t.TypedDict):
"""A typed dict for the authority info triple: userinfo, host, and port."""

userinfo: t.Optional[str]
host: t.Optional[str]
port: t.Optional[str]


class URIMixin:
"""Mixin with all shared methods for URIs and IRIs."""

__hash__ = tuple.__hash__
if t.TYPE_CHECKING:
scheme: t.Optional[str]
authority: t.Optional[str]
path: t.Optional[str]
query: t.Optional[str]
fragment: t.Optional[str]
encoding: str

def authority_info(self):
def authority_info(self) -> _AuthorityInfo:
"""Return a dictionary with the ``userinfo``, ``host``, and ``port``.
If the authority is not valid, it will raise a
Expand Down Expand Up @@ -53,11 +71,11 @@ def authority_info(self):

return matches

def _match_subauthority(self):
def _match_subauthority(self) -> t.Optional[t.Match[str]]:
return misc.SUBAUTHORITY_MATCHER.match(self.authority)

@property
def _validator(self):
def _validator(self) -> validators.Validator:
v = getattr(self, "_cached_validator", None)
if v is not None:
return v
Expand All @@ -67,7 +85,7 @@ def _validator(self):
return self._cached_validator

@property
def host(self):
def host(self) -> t.Optional[str]:
"""If present, a string representing the host."""
try:
authority = self.authority_info()
Expand All @@ -76,7 +94,7 @@ def host(self):
return authority["host"]

@property
def port(self):
def port(self) -> t.Optional[str]:
"""If present, the port extracted from the authority."""
try:
authority = self.authority_info()
Expand All @@ -85,15 +103,15 @@ def port(self):
return authority["port"]

@property
def userinfo(self):
def userinfo(self) -> t.Optional[str]:
"""If present, the userinfo extracted from the authority."""
try:
authority = self.authority_info()
except exc.InvalidAuthority:
return None
return authority["userinfo"]

def is_absolute(self):
def is_absolute(self) -> bool:
"""Determine if this URI Reference is an absolute URI.
See http://tools.ietf.org/html/rfc3986#section-4.3 for explanation.
Expand All @@ -103,7 +121,7 @@ def is_absolute(self):
"""
return bool(misc.ABSOLUTE_URI_MATCHER.match(self.unsplit()))

def is_valid(self, **kwargs):
def is_valid(self, **kwargs: bool) -> bool:
"""Determine if the URI is valid.
.. deprecated:: 1.1.0
Expand Down Expand Up @@ -137,7 +155,7 @@ def is_valid(self, **kwargs):
]
return all(v(r) for v, r in validators)

def authority_is_valid(self, require=False):
def authority_is_valid(self, require: bool = False) -> bool:
"""Determine if the authority component is valid.
.. deprecated:: 1.1.0
Expand Down Expand Up @@ -167,7 +185,7 @@ def authority_is_valid(self, require=False):
require=require,
)

def scheme_is_valid(self, require=False):
def scheme_is_valid(self, require: bool = False) -> bool:
"""Determine if the scheme component is valid.
.. deprecated:: 1.1.0
Expand All @@ -186,7 +204,7 @@ def scheme_is_valid(self, require=False):
)
return validators.scheme_is_valid(self.scheme, require)

def path_is_valid(self, require=False):
def path_is_valid(self, require: bool = False) -> bool:
"""Determine if the path component is valid.
.. deprecated:: 1.1.0
Expand All @@ -205,7 +223,7 @@ def path_is_valid(self, require=False):
)
return validators.path_is_valid(self.path, require)

def query_is_valid(self, require=False):
def query_is_valid(self, require: bool = False) -> bool:
"""Determine if the query component is valid.
.. deprecated:: 1.1.0
Expand All @@ -224,7 +242,7 @@ def query_is_valid(self, require=False):
)
return validators.query_is_valid(self.query, require)

def fragment_is_valid(self, require=False):
def fragment_is_valid(self, require: bool = False) -> bool:
"""Determine if the fragment component is valid.
.. deprecated:: 1.1.0
Expand All @@ -243,7 +261,7 @@ def fragment_is_valid(self, require=False):
)
return validators.fragment_is_valid(self.fragment, require)

def normalized_equality(self, other_ref):
def normalized_equality(self, other_ref: "uri.URIReference") -> bool:
"""Compare this URIReference to another URIReference.
:param URIReference other_ref: (required), The reference with which
Expand All @@ -253,7 +271,11 @@ def normalized_equality(self, other_ref):
"""
return tuple(self.normalize()) == tuple(other_ref.normalize())

def resolve_with(self, base_uri, strict=False):
def resolve_with( # noqa: C901
self,
base_uri: t.Union[str, "uri.URIReference"],
strict: bool = False,
) -> _Self:
"""Use an absolute URI Reference to resolve this relative reference.
Assuming this is a relative reference that you would like to resolve,
Expand All @@ -272,6 +294,9 @@ def resolve_with(self, base_uri, strict=False):
if not isinstance(base_uri, URIMixin):
base_uri = type(self).from_string(base_uri)

if t.TYPE_CHECKING:
base_uri = t.cast(uri.URIReference, base_uri)

try:
self._validator.validate(base_uri)
except exc.ValidationError:
Expand Down Expand Up @@ -325,14 +350,14 @@ def resolve_with(self, base_uri, strict=False):
)
return target

def unsplit(self):
def unsplit(self) -> str:
"""Create a URI string from the components.
:returns: The URI Reference reconstituted as a string.
:rtype: str
"""
# See http://tools.ietf.org/html/rfc3986#section-5.3
result_list = []
result_list: list[str] = []
if self.scheme:
result_list.extend([self.scheme, ":"])
if self.authority:
Expand All @@ -347,12 +372,12 @@ def unsplit(self):

def copy_with(
self,
scheme=misc.UseExisting,
authority=misc.UseExisting,
path=misc.UseExisting,
query=misc.UseExisting,
fragment=misc.UseExisting,
):
scheme: t.Optional[str] = misc.UseExisting,
authority: t.Optional[str] = misc.UseExisting,
path: t.Optional[str] = misc.UseExisting,
query: t.Optional[str] = misc.UseExisting,
fragment: t.Optional[str] = misc.UseExisting,
) -> _Self:
"""Create a copy of this reference with the new components.
:param str scheme:
Expand Down Expand Up @@ -380,6 +405,6 @@ def copy_with(
for key, value in list(attributes.items()):
if value is misc.UseExisting:
del attributes[key]
uri = self._replace(**attributes)
uri: _Self = self._replace(**attributes)
uri.encoding = self.encoding
return uri
19 changes: 19 additions & 0 deletions src/rfc3986/_typing_compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import sys
import typing as t

__all__ = ("Self",)

if sys.version_info >= (3, 11): # pragma: no cover
from typing import Self
elif t.TYPE_CHECKING:
from typing_extensions import Self
else: # pragma: no cover

class _PlaceholderMeta(type):
# This is meant to make it easier to debug the presence of placeholder
# classes.
def __repr__(self):
return f"placeholder for typing.{self.__name__}"

class Self(metaclass=_PlaceholderMeta):
"""Placeholder for "typing.Self"."""
Loading

0 comments on commit 75e77ba

Please sign in to comment.