From 7a6ae1f13658fe513e206341a811174f35b3cd31 Mon Sep 17 00:00:00 2001 From: Sorin Sbarnea Date: Thu, 4 May 2023 14:42:27 +0100 Subject: [PATCH] Replace flake8 and isort with ruff (#160) --- .pre-commit-config.yaml | 27 +++++++-------------- pyproject.toml | 42 ++++++++++++++++++++++++++++++++ src/mk/__main__.py | 49 +++++++++++++++++++++++++++----------- src/mk/_typer.py | 5 +++- src/mk/exec.py | 12 ++++++---- src/mk/runner.py | 10 ++++---- src/mk/tools/__init__.py | 20 +++++++++------- src/mk/tools/ansible.py | 11 +++++---- src/mk/tools/cmake.py | 19 ++++++++------- src/mk/tools/git.py | 26 ++++++++++---------- src/mk/tools/make.py | 19 ++++++++------- src/mk/tools/node.py | 20 +++++++--------- src/mk/tools/pre_commit.py | 9 +++---- src/mk/tools/py_package.py | 23 ++++++++++-------- src/mk/tools/pytest.py | 9 +++---- src/mk/tools/shell.py | 11 +++++---- src/mk/tools/taskfile.py | 18 +++++++------- src/mk/tools/tox.py | 24 ++++++++++--------- 18 files changed, 214 insertions(+), 140 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3f6cf0a..81c98d1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,9 +2,14 @@ ci: skip: [markdownlint_docker] repos: + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: "v0.0.264" + hooks: + - id: ruff + args: [--fix, --exit-non-zero-on-fix] - repo: https://github.com/pre-commit/mirrors-prettier # keep it before yamllint - rev: v3.0.0-alpha.6 + rev: v3.0.0-alpha.9-for-vscode hooks: - id: prettier always_run: true @@ -16,13 +21,6 @@ repos: rev: v0.12.0 hooks: - id: markdownlint_docker - - repo: https://github.com/PyCQA/isort - rev: 5.12.0 - hooks: - - id: isort - args: - # https://github.com/pre-commit/mirrors-isort/issues/9#issuecomment-624404082 - - --filter-files - repo: https://github.com/psf/black rev: 23.3.0 hooks: @@ -39,24 +37,15 @@ repos: - id: check-merge-conflict - id: debug-statements language_version: python3 - - repo: https://github.com/pycqa/flake8.git - rev: 6.0.0 - hooks: - - id: flake8 - additional_dependencies: - - flake8-absolute-import - - flake8-black>=0.1.1 - - flake8-docstrings>=1.5.0 - language_version: python3 - repo: https://github.com/adrienverge/yamllint.git - rev: v1.30.0 + rev: v1.31.0 hooks: - id: yamllint files: \.(yaml|yml)$ types: [file, yaml] entry: yamllint --strict - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.1.1 + rev: v1.2.0 hooks: - id: mypy # mypy args needed in order to match mypy cli behavior diff --git a/pyproject.toml b/pyproject.toml index 1a538a3..58547fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -85,6 +85,13 @@ show_missing = true [tool.isort] profile = "black" +[tool.mypy] +python_version = 3.9 +strict = true +color_output = true +error_summary = true +no_incremental = true + [tool.pylint."MESSAGES CONTROL"] # increase from default is 50 which is too aggressive max-statements = 60 @@ -103,6 +110,41 @@ disable = [ "too-few-public-methods", ] +[tool.ruff] +ignore = [ + # temporary disabled until we fix them: + "ANN", + "B", + "T", + "D", + "E", + "PT", + "ERA", + "PTH", + "C901", + "ARG", + "FBT", + "SIM", + "PGH", + "TCH", + "PLR", + "INP", + "RET", +] +select = ["ALL"] +target-version = "py39" +# Same as Black. +line-length = 88 + +[tool.ruff.flake8-pytest-style] +parametrize-values-type = "tuple" + +[tool.ruff.isort] +known-first-party = ["mk"] + +[tool.ruff.per-file-ignores] +"test/**/*.py" = ["S"] + [tool.setuptools.dynamic] optional-dependencies.test = { file = [".config/requirements-test.txt"] } optional-dependencies.docs = { file = [".config/requirements-docs.txt"] } diff --git a/src/mk/__main__.py b/src/mk/__main__.py index deb61d3..187eb5c 100644 --- a/src/mk/__main__.py +++ b/src/mk/__main__.py @@ -1,10 +1,12 @@ """Main module.""" +from __future__ import annotations + import argparse import itertools import logging import os import shlex -from typing import Any, Dict, List +from typing import Any import typer from rich.console import Console @@ -14,7 +16,7 @@ from mk._typer import CustomTyper from mk.ctx import ctx -handlers: List[logging.Handler] +handlers: list[logging.Handler] console_err = Console(stderr=True) app = CustomTyper(width=console_err.width, rich_markup_mode="rich") @@ -24,7 +26,12 @@ else: level = logging.DEBUG handlers = [ - RichHandler(console=console_err, show_time=False, show_path=False, markup=False) + RichHandler( + console=console_err, + show_time=False, + show_path=False, + markup=False, + ), ] logging.basicConfig( @@ -37,7 +44,7 @@ def version_callback(value: bool) -> None: if value: typer.echo(f"mk {__version__}") - raise typer.Exit() + raise typer.Exit @app.callback(invoke_without_command=True) @@ -45,10 +52,17 @@ def main( click_ctx: typer.Context, # pylint: disable=unused-argument version: bool = typer.Option( - None, "--version", callback=version_callback, is_eager=True - ), # noqa: B008 + None, + "--version", + callback=version_callback, + is_eager=True, + ), verbose: int = typer.Option( - 0, "--verbose", "-v", count=True, help="Increase verbosity." + 0, + "--verbose", + "-v", + count=True, + help="Increase verbosity.", ), ) -> None: # enforce coloring because some tools like npm may auto disable it due to @@ -72,31 +86,38 @@ def commands() -> None: print(action.name) -def cli() -> None: +def cli() -> None: # pylint: disable=too-many-locals parser = argparse.ArgumentParser( description="Preprocess arguments to set log level.", add_help=False, ) parser.add_argument( - "-v", "--verbose", action="append_const", const=1, dest="verbosity" + "-v", + "--verbose", + action="append_const", + const=1, + dest="verbosity", ) opt, _ = parser.parse_known_args() opt.verbosity = 0 if opt.verbosity is None else sum(opt.verbosity) if opt.verbosity: log_level = logging.INFO if opt.verbosity == 1 else logging.DEBUG logging.getLogger().setLevel(log_level) - logging.log(level=log_level, msg=f"Reconfigured logging level to {log_level}") + msg = f"Reconfigured logging level to {log_level}" + logging.log(level=log_level, msg=msg) existing_commands = [] for command_info in app.registered_commands: command = typer.main.get_command_from_info( - command_info, pretty_exceptions_short=False, rich_markup_mode="rich" + command_info, + pretty_exceptions_short=False, + rich_markup_mode="rich", ) existing_commands.append(command.name) # command = get_command_from_info(command_info=command_info) - action_map: Dict[str, Any] = {} + action_map: dict[str, Any] = {} for action in ctx.runner.actions: # Currently we rename action that overlap but in the future we may # want to allow one to shadow others or we may want to chain them @@ -135,9 +156,9 @@ def cli() -> None: for x, action in action_map.items(): alias = x[0:alias_len] # pylint: disable=consider-iterating-dictionary - if alias in action_map.keys(): + if alias in action_map: continue - if sum(1 for name in action_map.keys() if name.startswith(alias)) == 1: + if sum(1 for name in action_map if name.startswith(alias)) == 1: app.command( name=alias, short_help=f"Alias for [dim]mk {x}[/dim]", diff --git a/src/mk/_typer.py b/src/mk/_typer.py index 3e858a2..d811972 100644 --- a/src/mk/_typer.py +++ b/src/mk/_typer.py @@ -29,5 +29,8 @@ def command( **kwargs, ): return super().command( - *args, cls=cls, context_settings=context_settings, **kwargs + *args, + cls=cls, + context_settings=context_settings, + **kwargs, ) diff --git a/src/mk/exec.py b/src/mk/exec.py index 0470c12..dd42b48 100644 --- a/src/mk/exec.py +++ b/src/mk/exec.py @@ -1,8 +1,9 @@ +from __future__ import annotations + import logging import subprocess import sys from os import environ -from typing import Dict, Optional import subprocess_tee @@ -18,9 +19,9 @@ def run( check=False, cwd=None, tee=False, - env_overrides: Optional[Dict[str, str]] = None, + env_overrides: dict[str, str] | None = None, ) -> subprocess.CompletedProcess: - env: Optional[Dict[str, str]] = None + env: dict[str, str] | None = None if env_overrides: env = environ.copy() env.update(env_overrides) @@ -49,7 +50,10 @@ def run_or_raise(*args, cwd=None, tee=False) -> subprocess.CompletedProcess: def run_or_fail( - *args, cwd=None, tee=False, env_overrides: Optional[Dict[str, str]] = None + *args, + cwd=None, + tee=False, + env_overrides: dict[str, str] | None = None, ) -> subprocess.CompletedProcess: try: return run(*args, check=True, cwd=cwd, tee=tee, env_overrides=env_overrides) diff --git a/src/mk/runner.py b/src/mk/runner.py index 132852d..72b3b57 100644 --- a/src/mk/runner.py +++ b/src/mk/runner.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import hashlib import logging import sys @@ -8,7 +10,7 @@ from cached_property import cached_property # type: ignore from pathlib import Path -from typing import TYPE_CHECKING, List, Optional +from typing import TYPE_CHECKING if TYPE_CHECKING: from mk.tools import Action @@ -21,7 +23,7 @@ class Runner: def __init__(self) -> None: - self.root: Optional[Path] = None + self.root: Path | None = None try: self.repo = Repo(".", search_parent_directories=True) except GitError: @@ -38,7 +40,7 @@ def __init__(self) -> None: self.root = Path(self.repo.working_dir) hash_key = f"{sys.version_info.major}{sys.version_info.minor}{self.root}" - self.hash = hashlib.sha1(hash_key.encode("UTF-8")).hexdigest()[:5] + self.hash = hashlib.sha256(hash_key.encode("UTF-8")).hexdigest()[:5] self.cache = Cache(f"~/.cache/mk.{self.hash}/") if self.repo.is_dirty(): @@ -52,7 +54,7 @@ def pm(self) -> pluggy.PluginManager: return pm @cached_property - def actions(self) -> List["Action"]: + def actions(self) -> list[Action]: """List of discovered actions.""" if not self.root: return [] diff --git a/src/mk/tools/__init__.py b/src/mk/tools/__init__.py index 3c3d2d6..5f23824 100644 --- a/src/mk/tools/__init__.py +++ b/src/mk/tools/__init__.py @@ -1,6 +1,8 @@ +from __future__ import annotations + from dataclasses import dataclass, field from pathlib import Path -from typing import Any, List, Optional +from typing import Any @dataclass(order=True) @@ -8,11 +10,11 @@ class Action: name: str _name: str = field(default="undefined", init=False, compare=True, repr=False) - tool: Optional["Tool"] = field(default=None, compare=False) - description: Optional[str] = field(default="...", compare=False) - cwd: Optional[str] = field(default=None, compare=False) - filename: Optional[str] = field(default=None, compare=False) - args: Optional[List[Any]] = field(default_factory=list, compare=False) + tool: Tool | None = field(default=None, compare=False) + description: str | None = field(default="...", compare=False) + cwd: str | None = field(default=None, compare=False) + filename: str | None = field(default=None, compare=False) + args: list[Any] | None = field(default_factory=list, compare=False) # https://github.com/florimondmanca/www/issues/102#issuecomment-817279834 @property # type: ignore @@ -39,13 +41,13 @@ def is_present(self, path: Path) -> bool: """Return True if the tool configuration is present in the given path.""" return False - def actions(self) -> List[Action]: + def actions(self) -> list[Action]: return [] - def run(self, action: Optional[Action] = None): + def run(self, action: Action | None = None): pass - def __repr__(self): + def __repr__(self) -> str: return self.name def __rich__(self): diff --git a/src/mk/tools/ansible.py b/src/mk/tools/ansible.py index acd8e17..16d172f 100644 --- a/src/mk/tools/ansible.py +++ b/src/mk/tools/ansible.py @@ -1,7 +1,8 @@ +from __future__ import annotations + import glob import os from pathlib import Path -from typing import List, Optional from mk.exec import run_or_fail from mk.tools import Action, Tool @@ -10,7 +11,7 @@ class AnsibleTool(Tool): name = "ansible" - def run(self, action: Optional[Action] = None): + def run(self, action: Action | None = None): if action and action.filename: run_or_fail( ["ansible-playbook", "-vv", action.filename], @@ -23,8 +24,8 @@ def is_present(self, path: Path) -> bool: return True return False - def actions(self) -> List[Action]: - actions: List[Action] = [] + def actions(self) -> list[Action]: + actions: list[Action] = [] for filename in glob.glob("playbooks/*.yml"): name = os.path.splitext(os.path.basename(filename))[0] actions.append( @@ -33,6 +34,6 @@ def actions(self) -> List[Action]: description=f"[dim]ansible-playbook {filename}[/dim]", tool=self, filename=filename, - ) + ), ) return actions diff --git a/src/mk/tools/cmake.py b/src/mk/tools/cmake.py index 7d38a1b..04819ff 100644 --- a/src/mk/tools/cmake.py +++ b/src/mk/tools/cmake.py @@ -1,8 +1,9 @@ +from __future__ import annotations + import logging import os import shutil from pathlib import Path -from typing import List, Optional from mk.exec import run_or_fail from mk.tools import Action, Tool @@ -11,12 +12,12 @@ class CMakeTool(Tool): name = "cmake" - def __init__(self): + def __init__(self) -> None: super().__init__(self) - self.configfile = None - self._is_present = None + self.configfile: str | None = None + self._is_present: bool | None = None - def run(self, action: Optional[Action] = None) -> None: + def run(self, action: Action | None = None) -> None: cmd = ["cmake"] if action: cmd.append(action.name) @@ -32,18 +33,18 @@ def is_present(self, path: Path) -> bool: self.configfile = configfile if not shutil.which("cmake"): logging.warning( - "Unable to find cmake tool. See https://cmake.org/download/" + "Unable to find cmake tool. See https://cmake.org/download/", ) self._is_present = False break logging.warning( - "cmake is not fully supported yet by mk. See https://github.com/pycontribs/mk/issues/135" + "cmake is not fully supported yet by mk. See https://github.com/pycontribs/mk/issues/135", ) self._is_present = True break - return self._is_present + return bool(self._is_present) - def actions(self) -> List[Action]: + def actions(self) -> list[Action]: actions = [] if self.is_present(Path(".")): actions.append(Action(name=".", tool=self, description="Run 'cmake .'")) diff --git a/src/mk/tools/git.py b/src/mk/tools/git.py index 8d3dd79..d44967f 100644 --- a/src/mk/tools/git.py +++ b/src/mk/tools/git.py @@ -5,7 +5,6 @@ import shutil import sys from pathlib import Path -from typing import List, Optional from mk.ctx import ctx from mk.exec import fail, run_or_fail @@ -15,14 +14,15 @@ class GitTool(Tool): name = "git" - def __init__(self): + def __init__(self) -> None: super().__init__(self) - def run(self, action: Optional[Action] = None) -> None: + def run(self, action: Action | None = None) -> None: if action and action.name == "up": self.up() else: - raise NotImplementedError(f"Action {action} is not supported.") + msg = f"Action {action} is not supported." + raise NotImplementedError(msg) def is_present(self, path: Path) -> bool: if not shutil.which("gh"): @@ -30,8 +30,8 @@ def is_present(self, path: Path) -> bool: return False return True - def actions(self) -> List[Action]: - actions: List[Action] = [] + def actions(self) -> list[Action]: + actions: list[Action] = [] if ctx.runner.branch not in ["main", "master"]: if self.is_present(self.path): actions.append( @@ -39,11 +39,11 @@ def actions(self) -> List[Action]: name="up", description="Upload current change by creating or updating a CR/PR.", tool=self, - ) + ), ) else: logging.info( - "Not adding 'up' action as it does not work when current branch is main/master" + "Not adding 'up' action as it does not work when current branch is main/master", ) return actions @@ -68,7 +68,7 @@ def up(self): remotes = {r.name for r in repo.remotes} if not {"origin", "upstream"}.issubset(remotes): logging.debug( - "Assuring you have two remotes, your fork as [blue]origin[/] and [blue]upstream[/]" + "Assuring you have two remotes, your fork as [blue]origin[/] and [blue]upstream[/]", ) run_or_fail(["gh", "repo", "fork", "--remote=true"], tee=True) remotes = {r.name for r in repo.remotes} @@ -82,7 +82,8 @@ def up(self): logging.debug("Doing a git push") run_or_fail( - ["git", "push", "--force-with-lease", "-u", "origin", "HEAD"], tee=False + ["git", "push", "--force-with-lease", "-u", "origin", "HEAD"], + tee=False, ) # github for the moment @@ -91,7 +92,7 @@ def up(self): # --web option is of not use because it happens too soon, confusing github logging.debug("Tryging to detect if there are existing PRs open") result = run_or_fail( - ["gh", "pr", "list", "-S", f"head:{repo.active_branch}"] + ["gh", "pr", "list", "-S", f"head:{repo.active_branch}"], ) if result.returncode == 0: pr_list = [] @@ -118,7 +119,8 @@ def up(self): logging.debug(result.stdout) elif len(pr_list) == 1: logging.debug( - "PR #%s already exists, no need to create new one.", pr_list[0] + "PR #%s already exists, no need to create new one.", + pr_list[0], ) else: logging.warning( diff --git a/src/mk/tools/make.py b/src/mk/tools/make.py index a5e91a0..d1753c5 100644 --- a/src/mk/tools/make.py +++ b/src/mk/tools/make.py @@ -1,7 +1,8 @@ +from __future__ import annotations + import os import re from pathlib import Path -from typing import List, Optional from mk.exec import run_or_fail from mk.tools import Action, Tool @@ -10,11 +11,11 @@ class MakeTool(Tool): name = "make" - def __init__(self): + def __init__(self) -> None: super().__init__(self) - self.makefile = None + self.makefile: str | None = None - def run(self, action: Optional[Action] = None) -> None: + def run(self, action: Action | None = None) -> None: cmd = ["make"] if action: cmd.append(action.name) @@ -28,10 +29,12 @@ def is_present(self, path: Path) -> bool: return True return False - def actions(self) -> List[Action]: + def actions(self) -> list[Action]: actions = [] - - with open(self.makefile, "r", encoding="utf-8") as file: + if not self.makefile: + msg = "Makefile not found" + raise RuntimeError(msg) + with open(self.makefile, encoding="utf-8") as file: for line in file.readlines(): # Current implementation assumes that descriptions are added # using double ## after the target name. @@ -40,6 +43,6 @@ def actions(self) -> List[Action]: if match: target, description = match.groups() actions.append( - Action(name=target, tool=self, description=description) + Action(name=target, tool=self, description=description), ) return actions diff --git a/src/mk/tools/node.py b/src/mk/tools/node.py index f7107e3..a5fe7d8 100644 --- a/src/mk/tools/node.py +++ b/src/mk/tools/node.py @@ -1,6 +1,7 @@ +from __future__ import annotations + import json from pathlib import Path -from typing import List, Optional from mk.exec import run, run_or_fail from mk.tools import Action, Tool @@ -16,20 +17,17 @@ def __init__(self, path=".") -> None: if result.returncode != 0 or not result.stdout: self.present = False return - self._actions: List[Action] = [] + self._actions: list[Action] = [] for line in result.stdout.split(): # we consider only up to one level deep files if line.count("/") > 1: continue parts = line.split("/") - if len(parts) == 1: - cwd = None - else: - cwd = parts[0] - with open(line, "r", encoding="utf-8") as package_json: + cwd = None if len(parts) == 1 else parts[0] + with open(line, encoding="utf-8") as package_json: data = json.load(package_json) if "scripts" in data: - for k in data["scripts"].keys(): + for k in data["scripts"]: self._actions.append( Action( name=k, @@ -37,17 +35,17 @@ def __init__(self, path=".") -> None: description=data["scripts"][k], args=[k], cwd=cwd, - ) + ), ) self.present = bool(self._actions) def is_present(self, path: Path) -> bool: return self.present - def actions(self) -> List[Action]: + def actions(self) -> list[Action]: return self._actions - def run(self, action: Optional[Action] = None) -> None: + def run(self, action: Action | None = None) -> None: if not action: cmd = ["npm", "run"] cwd = None diff --git a/src/mk/tools/pre_commit.py b/src/mk/tools/pre_commit.py index 76092e0..9be82eb 100644 --- a/src/mk/tools/pre_commit.py +++ b/src/mk/tools/pre_commit.py @@ -1,6 +1,7 @@ +from __future__ import annotations + import os from pathlib import Path -from typing import List, Optional from mk.exec import run_or_fail from mk.tools import Action, Tool @@ -9,7 +10,7 @@ class PreCommitTool(Tool): name = "pre-commit" - def run(self, action: Optional[Action] = None): + def run(self, action: Action | None = None): run_or_fail(["pre-commit", "run", "-a"], tee=True) def is_present(self, path: Path) -> bool: @@ -17,7 +18,7 @@ def is_present(self, path: Path) -> bool: return True return False - def actions(self) -> List[Action]: + def actions(self) -> list[Action]: return [ - Action(name="lint", description="[dim]pre-commit run -a[/dim]", tool=self) + Action(name="lint", description="[dim]pre-commit run -a[/dim]", tool=self), ] diff --git a/src/mk/tools/py_package.py b/src/mk/tools/py_package.py index 93a1d5a..a3807d2 100644 --- a/src/mk/tools/py_package.py +++ b/src/mk/tools/py_package.py @@ -1,7 +1,8 @@ +from __future__ import annotations + import logging import sys from pathlib import Path -from typing import List, Optional from mk.exec import run_or_fail from mk.loaders import load_toml @@ -17,7 +18,7 @@ def __init__(self) -> None: super().__init__(self) self.pkg_name = "" - def run(self, action: Optional[Action] = None) -> None: + def run(self, action: Action | None = None) -> None: if not action: return if action.name in ["build", "install", "uninstall"]: @@ -25,7 +26,8 @@ def run(self, action: Optional[Action] = None) -> None: run_or_fail(cmd, tee=True) run_or_fail(f"{sys.executable} -m twine check dist/*", tee=True) else: - raise NotImplementedError(f"Action {action.name} not implemented") + msg = f"Action {action.name} not implemented" + raise NotImplementedError(msg) def is_present(self, path: Path) -> bool: data = load_toml(path / "pyproject.toml") @@ -40,7 +42,8 @@ def is_present(self, path: Path) -> bool: if not self.pkg_name: if (path / "setup.py").exists(): self.pkg_name = run_or_fail( - [sys.executable, "setup.py", "--name"], tee=False + [sys.executable, "setup.py", "--name"], + tee=False, ).stdout.strip() return True if (path / "setup.cfg").exists(): @@ -48,8 +51,8 @@ def is_present(self, path: Path) -> bool: return False return True - def actions(self) -> List[Action]: - actions: List[Action] = [] + def actions(self) -> list[Action]: + actions: list[Action] = [] if not self.is_present(Path(".")): return actions @@ -59,7 +62,7 @@ def actions(self) -> List[Action]: tool=self, description="Use pip to install the current package for current user.", args=["pip3", "install", "-e", "."], - ) + ), ) if self.pkg_name: actions.append( @@ -68,7 +71,7 @@ def actions(self) -> List[Action]: tool=self, description="Use pip to uninstall the current package.", args=["pip3", "uninstall", self.pkg_name], - ) + ), ) try: # pylint: disable=import-outside-toplevel,unused-import @@ -88,11 +91,11 @@ def actions(self) -> List[Action]: "--outdir", "dist", ], - ) + ), ) except ImportError: logging.warning( - "Python 'build' package not found, unable to provide build action." + "Python 'build' package not found, unable to provide build action.", ) return actions diff --git a/src/mk/tools/pytest.py b/src/mk/tools/pytest.py index a5ad5d4..85f2ec7 100644 --- a/src/mk/tools/pytest.py +++ b/src/mk/tools/pytest.py @@ -1,7 +1,8 @@ +from __future__ import annotations + import os import sys from pathlib import Path -from typing import List, Optional from mk.exec import run_or_fail from mk.loaders import load_toml @@ -16,7 +17,7 @@ class PyTestTool(Tool): def __init__(self) -> None: super().__init__(self) - def run(self, action: Optional[Action] = None) -> None: + def run(self, action: Action | None = None) -> None: if not action: return if action.name == "test": @@ -32,14 +33,14 @@ def is_present(self, path: Path) -> bool: return True return False - def actions(self) -> List[Action]: + def actions(self) -> list[Action]: actions = [] actions.append( Action( name="test", tool=self, description="Run pytest", - ) + ), ) return actions diff --git a/src/mk/tools/shell.py b/src/mk/tools/shell.py index 45902bd..8f12d31 100644 --- a/src/mk/tools/shell.py +++ b/src/mk/tools/shell.py @@ -1,7 +1,8 @@ +from __future__ import annotations + import glob import os from pathlib import Path -from typing import List, Optional from mk.exec import run_or_fail from mk.tools import Action, Tool @@ -10,15 +11,15 @@ class ShellTool(Tool): name = "shell" - def run(self, action: Optional[Action] = None) -> None: + def run(self, action: Action | None = None) -> None: if action and action.filename: run_or_fail(f"./{action.filename}", tee=True) def is_present(self, path: Path) -> bool: return True - def actions(self) -> List[Action]: - actions: List[Action] = [] + def actions(self) -> list[Action]: + actions: list[Action] = [] exclude_list = ["setup.py"] for filename in [ *glob.glob("*"), @@ -39,6 +40,6 @@ def actions(self) -> List[Action]: description=f"[dim]./{filename}[/dim]", tool=self, filename=filename, - ) + ), ) return actions diff --git a/src/mk/tools/taskfile.py b/src/mk/tools/taskfile.py index dba16ad..8b915be 100644 --- a/src/mk/tools/taskfile.py +++ b/src/mk/tools/taskfile.py @@ -1,10 +1,11 @@ +from __future__ import annotations + import json import logging import os import shutil import sys from pathlib import Path -from typing import List, Optional from mk.exec import run_or_fail from mk.tools import Action, Tool @@ -25,14 +26,14 @@ def is_present(self, path: Path) -> bool: self.executable = shutil.which("taskfile") or shutil.which("task") or "" if not self.executable: logging.error( - "taskfile.yml config found but the tool is not installed. See https://taskfile.dev/installation/" + "taskfile.yml config found but the tool is not installed. See https://taskfile.dev/installation/", ) sys.exit(1) return True return False - def actions(self) -> List[Action]: - actions: List[Action] = [] + def actions(self) -> list[Action]: + actions: list[Action] = [] tasks_json = ( run_or_fail( ["task", "--list", "--json"], @@ -51,13 +52,10 @@ def actions(self) -> List[Action]: tool=self, description=desc, args=[task["name"]], - ) + ), ) return actions - def run(self, action: Optional[Action] = None) -> None: - if not action: - cmd = ["task"] - else: - cmd = ["task", action.name] + def run(self, action: Action | None = None) -> None: + cmd = ["task"] if not action else ["task", action.name] run_or_fail(cmd, tee=True) diff --git a/src/mk/tools/tox.py b/src/mk/tools/tox.py index 8f191ee..b8ef70b 100644 --- a/src/mk/tools/tox.py +++ b/src/mk/tools/tox.py @@ -1,4 +1,6 @@ """Implementation of the tox tool support.""" +from __future__ import annotations + import logging import os import re @@ -6,7 +8,6 @@ import sys from configparser import ConfigParser, ParsingError from pathlib import Path -from typing import List, Optional from mk.exec import run_or_fail from mk.text import strip_ansi_escape @@ -21,9 +22,9 @@ def is_present(self, path: Path) -> bool: return True return False - def actions(self) -> List[Action]: + def actions(self) -> list[Action]: # -a is not supported by tox4! - actions: List[Action] = [] + actions: list[Action] = [] cp = ConfigParser(strict=False, interpolation=None) env_overrides = {"PY_COLORS": "0"} result = run_or_fail( @@ -36,7 +37,10 @@ def actions(self) -> List[Action]: # workaround for https://github.com/tox-dev/tox/issues/2030 # we remove all lines starting with .tox from output tox_cfg = re.sub( - r"^\.tox[^\r\n]*\n$", "", strip_ansi_escape(tox_cfg), re.MULTILINE + r"^\.tox[^\r\n]*\n$", + "", + strip_ansi_escape(tox_cfg), + re.MULTILINE, ) # now tox_cfg should have a valid ini content @@ -44,7 +48,8 @@ def actions(self) -> List[Action]: cp.read_string(tox_cfg) except ParsingError: logging.fatal( - "Unable to parse tox output from command: %s", shlex.join(result.args) + "Unable to parse tox output from command: %s", + shlex.join(result.args), ) print(tox_cfg, file=sys.stderr) sys.exit(22) @@ -59,14 +64,11 @@ def actions(self) -> List[Action]: tool=self, description=cp[section]["description"], args=[env_name], - ) + ), ) return actions - def run(self, action: Optional[Action] = None) -> None: - if not action: - cmd = ["tox"] - else: - cmd = ["tox", "-e", action.name] + def run(self, action: Action | None = None) -> None: + cmd = ["tox"] if not action else ["tox", "-e", action.name] run_or_fail(cmd, tee=True)