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

Type annotate the public API #167

Merged
merged 21 commits into from
Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
62fb515
Add changelog section for next release
pradyunsg Apr 7, 2023
cb566f5
Type annotate the public API
pradyunsg Apr 4, 2023
88906ab
Update configuration for `ruff`
pradyunsg Apr 5, 2023
69ed8b1
Drop support for Python 3.7
pradyunsg Apr 5, 2023
a2cd373
Add support for Python 3.12
pradyunsg Apr 7, 2023
87c779b
Enforce a Python version in Read the Docs builds
pradyunsg Apr 7, 2023
8a2adbf
Add changelog entry for type annotations
pradyunsg Apr 7, 2023
9cf1649
Revert "Drop support for Python 3.7"
pradyunsg Apr 8, 2023
26460ba
Add a `py.typed` file to denote type annotated sources
pradyunsg Apr 8, 2023
58e2814
Use more generic types in type annotations
pradyunsg Apr 8, 2023
96a6691
Correct the type of `prepare_metadata_for_build_wheel`
pradyunsg Apr 8, 2023
5b264f5
Use the correct directory for documentation gitignore
pradyunsg Apr 8, 2023
1017004
Drop the return types from docstrings
pradyunsg Apr 8, 2023
cffda1f
Don't set an incorrect name on `nox -s release`
pradyunsg Apr 8, 2023
12401f6
Fully document all parameters in the public API
pradyunsg Apr 8, 2023
db5ed31
Merge branch 'main' into typing
pradyunsg Apr 8, 2023
5c83df5
Expose `SubprocessRunner` for type checking
pradyunsg Apr 8, 2023
375b8fc
Don't meddle with `__all__`
pradyunsg Apr 8, 2023
228aef1
Document the usage of the subprocess protocol for type checking
pradyunsg Apr 8, 2023
47a3347
Merge branch 'main' into typing
pradyunsg Oct 31, 2023
1eea736
Remove whitespace that was accidentally added in a merge commit
pradyunsg Nov 1, 2023
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
os: [Ubuntu, macOS, Windows]

steps:
Expand Down
6 changes: 6 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ repos:
hooks:
- id: black

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.1.1
hooks:
- id: mypy
exclude: tests/samples

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
Expand Down
1 change: 1 addition & 0 deletions .readthedocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ sphinx:
configuration: docs/conf.py

python:
version: 3.8
install:
- requirements: docs/requirements.txt
- method: pip
Expand Down
5 changes: 5 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
Changelog
=========

v1.1
----

- Add type annotations to the public API.

v1.0
----

Expand Down
2 changes: 1 addition & 1 deletion noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
nox.options.reuse_existing_virtualenvs = True


