Ruff: replacing Flake8, isort and Black with one fast linter and formatter
How to set up Ruff as both linter and formatter, which rule sets are worth enabling, and how to roll it out on an existing codebase without a giant noisy diff.
Author's connection to this tool: No connection. An independent overview written by the RecallRun editors.
Many Python projects run three or four tools on every commit: Flake8 and its plugins for linting, isort for imports, Black for formatting, maybe pyupgrade. Ruff, from Astral, implements most of those checks in one Rust binary and runs them fast enough that linting a large codebase feels instant.
Install and run
pip install ruff # or: uv add --dev ruff
ruff check . # lint
ruff check . --fix # apply safe automatic fixes
ruff format . # format code (Black-compatible style)
Configure in pyproject.toml
Keep the configuration next to the rest of your project settings:
[tool.ruff]
line-length = 100
target-version = "py312"
[tool.ruff.lint]
select = [
"E", "F", # pycodestyle errors and Pyflakes (the classic Flake8 core)
"I", # import sorting (isort)
"B", # flake8-bugbear: likely bugs and design problems
"UP", # pyupgrade: modern syntax for your Python version
"SIM", # simplifications
]
ignore = ["E501"] # let the formatter handle line length
[tool.ruff.lint.per-file-ignores]
"tests/*" = ["B011"]
A sensible path is to start with E, F and I, then add B and UP, which catch real bugs such as mutable default arguments and outdated syntax. Ruff supports hundreds of rules; resist enabling everything at once.
What it catches that matters
A few examples of issues these rule sets flag:
def add_item(item, items=[]): # B006: mutable default argument
items.append(item)
return items
import os, sys # E401 / I001: multiple imports, unsorted
from typing import List
def names(xs: List[str]) -> List[str]: # UP006: use list[str] on modern Python
return [x for x in xs if x != None] # E711: comparison to None
ruff check --fix resolves many of these automatically; the rest come with a clear message and rule code you can look up.
Use it in your editor and pre-commit
Most editors have a Ruff extension that lints and formats on save. For pre-commit:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.9 # pin to the version your team uses
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
And in CI, fail the build on lint errors or unformatted files:
ruff check .
ruff format --check .
Rolling it out on an existing codebase
Turning on a new linter across a large project can produce thousands of warnings. A calmer approach:
- Format first, in one commit. Run
ruff format .and commit it alone, so later diffs stay readable. Add that commit's hash to.git-blame-ignore-revssogit blameskips it. - Enable a small rule set (
E,F,I) and fix or auto-fix everything. - Add rule groups one at a time, each in its own pull request.
- For legacy noise you can't fix yet, use per-file ignores, or Ruff's option to add
noqacomments to existing violations, so new code is held to the full standard while old code is cleaned up gradually.
Where Ruff doesn't replace other tools
Ruff is a linter and formatter, not a type checker. Keep mypy or Pyright for type checking. Some niche Flake8 plugins may not have Ruff equivalents yet, so check the rule list for anything your team depends on.
Verdict
Ruff gives most teams the same checks they already run with less configuration, one dependency and much faster feedback, which matters most in pre-commit hooks where slow tools get skipped. For a new project it's an obvious default; for an existing one, the gradual rollout above takes an afternoon.
Written by RecallRun Editors for the RecallRun community. Community posts are checked for safety and reviewed by our editors before publishing, but the views and claims are the author's own. Links are the author's; open them with care. Report this post.
More from the community
- Tools
uv: one fast tool for Python packages, virtual environments and versions
uv replaces pip, virtualenv, pip-tools and pyenv-style version management with a single fast binary. Here is how the everyday workflow looks and when it is worth switching.
- Tools
Pydantic v2: validate data at the edges of your Python application
Pydantic turns type hints into fast runtime validation and serialisation. Here are the core patterns for API payloads, settings and LLM outputs, plus the v2 changes that trip people up.
- Tools
DuckDB: fast SQL analytics on Parquet, CSV and DataFrames without a server
DuckDB is an in-process analytical database: no server, just a library. Here is how to query files and DataFrames directly, and where it fits next to pandas, Postgres and Spark.
Share a tech article or a tool you built. Every post is checked and reviewed before it goes live.