0015 — Ruff + mypy + pytest as the Python toolchain¶
- Status: Accepted
- Date: 2026-05-15
- Deciders: @sinidious
Context¶
A young Python project has to pick a linter, a formatter, a type checker, and a test runner. The cost of getting this wrong is not quality — it is friction. Tools that are slow, noisy, or configuration-heavy get disabled within months by a tired maintainer.
The Python ecosystem now has clear, fast defaults: ruff covers
linting and formatting in one binary, mypy (strict) remains the
gold standard for type checking, pytest is unchallenged as the
test runner.
Decision¶
CAESAR uses:
rufffor linting and formatting. Configured inpyproject.toml. Format on save (developer setup),ruff check --fixin pre-commit, andruff check+ruff format --checkin CI.mypyin strict mode for type checking. Strict by default (strict = true);tests/are relaxed slightly via per-module overrides so we don't fight fixtures.pytestas the runner, withpytest-covfor coverage. Filterwarnings are errors so we notice deprecations before they bite.pre-commitruns ruff, the standard hygiene hooks, gitleaks, andconventional-pre-commitoncommit-msg. Installed viajust setup.justas the task runner. Recipes live in theJustfile;just checkis the canonical pre-push gate (lint + typecheck + test).- Python build backend:
hatchling. Smallest config that supports optional dependency groups and editable installs.
CI workflows:
lint.ymlruns ruff check + ruff format --check.typecheck.ymlruns mypy.test.ymlruns pytest on Python 3.11 and 3.12.
Branch protection on main requires those checks plus the CLA gate
(ADR-0003).
Alternatives considered¶
black+isort+flake8+pylint— the previous generation. Slower, four binaries to keep configured.pyright— fast and excellent, but mypy's strict mode and ecosystem maturity win at this stage.unittest+nose2— workable, but pytest fixtures and parametrization are a productivity gap we don't need.tox— useful for matrix runs locally; we get the matrix from GitHub Actions instead and skip the extra tool.hatch(the full project manager) — promising; we usehatchlingas the build backend but defer adopting hatch's scripts/environments to keepjustas the single task entry point.
Consequences¶
Positive¶
- One binary (
ruff) replaces three. Fast feedback loop. just checkis one command and gates everything before push.- Pre-commit catches the easy stuff before CI does.
- Strict mypy from day one prevents a slow drift into untyped code.
Negative¶
- Some libraries lack type stubs; we will pay the
# type: ignoretax occasionally. We log each as[reason]so they're easy to audit. - New contributors have one more tool (
just) to install. Documented in CONTRIBUTING.md.
Neutral¶
- Coverage threshold is intentionally not yet enforced. We will introduce one when we have a real codebase to threshold against; premature minimums distort effort.