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:
Chuck
2026-09-28 15:01:35 -04:00
committed by GitHub
co-authored by Claude Opus 5.5
parent e6e0a16140
commit b8c01c69fb
42 changed files with 372 additions and 104 deletions
+4 -4
View File
@@ -7,7 +7,7 @@ plugin discovery / manifest / config-default logic lives in exactly one place.
import json
from pathlib import Path
from typing import Any, Dict, Optional, Sequence, Union
from typing import Any, Dict, Optional, Sequence, Union, cast
def find_plugin_dir(plugin_id: str, search_dirs: Sequence[Union[str, Path]]) -> Optional[Path]:
@@ -39,7 +39,7 @@ def load_manifest(plugin_dir: Union[str, Path]) -> Dict[str, Any]:
if not manifest_path.exists():
raise FileNotFoundError(f"No manifest.json in {plugin_dir}")
with open(manifest_path, 'r', encoding='utf-8') as f:
return json.load(f)
return cast(Dict[str, Any], json.load(f))
def merge_config(base: Dict[str, Any], override: Dict[str, Any]) -> Dict[str, Any]:
@@ -64,7 +64,7 @@ def load_schema(plugin_dir: Union[str, Path]) -> Optional[Dict[str, Any]]:
if not schema_path.exists():
return None
with open(schema_path, 'r', encoding='utf-8') as f:
return json.load(f)
return cast(Optional[Dict[str, Any]], json.load(f))
def load_config_defaults(plugin_dir: Union[str, Path]) -> Dict[str, Any]:
@@ -124,7 +124,7 @@ def load_harness_spec(plugin_dir: Union[str, Path]) -> Dict[str, Any]:
if not spec_path.exists():
return {}
with open(spec_path, 'r', encoding='utf-8') as f:
spec = json.load(f)
spec: Dict[str, Any] = json.load(f)
# Resolve mock_data path and inline its contents for convenience.
mock_rel = spec.get('mock_data')
+5 -5
View File
@@ -6,7 +6,7 @@ and plugin_manager for use in plugin unit tests.
"""
import warnings
from typing import Dict, Any, Optional
from typing import Dict, Any, List, Optional
from PIL import Image
#: Why draw_image() warns. Kept (rather than removed) so existing plugin test
@@ -32,7 +32,7 @@ class MockDisplayManager:
self.image = Image.new('RGB', (width, height), color=(0, 0, 0))
self.clear_called = False
self.update_called = False
self.draw_calls = []
self.draw_calls: List[Dict[str, Any]] = []
def clear(self):
"""Clear the display."""
@@ -43,7 +43,7 @@ class MockDisplayManager:
"""Update the display."""
self.update_called = True
def draw_text(self, text: str, x: int = None, y: int = None, color: tuple = (255, 255, 255),
def draw_text(self, text: str, x: Optional[int] = None, y: Optional[int] = None, color: tuple = (255, 255, 255),
font=None, small_font: bool = False, centered: bool = False):
"""Draw text on the display.
@@ -166,8 +166,8 @@ class MockConfigManager:
def __init__(self, config: Optional[Dict[str, Any]] = None):
self._config = config or {}
self.load_config_calls = []
self.save_config_calls = []
self.load_config_calls: List[Dict[str, Any]] = []
self.save_config_calls: List[Dict[str, Any]] = []
def load_config(self) -> Dict[str, Any]:
"""Load configuration."""