ruff linter and formatter
on this page
tl;dr
Install: | uvx ruff check for quick start or uv add --dev ruff |
Speed: | 10-100x faster than alternatives - rust implementation by astral |
Rules: | 942 rules across 54 linters - replaces flake8, black, isort, and more |
Formatting: | Drop-in replacement for black with additional options |
Language server: | VS Code extension, real-time linting and formatting |
Integration: | Works with uv - integrated astral toolchain |
overview
ruff - extremely fast python linter and code formatter written in rust
- created by astral (makers of uv and ty)
- performance leader: 10-100x faster than existing tools
- comprehensive coverage: 942 rules replacing dozens of tools
- production ready: used by major projects like pandas, fastapi, and pydantic
- unified tooling: single tool for linting, formatting, and import sorting
- official docs | configuration schema
performance comparison
speed benchmarks
tool combination | 100k loc codebase | typical django project |
---|---|---|
ruff check + format | ~200ms | ~50ms |
flake8 + black + isort | ~30s | ~8s |
pylint + black + isort | ~120s | ~30s |
architecture advantages
single-pass execution: ruff analyzes your code once and applies all enabled rules, versus running multiple separate tools sequentially.
rust performance: memory-efficient parallel processing with automatic cpu utilization.
intelligent caching: file-level caching with automatic invalidation based on content and configuration changes.
installation and quick start
assumes you have uv installed
quick start (no installation)
# lint current directory
uvx ruff check
# lint and auto-fix
uvx ruff check --fix
# format code (black-compatible)
uvx ruff format
# combined workflow
uvx ruff check --fix && uvx ruff format
permanent installation
# project dependency (recommended)
uv add --dev ruff
# global tool installation
uv tool install ruff
# traditional pip
pip install ruff
# pre-built binaries
curl -LsSf https://astral.sh/ruff/install.sh | sh
first project setup
# pyproject.toml
[tool.ruff]
target-version = "py38"
line-length = 88
[tool.ruff.lint]
select = ["E", "F", "B", "I"] # pycodestyle, pyflakes, bugbear, isort
ignore = ["E501"] # line-too-long (handled by formatter)
comprehensive rule system
rule organization (942 rules total)
ruff implements 54 linters with rules organized by tool origin:
core rules (essential)
[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"B", # flake8-bugbear
"I", # isort
]
security and quality
[tool.ruff.lint]
extend-select = [
"S", # flake8-bandit (security)
"N", # pep8-naming
"UP", # pyupgrade
"SIM", # flake8-simplify
"C4", # flake8-comprehensions
]
documentation and typing
[tool.ruff.lint]
extend-select = [
"D", # pydocstyle
"ANN", # flake8-annotations
"TCH", # flake8-type-checking
"FA", # flake8-future-annotations
]
framework-specific
[tool.ruff.lint]
extend-select = [
"DJ", # flake8-django
"NPY", # numpy-specific rules
"PD", # pandas-vet
"AIR", # airflow-specific rules
]
complete rule categories
prefix | tool | rules | description |
---|---|---|---|
E/W | pycodestyle | 91 rules | style and formatting |
F | pyflakes | 66 rules | logical errors and imports |
B | flake8-bugbear | 34 rules | common bug patterns |
S | flake8-bandit | 113 rules | security vulnerabilities |
D | pydocstyle | 58 rules | docstring conventions |
PL | pylint | 73 rules | general code quality |
UP | pyupgrade | 37 rules | python version upgrades |
I | isort | 12 rules | import sorting |
C4 | flake8-comprehensions | 19 rules | comprehension improvements |
SIM | flake8-simplify | 110 rules | code simplification |
RUF | ruff-specific | 35 rules | ruff’s own rules |
view all rules: ruff rule --all
or browse rules documentation
rule selection strategies
minimal setup (get started)
[tool.ruff.lint]
select = ["E", "F"] # just errors and basic checks
ignore = ["E501"] # let formatter handle line length
recommended setup (production)
[tool.ruff.lint]
select = [
"E", "W", # pycodestyle
"F", # pyflakes
"B", "B9", # bugbear
"C4", # comprehensions
"I", # isort
"UP", # pyupgrade
"SIM", # simplify
]
ignore = [
"E501", # line-too-long
"B008", # function-call-in-default-argument
]
strict setup (libraries)
[tool.ruff.lint]
select = ["ALL"] # enable everything
ignore = [
# formatter conflicts
"COM812", "ISC001",
# docstring conflicts
"D203", "D213",
# contextual ignores
"FBT003", # boolean-positional-value-in-call
]
advanced configuration
file discovery and filtering
[tool.ruff]
# include/exclude patterns
include = ["*.py", "*.pyi", "*.ipynb"]
exclude = [
".git", ".venv", "__pycache__",
"migrations", "node_modules",
"**/generated/**",
]
extend-exclude = ["custom-vendor/"]
# respect .gitignore (default: true)
respect-gitignore = true
per-file rule configuration
[tool.ruff.lint.per-file-ignores]
# ignore import errors in __init__.py
"__init__.py" = ["F401", "F403"]
# relax rules for test files
"tests/**/*.py" = [
"S101", # assert-used
"PLR2004", # magic-value-comparison
"SLF001", # private-member-access
]
# allow print statements in scripts
"scripts/**/*.py" = ["T201", "T203"]
# different rules for different areas
"src/core/**/*.py" = ["RUF001"] # strict core
"examples/**/*.py" = ["D100", "D103"] # relax docs
rule overrides by pattern
[[tool.ruff.lint.overrides]]
files = ["*.pyi"] # type stub files
[tool.ruff.lint.overrides.lint]
select = ["E", "F", "PYI"]
ignore = ["D"] # no docstrings in stubs
[[tool.ruff.lint.overrides]]
files = ["test_*.py", "*_test.py"]
[tool.ruff.lint.overrides.lint]
extend-ignore = ["S101", "PLR2004", "ARG001"]
plugin-specific configuration
isort configuration
[tool.ruff.lint.isort]
known-first-party = ["myproject"]
known-third-party = ["django", "requests"]
section-order = [
"future",
"standard-library",
"third-party",
"first-party",
"local-folder"
]
split-on-trailing-comma = true
force-single-line = false
lines-after-imports = 2
pydocstyle configuration
[tool.ruff.lint.pydocstyle]
convention = "google" # or "numpy", "pep257"
ignore-decorators = ["typing.overload"]
property-decorators = ["functools.cached_property"]
flake8-type-checking configuration
[tool.ruff.lint.flake8-type-checking]
strict = true
runtime-evaluated-base-classes = [
"pydantic.BaseModel",
"sqlalchemy.orm.DeclarativeBase",
]
runtime-evaluated-decorators = [
"attrs.define",
"dataclasses.dataclass",
]
formatter (black compatibility)
black-compatible defaults
ruff format is designed as a drop-in replacement for black:
[tool.ruff.format]
# black defaults
quote-style = "double"
indent-style = "space"
skip-magic-trailing-comma = false
line-ending = "auto"
ruff format extensions
[tool.ruff.format]
# ruff-specific options
quote-style = "preserve" # maintain existing quotes
docstring-code-format = true # format code in docstrings
docstring-code-line-length = "dynamic" # inherit line length
# explicit line length (overrides global)
line-length = 100
docstring code formatting
def example():
"""
Example function with code in docstring.
```python
# this code will be formatted by ruff
result=process_data(input_data,transform=True,validate=False)
```
"""
pass
formatted output:
def example():
"""
Example function with code in docstring.
```python
# this code will be formatted by ruff
result = process_data(input_data, transform=True, validate=False)
```
"""
pass
migration from black
ruff format produces nearly identical output to black. differences are minimal and documented:
# compare formatting differences
black --diff src/ > black.diff
ruff format --diff src/ > ruff.diff
diff black.diff ruff.diff
cli reference
primary commands
# linting
ruff check [paths] # lint files
ruff check --fix [paths] # lint and auto-fix
ruff check --watch [paths] # watch mode
# formatting
ruff format [paths] # format files
ruff format --check [paths] # check formatting
ruff format --diff [paths] # show formatting diff
# utilities
ruff rule [rule-code] # explain rule
ruff config [paths] # show effective config
ruff clean # clear cache
output formats
# linting output formats
ruff check --output-format text # default human-readable
ruff check --output-format json # machine-readable json
ruff check --output-format junit # junit xml
ruff check --output-format github # github actions format
ruff check --output-format gitlab # gitlab ci format
ruff check --output-format pylint # pylint-compatible
ruff check --output-format sarif # sarif format
ruff check --output-format grouped # group by file
rule selection flags
# runtime rule selection
ruff check --select E,F,B # specific rules
ruff check --ignore E501,B008 # ignore rules
ruff check --extend-select SIM # add to config
ruff check --per-file-ignores="test_*.py:S101"
# fix control
ruff check --fix # safe fixes only
ruff check --unsafe-fixes # include unsafe fixes
ruff check --fix-only # only apply fixes
advanced cli options
# performance and debugging
ruff check --statistics # show timing stats
ruff check --show-source # show source code
ruff check --show-fixes # show available fixes
ruff check --no-cache # disable caching
ruff check --cache-dir /tmp/ruff # custom cache dir
# configuration override
ruff check --config custom.toml # custom config file
ruff check --isolated # ignore config files
editor integration
vs code setup
- install extension:
charliermarsh.ruff
(official) - configure workspace:
{
"ruff.enable": true,
"ruff.organizeImports": true,
"ruff.fixAll": true,
// format on save
"[python]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "charliermarsh.ruff"
},
// custom args
"ruff.args": ["--config", "./custom-ruff.toml"],
"ruff.lint.args": ["--select", "E,F,B,I"],
"ruff.format.args": ["--line-length", "100"]
}
language server capabilities
ruff provides a full language server implementation:
- real-time diagnostics: immediate error highlighting
- auto-fixes: quick fixes and refactoring
- import organization: automatic import sorting
- format on save: integrated with editor formatting
- hover information: rule explanations and docs links
- code actions: bulk fixes and ignore directives
other editors
neovim
-- using nvim-lspconfig
require('lspconfig').ruff_lsp.setup{}
-- using mason
require('mason').setup()
require('mason-lspconfig').setup({
ensure_installed = { 'ruff_lsp' }
})
sublime text
install LSP-ruff
package via package control
emacs
(use-package lsp-mode
:config
(add-to-list 'lsp-language-id-configuration '(python-mode . "python"))
(lsp-register-client
(make-lsp-client :new-connection (lsp-stdio-connection "ruff-lsp")
:major-modes '(python-mode)
:server-id 'ruff)))
ci/cd integration
github actions
name: code quality
on: [push, pull_request]
jobs:
ruff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/ruff-action@v1
with:
src: './src'
version: '0.12.9'
advanced github actions
jobs:
ruff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: install uv
uses: astral-sh/setup-uv@v1
- name: install dependencies
run: uv sync
- name: ruff lint
run: uv run ruff check --output-format=github
- name: ruff format check
run: uv run ruff format --check
gitlab ci
ruff:
stage: test
image: python:3.12
script:
- pip install ruff
- ruff check --output-format=gitlab > code-quality-report.json
artifacts:
reports:
codequality: code-quality-report.json
pre-commit hooks
# .pre-commit-config.yaml
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.9
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format
migration guides
from flake8 + black + isort
before:
flake8 --max-line-length=88 --extend-ignore=E203 src/
black --line-length=88 src/
isort --profile=black src/
after:
ruff check --fix src/
ruff format src/
configuration migration:
# replace setup.cfg [flake8]
[tool.ruff.lint]
select = ["E", "F", "W", "B", "I"]
ignore = ["E203", "E501"]
max-line-length = 88
# replace pyproject.toml [tool.black]
[tool.ruff.format]
line-length = 88
target-version = "py38"
# replace .isort.cfg
[tool.ruff.lint.isort]
profile = "black"
multi-line-output = 3
from pylint
# enable pylint rules in ruff
[tool.ruff.lint]
extend-select = [
"PL", # pylint
"C90", # mccabe complexity
]
# configure complexity
[tool.ruff.lint.mccabe]
max-complexity = 10
# configure pylint options
[tool.ruff.lint.pylint]
max-args = 5
max-branches = 12
max-returns = 6
max-statements = 50
gradual migration strategy
week 1: replace black and isort
[tool.ruff]
line-length = 88
[tool.ruff.lint]
select = ["I"] # just import sorting
week 2: add basic linting
[tool.ruff.lint]
select = ["E", "F", "I"] # add pycodestyle + pyflakes
ignore = ["E501"]
week 3: expand rule coverage
[tool.ruff.lint]
select = ["E", "F", "B", "I", "UP", "SIM"]
month 2: comprehensive setup
[tool.ruff.lint]
select = [
"E", "W", "F", "B", "C4", "I", "UP", "SIM",
"S", "N", "ANN", "TCH"
]
performance optimization
caching strategies
# default cache location
~/.cache/ruff/
# custom cache location
export RUFF_CACHE_DIR=/fast-ssd/ruff-cache
# or
ruff check --cache-dir /fast-ssd/ruff-cache
parallel processing
ruff automatically utilizes all cpu cores:
# check utilization
time ruff check large-project/
# force single-threaded (debugging)
RAYON_NUM_THREADS=1 ruff check
file filtering optimization
# optimize file discovery
[tool.ruff]
extend-exclude = [
"**/.venv/**",
"**/node_modules/**",
"**/__pycache__/**",
"**/migrations/**",
]
# avoid expensive glob patterns
# good: exclude = ["build/**"]
# slow: exclude = ["**/build/**"]
ci optimization
# cache ruff installation
- name: cache ruff
uses: actions/cache@v4
with:
path: ~/.cargo/bin/ruff
key: ruff-${{ hashFiles('**/pyproject.toml') }}
# parallel jobs
jobs:
lint:
run: ruff check
format:
run: ruff format --check
integration with astral toolchain
uv integration
seamless workflow with uv:
# project setup
uv init my-project
cd my-project
# add ruff as dev dependency
uv add --dev ruff
# configure in pyproject.toml (uv manages this)
[tool.ruff]
target-version = "py311"
# run via uv
uv run ruff check
uv run ruff format
uv scripts integration
# pyproject.toml
[project.scripts]
lint = "ruff check"
format = "ruff format"
check-all = "ruff check && ruff format --check"
# run via uv
uv run lint
uv run format
uv run check-all
ty integration
ruff and ty share infrastructure:
- common ast: both use
ruff_python_ast
- shared semantic analysis: consistent understanding of python code
- coordinated development: rules and features developed together
# combined workflow (future)
uv run ruff check --fix # linting and formatting
uv run ty check # type checking
advanced features
notebook support
ruff fully supports jupyter notebooks:
# lint notebooks
ruff check notebook.ipynb
# format notebooks
ruff format notebook.ipynb
# configure notebook-specific rules
[tool.ruff.lint.per-file-ignores]
"*.ipynb" = [
"E402", # module-import-not-at-top-of-file
"T201", # print-found (allow print in notebooks)
]
security scanning
comprehensive security rule coverage:
[tool.ruff.lint]
extend-select = [
"S", # flake8-bandit security rules
"SIM", # flake8-simplify (some security)
]
# security-focused configuration
[tool.ruff.lint.flake8-bandit]
check-typed-exception = true
skips = ["B101"] # skip assert_used in tests
example security catches:
- hardcoded passwords and secrets
- sql injection vulnerabilities
- unsafe yaml loading
- weak cryptographic functions
- shell injection risks
custom rule development
while ruff doesn’t support external plugins, you can:
- contribute rules: submit new rules to ruff project
- request rules: open issues for missing functionality
- combine tools: use ruff + custom checkers for specific needs
api and programmatic usage
# python api (limited)
import subprocess
import json
result = subprocess.run([
"ruff", "check", "--output-format", "json", "."
], capture_output=True, text=True)
diagnostics = json.loads(result.stdout)
for diagnostic in diagnostics:
print(f"{diagnostic['filename']}:{diagnostic['location']['row']} - {diagnostic['message']}")
troubleshooting
common issues
performance problems
# profile slow runs
time ruff check --statistics
# check cache status
ls -la ~/.cache/ruff/
# clear cache if corrupted
ruff clean
configuration conflicts
# show effective configuration
ruff config
# validate configuration syntax
ruff check --config pyproject.toml --show-settings
# test specific files
ruff check --isolated specific_file.py
rule selection issues
# see which rules are enabled
ruff check --show-source
# explain specific rule
ruff rule F401
# test rule selection
ruff check --select F401 --no-fix
debugging tips
verbose output
# detailed error information
ruff check --verbose
# show all settings
ruff check --show-settings
# explain fix suggestions
ruff check --show-fixes
rule conflicts
# find formatter conflicts
ruff check --select ALL | grep -E "(COM812|ISC001)"
# test rule combinations
ruff check --select COM812,ISC001 --format --preview
environment issues
python version detection
# explicit python version
ruff check --target-version py311
# check detected version
ruff check --show-settings | grep target_version
path and import issues
# check namespace packages
ruff check --namespace-packages
# explicit source roots
ruff check --src src/
resources and references
official resources
- documentation: https://docs.astral.sh/ruff/
- github repository: https://github.com/astral-sh/ruff
- configuration schema: https://github.com/astral-sh/ruff/blob/main/ruff.schema.json
- rules reference: https://docs.astral.sh/ruff/rules/
- astral blog: https://astral.sh/blog
community and ecosystem
- vs code extension: charliermarsh.ruff
- pre-commit hooks: https://github.com/astral-sh/ruff-pre-commit
- github action: https://github.com/astral-sh/ruff-action
- discussions: https://github.com/astral-sh/ruff/discussions
related guides
migration resources
- black compatibility: https://docs.astral.sh/ruff/formatter/black/
- flake8 migration: https://docs.astral.sh/ruff/faq/#how-does-ruff-compare-to-flake8
- performance benchmarks: https://github.com/astral-sh/ruff#benchmarks