15 KiB
15 KiB
Ruff: Complete Guide
Ruff is an extremely fast Python linter and code formatter written in Rust. It's 10-100x faster than existing linters and formatters, combining functionality from Flake8, Black, isort, and more into a single tool.
Table of Contents
- Installation
- Linting
- Formatting
- Configuration
- Rule Selection
- Error Suppression
- Editor Integration
- CI/CD Integration
- Migration Guide
Installation
With UV (Recommended)
# Install as a tool
uv tool install ruff
# Or add to project
uv add --dev ruff
With pip
pip install ruff
With Homebrew
brew install ruff
With conda
conda install -c conda-forge ruff
Verify Installation
ruff version
# Output: ruff 0.12.8
Linting
Basic Linting
# Check current directory
ruff check .
# Check specific files
ruff check src/main.py tests/
# Check and auto-fix
ruff check --fix .
# Preview changes without applying
ruff check --diff .
# Show fixes that would be applied
ruff check --show-fixes .
Watch Mode
# Continuously check for errors
ruff check --watch .
Output Formats
# Default (human-readable)
ruff check .
# JSON format
ruff check --output-format json .
# GitHub Actions format
ruff check --output-format github .
# GitLab format
ruff check --output-format gitlab .
# JUnit XML format
ruff check --output-format junit .
Unsafe Fixes
# Include unsafe fixes
ruff check --fix --unsafe-fixes .
# Fix only (don't report remaining violations)
ruff check --fix-only .
Statistics and Debugging
# Show statistics for each rule
ruff check --statistics .
# Show files that will be checked
ruff check --show-files .
# Show configuration being used
ruff check --show-settings .
Formatting
Basic Formatting
# Format current directory
ruff format .
# Format specific files
ruff format src/main.py
# Check formatting without changes
ruff format --check .
# Show diff of changes
ruff format --diff .
Format and Lint Together
# Recommended workflow
ruff check --fix . && ruff format .
Format Options
# Use preview features
ruff format --preview .
# Specific target Python version
ruff format --target-version py312 .
Configuration
Configuration Files
Ruff looks for configuration in (in order of precedence):
.ruff.tomlruff.tomlpyproject.toml
Basic Configuration
pyproject.toml:
[tool.ruff]
# Set line length
line-length = 88
indent-width = 4
# Set Python version
target-version = "py311"
# Exclude directories
exclude = [
".git",
".venv",
"__pycache__",
"build",
"dist",
"*.egg-info",
]
[tool.ruff.lint]
# Enable specific rules
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # Pyflakes
"I", # isort
"B", # flake8-bugbear
"UP", # pyupgrade
"C4", # flake8-comprehensions
]
# Ignore specific rules
ignore = [
"E501", # line too long (handled by formatter)
]
# Allow auto-fixing
fixable = ["ALL"]
unfixable = []
# Allow unused variables prefixed with underscore
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
[tool.ruff.format]
# Use double quotes
quote-style = "double"
# Use spaces for indentation
indent-style = "space"
# Respect magic trailing commas
skip-magic-trailing-comma = false
# Auto-detect line endings
line-ending = "auto"
# Format code in docstrings
docstring-code-format = true
docstring-code-line-length = 72
Per-File Configuration
[tool.ruff.lint.per-file-ignores]
# Ignore unused imports in __init__.py
"__init__.py" = ["F401"]
# Ignore assert statements in tests
"tests/*" = ["S101"]
# Ignore print statements in scripts
"scripts/*" = ["T201"]
# Ignore import order in migrations
"migrations/*" = ["I"]
Import Sorting (isort)
[tool.ruff.lint.isort]
# Known first-party packages
known-first-party = ["myproject"]
# Known third-party packages
known-third-party = ["django", "requests"]
# Section order
section-order = [
"future",
"standard-library",
"third-party",
"first-party",
"local-folder"
]
# Combine as imports
combine-as-imports = true
# Split on trailing comma
split-on-trailing-comma = true
Docstring Configuration
[tool.ruff.lint.pydocstyle]
# Use Google-style docstrings
convention = "google" # or "numpy", "pep257"
Rule Selection
Available Rule Sets
| Code | Name | Description |
|---|---|---|
| E | pycodestyle errors | PEP 8 error codes |
| W | pycodestyle warnings | PEP 8 warning codes |
| F | Pyflakes | Logical errors |
| I | isort | Import sorting |
| N | pep8-naming | Naming conventions |
| D | pydocstyle | Docstring style |
| UP | pyupgrade | Modern Python syntax |
| B | flake8-bugbear | Common bugs |
| A | flake8-builtins | Builtin shadowing |
| C4 | flake8-comprehensions | List/dict/set comprehensions |
| T20 | flake8-print | Print statements |
| PT | flake8-pytest-style | Pytest style |
| S | flake8-bandit | Security issues |
| Q | flake8-quotes | Quote style |
| RUF | Ruff-specific | Ruff custom rules |
Selecting Rules
# Enable specific rule set
ruff check --select E,W,F .
# Enable all rules
ruff check --select ALL .
# Extend default rules
ruff check --extend-select B,I .
# Ignore specific rules
ruff check --ignore E501,W503 .
Configuration
[tool.ruff.lint]
# Start with Flake8 defaults and add more
select = ["E", "F"]
extend-select = ["B", "I", "N"]
# Ignore specific rules
ignore = ["E501"]
extend-ignore = ["W503"]
# Make specific rules fixable
fixable = ["I", "F401"]
# Make specific rules non-fixable
unfixable = ["B"]
Popular Rule Combinations
Minimal (Default):
select = ["E4", "E7", "E9", "F"]
Standard:
select = ["E", "W", "F", "I"]
Strict:
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # Pyflakes
"I", # isort
"N", # pep8-naming
"D", # pydocstyle
"UP", # pyupgrade
"B", # flake8-bugbear
"C4", # flake8-comprehensions
"S", # flake8-bandit
"T20", # flake8-print
"PT", # flake8-pytest-style
]
Error Suppression
Inline Comments
# Ignore specific rule on a line
import os # noqa: F401
# Ignore multiple rules
import sys, os # noqa: F401, E401
# Ignore all rules on a line
very_long_variable_name = "value" # noqa
# Ignore for next line
# noqa: E501
very_long_line = "This is a very long line that exceeds the limit"
File-Level Suppression
# At top of file
# ruff: noqa: F401, E402
import os
import sys
Auto-Add noqa Comments
# Automatically add noqa comments
ruff check --add-noqa .
Type-Aware Suppression
# Type checkers only
import TYPE_CHECKING # type: ignore
# Ruff specific
from typing import Optional # ruff: noqa: UP007
Editor Integration
VS Code
Install the Ruff VS Code extension:
.vscode/settings.json:
{
"[python]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.ruff": "explicit",
"source.organizeImports.ruff": "explicit"
},
"editor.defaultFormatter": "charliermarsh.ruff"
},
"ruff.lint.args": ["--config=pyproject.toml"],
"ruff.format.args": ["--config=pyproject.toml"]
}
PyCharm/IntelliJ
- Install Ruff via system package manager
- Configure as external tool:
- File → Settings → Tools → External Tools
- Add Ruff with appropriate arguments
Vim/Neovim
With ALE:
let g:ale_linters = {'python': ['ruff']}
let g:ale_fixers = {'python': ['ruff']}
With nvim-lspconfig:
require('lspconfig').ruff_lsp.setup{}
Emacs
;; With flycheck
(require 'flycheck)
(flycheck-define-checker python-ruff
"A Python checker using ruff."
:command ("ruff" "check" "--output-format" "text" source)
:error-patterns
((error line-start (file-name) ":" line ":" column ": " (message) line-end))
:modes python-mode)
(add-to-list 'flycheck-checkers 'python-ruff)
CI/CD Integration
GitHub Actions
name: Lint with Ruff
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
run: curl -LsSf https://astral.sh/uv/install.sh | sh
- name: Install dependencies
run: uv sync
- name: Lint with Ruff
run: uv run ruff check .
- name: Format check with Ruff
run: uv run ruff format --check .
Using Ruff Action:
- uses: chartboost/ruff-action@v1
with:
args: check --output-format github
GitLab CI
ruff:
image: python:3.12
before_script:
- pip install ruff
script:
- ruff check .
- ruff format --check .
Pre-commit Hooks
.pre-commit-config.yaml:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.8
hooks:
# Run the linter
- id: ruff
args: [--fix]
# Run the formatter
- id: ruff-format
Install:
pip install pre-commit
pre-commit install
Docker
FROM python:3.12-slim
# Install Ruff
RUN pip install ruff
# Copy code
COPY . /app
WORKDIR /app
# Run Ruff
RUN ruff check .
RUN ruff format --check .
Migration Guide
From Flake8
# Flake8 configuration
[flake8]
max-line-length = 88
extend-ignore = E203, W503
exclude = .git,__pycache__,build,dist
# Equivalent Ruff configuration
[tool.ruff]
line-length = 88
[tool.ruff.lint]
ignore = ["E203", "W503"]
exclude = [".git", "__pycache__", "build", "dist"]
From Black
Ruff format is designed to be a drop-in replacement for Black:
# Replace
black .
# With
ruff format .
Configuration is compatible:
[tool.black]
line-length = 88
target-version = ["py311"]
# Becomes
[tool.ruff]
line-length = 88
target-version = "py311"
From isort
# Replace
isort .
# With
ruff check --select I --fix .
# Or
ruff format . # Includes import sorting
Configuration:
[tool.isort]
profile = "black"
known_first_party = ["myproject"]
# Becomes
[tool.ruff.lint.isort]
known-first-party = ["myproject"]
From pyupgrade
# Replace
pyupgrade --py311-plus **/*.py
# With
ruff check --select UP --fix .
Complete Migration
Before:
# Multiple tools
isort .
black .
flake8 .
pyupgrade --py311-plus **/*.py
After:
# Single command
ruff check --fix . && ruff format .
Configuration consolidation:
# pyproject.toml - All in one place
[tool.ruff]
line-length = 88
target-version = "py311"
[tool.ruff.lint]
select = ["E", "W", "F", "I", "UP", "B"]
ignore = ["E501"]
[tool.ruff.format]
quote-style = "double"
Common Rules Explained
E/W (pycodestyle)
# E501: Line too long
very_long_line = "This line exceeds 88 characters and will be flagged by E501"
# E401: Multiple imports on one line
import os, sys # Bad
# E402: Module level import not at top
def foo():
pass
import os # Bad
# W503: Line break before binary operator
result = (value
+ other_value) # Warning in older style guides
F (Pyflakes)
# F401: Imported but unused
import os # Not used anywhere
# F841: Local variable assigned but never used
def foo():
x = 10 # Never used
# F821: Undefined name
print(undefined_var) # NameError at runtime
I (isort)
# Incorrect import order
from myproject import foo
import os
import sys
from third_party import bar
# Correct
import os
import sys
from third_party import bar
from myproject import foo
UP (pyupgrade)
# UP006: Use list instead of List from typing
from typing import List # Old
def foo() -> List[int]: # Old
pass
# New
def foo() -> list[int]: # New (Python 3.9+)
pass
# UP032: Use f-string instead of format
"{} {}".format(a, b) # Old
f"{a} {b}" # New
B (flake8-bugbear)
# B006: Mutable default argument
def foo(items=[]): # Bad - mutable default
items.append(1)
return items
# B008: Do not perform function call in default argument
def foo(timestamp=datetime.now()): # Bad
pass
# B011: Do not use assert False
assert False, "This should not happen" # Use raise instead
Advanced Features
Rule Explainer
# Get detailed explanation of a rule
ruff rule E501
# List all rules
ruff rule --all
# Search for rules
ruff rule --filter "import"
Custom Configuration per Directory
# Root pyproject.toml
[tool.ruff]
line-length = 88
# tests/ can have different config
Create tests/pyproject.toml:
[tool.ruff]
extend = "../pyproject.toml" # Inherit root config
line-length = 120 # Override for tests
Preview Mode
# Enable preview features
ruff check --preview .
ruff format --preview .
[tool.ruff]
preview = true
Unsafe Fixes
Some fixes are marked as "unsafe" because they might change semantics:
# Include unsafe fixes
ruff check --fix --unsafe-fixes .
Cache Management
# Clear Ruff cache
ruff clean
# Disable cache
ruff check --no-cache .
Performance Tips
- Use --no-cache for CI: Ensures fresh analysis
- Run in parallel: Ruff already parallelizes internally
- Use specific selects: Only enable rules you need
- Exclude large directories: Skip
node_modules,venv, etc. - Use fix in CI: Auto-fix what you can, then check remaining
Troubleshooting
Ruff Not Found
# Check installation
which ruff
ruff version
# Reinstall
uv tool uninstall ruff
uv tool install ruff
Configuration Not Loaded
# Show active configuration
ruff check --show-settings .
# Use specific config file
ruff check --config path/to/pyproject.toml .
Rules Not Working
# Check which rules are enabled
ruff rule --all | grep E501
# Explain specific rule
ruff rule E501
Performance Issues
# Profile ruff execution
time ruff check .
# Check file discovery
ruff check --show-files .
# Clear cache
ruff clean
Best Practices
- Start with defaults, add rules gradually
- Use format + lint together in your workflow
- Configure in pyproject.toml for centralized settings
- Use per-file-ignores sparingly
- Enable auto-fix in pre-commit hooks
- Run in CI/CD to enforce standards
- Use --diff in CI to show what would change
- Document rule exceptions with inline comments
- Keep Ruff updated for new features and fixes
- Use editor integration for real-time feedback