Tool Roles: Lint, Format, Sort
Tool Roles: Lint, Format, Sort
Three jobs, three concerns — one tool can cover them:
| Job | Tool | What it fixes |
|---|---|---|
| Linting | ruff | Unused imports, undefined names, bug patterns (flake8-style rules in Rust, fast) |
| Formatting | black | Canonical layout: 88-char default line length, trailing commas, normalized strings |
| Import order | isort or ruff I rules |
Groups stdlib / third-party / local, alphabetical within groups |
ruff replaces flake8, isort, pyupgrade, and several plugins in one binary. If you keep standalone isort, set profile = "black" so its splits match black's wrapping — otherwise the two tools fight over every file.
Configuration and Hooks
Configuration and Hooks
Single source of truth in pyproject.toml:
[tool.black]
line-length = 88
[tool.ruff]
line-length = 88 # must match black
[tool.ruff.lint]
select = ["E", "F", "I", "UP"] # pycodestyle, pyflakes, isort, pyupgrade
ignore = ["E501"] # black owns line length
Then pre-commit-config.yaml:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.4
hooks: [{ id: ruff }, { id: ruff-format }]
Run pre-commit install once per clone, and pre-commit run --all-files after changing hook revisions so already-committed files get migrated too.
Pitfalls and Interview Angles
Pitfalls and Interview Angles
Local passes, CI fails. Hooks only touch staged files; history that predates the config is unchecked until pre-commit run --all-files or a one-shot ruff check --fix + black ..
Formatter churn. Pin tool versions in the hook config — upgrading black mid-project rewrites hundreds of lines; do that as its own commit.
CI gates. Use check modes, not mutators: black --check ., ruff check ., mypy src. Auto-fixing in CI creates noise and hides that developers ran different versions locally.
Interview angle: explain why line length belongs to the formatter, linting to the linter, and why splitting rules prevents two tools from editing the same line repeatedly.
Quiz
1. Why set isort profile = "black" when black formats the project?
2. Which command belongs in CI to enforce formatting?
3. Why must [tool.ruff] line-length match black’s?
Flashcards
Question
ruff vs flake8
Click to reveal answer
Answer
Same rule families (E/F), written in Rust — orders of magnitude faster, plus I (isort) and UP (pyupgrade) rules.
Question
black defaults
Click to reveal answer
Answer
88-character lines, opinionated, no config beyond line-length/target-version.
Question
pre-commit run --all-files
Click to reveal answer
Answer
Applies hooks to every tracked file — required after installing or upgrading hooks.
Question
CI style gates
Click to reveal answer
Answer
black --check, ruff check, mypy: verify only, never mutate in CI.
Revision Notes
Key Takeaways
- 1. Linting finds bugs, formatting removes style debate, import sorting stays deterministic
- 2. Keep one shared line-length in pyproject.toml so tools never fight
- 3. Pin hook versions and enforce check-only commands in CI
Interview Tips
- • Explain the division of labor (formatter owns layout, linter owns correctness) rather than naming tools only
- • Describe migrating an existing repo: one formatting commit, then hooks plus CI to keep it clean
Cheat Sheet
ruff (lint+I+UP) / black (len 88) / isort profile="black"; pyproject [tool.ruff.lint] select+ignore; pre-commit run --all-files; CI: black --check, ruff check.