ruff — swiss knife for formatting & linting
Mental Model
Ruff is best understood as:
- A rule engine, not “a linter”
- A unification layer over many historical Python tools
- A fast, enforceable policy mechanism for code quality
Ruff’s real value is not individual rules — it is centralized, opinionated configuration with negligible runtime cost.
If Flake8 asked:
“Which plugins do you want?”
Ruff asks:
“Which categories of mistakes do you want to forbid?”
What Ruff Replaces (Conceptually)
Ruff reimplements rules from:
- pyflakes (logic errors)
- pycodestyle (PEP8)
- isort (imports)
- mccabe (complexity)
- many flake8 plugins
Key idea:
- Ruff flattens a fragmented ecosystem into one rule namespace
Ruff Rule Taxonomy
Ruff rules are grouped by prefix, each representing a domain.
Examples:
F— pyflakes (logic errors)E,W— style (pycodestyle)I— import sortingC90— complexityB— bugbear (likely bugs)UP— Python upgrade suggestionsSIM— simplificationsARG— unused arguments
Important:
- Prefixes matter more than individual rule numbers
- You usually enable/disable families, not single rules
Core Configuration File
Ruff is configured via pyproject.toml.
Minimal example:
[tool.ruff]line-length = 88target-version = "py311"This already enables a large default rule set.
Rule Selection Strategy
Explicit Is Better Than Implicit
Ruff encourages explicit rule selection:
[tool.ruff]select = ["E", "F", "I", "B"]Meaning:
- Enable only these rule families
- Everything else is off unless added
This avoids “rule creep”.
Incremental Strictness
Common senior-level strategy:
select = ["E", "F"]extend-select = ["I", "B"]Interpretation:
- Core correctness first
- Gradually add stricter domains
Ignoring Rules (Globally vs Locally)
Global Ignore
ignore = ["E203", "E266"]Use sparingly.
Global ignores:
- Apply everywhere
- Encode long-term policy decisions
Bad smell:
- Long ignore lists without justification
File-Specific Ignores
[tool.ruff.per-file-ignores]"__init__.py" = ["F401"]"tests/**.py" = ["ARG", "S"]This is where Ruff shines.
Use cases:
- Re-export patterns
- Test code flexibility
- Legacy modules
Rule:
Prefer per-file ignores over global ignores.
Import Sorting (isort Replacement)
Ruff replaces isort with I rules.
Example:
[tool.ruff]select = ["I"]Key properties:
- Deterministic ordering
- No runtime import analysis
- Fast enough to auto-fix everywhere
Ruff handles:
- Stdlib vs third-party vs local
- Section ordering
- Alphabetization
Auto-Fix Behavior
Ruff distinguishes:
- Safe fixes
- Unsafe fixes
Default:
- Safe fixes only
Enable fixing:
[tool.ruff]fix = trueOr in CI:
ruff check --fixSenior guideline:
- Auto-fix style
- Review logic-related fixes
Complexity Controls
Ruff includes McCabe-style complexity checks.
[tool.ruff]select = ["C90"]max-complexity = 10Important:
- Complexity limits are signals, not truths
- Use them to identify refactoring targets
Do not:
- Enforce extremely low limits
- Block PRs without context
Python Version Awareness
target-version = "py311"This enables:
- Syntax checks
- Upgrade suggestions (
UPrules) - Removal of compatibility clutter
This is not type checking. It is syntax-level correctness.
Lint vs Fix Philosophy
Ruff can:
- Report only
- Fix only
- Report + fix
Healthy setup:
- Auto-fix on save (local)
- Strict linting in CI
- Minimal manual intervention
Anti-pattern:
- Running Ruff only in CI
Ruff in CI (High-Level Pattern)
Typical usage:
ruff check .Strict mode:
ruff check . --select ALLMigration mode:
ruff check . --exit-zeroSenior takeaway:
- Ruff is fast enough to run everywhere
- CI should enforce, not surprise
Ruff vs Flake8 (Configuration Perspective)
| Aspect | Ruff | Flake8 |
|---|---|---|
| Config location | pyproject.toml | setup.cfg / tox.ini |
| Rule selection | Unified | Plugin-based |
| Performance | Extremely fast | Slow at scale |
| Fix support | Native | Limited |
| Cognitive load | Low | High |
Key insight:
- Ruff shifts linting from “tool wrangling” to “policy definition”
Common Ruff Misuse Patterns
- Enabling
ALLrules immediately - Disabling rules without understanding them
- Treating Ruff output as suggestions instead of policy
- Mixing formatting concerns with architectural concerns
Recommended Senior-Level Baseline
[tool.ruff]line-length = 88target-version = "py311"select = ["E", "F", "I", "B", "UP"]ignore = []fix = trueThen:
- Add per-file ignores
- Add complexity checks selectively
- Add stricter rule families intentionally
One-Sentence Mental Model
Ruff is not “a faster Flake8” —
it is a centralized rule engine that turns Python code quality into enforceable, low-friction policy.