Skip to content

Commit

Permalink
🔧 Make linting stricter
Browse files Browse the repository at this point in the history
  • Loading branch information
spapanik committed Dec 16, 2024
1 parent 5bf8643 commit 16695b3
Show file tree
Hide file tree
Showing 10 changed files with 239 additions and 191 deletions.
93 changes: 28 additions & 65 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ classifiers = [
requires-python = ">=3.9"
dependencies = [
"dj_settings~=6.0",
"pyutilkit~=0.5",
"pyutilkit~=0.10",
]

[project.urls]
Expand All @@ -55,6 +55,7 @@ lint = [
"black~=24.10",
"mypy~=1.13",
"ruff~=0.8",
"typing-extensions~=4.12", # upgrade: py3.10: use typing module
]
test = [
"pytest~=8.3",
Expand All @@ -78,7 +79,11 @@ target-version = [

[tool.mypy]
check_untyped_defs = true
disallow_any_decorated = true
disallow_any_explicit = true
disallow_any_expr = false # many builtins are Any
disallow_any_generics = true
disallow_any_unimported = true
disallow_incomplete_defs = true
disallow_subclassing_any = true
disallow_untyped_calls = true
Expand All @@ -87,13 +92,18 @@ disallow_untyped_defs = true
extra_checks = true
ignore_missing_imports = true
no_implicit_reexport = true
show_column_numbers = true
show_error_codes = true
strict_equality = true
warn_return_any = true
warn_redundant_casts = true
warn_return_any = true
warn_unused_configs = true
warn_unused_ignores = true
warn_unreachable = true
warn_unused_configs = true

[[tool.mypy.overrides]]
module = "tests.*"
disallow_any_decorated = false # mock.MagicMock is Any

[tool.ruff]
src = [
Expand All @@ -103,73 +113,23 @@ target-version = "py39"

[tool.ruff.lint]
select = [
"A",
"ANN",
"ARG",
"ASYNC",
"B",
"BLE",
"C4",
"COM",
"DTZ",
"E",
"EM",
"ERA",
"EXE",
"F",
"FA",
"FBT",
"FIX",
"FLY",
"FURB",
"G",
"I",
"ICN",
"INP",
"ISC",
"LOG",
"N",
"PGH",
"PERF",
"PIE",
"PL",
"PT",
"PTH",
"PYI",
"Q",
"RET",
"RSE",
"RUF",
"S",
"SIM",
"SLF",
"SLOT",
"T10",
"TCH",
"TD",
"TID",
"TRY",
"UP",
"W",
"YTT",
"ALL",
]
ignore = [
"ANN401",
"COM812",
"E501",
"FIX002",
"PLR09",
"TD002",
"TD003",
"TRY003",
"C901", # Adding a limit to complexity is too arbitrary
"COM812", # Avoid magic trailing commas
"D10", # Not everything needs a docstring
"D203", # Prefer `no-blank-line-before-class` (D211)
"D213", # Prefer `multi-line-summary-first-line` (D212)
"E501", # Avoid clashes with black
"PLR09", # Adding a limit to complexity is too arbitrary
]

[tool.ruff.lint.per-file-ignores]
"tests/**" = [
"FBT001",
"PLR2004",
"PT011",
"S101",
"FBT001", # Test arguments are handled by pytest
"PLR2004", # Tests should contain magic number comparisons
"S101", # Pytest needs assert statements
]

[tool.ruff.lint.flake8-tidy-imports]
Expand All @@ -196,12 +156,15 @@ source = [
"src/",
]
data_file = ".cov_cache/coverage.dat"
omit = [
"src/yamk/lib/types.py",
]

[tool.coverage.report]
exclude_also = [
"if TYPE_CHECKING:",
]
fail_under = 85
fail_under = 90
precision = 2
show_missing = true
skip_covered = true
Expand Down
63 changes: 35 additions & 28 deletions src/yamk/command/make.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
from __future__ import annotations

import itertools
import os
import pathlib
import re
import subprocess
import sys
from time import sleep
from typing import TYPE_CHECKING, Any, cast
from typing import TYPE_CHECKING, cast

from dj_settings import ConfigParser
from pyutilkit.term import SGRCodes, SGRString
from pyutilkit.term import SGRCodes, SGROutput, SGRString
from pyutilkit.timing import Stopwatch

from yamk.__version__ import __version__
Expand All @@ -29,13 +30,13 @@
import argparse
from collections.abc import Iterator

from yamk.lib.types import ExistenceCheck
from yamk.lib.types import ExistenceCheck, RawRecipe


class MakeCommand:
def __init__(self, args: argparse.Namespace) -> None:
self.verbosity = args.verbosity
self.regex_recipes: dict[str, Recipe] = {}
self.regex_recipes: dict[re.Pattern[str], Recipe] = {}
self.static_recipes: dict[str, Recipe] = {}
self.aliases: dict[str, str] = {}
self.target = args.target
Expand Down Expand Up @@ -106,7 +107,7 @@ def _run_command(self, command: str) -> int:

if i != self.retries:
a, b = b, a + b
print(f"{command} failed. Retrying in {a}s...")
SGRString(f"{command} failed. Retrying in {a}s...").print()
sleep(a)

report = CommandReport(
Expand All @@ -129,7 +130,7 @@ def _check_command(self, check: ExistenceCheck) -> bool:
return False
return result.returncode == check["returncode"]

def _parse_recipes(self, parsed_cookbook: dict[str, dict[str, Any]]) -> None:
def _parse_recipes(self, parsed_cookbook: dict[str, RawRecipe]) -> None:
for target, raw_recipe in parsed_cookbook.items():
recipe = Recipe(
target,
Expand All @@ -141,11 +142,11 @@ def _parse_recipes(self, parsed_cookbook: dict[str, dict[str, Any]]) -> None:
)

if recipe.alias:
self.aliases[recipe.target] = recipe.alias
self.aliases[cast(str, recipe.target)] = recipe.alias
elif recipe.regex:
self.regex_recipes[recipe.target] = recipe
self.regex_recipes[cast(re.Pattern[str], recipe.target)] = recipe
else:
self.static_recipes[recipe.target] = recipe
self.static_recipes[cast(str, recipe.target)] = recipe

def _preprocess_target(self) -> DAG:
recipe = self._extract_recipe(self.target, use_extra=True)
Expand All @@ -170,7 +171,7 @@ def _preprocess_target(self) -> DAG:
msg = f"No recipe to build {requirement}"
raise ValueError(msg)
else:
requirement = recipe.target
requirement = cast(str, recipe.target)
target_recipe.requires[index] = requirement

if requirement in dag:
Expand All @@ -188,13 +189,17 @@ def _preprocess_target(self) -> DAG:
dag.sort()
self._mark_unchanged(dag)
if self.verbosity > 3: # noqa: PLR2004
print("=== all targets ===")
SGRString("=== all targets ===").print()
for node in dag:
print(f"- {node.target}:")
print(f" timestamp: {human_readable_timestamp(node.timestamp)}")
print(f" should_build: {node.should_build}")
print(f" requires: {node.requires}")
print(f" required_by: {node.required_by}")
SGROutput(
[
f"- {node.target}:",
f" timestamp: {human_readable_timestamp(node.timestamp)}",
f" should_build: {node.should_build}",
f" requires: {node.requires}",
f" required_by: {node.required_by}",
]
).print(sep=os.linesep)
return dag

def _update_ts(self, node: Node) -> None:
Expand All @@ -207,7 +212,7 @@ def _update_ts(self, node: Node) -> None:
self.phony_dir.mkdir(exist_ok=True)
path.touch()
if not recipe.phony and recipe.update:
pathlib.Path(recipe.target).touch()
pathlib.Path(cast(str, recipe.target)).touch()

def _make_target(self, node: Node) -> None:
recipe = node.recipe
Expand All @@ -216,7 +221,7 @@ def _make_target(self, node: Node) -> None:
raise ValueError(msg)

if self.verbosity > 1:
print(f"=== target: {recipe.target} ===")
SGRString(f"=== target: {recipe.target} ===").print()

n = len(recipe.commands)
for i, raw_command in enumerate(recipe.commands):
Expand All @@ -236,7 +241,7 @@ def _make_target(self, node: Node) -> None:
print_reports(self.reports)
sys.exit(return_code)
if i != n - 1:
print()
SGRString("").print()
self._update_ts(node)

def _extract_recipe(self, target: str, *, use_extra: bool = False) -> Recipe | None:
Expand Down Expand Up @@ -287,7 +292,7 @@ def _path_exists(self, node: Node) -> bool:
if not recipe.exists_only:
msg = "Existence commands need exists_only"
raise ValueError(msg)
return self._check_command(recipe.existence_check)
return self._check_command(recipe.existence_check) # type: ignore[arg-type]

return path.exists()

Expand Down Expand Up @@ -336,18 +341,20 @@ def _print_reasons(self, recipe: Recipe, options: set[str]) -> Iterator[bool]:
yield self.echo_override

def _print_command(self, command: str) -> None:
bold_command = SGRString(command, params=[SGRCodes.BOLD])
print(f"🔧 Running `{bold_command}`")
SGROutput(
["🔧 Running ", "`", SGRString(command, params=[SGRCodes.BOLD]), "`"]
).print()

def _print_result(self, command: str, return_code: int) -> None:
bold_command = SGRString(command, params=[SGRCodes.BOLD])
if return_code:
prefix = "❌"
suffix = f"failed with exit code {return_code}"
prefix = "❌ "
suffix = f" failed with exit code {return_code}"
else:
prefix = "✅"
suffix = "run successfully!"
print(f"{prefix} `{bold_command}` {suffix}")
prefix = "✅ "
suffix = " run successfully!"
SGROutput(
[prefix, "`", SGRString(command, params=[SGRCodes.BOLD]), "`", suffix]
).print()

def _get_version(self) -> Version:
return Version.from_string(self.globals["version"])
31 changes: 19 additions & 12 deletions src/yamk/lib/functions.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
from __future__ import annotations

from functools import reduce
from typing import TYPE_CHECKING, Any, cast
from typing import TYPE_CHECKING, Any, Generic, TypeVar, cast

if TYPE_CHECKING:
from collections.abc import Iterable
from pathlib import Path

from yamk.lib.types import Pathlike
from yamk.lib.types import Comparable, Pathlike

S = TypeVar("S")
T = TypeVar("T")


class Function:
Expand All @@ -15,7 +19,7 @@ class Function:
def __init__(self, base_dir: Path) -> None:
self.base_dir = base_dir

def __call__(self, *args: Any, **kwargs: Any) -> Any:
def __call__(self, *args: Any, **kwargs: Any) -> Any: # type: ignore[misc] # noqa: ANN401
raise NotImplementedError


Expand All @@ -26,11 +30,11 @@ def __call__(self, pattern: str) -> list[str]:
return [path.as_posix() for path in self.base_dir.glob(pattern)]


class Sort(Function):
class Sort(Function, Generic[T]):
name = "sort"

def __call__(self, *args: Any) -> list[Any]:
return sorted(*args)
def __call__(self, args: Iterable[Comparable]) -> list[Comparable]:
return sorted(args)


class Exists(Function):
Expand Down Expand Up @@ -109,16 +113,16 @@ def __call__(self) -> str:
class FilterOut(Function):
name = "filter_out"

def __call__(self, odd: Any, obj: list[Any]) -> list[Any]:
def __call__(self, odd: T, obj: list[T]) -> list[T]:
return list(filter(lambda x: x != odd, obj))


class TernaryIf(Function):
name = "ternary_if"

def __call__(
self, condition: bool, if_true: Any, if_false: Any # noqa: FBT001
) -> Any:
self, condition: bool, if_true: S, if_false: T # noqa: FBT001
) -> S | T:
return if_true if condition else if_false


Expand All @@ -135,11 +139,14 @@ class Merge(Function):
name = "merge"

@staticmethod
def _as_list(obj: Any | list[Any]) -> list[Any]:
def _as_list(obj: T | list[T]) -> list[T]:
return obj if isinstance(obj, list) else [obj]

def __call__(self, *args: Any | list[Any]) -> list[Any]:
return reduce(lambda x, y: self._as_list(x) + self._as_list(y), args)
def _concat(self, x: T | list[T], y: T | list[T]) -> list[T]:
return self._as_list(x) + self._as_list(y)

def __call__(self, *args: T | list[T]) -> list[T]:
return reduce(self._concat, args) # type: ignore[arg-type]


functions = {function.name: function for function in Function.__subclasses__()}
Loading

0 comments on commit 16695b3

Please sign in to comment.