@nox.session(python=["3.7", "3.8", "3.9", "3.10", "3.11", "pypy3"])
@nox.session(python=["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "pypy3"])
def test(session: nox.Session) -> None:
session.install("-r", "dev-requirements.txt")
session.install(".")
Expand Down
7 changes: 5 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,8 @@ Source = "https://github.com/pypa/pyproject-hooks"
Documentation = "https://pyproject-hooks.readthedocs.io/"
Changelog = "https://pyproject-hooks.readthedocs.io/en/latest/changelog.html"

[tool.isort]
profile = "black"
[tool.ruff]
src = ["src", "tests"]

[tool.ruff.isort]
known-first-party = ["pyproject_hooks", "tests"]
116 changes: 83 additions & 33 deletions src/pyproject_hooks/_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,51 @@
from os.path import abspath
from os.path import join as pjoin
from subprocess import STDOUT, check_call, check_output
from typing import TYPE_CHECKING, Any, Iterator, Mapping, Optional, Sequence
pradyunsg marked this conversation as resolved.
Show resolved Hide resolved

from ._in_process import _in_proc_script_path

if TYPE_CHECKING:
from typing import Protocol

def write_json(obj, path, **kwargs):
class SubprocessRunner(Protocol):
"""A protocol for the subprocess runner."""

def __call__(
self,
cmd: Sequence[str],
cwd: Optional[str] = None,
pradyunsg marked this conversation as resolved.
Show resolved Hide resolved
extra_environ: Optional[Mapping[str, str]] = None,
) -> None:
...
pradyunsg marked this conversation as resolved.
Show resolved Hide resolved


def write_json(obj: Mapping[str, Any], path: str, **kwargs) -> None:
with open(path, "w", encoding="utf-8") as f:
json.dump(obj, f, **kwargs)


def read_json(path):
def read_json(path: str) -> Mapping[str, Any]:
with open(path, encoding="utf-8") as f:
return json.load(f)


class BackendUnavailable(Exception):
"""Will be raised if the backend cannot be imported in the hook process."""

def __init__(self, traceback):
def __init__(self, traceback: str) -> None:
self.traceback = traceback
pradyunsg marked this conversation as resolved.
Show resolved Hide resolved


class BackendInvalid(Exception):
"""Will be raised if the backend is invalid."""

def __init__(self, backend_name, backend_path, message):
def __init__(
self,
backend_name: str,
backend_path: Optional[Sequence[str]],
message: str,
) -> None:
super().__init__(message)
self.backend_name = backend_name
self.backend_path = backend_path
Expand All @@ -39,19 +59,23 @@ def __init__(self, backend_name, backend_path, message):
class HookMissing(Exception):
"""Will be raised on missing hooks (if a fallback can't be used)."""

def __init__(self, hook_name):
def __init__(self, hook_name: str) -> None:
super().__init__(hook_name)
self.hook_name = hook_name


class UnsupportedOperation(Exception):
"""May be raised by build_sdist if the backend indicates that it can't."""

def __init__(self, traceback):
def __init__(self, traceback: str) -> None:
self.traceback = traceback


def default_subprocess_runner(cmd, cwd=None, extra_environ=None):
def default_subprocess_runner(
cmd: Sequence[str],
cwd: Optional[str] = None,
pradyunsg marked this conversation as resolved.
Show resolved Hide resolved
extra_environ: Optional[Mapping[str, str]] = None,
pradyunsg marked this conversation as resolved.
Show resolved Hide resolved
) -> None:
"""The default method of calling the wrapper subprocess.

This uses :func:`subprocess.check_call` under the hood.
Expand All @@ -63,7 +87,11 @@ def default_subprocess_runner(cmd, cwd=None, extra_environ=None):
check_call(cmd, cwd=cwd, env=env)


def quiet_subprocess_runner(cmd, cwd=None, extra_environ=None):
def quiet_subprocess_runner(
cmd: Sequence[str],
cwd: Optional[str] = None,
pradyunsg marked this conversation as resolved.
Show resolved Hide resolved
extra_environ: Optional[Mapping[str, str]] = None,
pradyunsg marked this conversation as resolved.
Show resolved Hide resolved
) -> None:
"""Call the subprocess while suppressing output.

This uses :func:`subprocess.check_output` under the hood.
Expand All @@ -75,7 +103,7 @@ def quiet_subprocess_runner(cmd, cwd=None, extra_environ=None):
check_output(cmd, cwd=cwd, env=env, stderr=STDOUT)


def norm_and_check(source_tree, requested):
def norm_and_check(source_tree: str, requested: str) -> str:
"""Normalise and check a backend path.

Ensure that the requested backend path is specified as a relative path,
Expand Down Expand Up @@ -104,12 +132,12 @@ class BuildBackendHookCaller:

def __init__(
self,
source_dir,
build_backend,
backend_path=None,
runner=None,
python_executable=None,
):
source_dir: str,
pradyunsg marked this conversation as resolved.
Show resolved Hide resolved
build_backend: str,
backend_path: Optional[Sequence[str]] = None,
runner: Optional["SubprocessRunner"] = None,
pradyunsg marked this conversation as resolved.
Show resolved Hide resolved
pradyunsg marked this conversation as resolved.
Show resolved Hide resolved
python_executable: Optional[str] = None,
) -> None:
"""
:param source_dir: The source directory to invoke the build backend for
:param build_backend: The build backend spec
Expand All @@ -132,7 +160,7 @@ def __init__(
self.python_executable = python_executable

@contextmanager
def subprocess_runner(self, runner):
def subprocess_runner(self, runner: "SubprocessRunner") -> Iterator[None]:
pradyunsg marked this conversation as resolved.
Show resolved Hide resolved
pradyunsg marked this conversation as resolved.
Show resolved Hide resolved
"""A context manager for temporarily overriding the default
:ref:`subprocess runner <Subprocess Runners>`.

Expand All @@ -149,15 +177,17 @@ def subprocess_runner(self, runner):
finally:
self._subprocess_runner = prev

def _supported_features(self):
def _supported_features(self) -> Sequence[str]:
"""Return the list of optional features supported by the backend."""
return self._call_hook("_supported_features", {})

def get_requires_for_build_wheel(self, config_settings=None):
def get_requires_for_build_wheel(
self,
config_settings: Optional[Mapping[str, Any]] = None,
pradyunsg marked this conversation as resolved.
Show resolved Hide resolved
) -> Sequence[str]:
"""Get additional dependencies required for building a wheel.

:returns: A list of :pep:`dependency specifiers <508>`.
:rtype: list[str]

.. admonition:: Fallback

Expand All @@ -169,8 +199,11 @@ def get_requires_for_build_wheel(self, config_settings=None):
)

def prepare_metadata_for_build_wheel(
self, metadata_directory, config_settings=None, _allow_fallback=True
):
self,
metadata_directory: str,
config_settings: Optional[Mapping[str, Any]] = None,
pradyunsg marked this conversation as resolved.
Show resolved Hide resolved
_allow_fallback: bool = True,
) -> Optional[str]:
pradyunsg marked this conversation as resolved.
Show resolved Hide resolved
"""Prepare a ``*.dist-info`` folder with metadata for this project.

:returns: Name of the newly created subfolder within
Expand All @@ -194,8 +227,11 @@ def prepare_metadata_for_build_wheel(
)

def build_wheel(
self, wheel_directory, config_settings=None, metadata_directory=None
):
self,
wheel_directory: str,
config_settings: Optional[Mapping[str, Any]] = None,
pradyunsg marked this conversation as resolved.
Show resolved Hide resolved
metadata_directory: Optional[str] = None,
) -> str:
"""Build a wheel from this project.

:returns:
Expand All @@ -219,11 +255,13 @@ def build_wheel(
},
)

