Ruff is the extremely fast Python linter and formatter from Astral, written in Rust, where one binary replaces Flake8, isort, pydocstyle, pyupgrade, autoflake, and Black. It is a command-line tool rather than an importable library, so this sheet centers on subcommands, flags, rule codes, and pyproject.toml config instead of a Python API. The recurring mental model in every panel is one picture: a stack of .py files flows along a gray arrow through the lime ruff engine and comes out as either a green “All checks passed” badge, a red violation list (each row CODE message file:line), or a blue diff of applied fixes. Where this looks like the uv sheet, the pairing is the point: uv installs and runs your project, ruff lints and formats it. Ruff is a standalone binary, so there is no import line; each panel shows the bare ruff ... form, and the series-recommended way to drive it is through uv with uv run ruff check inside a project or uvx ruff check to run it ephemerally with no install.
Lint with ruff check
ruff check scans your Python (and notebooks) and prints each violation as CODE message with the offending file:line, then exits non-zero if anything is found, which is exactly what you want in CI. It is fast enough to run on the whole repo on every keystroke, so reach for --watch while editing, --statistics to see which rules dominate, and --output-format=github (or gitlab, junit, json) to feed other tools.
ruff check # lint the whole project
ruff check src/ tests/ # lint specific paths only
ruff check # clean run -> "All checks passed!"
ruff check --statistics # count violations per rule
ruff check --watch # re-lint on every save
ruff check --output-format=github # full|concise|json|github|gitlab|junit
# via uv: uv run ruff checkSee The linter. Use the explicit ruff check <path> subcommand; the legacy top-level ruff <path> form is deprecated.
Fix with ruff check –fix
Many violations carry an automatic fix, and --fix rewrites your files to resolve every safe one in a single pass, reporting how many were fixed and how many remain. Preview first with --diff, see exactly what changed with --show-fixes, and only reach for --unsafe-fixes when you have read the diff, since those may change the meaning of your code rather than just its form.
ruff check --fix # apply all safe fixes
ruff check --diff # preview fixes, write nothing
ruff check --fix --show-fixes # list each fix applied
ruff check --fix --unsafe-fixes # also apply fixes that may change intent
ruff check --fix-only # apply fixes, do not report leftovers
ruff check --fix --fixable I # restrict which rules may fixSee Fixes. Read the diff before --unsafe-fixes; unsafe fixes can change behavior, not just style.
Format with ruff format
ruff format is a Black-compatible formatter built into the same binary, so one tool both lints and formats with no second dependency to install or pin. Use --check (and --diff) in CI to fail when code is not formatted, and remember the division of labor: the formatter handles whitespace, quotes, and line wrapping, while import sorting is the I lint rule applied with ruff check --select I --fix.
ruff format # format files in place
ruff format --check # fail CI if any file is unformatted
ruff format --diff # show what formatting would change
ruff format app.py src/ # format specific files
ruff check --select I --fix # sort imports (format does NOT sort)
ruff format # then format whitespace and quotesSee The formatter. ruff format does not sort imports; run the I rule with ruff check --select I --fix for that.
Select and Ignore Rules
Ruff decides which of its 800-plus rules run from their letter-prefixed codes: --select sets the active set, --extend-select adds to whatever is already configured, and --ignore switches individual rules off. --select ALL turns on everything (great for discovery, too noisy to keep), --preview opts into unstable rules, and --unfixable lets a rule report without auto-fixing.
ruff check --select E,F,I,UP,B,SIM # choose exactly which rules run
ruff check --extend-select B,SIM # add to the configured set
ruff check --ignore E501 # silence one rule
ruff check --select ALL # everything (discovery only, too noisy)
ruff check --preview # opt into unstable rules and fixes
ruff check --fix --unfixable F401 # report F401 but block its auto-fixSee Rule selection. Codes are letter-prefixed by source linter, for example F, E, I, UP, B, SIM.
Configure with pyproject.toml
Put the flags you would otherwise retype into [tool.ruff] (project-wide options like line-length and target-version) and [tool.ruff.lint] (select, ignore), with sub-tables such as [tool.ruff.lint.isort] and [tool.ruff.format] for finer control. Any project can use a standalone ruff.toml instead (same keys, without the [tool.ruff] prefix), and ruff discovers the nearest config by walking up from each file.
[tool.ruff]
line-length = 100
target-version = "py313"
[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B"] # active rules (lint table, not top level)
ignore = ["E501"] # let the formatter own line length
[tool.ruff.lint.isort]
known-first-party = ["myapp"] # group your code separately
[tool.ruff.format]
quote-style = "double" # normalize to double quotesSee Configuring Ruff. select/ignore live under [tool.ruff.lint]; the old top-level keys are deprecated and warn.
Suppress with noqa and per-file ignores
When a rule is right almost everywhere, suppress the exceptions rather than disabling it: add # noqa: F401 (a specific code) or a bare # noqa (everything on the line) inline, or list per-file-ignores globs in config for whole categories of files such as tests. ruff check --add-noqa retro-fits suppression comments onto an existing codebase, --ignore-noqa audits what those comments are hiding, and ruff rule F401 explains any code in full.
import os # noqa: F401 # ignore one specific code on this line
weird_code() # noqa # blanket-ignore the line (use sparingly)
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401"] # re-exports are fine here
"tests/**" = ["S101"] # assert is expected in tests
# ruff check --add-noqa # insert # noqa on failing lines
# ruff check --ignore-noqa # audit what those comments hide
# ruff rule F401 # explain a rule code in fullSee Error suppression. # noqa: F401 targets one code; a bare # noqa silences every rule on the line.
Rule-code Categories
Every rule code starts with a short prefix naming the upstream linter it came from: F is Pyflakes (real correctness bugs, keep it on always), E/W are pycodestyle style, I is isort import ordering, UP is pyupgrade modernization, and B is flake8-bugbear’s likely-bug patterns. There are dozens more (SIM, N, S, C4, PT, RUF, and so on); ruff linter lists them all, and you build your project’s rule set by combining the prefixes you trust.
F # Pyflakes: undefined names, unused imports/vars (always keep on)
E, W # pycodestyle: whitespace, blank lines, E501 line length
I # isort: import ordering and grouping
UP # pyupgrade: Dict[str, int] -> dict[str, int] for your target
B # flake8-bugbear: likely bugs, e.g. mutable default def f(x=[])
SIM, N # flake8-simplify (collapse if/else) and pep8-naming
# ruff linter lists every prefixSee Rules reference. Run ruff linter to list every prefix; combine the families you trust into your project’s set.
Integrate with editors and pre-commit
Ruff ships a language server (ruff server) that editors use for live diagnostics and format-on-save, and an official astral-sh/ruff-pre-commit repo whose ruff-check and ruff-format hooks gate every commit. Drive it ephemerally with uvx ruff (no install), gate CI with ruff check --output-format=github plus ruff format --check, and pin one rev / version across editor, pre-commit, and CI so everyone runs the same rules.
# ruff server # editor language server (format on save)
# uvx ruff check # run ephemerally via uv, no install
repos: # .pre-commit-config.yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.14.10 # pin one version: editor, hooks, CI
hooks:
- id: ruff-check
args: [--fix] # auto-fix in the commit hook
- id: ruff-format
# CI gate: ruff check --output-format=github && ruff format --checkSee Other integrations. The hook ids are ruff-check and ruff-format; the bare id: ruff is the older linter alias.
Quick Reference
| Command | What it does | Area |
|---|---|---|
ruff check |
Lint the project, list violations | Check |
ruff check src/ tests/ |
Lint specific paths | Check |
ruff check --watch |
Re-lint on every save | Check |
ruff check --statistics |
Count violations per rule | Check |
ruff check --fix |
Apply all safe fixes | Fix |
ruff check --diff |
Preview fixes, write nothing | Fix |
ruff check --fix --unsafe-fixes |
Also apply unsafe fixes | Fix |
ruff format |
Format files in place | Format |
ruff format --check |
Fail if any file is unformatted | Format |
ruff format --diff |
Show what formatting would change | Format |
ruff check --select E,F,I,UP,B |
Choose exactly which rules run | Select |
ruff check --extend-select B,SIM |
Add to the configured set | Select |
ruff check --ignore E501 |
Silence one rule | Select |
ruff check --add-noqa |
Insert # noqa on failing lines |
Suppress |
ruff rule F401 |
Explain a rule code | Suppress |
ruff linter |
List every rule prefix | Rules |
ruff server |
Run the editor language server | Integrate |
uvx ruff check |
Run ruff ephemerally via uv | Integrate |
| Flag | Meaning |
|---|---|
--fix |
Apply safe automatic fixes |
--unsafe-fixes |
Also apply fixes that may change intent |
--fix-only |
Apply fixes, do not report leftovers |
--diff |
Print a diff instead of writing files |
--show-fixes |
Enumerate each fix applied |
--select / --ignore |
Set / unset active rule codes |
--extend-select |
Add rules on top of the config |
--preview |
Enable unstable rules and fixes |
--statistics |
Per-rule violation counts |
--output-format |
full, concise, json, github, gitlab, junit, … |
--target-version |
py38 … py314 syntax target |
--watch |
Re-run on file changes |
--add-noqa |
Insert suppression comments |
--ignore-noqa |
Ignore existing # noqa comments |
| Prefix | Source linter | Catches |
|---|---|---|
F |
Pyflakes | Real bugs: undefined names, unused imports/vars |
E, W |
pycodestyle | Style: whitespace, blank lines, E501 length |
I |
isort | Import ordering and grouping |
UP |
pyupgrade | Modernize syntax for your target version |
B |
flake8-bugbear | Likely-bug patterns (mutable defaults, …) |
SIM |
flake8-simplify | Simpler equivalent code |
N |
pep8-naming | Class / function / variable naming |
S |
flake8-bandit | Security anti-patterns |
C4 |
flake8-comprehensions | Better comprehensions |
PT |
flake8-pytest-style | Pytest conventions |
D |
pydocstyle | Docstring conventions |
ANN |
flake8-annotations | Missing type annotations |
RUF |
Ruff | Ruff-specific lints |
ALL |
(meta) | Every rule (discovery only, too noisy to keep) |
| You want to | Do this |
|---|---|
| Ignore one code on one line | # noqa: F401 after the code |
| Ignore several codes on a line | # noqa: F401, E501 |
| Ignore everything on a line | bare # noqa (use sparingly) |
| Ignore codes across a file glob | [tool.ruff.lint.per-file-ignores] map |
| Disable a rule project-wide | add it to ignore = [...] in config |
| Add suppressions to old code | ruff check --add-noqa |
See what # noqa hides |
ruff check --ignore-noqa |
Appendix: Sample Code
The lint to fix to format loop
# 1. See what is wrong
ruff check
# 2. Apply every safe fix, and sort imports (the I rule)
ruff check --fix
# 3. Format whitespace, quotes, and line wrapping
ruff format
# In CI, make both gate the build (write nothing, fail loudly):
ruff check --output-format=github
ruff format --checkA practical pyproject.toml config
[tool.ruff]
line-length = 100
target-version = "py313"
[tool.ruff.lint]
# Pyflakes (F) + pycodestyle (E/W) + isort (I) + pyupgrade (UP)
# + bugbear (B) + simplify (SIM)
select = ["E", "F", "I", "UP", "B", "SIM"]
ignore = ["E501"] # let the formatter own line length
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401"] # re-exports are fine here
"tests/**" = ["S101"] # assert is expected in tests
[tool.ruff.lint.isort]
known-first-party = ["myapp"]
[tool.ruff.format]
quote-style = "double"Both ruff check and ruff format honor this file. For example line-length = 100 shows up under ruff check --show-settings as linter.line_length = 100, and E501 no longer fires.
Inline suppression that ruff itself can write
import os # noqa: F401 (kept intentionally)
# Generate these automatically on an existing codebase:
# ruff check --add-noqa
# Then audit what they hide:
# ruff check --ignore-noqaruff check --add-noqa on a file with an unused import os rewrites the line to exactly import os # noqa: F401 and prints Added 1 noqa directive.
Pre-commit hooks (.pre-commit-config.yaml)
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.14.10
hooks:
# Lint, fixing what is safe and re-staging the result
- id: ruff-check
args: [--fix]
# Then format
- id: ruff-formatThe two hook ids are ruff-check and ruff-format. Pin rev to the same ruff version your editor and CI use so nobody sees different results.
Running ruff through uv (the series-recommended path)
# Inside a uv-managed project (ruff added as a dev dependency):
uv add --dev ruff
uv run ruff check --fix
uv run ruff format
# Or run it ephemerally, no install, no project needed:
uvx ruff check
uvx ruff format --checkuvx ruff check is the zero-setup way to lint any file; uv run ruff ... uses the version pinned in your project’s lockfile.
Behavior notes
ruff checkis the entry point, not bareruff. The legacy top-levelruff <path>form to lint is deprecated; always use the explicitruff check <path>subcommand.select/ignorelive under[tool.ruff.lint]. The old top-level keys in[tool.ruff]are deprecated and emit a warning; nest them in thelinttable.- The formatter does not sort imports.
ruff formathandles whitespace, quotes, and wrapping; import sorting is theIlint rule, applied withruff check --select I --fix. - Safe fixes are automatic, unsafe fixes are opt-in.
--fixapplies only the safe fixes; read the--diffbefore adding--unsafe-fixes, since those can change behavior, not just style. - The pre-commit hook ids are
ruff-checkandruff-format. The bareid: ruffhook is the older alias for the linter; prefer the explicitruff-check. - Pin one version everywhere. Match the
revin.pre-commit-config.yaml, your editor extension, and CI to the same ruff version so nobody sees different results.
References
Ruff documentation (latest)
- Documentation home and Installation
- Tutorial, The linter, The formatter
- Configuring Ruff, Settings reference, Rules reference
- Editor setup, Other integrations, Preview mode
Project and related
- ruff on PyPI and on GitHub
- Official pre-commit hooks repo, per-file ignores, config file discovery
- uv (Astral packaging sibling) and ty (Astral type-checker sibling)