From d14728a4d57eddbbe3c530884d193ce8f407fbda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bern=C3=A1t=20G=C3=A1bor?= Date: Wed, 14 Jun 2023 13:39:13 -0700 Subject: [PATCH] Add ruff (#81) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Bernát Gábor --- .github/workflows/check.yml | 12 +-- .github/workflows/release.yml | 2 +- .markdownlint.yaml | 12 --- .pre-commit-config.yaml | 67 +++++----------- docs/conf.py | 3 +- pyproject.toml | 54 ++++++++----- src/pyproject_api/__init__.py | 1 + src/pyproject_api/__main__.py | 29 +++---- src/pyproject_api/_backend.pyi | 10 +-- src/pyproject_api/_frontend.py | 69 ++++++++++------- src/pyproject_api/_util.py | 5 +- src/pyproject_api/_via_fresh_subprocess.py | 20 +++-- tests/demo_pkg_inline/build.py | 90 +++++++++++++--------- tests/demo_pkg_inline/build.pyi | 19 ----- tests/demo_pkg_inline/pyproject.toml | 2 +- tests/test_backend.py | 13 ++-- tests/test_frontend_setuptools.py | 17 ++-- tests/test_main.py | 11 ++- tests/test_util.py | 5 +- tox.ini | 4 +- whitelist.txt | 33 -------- 21 files changed, 228 insertions(+), 250 deletions(-) delete mode 100644 .markdownlint.yaml delete mode 100644 tests/demo_pkg_inline/build.pyi delete mode 100644 whitelist.txt diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index ec314c0..1728f4b 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -25,9 +25,9 @@ jobs: - "3.8" - "3.7" os: - - ubuntu-22.04 - - windows-2022 - - macos-12 + - ubuntu-latest + - windows-latest + - macos-latest steps: - name: Setup python for tox uses: actions/setup-python@v4 @@ -71,10 +71,10 @@ jobs: - docs - pkg_meta os: - - ubuntu-22.04 - - windows-2022 + - ubuntu-latest + - windows-latest exclude: - - { os: windows-2022, tox_env: pkg_meta } + - { os: windows-latest, tox_env: pkg_meta } steps: - uses: actions/checkout@v3 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3535e60..6a118b7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,7 +5,7 @@ on: jobs: release: - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest environment: name: release url: https://pypi.org/p/pyproject-api diff --git a/.markdownlint.yaml b/.markdownlint.yaml deleted file mode 100644 index 68e8082..0000000 --- a/.markdownlint.yaml +++ /dev/null @@ -1,12 +0,0 @@ -MD013: - code_blocks: false - headers: false - line_length: 120 - tables: false - -MD046: - style: fenced - -MD033: - allowed_elements: - - a diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dca3281..a3ce22a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,74 +2,43 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: - - id: check-ast - - id: check-builtin-literals - - id: check-docstring-first - - id: check-merge-conflict - - id: check-yaml - - id: check-toml - - id: debug-statements - id: end-of-file-fixer - id: trailing-whitespace - - repo: https://github.com/asottile/pyupgrade - rev: v3.3.2 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: "v0.0.272" hooks: - - id: pyupgrade - args: ["--py37-plus"] - exclude: "^(src/pyproject_api/_backend.py|tests/demo_pkg_inline/build.py)$" - - repo: https://github.com/PyCQA/isort - rev: 5.12.0 - hooks: - - id: isort + - id: ruff + exclude: src/pyproject_api/_backend.py + args: [--fix, --exit-non-zero-on-fix] - repo: https://github.com/psf/black rev: 23.3.0 hooks: - id: black - args: [--safe] - - repo: https://github.com/asottile/blacken-docs - rev: 1.13.0 - hooks: - - id: blacken-docs - additional_dependencies: [black==23.3] - - repo: https://github.com/pre-commit/pygrep-hooks - rev: v1.10.0 - hooks: - - id: rst-backticks - repo: https://github.com/tox-dev/tox-ini-fmt rev: "1.3.0" hooks: - id: tox-ini-fmt args: ["-p", "fix"] - - repo: https://github.com/PyCQA/flake8 - rev: 6.0.0 + - repo: https://github.com/tox-dev/pyproject-fmt + rev: "0.11.2" hooks: - - id: flake8 - additional_dependencies: - - flake8-bugbear==23.3.23 - - flake8-comprehensions==3.12 - - flake8-pytest-style==1.7.2 - - flake8-spellcheck==0.28 - - flake8-unused-arguments==0.0.13 - - flake8-noqa==1.3.1 - - pep8-naming==0.13.3 - - flake8-pyproject==1.2.3 + - id: pyproject-fmt + additional_dependencies: ["tox>=4.6"] - repo: https://github.com/pre-commit/mirrors-prettier - rev: "v2.7.1" + rev: "v3.0.0-alpha.9-for-vscode" hooks: - id: prettier - additional_dependencies: - - prettier@2.7.1 - - "@prettier/plugin-xml@2.2" args: ["--print-width=120", "--prose-wrap=always"] - - repo: https://github.com/igorshubovych/markdownlint-cli - rev: v0.33.0 + - repo: https://github.com/asottile/blacken-docs + rev: 1.14.0 + hooks: + - id: blacken-docs + additional_dependencies: [black==23.3] + - repo: https://github.com/pre-commit/pygrep-hooks + rev: v1.10.0 hooks: - - id: markdownlint + - id: rst-backticks - repo: meta hooks: - id: check-hooks-apply - id: check-useless-excludes - - repo: https://github.com/tox-dev/pyproject-fmt - rev: "0.10.0" - hooks: - - id: pyproject-fmt diff --git a/docs/conf.py b/docs/conf.py index 9f09f0b..815ad5a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,10 +1,11 @@ +# noqa: D100 from __future__ import annotations from pyproject_api import __version__ project = name = "pyproject_api" company = "tox-dev" -copyright = f"{company}" +copyright = f"{company}" # noqa: A001 version, release = __version__, __version__.split("+")[0] extensions = [ diff --git a/pyproject.toml b/pyproject.toml index 59ce181..1b7b635 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ build-backend = "hatchling.build" requires = [ "hatch-vcs>=0.3", - "hatchling>=1.14.1", + "hatchling>=1.17.1", ] [project] @@ -28,6 +28,13 @@ classifiers = [ "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Testing", "Topic :: Utilities", @@ -40,17 +47,17 @@ dependencies = [ 'tomli>=2.0.1; python_version < "3.11"', ] optional-dependencies.docs = [ - "furo>=2023.3.27", - "sphinx>=6.2.1", + "furo>=2023.5.20", + "sphinx>=7.0.1", "sphinx-autodoc-typehints!=1.23.4,>=1.23", ] optional-dependencies.testing = [ "covdefaults>=2.3", 'importlib-metadata>=6.6; python_version < "3.8"', "pytest>=7.3.1", - "pytest-cov>=4", + "pytest-cov>=4.1", "pytest-mock>=3.10", - "setuptools>=67.7.2", + "setuptools>=67.8", "wheel>=0.40", ] urls.Homepage = "http://pyproject_api.readthedocs.org" @@ -64,18 +71,6 @@ version.source = "vcs" [tool.black] line-length = 120 -[tool.isort] -known_first_party = ["pyproject_api"] -profile = "black" -line_length = 120 - -[tool.flake8] -max-complexity = 22 -max-line-length = 120 -unused-arguments-ignore-abstract-functions = true -noqa-require-code = true -dictionaries = ["en_US", "python", "technical", "django"] - [tool.coverage] html.show_contexts = true html.skip_covered = false @@ -98,5 +93,26 @@ show_error_codes = true strict = true overrides = [{ module = ["virtualenv.*"], ignore_missing_imports = true }] -[tool.pep8] -max-line-length = "120" +[tool.ruff] +select = ["ALL"] +line-length = 120 +target-version = "py37" +isort = {known-first-party = ["pyproject_api"], required-imports = ["from __future__ import annotations"]} +ignore = [ + "INP001", # no implicit namespaces here + "ANN101", # Missing type annotation for `self` in method + "ANN102", # Missing type annotation for `cls` in classmethod" + "ANN401", # Dynamically typed expressions + "D203", # `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible + "D212", # `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible + "S104", # Possible binding to all interface +] +[tool.ruff.per-file-ignores] +"tests/**/*.py" = [ + "S101", # asserts allowed in tests... + "FBT", # don"t care about booleans as positional arguments in tests + "INP001", # no implicit namespace + "D", # don"t care about documentation in tests + "S603", # `subprocess` call: check for execution of untrusted input + "PLR2004", # Magic value used in comparison, consider replacing with a constant variable +] diff --git a/src/pyproject_api/__init__.py b/src/pyproject_api/__init__.py index 3491c49..a711024 100644 --- a/src/pyproject_api/__init__.py +++ b/src/pyproject_api/__init__.py @@ -1,3 +1,4 @@ +"""PyProject API interface.""" from __future__ import annotations from ._frontend import ( diff --git a/src/pyproject_api/__main__.py b/src/pyproject_api/__main__.py index a54e89d..751339d 100644 --- a/src/pyproject_api/__main__.py +++ b/src/pyproject_api/__main__.py @@ -1,15 +1,18 @@ -from __future__ import annotations +from __future__ import annotations # noqa: D100 import argparse import os import pathlib import sys +from typing import TYPE_CHECKING -from ._frontend import EditableResult, SdistResult, WheelResult from ._via_fresh_subprocess import SubprocessFrontend +if TYPE_CHECKING: + from ._frontend import EditableResult, SdistResult, WheelResult -def main_parser() -> argparse.ArgumentParser: + +def main_parser() -> argparse.ArgumentParser: # noqa: D103 parser = argparse.ArgumentParser( description=( "A pyproject.toml-based build frontend. " @@ -58,7 +61,7 @@ def main_parser() -> argparse.ArgumentParser: return parser -def main(argv: list[str]) -> None: +def main(argv: list[str]) -> None: # noqa: D103 parser = main_parser() args = parser.parse_args(argv) @@ -70,22 +73,22 @@ def main(argv: list[str]) -> None: res: SdistResult | WheelResult | EditableResult if "sdist" in distributions: - print("Building sdist...") + print("Building sdist...") # noqa: T201 res = frontend.build_sdist(outdir) - print(res.out) - print(res.err, file=sys.stderr) + print(res.out) # noqa: T201 + print(res.err, file=sys.stderr) # noqa: T201 if "wheel" in distributions: - print("Building wheel...") + print("Building wheel...") # noqa: T201 res = frontend.build_wheel(outdir) - print(res.out) - print(res.err, file=sys.stderr) + print(res.out) # noqa: T201 + print(res.err, file=sys.stderr) # noqa: T201 if "editable" in distributions: - print("Building editable wheel...") + print("Building editable wheel...") # noqa: T201 res = frontend.build_editable(outdir) - print(res.out) - print(res.err, file=sys.stderr) + print(res.out) # noqa: T201 + print(res.err, file=sys.stderr) # noqa: T201 if __name__ == "__main__": diff --git a/src/pyproject_api/_backend.pyi b/src/pyproject_api/_backend.pyi index d2cd946..45f3b75 100644 --- a/src/pyproject_api/_backend.pyi +++ b/src/pyproject_api/_backend.pyi @@ -1,15 +1,13 @@ -"""Handles communication on the backend side between frontend and backend""" -from typing import Any, Optional, Sequence +from typing import Any, Sequence -class MissingCommand(TypeError): ... +class MissingCommand(TypeError): ... # noqa: N818 class BackendProxy: backend_module: str - backend_object: Optional[str] + backend_object: str | None backend: Any - def __init__(self, backend_module: str, backend_obj: Optional[str]) -> None: ... + def __init__(self, backend_module: str, backend_obj: str | None) -> None: ... def __call__(self, name: str, *args: Any, **kwargs: Any) -> Any: ... - def __str__(self) -> str: ... def _exit(self) -> None: ... def _optional_commands(self) -> dict[str, bool]: ... diff --git a/src/pyproject_api/_frontend.py b/src/pyproject_api/_frontend.py index 95fee45..4bae6b3 100644 --- a/src/pyproject_api/_frontend.py +++ b/src/pyproject_api/_frontend.py @@ -1,4 +1,4 @@ -"""Build frontend for PEP-517""" +"""Build frontend for PEP-517.""" from __future__ import annotations import json @@ -30,7 +30,7 @@ class OptionalHooks(TypedDict, total=True): - """A flag indicating if the backend supports the optional hook or not""" + """A flag indicating if the backend supports the optional hook or not.""" get_requires_for_build_sdist: bool prepare_metadata_for_build_wheel: bool @@ -54,7 +54,7 @@ def out_err(self) -> tuple[str, str]: class RequiresBuildSdistResult(NamedTuple): - """Information collected while acquiring the source distribution build dependencies""" + """Information collected while acquiring the source distribution build dependencies.""" #: wheel build dependencies requires: tuple[Requirement, ...] @@ -65,7 +65,7 @@ class RequiresBuildSdistResult(NamedTuple): class RequiresBuildWheelResult(NamedTuple): - """Information collected while acquiring the wheel build dependencies""" + """Information collected while acquiring the wheel build dependencies.""" #: wheel build dependencies requires: tuple[Requirement, ...] @@ -76,7 +76,7 @@ class RequiresBuildWheelResult(NamedTuple): class RequiresBuildEditableResult(NamedTuple): - """Information collected while acquiring the wheel build dependencies""" + """Information collected while acquiring the wheel build dependencies.""" #: editable wheel build dependencies requires: tuple[Requirement, ...] @@ -87,7 +87,7 @@ class RequiresBuildEditableResult(NamedTuple): class MetadataForBuildWheelResult(NamedTuple): - """Information collected while acquiring the wheel metadata""" + """Information collected while acquiring the wheel metadata.""" #: path to the wheel metadata metadata: Path @@ -98,7 +98,7 @@ class MetadataForBuildWheelResult(NamedTuple): class MetadataForBuildEditableResult(NamedTuple): - """Information collected while acquiring the editable metadata""" + """Information collected while acquiring the editable metadata.""" #: path to the wheel metadata metadata: Path @@ -109,7 +109,7 @@ class MetadataForBuildEditableResult(NamedTuple): class SdistResult(NamedTuple): - """Information collected while building a source distribution""" + """Information collected while building a source distribution.""" #: path to the built source distribution sdist: Path @@ -120,7 +120,7 @@ class SdistResult(NamedTuple): class WheelResult(NamedTuple): - """Information collected while building a wheel""" + """Information collected while building a wheel.""" #: path to the built wheel artifact wheel: Path @@ -131,7 +131,7 @@ class WheelResult(NamedTuple): class EditableResult(NamedTuple): - """Information collected while building an editable wheel""" + """Information collected while building an editable wheel.""" #: path to the built wheel artifact wheel: Path @@ -141,7 +141,7 @@ class EditableResult(NamedTuple): err: str -class BackendFailed(RuntimeError): +class BackendFailed(RuntimeError): # noqa: N818 """An error of the build backend.""" def __init__(self, result: dict[str, Any], out: str, err: str) -> None: @@ -172,23 +172,21 @@ def __repr__(self) -> str: class Frontend(ABC): - """ - Abstract base class for a pyproject frontend. - """ + """Abstract base class for a pyproject frontend.""" #: backend key when the ``pyproject.toml`` does not specify it LEGACY_BUILD_BACKEND: str = "setuptools.build_meta:__legacy__" #: backend requirements when the ``pyproject.toml`` does not specify it LEGACY_REQUIRES: tuple[Requirement, ...] = (Requirement("setuptools >= 40.8.0"), Requirement("wheel")) - def __init__( + def __init__( # noqa: PLR0913 self, root: Path, backend_paths: tuple[Path, ...], backend_module: str, backend_obj: str | None, requires: tuple[Requirement, ...], - reuse_backend: bool = True, + reuse_backend: bool = True, # noqa: FBT001, FBT002 ) -> None: """ Create a new frontend. @@ -210,7 +208,8 @@ def __init__( @classmethod def create_args_from_folder( - cls, folder: Path + cls, + folder: Path, ) -> tuple[Path, tuple[Path, ...], str, str | None, tuple[Requirement, ...], bool]: """ Frontend creation arguments from a python project folder (thould have a ``pypyproject.toml`` file per PEP-518). @@ -299,7 +298,8 @@ def get_requires_for_build_wheel(self, config_settings: ConfigSettings | None = return RequiresBuildWheelResult(tuple(Requirement(r) for r in cast(List[str], result)), out, err) def get_requires_for_build_editable( - self, config_settings: ConfigSettings | None = None + self, + config_settings: ConfigSettings | None = None, ) -> RequiresBuildEditableResult: """ Get build requirements for an editable wheel build (per PEP-660). @@ -316,7 +316,9 @@ def get_requires_for_build_editable( return RequiresBuildEditableResult(tuple(Requirement(r) for r in cast(List[str], result)), out, err) def prepare_metadata_for_build_wheel( - self, metadata_directory: Path, config_settings: ConfigSettings | None = None + self, + metadata_directory: Path, + config_settings: ConfigSettings | None = None, ) -> MetadataForBuildWheelResult: """ Build wheel metadata (per PEP-517). @@ -343,13 +345,16 @@ def prepare_metadata_for_build_wheel( def _check_metadata_dir(self, metadata_directory: Path) -> None: if metadata_directory == self._root: - raise RuntimeError(f"the project root and the metadata directory can't be the same {self._root}") + msg = f"the project root and the metadata directory can't be the same {self._root}" + raise RuntimeError(msg) if metadata_directory.exists(): # start with fresh ensure_empty_dir(metadata_directory) metadata_directory.mkdir(parents=True, exist_ok=True) def prepare_metadata_for_build_editable( - self, metadata_directory: Path, config_settings: ConfigSettings | None = None + self, + metadata_directory: Path, + config_settings: ConfigSettings | None = None, ) -> MetadataForBuildEditableResult: """ Build editable wheel metadata (per PEP-660). @@ -442,12 +447,22 @@ def build_editable( self._unexpected_response("build_editable", basename, str, out, err) return EditableResult(wheel_directory / basename, out, err) - def _unexpected_response(self, cmd: str, got: Any, expected_type: Any, out: str, err: str) -> NoReturn: + def _unexpected_response( # noqa: PLR0913 + self, + cmd: str, + got: Any, + expected_type: Any, + out: str, + err: str, + ) -> NoReturn: msg = f"{cmd!r} on {self.backend!r} returned {got!r} but expected type {expected_type!r}" raise BackendFailed({"code": None, "exc_type": TypeError.__name__, "exc_msg": msg}, out, err) def _metadata_from_built_wheel( - self, config_settings: ConfigSettings | None, metadata_directory: Path | None, cmd: str + self, + config_settings: ConfigSettings | None, + metadata_directory: Path | None, + cmd: str, ) -> tuple[str, str, str]: with self._wheel_directory() as wheel_directory: wheel_result = getattr(self, cmd)( @@ -457,7 +472,8 @@ def _metadata_from_built_wheel( ) wheel = wheel_result.wheel if not wheel.exists(): - raise RuntimeError(f"missing wheel file return by backed {wheel!r}") + msg = f"missing wheel file return by backed {wheel!r}" + raise RuntimeError(msg) out, err = wheel_result.out, wheel_result.err extract_to = str(metadata_directory) basename = None @@ -468,7 +484,8 @@ def _metadata_from_built_wheel( basename = path.parts[0] zip_file.extract(name, extract_to) if basename is None: # pragma: no branch - raise RuntimeError(f"no .dist-info found inside generated wheel {wheel}") + msg = f"no .dist-info found inside generated wheel {wheel}" + raise RuntimeError(msg) return basename, err, out @contextmanager @@ -484,7 +501,7 @@ def _send(self, cmd: str, **kwargs: Any) -> tuple[Any, str, str]: "cmd": cmd, "kwargs": {k: (str(v) if isinstance(v, Path) else v) for k, v in kwargs.items()}, "result": str(result_file), - } + }, ) with self._send_msg(cmd, result_file, msg) as status: while not status.done: # pragma: no branch diff --git a/src/pyproject_api/_util.py b/src/pyproject_api/_util.py index b4a140b..67240c1 100644 --- a/src/pyproject_api/_util.py +++ b/src/pyproject_api/_util.py @@ -1,7 +1,10 @@ from __future__ import annotations -from pathlib import Path from shutil import rmtree +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from pathlib import Path def ensure_empty_dir(path: Path) -> None: diff --git a/src/pyproject_api/_via_fresh_subprocess.py b/src/pyproject_api/_via_fresh_subprocess.py index eba7b0a..31abc3c 100644 --- a/src/pyproject_api/_via_fresh_subprocess.py +++ b/src/pyproject_api/_via_fresh_subprocess.py @@ -3,15 +3,17 @@ import os import sys from contextlib import contextmanager -from pathlib import Path from subprocess import PIPE, Popen from threading import Thread -from typing import IO, Any, Iterator, Tuple, cast - -from packaging.requirements import Requirement +from typing import IO, TYPE_CHECKING, Any, Iterator, Tuple, cast from ._frontend import CmdStatus, Frontend +if TYPE_CHECKING: + from pathlib import Path + + from packaging.requirements import Requirement + class SubprocessCmdStatus(CmdStatus, Thread): def __init__(self, process: Popen[str]) -> None: @@ -34,15 +36,17 @@ def out_err(self) -> tuple[str, str]: class SubprocessFrontend(Frontend): """A frontend that creates fresh subprocess at every call to communicate with the backend.""" - def __init__( + def __init__( # noqa: PLR0913 self, root: Path, backend_paths: tuple[Path, ...], backend_module: str, backend_obj: str | None, requires: tuple[Requirement, ...], - ): + ) -> None: """ + Create a subprocess frontend. + :param root: the root path to the built project :param backend_paths: paths that are available on the python path for the backend :param backend_module: module where the backend is located @@ -53,13 +57,13 @@ def __init__( self.executable = sys.executable @contextmanager - def _send_msg(self, cmd: str, result_file: Path, msg: str) -> Iterator[SubprocessCmdStatus]: # noqa: U100 + def _send_msg(self, cmd: str, result_file: Path, msg: str) -> Iterator[SubprocessCmdStatus]: # noqa: ARG002 env = os.environ.copy() backend = os.pathsep.join(str(i) for i in self._backend_paths).strip() if backend: env["PYTHONPATH"] = backend process = Popen( - args=[self.executable] + self.backend_args, + args=[self.executable, *self.backend_args], stdout=PIPE, stderr=PIPE, stdin=PIPE, diff --git a/tests/demo_pkg_inline/build.py b/tests/demo_pkg_inline/build.py index bce31e5..b0ef8c8 100644 --- a/tests/demo_pkg_inline/build.py +++ b/tests/demo_pkg_inline/build.py @@ -2,10 +2,12 @@ Please keep this file Python 2.7 compatible. See https://tox.readthedocs.io/en/rewrite/development.html#code-style-guide """ +from __future__ import annotations import os import sys import tarfile +from pathlib import Path from textwrap import dedent from zipfile import ZipFile @@ -13,16 +15,16 @@ pkg_name = name.replace("_", "-") version = "1.0.0" -dist_info = "{}-{}.dist-info".format(name, version) -logic = "{}/__init__.py".format(name) -metadata = "{}/METADATA".format(dist_info) -wheel = "{}/WHEEL".format(dist_info) -record = "{}/RECORD".format(dist_info) +dist_info = f"{name}-{version}.dist-info" +logic = f"{name}/__init__.py" +metadata_file = f"{dist_info}/METADATA" +wheel = f"{dist_info}/WHEEL" +record = f"{dist_info}/RECORD" content = { - logic: "def do():\n print('greetings from {}')".format(name), + logic: f"def do():\n print('greetings from {name}')", } metadata = { - metadata: """ + metadata_file: """ Metadata-Version: 2.1 Name: {} Version: {} @@ -35,7 +37,8 @@ UNKNOWN """.format( - pkg_name, version + pkg_name, + version, ), wheel: """ Wheel-Version: 1.0 @@ -43,9 +46,11 @@ Root-Is-Purelib: true Tag: py{}-none-any """.format( - name, version, sys.version_info[0] + name, + version, + sys.version_info[0], ), - "{}/top_level.txt".format(dist_info): name, + f"{dist_info}/top_level.txt": name, record: """ {0}/__init__.py,, {1}/METADATA,, @@ -53,70 +58,81 @@ {1}/top_level.txt,, {1}/RECORD,, """.format( - name, dist_info + name, + dist_info, ), } -def build_wheel(wheel_directory, metadata_directory=None, config_settings=None): # noqa: U100 - base_name = "{}-{}-py{}-none-any.whl".format(name, version, sys.version_info[0]) - path = os.path.join(wheel_directory, base_name) - with ZipFile(path, "w") as zip_file_handler: +def build_wheel( + wheel_directory: str, + metadata_directory: str | None = None, + config_settings: dict[str, str] | None = None, # noqa: ARG001 +) -> str: + base_name = f"{name}-{version}-py{sys.version_info[0]}-none-any.whl" + path = Path(wheel_directory) / base_name + with ZipFile(str(path), "w") as zip_file_handler: for arc_name, data in content.items(): # pragma: no branch zip_file_handler.writestr(arc_name, dedent(data).strip()) if metadata_directory is not None: for sub_directory, _, filenames in os.walk(metadata_directory): for filename in filenames: zip_file_handler.write( - os.path.join(metadata_directory, sub_directory, filename), - os.path.join(sub_directory, filename), + str(Path(metadata_directory) / sub_directory / filename), + str(Path(sub_directory) / filename), ) else: for arc_name, data in metadata.items(): # pragma: no branch zip_file_handler.writestr(arc_name, dedent(data).strip()) - print("created wheel {}".format(path)) + print(f"created wheel {path}") # noqa: T201 return base_name -def get_requires_for_build_wheel(config_settings=None): # noqa: U100 +def get_requires_for_build_wheel(config_settings: dict[str, str] | None = None) -> list[str]: # noqa: ARG001 return [] # pragma: no cover # only executed in non-host pythons -def build_sdist(sdist_directory, config_settings=None): # noqa: U100 - result = "{}-{}.tar.gz".format(name, version) - with tarfile.open(os.path.join(sdist_directory, result), "w:gz") as tar: - root = os.path.dirname(os.path.abspath(__file__)) - tar.add(os.path.join(root, "build.py"), "build.py") - tar.add(os.path.join(root, "pyproject.toml"), "pyproject.toml") +def build_sdist(sdist_directory: str, config_settings: dict[str, str] | None = None) -> str: # noqa: ARG001 + result = f"{name}-{version}.tar.gz" + with tarfile.open(str(Path(sdist_directory) / result), "w:gz") as tar: + root = Path(__file__).parent + tar.add(str(root / "build.py"), "build.py") + tar.add(str(root / "pyproject.toml"), "pyproject.toml") return result -def get_requires_for_build_sdist(config_settings=None): # noqa: U100 +def get_requires_for_build_sdist(config_settings: dict[str, str] | None = None) -> list[str]: # noqa: ARG001 return [] # pragma: no cover # only executed in non-host pythons if "HAS_REQUIRES_EDITABLE" in os.environ: - def get_requires_for_build_editable(config_settings=None): # noqa: U100 - return [1] if "REQUIRES_EDITABLE_BAD_RETURN" in os.environ else ["editables"] + def get_requires_for_build_editable(config_settings: dict[str, str] | None = None) -> list[str]: # noqa: ARG001 + return [1] if "REQUIRES_EDITABLE_BAD_RETURN" in os.environ else ["editables"] # type: ignore[list-item] if "HAS_PREPARE_EDITABLE" in os.environ: - def prepare_metadata_for_build_editable(metadata_directory, config_settings=None): # noqa: U100 - dest = os.path.join(metadata_directory, dist_info) - os.mkdir(dest) + def prepare_metadata_for_build_editable( + metadata_directory: str, + config_settings: dict[str, str] | None = None, # noqa: ARG001 + ) -> str: + dest = Path(metadata_directory) / dist_info + dest.mkdir(parents=True) for arc_name, data in content.items(): if arc_name.startswith(dist_info): - with open(os.path.join(metadata_directory, arc_name), "w") as file_handler: - file_handler.write(dedent(data).strip()) - print("created metadata {}".format(dest)) + (dest.parent / arc_name).write_text(dedent(data).strip()) + print(f"created metadata {dest}") # noqa: T201 if "PREPARE_EDITABLE_BAD" in os.environ: - return 1 # type: ignore # checking bad type on purpose + return 1 # type: ignore[return-value] # checking bad type on purpose return dist_info -def build_editable(wheel_directory, metadata_directory=None, config_settings=None): +def build_editable( + wheel_directory: str, + metadata_directory: str | None = None, + config_settings: dict[str, str] | None = None, +) -> str: if "BUILD_EDITABLE_BAD" in os.environ: - return 1 # type: ignore # checking bad type on purpose + return 1 # type: ignore[return-value] # checking bad type on purpose return build_wheel(wheel_directory, metadata_directory, config_settings) diff --git a/tests/demo_pkg_inline/build.pyi b/tests/demo_pkg_inline/build.pyi deleted file mode 100644 index bdae8d9..0000000 --- a/tests/demo_pkg_inline/build.pyi +++ /dev/null @@ -1,19 +0,0 @@ -name: str = ... -pkg_name: str = ... -version: str = ... -dist_info: str = ... -content: dict[str, str] = ... - -def build_wheel( - wheel_directory: str, metadata_directory: str | None = ..., config_settings: dict[str, str] | None = ... -) -> str: ... -def get_requires_for_build_wheel(config_settings: dict[str, str] | None = ...) -> list[str]: ... -def build_sdist(sdist_directory: str, config_settings: dict[str, str] | None = ...) -> str: ... -def get_requires_for_build_sdist(config_settings: dict[str, str] | None = ...) -> list[str]: ... -def get_requires_for_build_editable(config_settings: dict[str, str] | None = ...) -> list[str]: ... -def prepare_metadata_for_build_editable( - metadata_directory: str, config_settings: dict[str, str] | None = None -) -> str: ... -def build_editable( - wheel_directory: str, metadata_directory: str | None = ..., config_settings: dict[str, str] | None = ... -) -> str: ... diff --git a/tests/demo_pkg_inline/pyproject.toml b/tests/demo_pkg_inline/pyproject.toml index 24c7f1d..9e868a6 100644 --- a/tests/demo_pkg_inline/pyproject.toml +++ b/tests/demo_pkg_inline/pyproject.toml @@ -1,4 +1,4 @@ -[build-system] +[build-system] # noqa: D100 build-backend = "build" requires = [ ] diff --git a/tests/test_backend.py b/tests/test_backend.py index 4b682b3..58cb731 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -1,14 +1,17 @@ from __future__ import annotations import json -from pathlib import Path -from typing import Any +from typing import TYPE_CHECKING, Any import pytest -import pytest_mock from pyproject_api._backend import BackendProxy, run +if TYPE_CHECKING: + from pathlib import Path + + import pytest_mock + def test_invalid_module(capsys: pytest.CaptureFixture[str]) -> None: with pytest.raises(ImportError): @@ -76,7 +79,7 @@ def test_valid_request(mocker: pytest_mock.MockerFixture, capsys: pytest.Capture assert "started backend FakeBackendProxy" in captured.out assert "Backend: run command dummy_command with args {'foo': 'bar'}" in captured.out assert "Backend: Wrote response " in captured.out - assert "" == captured.err + assert not captured.err def test_reuse_process(mocker: pytest_mock.MockerFixture, capsys: pytest.CaptureFixture[str], tmp_path: Path) -> None: @@ -101,7 +104,7 @@ def test_reuse_process(mocker: pytest_mock.MockerFixture, capsys: pytest.Capture json.dumps({"cmd": "_exit", "kwargs": {}, "result": results[3]}), ] - def fake_backend(name: str, *args: Any, **kwargs: Any) -> Any: # noqa: U100 + def fake_backend(name: str, *args: Any, **kwargs: Any) -> Any: # noqa: ARG001 if name == "dummy_command_b": raise SystemExit(2) diff --git a/tests/test_frontend_setuptools.py b/tests/test_frontend_setuptools.py index 29a56bb..cb2d346 100644 --- a/tests/test_frontend_setuptools.py +++ b/tests/test_frontend_setuptools.py @@ -2,18 +2,21 @@ import sys from contextlib import contextmanager -from pathlib import Path from stat import S_IWGRP, S_IWOTH, S_IWUSR -from typing import Iterator, NamedTuple +from typing import TYPE_CHECKING, Iterator, NamedTuple import pytest -from _pytest.tmpdir import TempPathFactory from packaging.requirements import Requirement -from pytest_mock import MockerFixture from pyproject_api._frontend import BackendFailed from pyproject_api._via_fresh_subprocess import SubprocessFrontend +if TYPE_CHECKING: + from pathlib import Path + + from _pytest.tmpdir import TempPathFactory + from pytest_mock import MockerFixture + if sys.version_info >= (3, 8): # pragma: no cover (py38+) from importlib.metadata import Distribution, EntryPoint else: # pragma: no cover ( SubprocessFrontend: prj = tmp_path_factory.mktemp("proj") (prj / "pyproject.toml").write_text( - '[build-system]\nrequires=["setuptools","wheel"]\nbuild-backend = "setuptools.build_meta"' + '[build-system]\nrequires=["setuptools","wheel"]\nbuild-backend = "setuptools.build_meta"', ) cfg = """ [metadata] @@ -73,7 +76,7 @@ def test_setuptools_prepare_metadata_for_build_wheel(frontend_setuptools: Subpro assert list(dist.entry_points) == [EntryPoint(name="demo_exe", value="demo:a", group="console_scripts")] assert dist.version == "1.0" assert dist.metadata["Name"] == "demo" - values = [v for k, v in dist.metadata.items() if k == "Requires-Dist"] # type: ignore + values = [v for k, v in dist.metadata.items() if k == "Requires-Dist"] # type: ignore[attr-defined] # ignore because "PackageMetadata" has no attribute "items" assert values == ["requests (>2)", "magic (>3)"] assert isinstance(result.out, str) @@ -135,7 +138,7 @@ def test_setuptools_exception(frontend_setuptools: SubprocessFrontend) -> None: def test_bad_message(frontend_setuptools: SubprocessFrontend, tmp_path: Path) -> None: - with frontend_setuptools._send_msg("bad_cmd", tmp_path / "a", "{{") as status: + with frontend_setuptools._send_msg("bad_cmd", tmp_path / "a", "{{") as status: # noqa: SLF001 while not status.done: # pragma: no branch pass out, err = status.out_err() diff --git a/tests/test_main.py b/tests/test_main.py index 116cd50..7042310 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1,13 +1,16 @@ from __future__ import annotations from pathlib import Path +from typing import TYPE_CHECKING import pytest -import pytest_mock import pyproject_api.__main__ from pyproject_api._frontend import EditableResult, SdistResult, WheelResult +if TYPE_CHECKING: + import pytest_mock + @pytest.mark.parametrize( ("cli_args", "srcdir", "outdir", "hooks"), @@ -56,7 +59,7 @@ ), ], ) -def test_parse_args( +def test_parse_args( # noqa: PLR0913 mocker: pytest_mock.MockerFixture, capsys: pytest.CaptureFixture[str], cli_args: list[str], @@ -72,7 +75,9 @@ def test_parse_args( err="sdist err", ) subprocess_frontend.return_value.build_wheel.return_value = WheelResult( - wheel=outdir / "foo.whl", out="wheel out", err="wheel err" + wheel=outdir / "foo.whl", + out="wheel out", + err="wheel err", ) subprocess_frontend.return_value.build_editable.return_value = EditableResult( wheel=outdir / "foo.whl", diff --git a/tests/test_util.py b/tests/test_util.py index b304b12..003df63 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -1,9 +1,12 @@ from __future__ import annotations -from pathlib import Path +from typing import TYPE_CHECKING from pyproject_api._util import ensure_empty_dir +if TYPE_CHECKING: + from pathlib import Path + def test_ensure_empty_dir_on_empty(tmp_path: Path) -> None: ensure_empty_dir(tmp_path) diff --git a/tox.ini b/tox.ini index 1600c33..d1c3fe2 100644 --- a/tox.ini +++ b/tox.ini @@ -37,7 +37,7 @@ labels = test description = run formatter and linters skip_install = true deps = - pre-commit>=3.2.2 + pre-commit>=3.3.2 pass_env = {[testenv]passenv} PROGRAMDATA @@ -47,7 +47,7 @@ commands = [testenv:type] description = run type check on code base deps = - mypy==1.2 + mypy==1.3 set_env = {tty:MYPY_FORCE_COLOR = 1} commands = diff --git a/whitelist.txt b/whitelist.txt deleted file mode 100644 index d805f52..0000000 --- a/whitelist.txt +++ /dev/null @@ -1,33 +0,0 @@ -autoclass -autodoc -capsys -cfg -delenv -exe -extlinks -intersphinx -iterdir -iwgrp -iwoth -iwusr -mktemp -namelist -nitpicky -outdir -prj -py311 -py38 -pygments -pyproject -readouterr -sdist -setenv -srcdir -tmpdir -toml -tomli -tomllib -typehints -unbuffered -virtualenv -win32