Ruff
Overview
Ruff is a Python linter and code formatter written in Rust. It is 10–100x faster than existing tools and replaces flake8 (plus plugins), black, isort, pyupgrade, and others with a single binary. Ruff supports over 800 built-in rules including Pyflakes, pycodestyle, pep8-naming, flake8-bugbear, flake8-bandit (security), isort, and more. It integrates with pre-commit, editors via LSP, and CI/CD pipelines.
Installation
pip / uv (recommended per-project)
pip install ruff
# or
uv add --dev ruff
# or globally
uv tool install ruff
Homebrew
brew install ruff
Cargo (from source)
cargo install ruff
Verify installation
ruff --version
ruff check --help
Configuration
pyproject.toml (recommended)
[tool.ruff]
line-length = 88
target-version = "py311"
exclude = [
".git", ".venv", "__pycache__",
"migrations", "dist", "build",
]
[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"B", # flake8-bugbear
"C4", # flake8-comprehensions
"UP", # pyupgrade
"S", # flake8-bandit (security)
"N", # pep8-naming
"ANN", # flake8-annotations
"SIM", # flake8-simplify
"RUF", # Ruff-specific rules
]
ignore = [
"E501", # line too long (handled by formatter)
"S101", # use of assert (ok in tests)
"ANN101", # missing type annotation for self
]
[tool.ruff.lint.per-file-ignores]
"tests/**/*.py" = ["S101", "ANN", "D"]
"scripts/**/*.py" = ["S603", "S607"]
"migrations/**/*.py" = ["E501", "N806"]
[tool.ruff.lint.isort]
known-first-party = ["my_package"]
force-single-line = false
combine-as-imports = true
[tool.ruff.lint.flake8-bandit]
check-typed-exception = true
[tool.ruff.format]
quote-style = "double"
indent-style = "space"
skip-magic-trailing-comma = false
line-ending = "auto"
docstring-code-format = true
ruff.toml (standalone)
line-length = 88
target-version = "py311"
[lint]
select = ["E", "F", "I", "B", "S"]
ignore = ["E501"]
[format]
quote-style = "double"
Core Commands
| Command | Description |
|---|---|
ruff check . | Lint all Python files in current directory |
ruff check src/ | Lint a specific directory |
ruff check file.py | Lint a single file |
ruff check --fix . | Auto-fix safe, fixable violations |
ruff check --fix --unsafe-fixes . | Apply all fixes including unsafe ones |
ruff format . | Format all Python files |
ruff format --check . | Check formatting without modifying files |
ruff format --diff . | Show formatting diff |
ruff check --select E,F . | Run only specific rule categories |
ruff check --ignore E501 . | Ignore specific rules |
ruff check --per-file-ignores "tests/*.py:S101" . | Per-file rule ignores |
ruff check --show-source . | Show offending source code |
ruff check --show-fixes . | Show suggested fixes |
ruff check --statistics . | Summarize violations by rule |
ruff check --output-format json . | JSON output for tooling |
ruff rule E501 | Explain a specific rule |
ruff linter | List all available linters |
ruff clean | Clear ruff cache |
ruff version | Show version info |
Advanced Usage
Rule categories reference
| Prefix | Plugin | Description |
|---|---|---|
E / W | pycodestyle | PEP 8 style errors and warnings |
F | Pyflakes | Undefined names, unused imports |
I | isort | Import ordering |
N | pep8-naming | Naming conventions |
D | pydocstyle | Docstring conventions |
UP | pyupgrade | Modernize Python syntax |
B | flake8-bugbear | Likely bugs and design issues |
S | flake8-bandit | Security vulnerabilities |
C4 | flake8-comprehensions | Comprehension improvements |
SIM | flake8-simplify | Code simplification |
ANN | flake8-annotations | Type annotation enforcement |
RUF | Ruff-specific | Ruff native rules |
PT | flake8-pytest-style | pytest style enforcement |
ERA | eradicate | Commented-out code |
PL | Pylint | Pylint rules ported to Ruff |
TRY | tryceratops | Exception handling patterns |
Security rules (S / bandit)
[tool.ruff.lint]
select = ["S"] # Enable all bandit security rules
# Key security rules:
# S101 - assert usage (disable in tests)
# S102 - exec usage
# S103 - bad file permissions
# S105 - hardcoded password string
# S106 - hardcoded password in function arg
# S108 - probable insecure usage of temp file
# S113 - probable use of requests without timeout
# S301 - pickle deserialization
# S324 - hashlib use of insecure hash functions (md5, sha1)
# S501 - ssl context without verification
# S506 - unsafe yaml.load (use yaml.safe_load)
# S608 - possible SQL injection
# S701 - jinja2 autoescape disabled
Inline rule suppression
import os # noqa: F401
x = eval(user_input) # noqa: S307 - we validate user_input before this
result = md5(data).hexdigest() # noqa: S324
# Suppress for entire file (at top of file):
# ruff: noqa: E501
# ruff: noqa
Watching for changes
# Watch mode (re-run on file changes)
ruff check --watch .
# Combined with formatter watch
ruff format --watch .
Output formats for CI
# GitHub Actions annotations
ruff check --output-format github .
# GitLab Code Quality
ruff check --output-format gitlab .
# SARIF (for security dashboards)
ruff check --output-format sarif . > ruff-results.sarif
# JSON for custom tooling
ruff check --output-format json . | jq '.[] | select(.code == "S")'
Combining with type checkers
# Run ruff then mypy
ruff check . && mypy src/
# In Makefile
lint:
ruff check .
ruff format --check .
mypy src/ --strict
Common Workflows
Pre-commit integration
# .pre-commit-config.yaml
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.9
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
# Install and run pre-commit
pip install pre-commit
pre-commit install
pre-commit run --all-files
GitHub Actions CI
name: Lint
on: [push, pull_request]
jobs:
ruff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
- run: uv tool install ruff
- name: Lint
run: ruff check --output-format github .
- name: Format check
run: ruff format --check .
Migrating from flake8 + black + isort
# 1. Remove old tools
pip uninstall flake8 black isort pylint
# 2. Install ruff
pip install ruff
# 3. Convert .flake8 / setup.cfg to pyproject.toml
# Most options map directly. See ruff docs for mapping.
# 4. Run to see current violations
ruff check .
# 5. Auto-fix safe issues
ruff check --fix .
# 6. Format code (equivalent to black)
ruff format .
Editor integration
# VS Code: install "Ruff" extension from Astral
# Then in settings.json:
# "[python]": {
# "editor.defaultFormatter": "charliermarsh.ruff",
# "editor.formatOnSave": true,
# "editor.codeActionsOnSave": {
# "source.fixAll.ruff": "explicit",
# "source.organizeImports.ruff": "explicit"
# }
# }
# Neovim via nvim-lspconfig
# require('lspconfig').ruff_lsp.setup({})
Checking specific violation counts
# See violation counts by rule
ruff check --statistics . | sort -rn | head -20
# Count total violations
ruff check . 2>&1 | tail -1
# Find all security violations
ruff check --select S --output-format json . | jq 'length'
# Only show errors, not warnings
ruff check --select E .
Tips and Best Practices
Start with a broad ruleset and expand. Begin with ["E", "F", "I"] (the safe essentials), fix violations, then progressively add B, UP, S, and others. This prevents overwhelming developers with thousands of violations at once.
Commit --fix changes separately. When introducing ruff to an existing codebase, apply ruff check --fix . as a single dedicated commit before adding it to CI. This separates refactoring noise from logic changes in history.
Enable security rules (S) in every project. The bandit-derived rules catch real vulnerabilities like SQL injection patterns, unsafe deserialization, hardcoded credentials, and weak hashing. They are disabled by default — explicitly opt in.
Use per-file-ignores wisely. Tests often need S101 (assert) and ANN (annotations) silenced. Migration files often need E501 and N806 ignored. Keep ignores narrow and documented.
Prefer ruff format over black. Ruff’s formatter is intentionally black-compatible. Switching is usually zero-diff and gives a massive speed improvement.
Run in watch mode during development. ruff check --watch . provides instant feedback as you edit files, making it as responsive as a type checker.
Do not suppress with # noqa broadly. Use specific rule codes (# noqa: S301) rather than bare # noqa. Broad suppression hides real issues introduced later.
Use ruff rule RULE_CODE to understand violations. Before ignoring a rule, run ruff rule S324 to read its rationale, severity, and fix options directly in the terminal.
Integrate SARIF output with GitHub Advanced Security. Upload ruff check --output-format sarif output to GitHub’s code scanning dashboard for security rule findings to appear in the Security tab.
Pin ruff version. Ruff adds new rules in minor versions that can break CI. Pin with ruff==0.4.x in pyproject.toml or pre-commit config, and upgrade intentionally.