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 12 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"
10 changes: 5 additions & 5 deletions src/rfc3986/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from .uri import URIReference


def uri_reference(uri, encoding="utf-8"):
def uri_reference(uri: str, 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 +36,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: str, 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 +50,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: str, 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 +75,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: str, encoding: str = "utf-8") -> str:
"""Normalize the given URI.

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


def urlparse(uri, encoding="utf-8"):
def urlparse(uri: str, 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
47 changes: 28 additions & 19 deletions src/rfc3986/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,22 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""Module containing the logic for the URIBuilder object."""
import typing as t
from urllib.parse import parse_qsl
from urllib.parse import urlencode

from . import normalizers
from . import uri
from . import uri_reference

# Copied from urllib.parse in typeshed.
_QueryType = t.Union[
t.Mapping[t.Any, t.Any],
t.Mapping[t.Any, t.Sequence[t.Any]],
t.List[t.Tuple[t.Any, t.Any]],
t.List[t.Tuple[t.Any, t.Sequence[t.Any]]],
]


class URIBuilder:
"""Object to aid in building up a URI Reference from parts.
Expand All @@ -33,13 +42,13 @@ class URIBuilder:

def __init__(
self,
scheme=None,
userinfo=None,
host=None,
port=None,
path=None,
query=None,
fragment=None,
scheme: t.Optional[str] = None,
userinfo: t.Optional[str] = None,
host: t.Optional[str] = None,
port: t.Optional[str] = None,
path: t.Optional[str] = None,
query: t.Optional[str] = None,
fragment: t.Optional[str] = None,
):
"""Initialize our URI builder.

Expand Down Expand Up @@ -76,7 +85,7 @@ def __repr__(self):
return formatstr.format(b=self)

@classmethod
def from_uri(cls, reference):
def from_uri(cls, reference: t.Union["uri.URIReference", str]):
"""Initialize the URI builder from another URI.

Takes the given URI reference and creates a new URI builder instance
Expand All @@ -95,7 +104,7 @@ def from_uri(cls, reference):
fragment=reference.fragment,
)

def add_scheme(self, scheme):
def add_scheme(self, scheme: str):
"""Add a scheme to our builder object.

After normalizing, this will generate a new URIBuilder instance with
Expand All @@ -119,7 +128,7 @@ def add_scheme(self, scheme):
fragment=self.fragment,
)

def add_credentials(self, username, password):
def add_credentials(self, username: str, password: t.Optional[str]):
"""Add credentials as the userinfo portion of the URI.

.. code-block:: python
Expand Down Expand Up @@ -152,7 +161,7 @@ def add_credentials(self, username, password):
fragment=self.fragment,
)

def add_host(self, host):
def add_host(self, host: str):
"""Add hostname to the URI.

.. code-block:: python
Expand All @@ -172,7 +181,7 @@ def add_host(self, host):
fragment=self.fragment,
)

def add_port(self, port):
def add_port(self, port: t.Union[str, int]):
"""Add port to the URI.

.. code-block:: python
Expand Down Expand Up @@ -211,7 +220,7 @@ def add_port(self, port):
fragment=self.fragment,
)

def add_path(self, path):
def add_path(self, path: str):
"""Add a path to the URI.

.. code-block:: python
Expand All @@ -238,7 +247,7 @@ def add_path(self, path):
fragment=self.fragment,
)

def extend_path(self, path):
def extend_path(self, path: str):
"""Extend the existing path value with the provided value.

.. versionadded:: 1.5.0
Expand Down Expand Up @@ -267,7 +276,7 @@ def extend_path(self, path):

return self.add_path(path)

def add_query_from(self, query_items):
def add_query_from(self, query_items: _QueryType):
"""Generate and add a query a dictionary or list of tuples.

.. code-block:: python
Expand All @@ -293,7 +302,7 @@ def add_query_from(self, query_items):
fragment=self.fragment,
)

def extend_query_with(self, query_items):
def extend_query_with(self, query_items: _QueryType):
"""Extend the existing query string with the new query items.

.. versionadded:: 1.5.0
Expand All @@ -314,7 +323,7 @@ def extend_query_with(self, query_items):

return self.add_query_from(original_query_items + query_items)

def add_query(self, query):
def add_query(self, query: str):
"""Add a pre-formated query string to the URI.

.. code-block:: python
Expand All @@ -334,7 +343,7 @@ def add_query(self, query):
fragment=self.fragment,
)

def add_fragment(self, fragment):
def add_fragment(self, fragment: str):
"""Add a fragment to the URI.

.. code-block:: python
Expand All @@ -354,7 +363,7 @@ def add_fragment(self, fragment):
fragment=normalizers.normalize_fragment(fragment),
)

def finalize(self):
def finalize(self) -> "uri.URIReference":
"""Create a URIReference from our builder.

