Hi all,
Another roadmap thread, alongside Path to Vite and Path to Vitest. This one is the Python side: black and flake8 to Ruff.
What black and flake8 are
black is the Python formatter, the tool that decides where the line breaks go so nobody has to argue about it, run here through make black and pinned at 26.5.1.
flake8 is the linter that runs alongside black, wrapping pyflakes and pycodestyle to catch unused imports, undefined names and broken format strings. Both are pinned in [testenv:linters] so results do not drift as new releases land.
What Ruff is
Ruff is a linter and formatter for Python written in Rust, reimplementing the rules of flake8, pycodestyle, pyflakes, isort and others, and black's formatting, behind a single binary. It is typically one to two orders of magnitude faster than the tools it replaces, which matters less here than the fact that it is one dependency and one config block instead of two. We already run one tool from the same family: ty is the type checker in [testenv:typecheck].
The move
Two tools come out and one goes in, each replaced by a subcommand of the same binary:
black 26.5.1 -> ruff format
flake8 7.3.0 -> ruff check
Formatting settings map across directly. Our [tool.black] sets line-length = 160 and skip-string-normalization = true, and the Ruff equivalents are line-length = 160 and quote-style = "preserve".
There is an upstream precedent
AWX has already made this move. Their pyproject.toml carries [tool.ruff], [tool.ruff.format] and [tool.ruff.lint], with line-length = 160 and quote-style = "preserve", which is to say the same two settings we would be porting.
The catch, and it is the whole job
Their rule selection is not ours, and copying it would quietly cost us checks. Our [flake8] block in tox.ini selects 48 rules explicitly, chosen because black owns the formatting and most of pycodestyle would fight it. AWX's Ruff config selects 12. Adopting their block as written would drop 36 checks we currently run.
So the work is porting our list, not adopting theirs. Each rule needs verifying against what Ruff actually implements, and the families worth checking first are the F5xx string formatting rules, F6xx, and the E7xx statement checks, which is where the coverage is least likely to be one for one.
What does not move
yamllint stays where it is, at 1.38.0 in the same tox environment. Ruff replaces black and flake8, nothing else.
The sequence
Three steps, each reviewable on its own:
- Add
[tool.ruff] with our 48 rules ported, run ruff check beside flake8, and diff the findings.
- Swap
ruff format for black, with the whole-tree reformat as its own commit.
- Drop black and flake8 from
[testenv:linters] and retire the black make target.
Step 1 is where the risk is, and it answers a real question: whether Ruff covers every check we rely on. If some rule has no equivalent, that is worth knowing before anything is deleted rather than after.
Pull requests
Safe to merge in this order, and the second carries the first, so it can also go in alone:
Thanks for reading.