Open this lesson in your favourite AI. It'll walk you through the why, explain the demo, and quiz you on the try-it list.
Ruff (also from Astral) replaced the entire linter stack — pyflakes, pycodestyle, pylint, isort, even Black for formatting. It's 10–100× faster and reads its config from pyproject.toml. Mypy adds type checking on top. The 2026 baseline: ruff check + ruff format + mypy --strict in CI, with project-specific allows in pyproject.toml. A team that adopts both from day 1 has clean code forever; a team that doesn't will fight 10 paper cuts a week.
Ruff is a Rust-based linter and formatter that replaces pyflakes, pycodestyle, isort, pyupgrade, and Black in a single binary, running 10-100× faster than any of them individually. It reads all configuration from [tool.ruff] in pyproject.toml, which means one file governs both linting rules and formatting style for your entire team. Layering mypy on top adds static type checking — when both run in a pre-commit hook or CI step, the feedback loop from 'I wrote a bug' to 'the tool caught it' shrinks to under a second.
[tool.ruff] section to a real project. Run ruff check . and fix every finding (most are auto-fixable with ruff check --fix).ruff format . on a Python file with mixed quoting / spacing. It rewrites it to one canonical style.[tool.mypy] with strict = true. Run mypy src/. Expect findings — strict mode is uncompromising on first use.Use these three in order. Each builds on the one before.
In one paragraph, explain why ruff replaced 5+ tools (pyflakes, isort, pycodestyle, pylint, black) — what about its design makes that possible?
How does mypy actually do type checking on Python (a dynamically typed language)? Walk me through type stubs (.pyi), `typing.cast`, and `Any`.
On a legacy codebase with no types, what's the right path to mypy strict — the gradual typing playbook? Cover `--ignore-missing-imports`, file-by-file adoption, and `[tool.mypy.overrides]`.
# pyproject.toml
[tool.ruff]
line-length = 100
target-version = "py312"
[tool.ruff.lint]
select = ["E", "F", "I", "B", "UP", "N", "RUF"]
# E/F = pycodestyle/pyflakes; I = isort; B = bugbear; UP = pyupgrade; N = pep8 names; RUF = ruff-specific
[tool.mypy]
python_version = "3.12"
strict = true
warn_unused_ignores = true
# typical lifecycle:
$ ruff check . # lint
$ ruff format . # format (replaces black)
$ mypy src/ # type check
# pre-commit hook (.git/hooks/pre-commit):
#!/bin/sh
ruff check . && ruff format --check . && mypy src/python3 main.py