Research: Python type-annotations audit (2026-05-30)¶
- Status: Active
- Workstream: type-annotations cleanup (no associated ADR — chore-class PR)
- Last updated: 2026-05-30
- Author: scheduled-agent (chore/python-type-annotations-audit)
Why¶
The fork has ~150 KLOC of Python across four trees (ai/src, mcp-server/vmaf-mcp/src, tools/vmaf-tune/src, plus the upstream-mirrored compat/python-vmaf). None of the fork-local trees had ever been audited under mypy --strict; the project-root pyproject.toml already declares strict = true but only when invoked via mypy against explicit packages, never as a CI gate. This created two classes of risk:
- Latent real bugs masked by missing or loose annotations (the
dictwithout parameters, theobjectfallback when the real type is known, the# type: ignorethat drifted past its expiry). - Maintenance friction — every new contributor has to re-derive the implicit shape of a parameter from call-site reading.
How (the audit method)¶
For each fork-local package:
# ai/src — namespace packages under src-layout
MYPYPATH=ai/src mypy --strict --explicit-package-bases \
-p vmaf_train -p corpus -p aiutils
# mcp-server
MYPYPATH=mcp-server/vmaf-mcp/src mypy --strict --explicit-package-bases \
-p vmaf_mcp
# vmaf-tune
MYPYPATH=tools/vmaf-tune/src mypy --strict --explicit-package-bases \
-p vmaftune
compat/python-vmaf/ is upstream-mirrored and already covered by the project-root [[tool.mypy.overrides]] block module = "vmaf.*" → ignore_errors = true. Out of scope for this audit (the rule predates the fork — see root pyproject.toml).
Findings (per package, baseline → after)¶
| Package | Errors baseline | Errors after | Δ |
|---|---|---|---|
ai/src/vmaf_train,corpus,aiutils | 7 | 0 | -7 |
mcp-server/vmaf-mcp/src/vmaf_mcp | 16 | 0 | -16 |
tools/vmaf-tune/src/vmaftune | 261 | 196 | -65 |
The vmaf-tune residue is dominated by three families that need follow-up PRs scoped per-family (this PR does not bundle them to keep the diff reviewable):
- CodecAdapter Protocol mismatch (~17
[dict-item]errors incodec_adapters/__init__.py) — every concrete adapter fails theProtocolmembership check; root cause is likely a method-signature drift between the Protocol and at least one concrete adapter. Requires a per-adapter signature audit, not a localized fix. cli.pymega-file (67 errors, most[no-untyped-def]) — the CLI entry-point has ~30 internal helper functions that lack annotations. Better tackled as its own follow-up since cli.py is 4500+ LOC and the helpers are tightly coupled.predictor_train.py(25 errors) — PyTorch / numpy interop that needs careful per-call review.
Bugs surfaced (and fixed)¶
-
Duplicate
_run_benchmarkdefinition (mcp-server/vmaf-mcp/src/vmaf_mcp/server.py). Twoasync def _run_benchmark(...)definitions co-existed in the same module — one at line 720 (older, noprogress_tokenparameter), one at line 1295 (newer, with progress notifications per ADR-0608). Python silently rebinds the symbol to the later definition at import time, so the older copy was unreachable dead code yet future edits to it would be invisible at runtime. The fix replaces the dead copy with aNOTEcomment pointing at the live implementation. -
seen: set[str]actually holding tuples (mcp-server/vmaf-mcp/src/vmaf_mcp/server.py,list_extractors). Declaredset[str]but populated with(sym, name)tuples; the containment checkif key in seenworked at runtime because Python doesn't check element types, but every mypy run would have caught this before commit. Fixed toset[tuple[str, str]]. -
Loop-variable shadowing in
report.py(tools/vmaf-tune/src/vmaftune/report.py, ABR-ladder block).for r in data.ladder_rungsreused the namerfrom a previousfor r in data.codec_rowsloop further up the same function; mypy still trackedrasCodecRowand flagged everyLadderRung-specific attribute access (r.width,r.height,r.crf,r.vmaf). Renamed torung.
Bugs surfaced but NOT fixed in this PR¶
- CodecAdapter Protocol mismatch (above) — needs a dedicated follow-up PR.
cli.py:4569,4583,4606— threeArgument 1 to "int" has incompatible type "Any | int | None"errors. Each call site takes a CLI-parsed value that should have been validated upstream; the fix is per-call-site and benefits from a single reviewer pass overcli.pyas a whole.- HTTP transport pre-existing failure in
mcp-server/vmaf-mcp/src/vmaf_mcp/http_transport.py::_handle_metrics— uses an aiohttp API surface that newer aiohttp rejects (charset must not be in content_type argument). Unrelated to type annotations; flagged for a follow-up.
Reproducer¶
# Re-run the audit from a clean worktree:
MYPYPATH=ai/src mypy --strict --explicit-package-bases \
-p vmaf_train -p corpus -p aiutils
MYPYPATH=mcp-server/vmaf-mcp/src mypy --strict --explicit-package-bases \
-p vmaf_mcp
MYPYPATH=tools/vmaf-tune/src mypy --strict --explicit-package-bases \
-p vmaftune
Follow-ups¶
- Per-adapter
CodecAdapterProtocol audit (separate PR). tools/vmaf-tune/src/vmaftune/cli.pyannotation pass (separate PR).tools/vmaf-tune/src/vmaftune/predictor_train.pynumpy/torch interop typing (separate PR).- HTTP transport aiohttp compatibility fix (separate PR, unrelated to the type audit).
- Wire
mypy --strictinto CI for the three fork-local trees so fresh regressions are caught at PR time.