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 combination100k loc codebasetypical 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

prefixtoolrulesdescription
E/Wpycodestyle91 rulesstyle and formatting
Fpyflakes66 ruleslogical errors and imports
Bflake8-bugbear34 rulescommon bug patterns
Sflake8-bandit113 rulessecurity vulnerabilities
Dpydocstyle58 rulesdocstring conventions
PLpylint73 rulesgeneral code quality
UPpyupgrade37 rulespython version upgrades
Iisort12 rulesimport sorting
C4flake8-comprehensions19 rulescomprehension improvements
SIMflake8-simplify110 rulescode simplification
RUFruff-specific35 rulesruff’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
[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

  1. install extension: charliermarsh.ruff (official)
  2. 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:

  1. contribute rules: submit new rules to ruff project
  2. request rules: open issues for missing functionality
  3. 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

community and ecosystem

migration resources

on this page