Skip to content

Quality Checks: Ruff, Mypy, Pytest & Coverage

Up to now, Ruff has been running through the VS Code extension and pytest has been running on demand. Production-track projects need every check available from a terminal so contributors and CI can run the same commands. Four tools cover the ground: Ruff (linting and formatting), mypy (static types), pytest (tests), and coverage (test coverage reporting).

Adding the Dev Dependencies

Each tool installs into the project's dev group, not the runtime dependencies. They run on the developer's machine and in CI, but never inside the production container.

shell
uv add --dev coverage mypy pytest ruff

pytest may already be in dev from earlier chapters; the second uv add --dev is a no-op for it. ruff was previously available via the VS Code extension only; pinning it as a project dev dependency means CI runs the same version contributors run.

Ruff: Lint and Format from the Command Line

Two commands:

shell
uv run ruff check --no-cache .
uv run ruff format --check .

ruff check runs the lint rules configured in ruff.toml (the project has been carrying that file since the intro chapter). ruff format --check verifies code is already formatted; if a file needs changes, the command exits non-zero and prints the offending paths. Running ruff format . (no --check) rewrites the files in place.

Flags to note:

  • --fix on ruff check applies safe auto-fixes (import sorting, unused-import removal, simple modernizations). Useful locally; CI should run the un-fixed form so a missed fix doesn't slip through.
  • --no-cache skips Ruff's on-disk cache. CI environments don't benefit from the cache, and skipping it avoids stale state in containerized builds.

Mypy: Static Type Checking

mypy reads the type annotations the codebase already has and checks they line up with how each value is used. Add it to pyproject.toml:

pyproject.toml
[tool.mypy]
python_version = "3.14"
warn_unused_ignores = true
warn_redundant_casts = true
disallow_incomplete_defs = true
no_implicit_optional = true
exclude = [
    "^tests/",
    "^alembic/versions/",
]

Run it:

shell
uv run mypy .

Pytest with Coverage

pytest already runs on its own. Coverage data is what answers "which lines of the codebase did the tests actually exercise?" coverage runs the test suite and records every line that executed.

pyproject.toml
[tool.coverage.run]
branch = true
source = ["release_tracker"]

[tool.coverage.report]
show_missing = true
skip_covered = false

branch = true tracks both line and branch coverage, so a function whose if was always taken on the truthy side counts the falsy branch as missed. source = ["release_tracker"] scopes coverage to the application code; the test suite itself isn't measured.

Run the tests under coverage, then print the report:

shell
uv run coverage run -m pytest
uv run coverage report

The report prints per-file totals, plus a list of line numbers that weren't covered. For a richer view, an HTML report drops into htmlcov/:

shell
uv run coverage html

Open htmlcov/index.html and click into a file to see exactly which lines and branches the suite missed.

Coverage isn't a quality measurement

100% coverage means every line ran during tests, not that the tests are good. A test that calls a function and asserts nothing still bumps the coverage number. Treat the report as a tool for finding unintentional gaps (an entire route handler that no test touches), not as a target.

Recipe for Checks

Keeping the commands in the same order as CI runs them makes local-vs-CI mismatches easier to debug:

shell
uv run ruff check --no-cache .
uv run ruff format --check .
uv run mypy .
uv run coverage run -m pytest
uv run coverage report

Larger projects might use a Makefile, a task runner, or pre-commit hooks.

Pre-commit hooks via prek

If you'd rather have these checks run automatically before each commit, prek (a fast Rust reimplementation of pre-commit) is recommended. Install it with:

shell
uvx prek install

Configure the hooks in .pre-commit-config.yaml. The tooling is out of scope for this chapter; the pre-commit documentation covers the setup.