Zum Inhalt springen

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 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

[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

CommandDescription
ruff check .Lint all Python files in current directory
ruff check src/Lint a specific directory
ruff check file.pyLint 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 E501Explain a specific rule
ruff linterList all available linters
ruff cleanClear ruff cache
ruff versionShow version info

Advanced Usage

Rule categories reference

PrefixPluginDescription
E / WpycodestylePEP 8 style errors and warnings
FPyflakesUndefined names, unused imports
IisortImport ordering
Npep8-namingNaming conventions
DpydocstyleDocstring conventions
UPpyupgradeModernize Python syntax
Bflake8-bugbearLikely bugs and design issues
Sflake8-banditSecurity vulnerabilities
C4flake8-comprehensionsComprehension improvements
SIMflake8-simplifyCode simplification
ANNflake8-annotationsType annotation enforcement
RUFRuff-specificRuff native rules
PTflake8-pytest-stylepytest style enforcement
ERAeradicateCommented-out code
PLPylintPylint rules ported to Ruff
TRYtryceratopsException 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.