.. code-block:: python
Expand Down
37 changes: 35 additions & 2 deletions src/rfc3986/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,54 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""Compatibility module for Python 2 and 3 support."""
import typing as t

__all__ = (
"to_bytes",
"to_str",
)


def to_str(b, encoding="utf-8"):
@t.overload
def to_str( # noqa: D103
b: t.Union[str, bytes],
encoding: str = "utf-8",
) -> str:
...


@t.overload
def to_str(b: None, encoding: str = "utf-8") -> None: # noqa: D103
...


def to_str(
b: t.Optional[t.Union[str, bytes]],
encoding: str = "utf-8",
) -> t.Optional[str]:
"""Ensure that b is text in the specified encoding."""
if hasattr(b, "decode") and not isinstance(b, str):
b = b.decode(encoding)
return b


def to_bytes(s, encoding="utf-8"):
@t.overload
def to_bytes( # noqa: D103
s: t.Union[str, bytes],
encoding: str = "utf-8",
) -> bytes:
...


@t.overload
def to_bytes(s: None, encoding: str = "utf-8") -> None: # noqa: D103
...


def to_bytes(
s: t.Optional[t.Union[str, bytes]],
encoding: str = "utf-8",
) -> t.Optional[bytes]:
"""Ensure that s is converted to bytes from the encoding."""
if hasattr(s, "encode") and not isinstance(s, bytes):
s = s.encode(encoding)
Expand Down
40 changes: 28 additions & 12 deletions src/rfc3986/normalizers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,21 @@
# limitations under the License.
"""Module with functions to normalize components."""
import re
import typing as t
from urllib.parse import quote as urlquote

from . import compat
from . import misc


def normalize_scheme(scheme):
def normalize_scheme(scheme: str) -> str:
"""Normalize the scheme component."""
return scheme.lower()


def normalize_authority(authority):
def normalize_authority(
authority: t.Tuple[t.Optional[str], t.Optional[str], t.Optional[str]],
) -> str:
"""Normalize an authority tuple to a string."""
userinfo, host, port = authority
result = ""
Expand All @@ -37,17 +40,17 @@ def normalize_authority(authority):
return result


def normalize_username(username):
def normalize_username(username: str) -> str:
"""Normalize a username to make it safe to include in userinfo."""
return urlquote(username)


def normalize_password(password):
def normalize_password(password: str) -> str:
"""Normalize a password to make safe for userinfo."""
return urlquote(password)


def normalize_host(host):
def normalize_host(host: str) -> str:
"""Normalize a host string."""
if misc.IPv6_MATCHER.match(host):
percent = host.find("%")
Expand All @@ -70,7 +73,7 @@ def normalize_host(host):
return host.lower()


def normalize_path(path):
def normalize_path(path: str) -> str:
"""Normalize the path string."""
if not path:
return path
Expand All @@ -79,14 +82,14 @@ def normalize_path(path):
return remove_dot_segments(path)


def normalize_query(query):
def normalize_query(query: str) -> str:
"""Normalize the query string."""
if not query:
return query
return normalize_percent_characters(query)


def normalize_fragment(fragment):
def normalize_fragment(fragment: str) -> str:
"""Normalize the fragment string."""
if not fragment:
return fragment
Expand All @@ -96,7 +99,7 @@ def normalize_fragment(fragment):
PERCENT_MATCHER = re.compile("%[A-Fa-f0-9]{2}")


def normalize_percent_characters(s):
def normalize_percent_characters(s: str) -> str:
"""All percent characters should be upper-cased.

For example, ``"%3afoo%DF%ab"`` should be turned into ``"%3Afoo%DF%AB"``.
Expand All @@ -108,14 +111,14 @@ def normalize_percent_characters(s):
return s


def remove_dot_segments(s):
def remove_dot_segments(s: str) -> str:
"""Remove dot segments from the string.

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
output = [] # Initialize the variable to use to store output
output: list[str] = [] # Initialize the variable to use to store output

for segment in segments:
# '.' is the current directory, so ignore it, it is superfluous
Expand All @@ -142,7 +145,20 @@ def remove_dot_segments(s):
return "/".join(output)


def encode_component(uri_component, encoding):
@t.overload
def encode_component(uri_component: None, encoding: str) -> None: # noqa: D103
...


@t.overload
def encode_component(uri_component: str, encoding: str) -> str: # noqa: D103
...


def encode_component(
uri_component: t.Optional[str],
encoding: str,
) -> t.Optional[str]:
"""Encode the specific component in the provided encoding."""
if uri_component is None:
return uri_component
Expand Down
Empty file added src/rfc3986/py.typed
Empty file.
Loading
Loading