From ea9bb0f135de78ae6e5c7493b7b546c08b376799 Mon Sep 17 00:00:00 2001 From: Sorin Sbarnea Date: Mon, 18 Nov 2024 16:36:50 +0000 Subject: [PATCH] Upgrade list of linters (#115) --- .flake8 | 9 --- .gitignore | 1 + .pre-commit-config.yaml | 45 +++++++------- pyproject.toml | 120 ++++++++++++++++++++++++++++++-------- src/tox_extra/__init__.py | 1 + src/tox_extra/bindep.py | 37 +++--------- src/tox_extra/hooks.py | 27 ++++++--- tests/test_bindep.py | 2 +- tests/test_plugin.py | 10 ++-- 9 files changed, 153 insertions(+), 99 deletions(-) delete mode 100644 .flake8 diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 6e54175..0000000 --- a/.flake8 +++ /dev/null @@ -1,9 +0,0 @@ -[flake8] -# do not add excludes for files in repo -exclude = .venv/,.tox/,dist/,build/,.eggs/ -format = pylint -# E203: https://github.com/python/black/issues/315 -ignore = E203 -#E741,W503,W504,H,E501,E203 -# 88 is official black default: -max-line-length = 88 diff --git a/.gitignore b/.gitignore index b6e4761..60d4f96 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,4 @@ dmypy.json # Pyre type checker .pyre/ +src/tox_extra/_version.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a8e3361..3072223 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,39 +1,20 @@ -default_language_version: - # pylint is not yet compatible with py311 - python: python3.10 repos: - - repo: https://github.com/pappasam/toml-sort - rev: v0.23.1 - hooks: - - id: toml-sort-fix - repo: https://github.com/pre-commit/mirrors-prettier # keep it before markdownlint and eslint rev: "v4.0.0-alpha.8" hooks: - id: prettier - - repo: https://github.com/PyCQA/isort - rev: 5.13.2 - hooks: - - id: isort - - repo: https://github.com/psf/black - rev: 24.10.0 - hooks: - - id: black - language_version: python3 - repo: https://github.com/pre-commit/pre-commit-hooks.git rev: v5.0.0 hooks: - id: end-of-file-fixer - id: trailing-whitespace - id: mixed-line-ending - - id: check-byte-order-marker + - id: fix-byte-order-marker - id: check-executables-have-shebangs - id: check-merge-conflict - id: debug-statements - - repo: https://github.com/PyCQA/flake8 - rev: 7.1.1 - hooks: - - id: flake8 + - repo: https://github.com/adrienverge/yamllint.git rev: v1.35.1 hooks: @@ -41,6 +22,24 @@ repos: files: \.(yaml|yml)$ types: [file, yaml] entry: yamllint --strict + - repo: https://github.com/pappasam/toml-sort + rev: v0.23.1 + hooks: + - id: toml-sort-fix + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.7.3 + hooks: + - id: ruff + args: + - --fix + - --exit-non-zero-on-fix + types_or: [python, pyi] + - id: ruff-format # must be after ruff + types_or: [python, pyi] + - repo: https://github.com/psf/black # must be after ruff + rev: 24.10.0 + hooks: + - id: black - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.13.0 hooks: @@ -54,8 +53,8 @@ repos: - packaging - py>=1.9.0 - tox>=4.23.2 - - repo: https://github.com/pre-commit/mirrors-pylint - rev: v3.0.0a5 + - repo: https://github.com/pycqa/pylint + rev: v3.3.1 hooks: - id: pylint additional_dependencies: diff --git a/pyproject.toml b/pyproject.toml index 5f6861c..08b2a98 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,24 +1,14 @@ [build-system] +build-backend = "setuptools.build_meta" requires = [ - "setuptools >= 61.0", - "setuptools_scm[toml] >= 7.0.0" + "setuptools >= 65.3.0", # required by pyproject+setuptools_scm integration and editable installs + "setuptools_scm[toml] >= 7.0.5" # required for "no-local-version" scheme ] -build-backend = "setuptools.build_meta" [project] -# https://peps.python.org/pep-0621/#readme -requires-python = ">=3.9" -dynamic = ["version"] -name = "tox-extra" -description = "Performs extra checks before or after running" -readme = "README.md" authors = [ - {"name" = "Sorin Sbarnea", "email" = "sorin.sbarnea@gmail.com"} + {"email" = "sorin.sbarnea@gmail.com", "name" = "Sorin Sbarnea"} ] -maintainers = [ - {"name" = "Sorin Sbarnea", "email" = "sorin.sbarnea@gmail.com"} -] -license = {text = "MIT"} classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", @@ -33,13 +23,23 @@ classifiers = [ "Programming Language :: Python :: 3.13", "Topic :: Software Development :: Testing" ] -keywords = ["git", "tox", "tox-plugin"] dependencies = [ "bindep>2.8.1", "gitpython", "packaging", "tox" ] +description = "Performs extra checks before or after running" +dynamic = ["version"] +keywords = ["git", "tox", "tox-plugin"] +license = {text = "MIT"} +maintainers = [ + {"email" = "sorin.sbarnea@gmail.com", "name" = "Sorin Sbarnea"} +] +name = "tox-extra" +readme = "README.md" +# https://peps.python.org/pep-0621/#readme +requires-python = ">=3.9" [project.entry-points.tox] extra = "tox_extra.hooks" @@ -53,37 +53,107 @@ test = [ ] [project.urls] +changelog = "https://github.com/tox-dev/tox-extra/releases" homepage = "https://github.com/tox-dev/tox-extra" issues = "https://github.com/tox-dev/tox-extra/issues" repository = "https://github.com/tox-dev/tox-extra" -changelog = "https://github.com/tox-dev/tox-extra/releases" [tool.coverage.paths] source = ["src"] [tool.coverage.report] -omit = [".tox/*/lib/python*/site-packages/*"] +fail_under = 92.0 include = ["src/*"] -fail_under = 100.0 -skip_covered = true +omit = [".tox/*/lib/python*/site-packages/*", "src/*/_version.py"] show_missing = true +skip_covered = true +skip_empty = true [tool.coverage.run] +concurrency = ["multiprocessing", "thread"] +# Do not use branch until bug is fixes: +# https://github.com/nedbat/coveragepy/issues/605 +# branch = true parallel = true - -[tool.isort] -profile = "black" +source = ["src"] [tool.mypy] -python_version = 3.9 color_output = true -error_summary = true +disallow_any_generics = true disallow_untyped_calls = true disallow_untyped_defs = true -disallow_any_generics = true +error_summary = true +python_version = 3.9 [tool.pytest.ini_options] +# do not add options here as this will likely break either console runs or IDE +# integration like vscode or pycharm addopts = "-ra --showlocals" +[tool.ruff] +cache-dir = "./.cache/.ruff" +fix = true +# Same as Black. +line-length = 88 + +[tool.ruff.lint] +ignore = [ + "COM812", # conflicts with ISC001 on format + "D203", # incompatible with D211 + "D213", # incompatible with D212 + "E501", # we use black + "ERA001", # auto-removal of commented out code affects development and vscode integration + "INP001", # "is part of an implicit namespace package", all false positives + "ISC001", # conflicts with COM812 on format + "PLW2901", # PLW2901: Redefined loop variable + "RET504", # Unnecessary variable assignment before `return` statement + # temporary disabled until we fix them: + "ANN", + "ARG001", + "PTH109", + "PTH112", + "PTH113", + "PTH123", + "S603", + "S605", + "S607", + "SIM117", + "T201", + "UP022" +] +select = ["ALL"] + +[tool.ruff.lint.flake8-builtins] +builtins-ignorelist = ["id"] + +[tool.ruff.lint.flake8-pytest-style] +parametrize-values-type = "tuple" + +[tool.ruff.lint.isort] +known-first-party = ["src"] + +[tool.ruff.lint.per-file-ignores] +"tests/**/*.py" = ["S"] + +[tool.ruff.lint.pydocstyle] +convention = "google" + [tool.setuptools_scm] +# To prevent accidental pick of mobile version tags such 'v6' +git_describe_command = [ + "git", + "describe", + "--dirty", + "--long", + "--tags", + "--match", + "v*.*" +] local_scheme = "no-local-version" +tag_regex = "^(?Pv)?(?P[0-9.]+)(?P.*)?$" +write_to = "src/tox_extra/_version.py" + +[tool.tomlsort] +in_place = true +sort_inline_tables = true +sort_table_keys = true diff --git a/src/tox_extra/__init__.py b/src/tox_extra/__init__.py index e69de29..81b5053 100644 --- a/src/tox_extra/__init__.py +++ b/src/tox_extra/__init__.py @@ -0,0 +1 @@ +"""Module docstring.""" diff --git a/src/tox_extra/bindep.py b/src/tox_extra/bindep.py index 86acc53..b35248d 100644 --- a/src/tox_extra/bindep.py +++ b/src/tox_extra/bindep.py @@ -1,22 +1,20 @@ """Bindep check feature implementations.""" -from __future__ import print_function +from __future__ import annotations import os import subprocess import sys -from functools import lru_cache -from pathlib import Path -from typing import Iterable, Optional +from functools import cache +from typing import TYPE_CHECKING -if sys.version_info >= (3, 9): # pragma: no cover - from functools import cache -else: # pragma: no cover - cache = lru_cache(maxsize=None) +if TYPE_CHECKING: + from collections.abc import Iterable + from pathlib import Path @cache -def check_bindep(path: Path, profiles: Optional[Iterable[str]] = None) -> None: +def check_bindep(path: Path, profiles: Iterable[str] | None = None) -> None: """Check bindeps requirements or exit.""" if profiles is None: # pragma: no cover profiles = [] @@ -24,28 +22,11 @@ def check_bindep(path: Path, profiles: Optional[Iterable[str]] = None) -> None: # as 'bindep --profiles' does not show user defined profiles like 'test' # it makes no sense to list them. cmd = [sys.executable, "-m", "bindep", "-b", *sorted(profiles)] - # # determine profiles - # result = subprocess.run( - # [sys.executable, "-m", "bindep", "--profiles"], - # check=False, - # universal_newlines=True, - # stdout=subprocess.PIPE, - # ) - # if result.returncode: - # print("Bindep failed to list profiles: %s", result.stdout) - # sys.exit(result.returncode) - # lines = result.stdout.splitlines() - # try: - # profiles = lines[lines.index("Configuration profiles:") + 1 :] - # if "test" in profiles: - # cmd.append("test") - # except ValueError: - # pass result = subprocess.run( cmd, check=False, - universal_newlines=True, + text=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE, cwd=path, @@ -53,7 +34,7 @@ def check_bindep(path: Path, profiles: Optional[Iterable[str]] = None) -> None: if result.returncode: print( f"Running '{' '.join(cmd)}' returned {result.returncode}, " - "likely missing system dependencies." + "likely missing system dependencies.", ) if result.stdout: print(result.stdout) diff --git a/src/tox_extra/hooks.py b/src/tox_extra/hooks.py index 0f2088d..3ffec6a 100644 --- a/src/tox_extra/hooks.py +++ b/src/tox_extra/hooks.py @@ -5,18 +5,22 @@ import os import pathlib import sys -from argparse import ArgumentParser -from typing import Any, List +from typing import TYPE_CHECKING, Any import git -from tox.execute import Outcome from tox.plugin import impl -from tox.tox_env.api import ToxEnv from tox.tox_env.errors import Fail from tox.tox_env.python.api import Python from tox_extra.bindep import check_bindep +if TYPE_CHECKING: + from argparse import ArgumentParser + + from tox.execute import Outcome + from tox.tox_env.api import ToxEnv + + MSG_GIT_DIRTY = ( "exit code 1 due to 'git status -s' reporting dirty. " "That should not happen regardless if status is passed, failed or aborted. " @@ -57,7 +61,12 @@ def tox_add_option(parser: ArgumentParser) -> None: @impl # pylint: disable=unused-argument -def tox_on_install(tox_env: ToxEnv, arguments: Any, section: str, of_type: str) -> None: +def tox_on_install( + tox_env: ToxEnv, + arguments: Any, + section: str, + of_type: str, +) -> None: """Runs just before installing package.""" if os.environ.get("TOX_EXTRA_BINDEP", "1") != "0": profiles = { @@ -68,8 +77,8 @@ def tox_on_install(tox_env: ToxEnv, arguments: Any, section: str, of_type: str) if isinstance(tox_env, Python): profiles.add(f"python{tox_env.py_dot_ver()}") # python3.12 like profile profiles.add( - f"py{tox_env.py_dot_ver().replace('.', '')}" - ) # py312 like profile # type: ignore + f"py{tox_env.py_dot_ver().replace('.', '')}", + ) # py312 like profile check_bindep(path=pathlib.Path.cwd(), profiles=frozenset(profiles)) @@ -77,7 +86,9 @@ def tox_on_install(tox_env: ToxEnv, arguments: Any, section: str, of_type: str) @impl # pylint: disable=unused-argument def tox_after_run_commands( - tox_env: ToxEnv, exit_code: int, outcomes: List[Outcome] + tox_env: ToxEnv, + exit_code: int, + outcomes: list[Outcome], ) -> None: """Hook that runs after test commands.""" allow_dirty = getattr(tox_env.options, "allow_dirty", False) diff --git a/tests/test_bindep.py b/tests/test_bindep.py index 4fe89a2..897b86d 100644 --- a/tests/test_bindep.py +++ b/tests/test_bindep.py @@ -24,5 +24,5 @@ def test_bindep(folder: str, expected_rc: int) -> None: with patch("sys.argv", ["tox"]): with pytest.raises(SystemExit) as exc: run_module("tox", run_name="__main__") - assert exc.type == SystemExit + assert exc.type is SystemExit assert exc.value.code == expected_rc diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 47adeeb..6b03f51 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -32,7 +32,7 @@ def test_fail_if_dirty(tmp_path, monkeypatch: pytest.MonkeyPatch) -> None: # check running tox w/o git repo is passing with pytest.raises(SystemExit) as exc: run_module("tox", run_name="__main__") # , alter_sys=True) - assert exc.type == SystemExit + assert exc.type is SystemExit assert exc.value.code == 0 # create temp git repository @@ -53,7 +53,7 @@ def test_fail_if_dirty(tmp_path, monkeypatch: pytest.MonkeyPatch) -> None: result = run( "git status --porcelain", shell=True, - universal_newlines=True, + text=True, cwd=tmp_path, stdout=PIPE, check=False, @@ -65,7 +65,7 @@ def test_fail_if_dirty(tmp_path, monkeypatch: pytest.MonkeyPatch) -> None: result = run( "python -m tox --version", shell=True, - universal_newlines=True, + text=True, stdout=PIPE, check=False, ) @@ -77,7 +77,7 @@ def test_fail_if_dirty(tmp_path, monkeypatch: pytest.MonkeyPatch) -> None: # running a subprocess would prevent it from working. with pytest.raises(SystemExit) as exc: run_module("tox", run_name="__main__", alter_sys=True) - assert exc.type == SystemExit + assert exc.type is SystemExit assert exc.value.code == 1 # add untracked files @@ -87,5 +87,5 @@ def test_fail_if_dirty(tmp_path, monkeypatch: pytest.MonkeyPatch) -> None: # check that tox is now passing with pytest.raises(SystemExit) as exc: run_module("tox", run_name="__main__", alter_sys=True) - assert exc.type == SystemExit + assert exc.type is SystemExit assert exc.value.code == 0