Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add type annotations #118

Merged
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2dc1f0c
Add a few annotations.
Sachaa-Thanasius Jun 14, 2024
ae63851
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 14, 2024
c28dc0a
Add annotations to api.py.
Sachaa-Thanasius Jun 14, 2024
602934c
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 14, 2024
be28283
Preliminary, but added minimalistic typing check to tox and CI, as we…
Sachaa-Thanasius Jun 14, 2024
719da58
Merge branch 'feature/just-annotations' of github.com:Sachaa-Thanasiu…
Sachaa-Thanasius Jun 14, 2024
9df0355
Add parameter annotations to builder.py.
Sachaa-Thanasius Jun 15, 2024
6c3d109
Add type annotations to `compat.to_str()` and `compat.to_bytes()`.
Sachaa-Thanasius Jun 15, 2024
a7622de
Finish annotating `query_items` parameters in builder.py.
Sachaa-Thanasius Jun 15, 2024
8cff806
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 15, 2024
57f5621
Account for current flake8 errors with noqa.
Sachaa-Thanasius Jun 15, 2024
f305405
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 15, 2024
c7ab1db
Annotate `misc.get_path()` while breaking an import cycle.
Sachaa-Thanasius Jun 15, 2024
c3b49f2
Add a comment explaining the reason `_QueryType` in builder.py is mod…
Sachaa-Thanasius Jun 15, 2024
dd96a34
Fix compat.py and api.py parameter annotations to accept bytearray as…
Sachaa-Thanasius Jun 15, 2024
d99f274
Started annotating IRIReference and URIReference.
Sachaa-Thanasius Jun 15, 2024
f4d150f
Fix: Removing the bytearray hints. Sticking to bytes and str is simpl…
Sachaa-Thanasius Jun 15, 2024
0179352
Partially annotated parseresult.py and _mixin.py.
Sachaa-Thanasius Jun 16, 2024
3969c1d
Finish annotating return types in builder.py.
Sachaa-Thanasius Jun 16, 2024
9e812f3
Made minor adjustments to a few annotations.
Sachaa-Thanasius Jun 16, 2024
9862d65
Fix port not being marked as `int` in several places.
Sachaa-Thanasius Jun 17, 2024
3c22353
More annotations that I forgot to break up into multiple commits.
Sachaa-Thanasius Jun 17, 2024
ee23708
Fix variable annotation for `uri` in `URIMixin`.copy_with.
Sachaa-Thanasius Jun 20, 2024
fadc962
Fix annotation for `misc.UseExisting` to be `Final` to avoid reassign…
Sachaa-Thanasius Jun 20, 2024
c923049
Change how port is determined/validated in `validators.subauthority_c…
Sachaa-Thanasius Jun 22, 2024
7fc9af0
Replace reorder-python-imports and flake8-import-order with isort, al…
Sachaa-Thanasius Jul 3, 2024
bf1a44d
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 3, 2024
283f910
Add exclude lines to .coveragerc to account for a) `if t.TYPE_CHECKIN…
Sachaa-Thanasius Jul 3, 2024
6a4daf3
Merge branch 'feature/just-annotations' of github.com:Sachaa-Thanasiu…
Sachaa-Thanasius Jul 3, 2024
1443cd1
Add `#pragma: no cover` to final line missing coverage, as well as a …
Sachaa-Thanasius Jul 3, 2024
be1a4e8
Adjust typing check to use wrapper/shim script for now.
Sachaa-Thanasius Jul 6, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still bitter there's no way to tell GHA that "Hey, I have this list elsewhere of things I want to run against, can you just pick the 'earliest'/'oldest'/'smallest' so that when I update the list I don't have to update every goddamn reference to remove the oldest?"

Copy link
Contributor Author

@Sachaa-Thanasius Sachaa-Thanasius Jun 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a novice at best when it comes to GHA and just went off of what the rest of the workflow looked like, so I have no idea if the technology exists, lol. It is a bit annoying, but I figured if a better way is found and it does get refactored, this would temporarily showcase what the output would look like in CI for pyright --verifytypes and either help or hurt the case for switching to and/or including mypy :D.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I'm just griping aloud, not hoping you would magically fix GitHub's product. 😂

toxenv: typing
# misc
- os: ubuntu-latest
python: '3.12'
Expand Down
10 changes: 10 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[tool.pyright]
include = ["src/rfc3986"]
ignore = ["tests"]
pythonVersion = "3.8"
typeCheckingMode = "strict"

reportPrivateUsage = "none"
reportImportCycles = "warning"
reportPropertyTypeMismatch = "warning"
reportUnnecessaryTypeIgnoreComment = "warning"
74 changes: 49 additions & 25 deletions src/rfc3986/_mixin.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,35 @@
"""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__
sigmavirus24 marked this conversation as resolved.
Show resolved Hide resolved
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 +70,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 +84,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 +93,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 +102,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 +120,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 +154,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 +184,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 +203,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 +222,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 +241,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 +260,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 +270,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(
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 +293,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 +349,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 +371,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 +404,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):
from typing import Self
elif t.TYPE_CHECKING:
from typing_extensions import Self
else:

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"."""
22 changes: 17 additions & 5 deletions src/rfc3986/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,17 @@
This module defines functions and provides access to the public attributes
and classes of rfc3986.
"""
import typing as t

from .iri import IRIReference
from .parseresult import ParseResult
from .uri import URIReference


def uri_reference(uri, encoding="utf-8"):
def uri_reference(
uri: t.Union[str, bytes],
encoding: str = "utf-8",
) -> URIReference:
"""Parse a URI string into a URIReference.

This is a convenience function. You could achieve the same end by using
Expand All @@ -36,7 +41,10 @@ def uri_reference(uri, encoding="utf-8"):
return URIReference.from_string(uri, encoding)


def iri_reference(iri, encoding="utf-8"):
def iri_reference(
iri: t.Union[str, bytes],
encoding: str = "utf-8",
) -> IRIReference:
"""Parse a IRI string into an IRIReference.

This is a convenience function. You could achieve the same end by using
Expand All @@ -50,7 +58,11 @@ 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: t.Union[str, bytes],
encoding: str = "utf-8",
**kwargs: bool,
) -> bool:
"""Determine if the URI given is valid.

This is a convenience function. You could use either
Expand All @@ -75,7 +87,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: t.Union[str, bytes], encoding: str = "utf-8") -> str:
"""Normalize the given URI.

This is a convenience function. You could use either
Expand All @@ -91,7 +103,7 @@ def normalize_uri(uri, encoding="utf-8"):
return normalized_reference.unsplit()


def urlparse(uri, encoding="utf-8"):
def urlparse(uri: t.Union[str, bytes], encoding: str = "utf-8") -> ParseResult:
"""Parse a given URI and return a ParseResult.

This is a partial replacement of the standard library's urlparse function.
Expand Down
Loading
Loading