mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-04 14:25:08 +00:00
ci: mypy ratchet -- keep type-clean modules clean (71 modules, 536 -> 442 errors) (#661)
* ci: mypy ratchet -- keep type-clean modules clean mypy-clean.txt lists the 71 modules under src/ that type-check clean; scripts/check_types.py runs mypy (--follow-imports=silent) on exactly those files and fails on any error or a missing/unsorted/duplicate entry. A new "Type check (mypy ratchet)" CI job runs it with mypy 1.20.2 and pinned stubs; the manual pre-commit mypy hook now runs the same script (a local hook, so mypy sees the installed requirements like CI does). 35 modules were made clean with annotation-only fixes: hints, typing.cast, TYPE_CHECKING imports, implicit-Optional defaults made explicit, and annotations widened (never guards removed) where mypy called a defensive isinstance check unreachable. No runtime behaviour change. mypy.ini: numpy and orjson are treated as Any (follow_imports=skip, also for stubs). numpy 2.3+ stubs use 3.12 `type` statements that mypy won't parse at python_version 3.10, and orjson is optional, so seeing its stubs made the result depend on whether it was installed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: annotate check_types.py's list-form mypy subprocess Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Type-check the modules listed in mypy-clean.txt (the mypy ratchet).
|
||||
|
||||
Most of src/ still has mypy errors, so CI can't require a clean `mypy src`.
|
||||
Instead mypy-clean.txt lists the modules that *are* clean, and this script
|
||||
fails if any of them regresses. When you make another module clean, add it to
|
||||
the list; nothing ever comes off it.
|
||||
|
||||
Imports are followed silently: a listed module is checked against the types of
|
||||
everything it imports, but errors inside those imported modules are not
|
||||
reported, so a clean file isn't failed by an unlisted neighbour.
|
||||
|
||||
Usage:
|
||||
python scripts/check_types.py # check the listed modules
|
||||
python scripts/check_types.py --list # print the list and exit
|
||||
|
||||
Extra arguments after ``--`` are passed to mypy.
|
||||
Exit status: 0 clean, 1 mypy errors, 2 a bad list (missing file, duplicate,
|
||||
unsorted, or empty).
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import subprocess # nosec B404 - list-form argv only, no shell # nosemgrep
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
LIST_FILE = REPO_ROOT / "mypy-clean.txt"
|
||||
|
||||
|
||||
def read_list(path: Path = LIST_FILE) -> list:
|
||||
"""The listed paths, in file order, with comments and blank lines dropped."""
|
||||
entries = []
|
||||
for raw in path.read_text(encoding="utf-8").splitlines():
|
||||
line = raw.split("#", 1)[0].strip()
|
||||
if line:
|
||||
entries.append(line)
|
||||
return entries
|
||||
|
||||
|
||||
def list_problems(entries: list, root: Path = REPO_ROOT) -> list:
|
||||
"""Why the list can't be used as-is; empty when it is fine."""
|
||||
problems = []
|
||||
if not entries:
|
||||
problems.append(f"{LIST_FILE.name} lists no modules")
|
||||
seen = set()
|
||||
for entry in entries:
|
||||
if entry in seen:
|
||||
problems.append(f"listed twice: {entry}")
|
||||
seen.add(entry)
|
||||
if "\\" in entry:
|
||||
problems.append(f"use forward slashes: {entry}")
|
||||
elif not (root / entry).is_file():
|
||||
problems.append(f"listed but not found (renamed or deleted? update the list): {entry}")
|
||||
if entries != sorted(entries):
|
||||
problems.append(f"{LIST_FILE.name} is not sorted")
|
||||
return problems
|
||||
|
||||
|
||||
def main(argv=None) -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
|
||||
parser.add_argument("--list", action="store_true", help="print the listed modules and exit")
|
||||
parser.add_argument("mypy_args", nargs="*", help="extra mypy arguments (after --)")
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
entries = read_list()
|
||||
problems = list_problems(entries)
|
||||
if problems:
|
||||
for problem in problems:
|
||||
print(f"check_types: {problem}", file=sys.stderr)
|
||||
return 2
|
||||
if args.list:
|
||||
print("\n".join(entries))
|
||||
return 0
|
||||
|
||||
cmd = [
|
||||
sys.executable, "-m", "mypy",
|
||||
"--config-file", str(REPO_ROOT / "mypy.ini"),
|
||||
"--follow-imports=silent",
|
||||
*args.mypy_args,
|
||||
*entries,
|
||||
]
|
||||
print(f"check_types: mypy on {len(entries)} modules from {LIST_FILE.name}", flush=True)
|
||||
# This interpreter's mypy, fixed flags, and paths from the checked-in list.
|
||||
result = subprocess.run(cmd, cwd=REPO_ROOT) # nosec B603 - list-form argv, no shell # nosemgrep
|
||||
if result.returncode > 1: # mypy itself failed (bad config, crash)
|
||||
return result.returncode
|
||||
if result.returncode != 0:
|
||||
print(
|
||||
"check_types: a module on the mypy ratchet has type errors. Fix them "
|
||||
f"(annotation-only where possible) rather than taking it off {LIST_FILE.name}.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user