Skip to content

chore: check the Python code with mypy --strict and ruff - #99

Merged
cristiangirlea merged 4 commits into
mainfrom
chore/python-static-analysis
Oct 26, 2026
Merged

cristiangirlea merged 4 commits into
mainfrom
chore/python-static-analysis

Conversation

@cristiangirlea

Copy link
Copy Markdown
Owner

Fixes #77.

What

The PHP side has had PHPStan at level max; the Python worker and the MCP server had no static analysis. Now:

  • mypy --strict over worker/ and mcp_server/, and ruff check over worker/, mcp_server/, scripts/ and tests/, with pycodestyle's errors and warnings, pyflakes, bugbear and import order (E, W, F, B, I). Two rules are left out: E501, line length, so long literal data and messages aren't restyled, and B905, since the zips there pair sequences of equal length by construction. Configuration is in a new pyproject.toml, which holds tool settings only. The mcp SDK is marked third-party so ruff doesn't confuse it with tests/mcp.

  • Pinned versions with one hash per wheel (CPython 3.12, Linux x86_64) in requirements-dev.lock, from requirements-dev.txt (mypy 2.4.0, ruff 0.17.2). A network-enabled py-dev-fetcher service fetches them into .cache/ once. py-analysis then installs them, with the MCP server's own locked wheels so mypy can type-check it, into a tmpfs, and runs offline under the hardening block. docs/testing.md records the command that regenerates the lock.

  • A step in tests.yml's mcp job, so the required deterministic check covers it. The same two commands are in AGENTS.md, the check skill, .claude/settings.json (Bash and PowerShell twins), README's test layers and docs/testing.md. .gitattributes keeps the three new files out of the dist archive.

  • Every finding is fixed with annotations and small guards that keep behaviour: 377 from mypy and 18 from ruff before, none after, plus four ruff findings in measurement scripts that reached main while this branch was open. Three narrow # type: ignore[assignment]s carry their reasons:

    • Handler.setup's rfile wrapper, which forwards attributes rather than subclassing;
    • handle_one_request's per-request reset of command and path to None;
    • bridge_redact's tuple default, kept rather than a mutable list.

    One # noqa: F401 keeps MAX_DOCUMENTS imported in scripts/relevance_measure.py: its test reads the limit through the script. The ONNX imports stay inside their functions; mypy is told to ignore only numpy, onnxruntime and tokenizers.

Files

  • New: pyproject.toml, requirements-dev.txt, requirements-dev.lock.
  • Pipeline and docs: docker/compose.yaml, .github/workflows/tests.yml, .gitattributes, AGENTS.md, .claude/skills/check/SKILL.md, .claude/settings.json, README.md, docs/testing.md, CHANGELOG.md.
  • Annotations only: worker/ai_bridge/{jobs,log,metrics,server,tasks}.py, mcp_server/bridge_mcp/{__main__,index,protocol,tools}.py.
  • Ruff fixes: scripts/{mcp_evaluate,ner_measure,relevance_measure}.py, tests/mcp/test_index.py, tests/mcp_model_probe.py, tests/model_integration.py, tests/python/{test_jobs,test_server,test_workflows}.py.

Checks

The full command block of AGENTS.md, with the new steps, run as one command on this head (216b6f7: the slice commits merged with main at 61f9251) in a worktree, PHP 8.5:

  • --profile test config: ok
  • python-tests: Ran 406 tests, OK
  • php-tests: PASS: 148 PHP contract checks; php -l: no syntax errors
  • php-integration: PASS: 192 total PHP checks; integration: PASS
  • mcp-tests: Ran 125 tests, OK
  • py-dev-fetcher; py-analysis: "All checks passed!" and "Success: no issues found in 11 source files"
  • composer-install: ok
  • php-analysis: [OK] No errors; php-psr18-tests: PASS: 178
  • laravel-tests: 94 checks; symfony-tests: 96 checks; tls-check: 7 + 3 checks

The analysis was seen failing before the fixes: ruff 18, mypy 377 in 9 files. PHP 8.2, 8.3 and 8.4 are left to CI.

Review

Independent read-only review by codex (another model family). It went through every non-test hunk for behaviour hidden in "annotation-only" edits and found each one identical in behaviour:

  • the guards and assertions hold under the store's invariants and still behave with -O;
  • lambda to functools.partial keeps the bound arguments;
  • the B023 closures keep their binding;
  • ner_measure's from None only drops the chained traceback from the CLI's exit.

Its one blocking finding, that array.array[float] fails at import on Python 3.12, is wrong: Python 3.12 made array.array subscriptable. The pinned image (3.12.14) evaluates it, and the MCP suite, which imports that module, passes. Security: nothing open.

Considered, not done: Dependabot for requirements-dev.*. The supply-chain slice covers the runtime and MCP locks, and the dev tools never ship.

mypy --strict covers the worker and the MCP server, and ruff (pycodestyle,
pyflakes, bugbear and import order, without the line-length rule) covers
them plus the scripts and tests. Both are pinned by hash in
requirements-dev.lock, fetched once by py-dev-fetcher and run offline by
py-analysis in the MCP job, so the deterministic check requires them. The
findings are fixed with annotations and small guards that keep behaviour,
and three narrow ignores carry their reason. The worker still imports only
the standard library at run time, and the dev files stay out of the dist
archive.
@cristiangirlea
cristiangirlea merged commit 11ffa30 into main Oct 26, 2026
13 checks passed
@cristiangirlea
cristiangirlea deleted the chore/python-static-analysis branch October 26, 2026 11:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chore: mypy --strict and ruff over the worker and the MCP server in CI

1 participant