def get_requires_for_build_editable(self, config_settings=None):
def get_requires_for_build_editable(
self,
config_settings: Optional[Mapping[str, Any]] = None,
pradyunsg marked this conversation as resolved.
Show resolved Hide resolved
) -> Sequence[str]:
"""Get additional dependencies required for building an editable wheel.

:returns: A list of :pep:`dependency specifiers <508>`.
:rtype: list[str]

.. admonition:: Fallback

Expand All @@ -235,8 +273,11 @@ def get_requires_for_build_editable(self, config_settings=None):
)

def prepare_metadata_for_build_editable(
self, metadata_directory, config_settings=None, _allow_fallback=True
):
self,
metadata_directory: str,
config_settings: Optional[Mapping[str, Any]] = None,
pradyunsg marked this conversation as resolved.
Show resolved Hide resolved
_allow_fallback: bool = True,
) -> Optional[str]:
"""Prepare a ``*.dist-info`` folder with metadata for this project.

:returns: Name of the newly created subfolder within
Expand All @@ -260,8 +301,11 @@ def prepare_metadata_for_build_editable(
)

def build_editable(
self, wheel_directory, config_settings=None, metadata_directory=None
):
self,
wheel_directory: str,
config_settings: Optional[Mapping[str, Any]] = None,
pradyunsg marked this conversation as resolved.
Show resolved Hide resolved
metadata_directory: Optional[str] = None,
) -> str:
"""Build an editable wheel from this project.

:returns:
Expand All @@ -286,17 +330,23 @@ def build_editable(
},
)

def get_requires_for_build_sdist(self, config_settings=None):
def get_requires_for_build_sdist(
self,
config_settings: Optional[Mapping[str, Any]] = None,
pradyunsg marked this conversation as resolved.
Show resolved Hide resolved
) -> Sequence[str]:
"""Get additional dependencies required for building an sdist.

:returns: A list of :pep:`dependency specifiers <508>`.
:rtype: list[str]
"""
return self._call_hook(
"get_requires_for_build_sdist", {"config_settings": config_settings}
)

def build_sdist(self, sdist_directory, config_settings=None):
def build_sdist(
self,
sdist_directory: str,
config_settings: Optional[Mapping[str, Any]] = None,
pradyunsg marked this conversation as resolved.
Show resolved Hide resolved
) -> str:
"""Build an sdist from this project.

:returns:
Expand All @@ -310,7 +360,7 @@ def build_sdist(self, sdist_directory, config_settings=None):
},
)

def _call_hook(self, hook_name, kwargs):
def _call_hook(self, hook_name: str, kwargs: Mapping[str, Any]) -> Any:
extra_environ = {"PEP517_BUILD_BACKEND": self.build_backend}

if self.backend_path:
Expand Down
Empty file added src/pyproject_hooks/py.typed
Empty file.