À quoi ça sert

Deux jobs très complémentaires :

  • Linter — trouver les problèmes dans ton code avant l'exécution : variables non utilisées, imports manquants, mauvais idioms, fautes de logique, anti-patterns de sécurité, code mort…
  • Formatter — réécrire ton code dans un style standard (espaces, sauts de ligne, longueur des lignes), pour que toute l'équipe livre le même format sans débat.

Historiquement il fallait plusieurs outils : flake8 + plugins pour linter, black pour formatter, isort pour trier les imports, pyupgrade pour moderniser la syntaxe… Ruff fait tout ça, configuré en un seul endroit, et 10 à 100× plus rapide.

Pourquoi la vitesse change tout

flake8 sur un gros projet prend 20-30 secondes. Black 5-10 secondes. Avec Ruff, c'est quelques dizaines de millisecondes. Du coup tu peux lancer le linter à chaque sauvegarde dans VS Code sans ralentissement perceptible — ce qui change complètement le rapport au code : tu vois les erreurs en direct au lieu de les découvrir en CI.

Le concept : règles et codes

Ruff agrège des centaines de règles, chacune identifiée par un code de la forme F401, E501, UP007. Le préfixe correspond au plugin d'origine (avant Ruff existait) :

  • E/W — pycodestyle (PEP 8)
  • F — pyflakes (erreurs de base)
  • I — isort (ordre des imports)
  • UP — pyupgrade (moderniser la syntaxe)
  • B — flake8-bugbear (bugs courants)
  • SIM — flake8-simplify (refacto évidentes)
  • N — pep8-naming (conventions de noms)
  • D — pydocstyle (docstrings)

Tu actives les familles qui t'intéressent dans la config, et tu désactives les règles qui te gênent. Par défaut, Ruff active un sous-ensemble raisonnable (E, F).

Auto-fix : la fonctionnalité qui rend Ruff addictif

Pour beaucoup de règles, Ruff sait réparer tout seul : supprimer un import inutile, trier les imports, moderniser List[int] en list[int]… Une commande (ruff check --fix) et le code est nettoyé, sans risque car les fixes sont conservateurs.

Un exemple d'usage

Code volontairement « sale » :

python (avant)
import os
import sys
from typing import List, Optional

def greet(names: List[str], prefix: Optional[str]=None) -> None:
    for n in names:
        print(f"hello {n}")
bash
ruff check --fix --select ALL fichier.py
ruff format fichier.py
python (après)
def greet(names: list[str], prefix: str | None = None) -> None:
    for n in names:
        print(f"hello {n}")

Imports inutiles supprimés, types modernisés (Python 3.10+), formatage standardisé. Le tout en quelques millisecondes.

How-to : installer et utiliser Ruff

  1. Installer Ruff en dépendance dev

    Ruff est un outil de développement uniquement (jamais nécessaire à l'exécution), donc en dépendance dev avec UV :

    bash
    uv add --dev ruff
  2. Linter le projet

    bash
    uv run ruff check .             # scan tout le projet
    uv run ruff check src/         # un sous-dossier
    uv run ruff check --fix .       # corriger ce qui peut l'être

    Chaque problème affiche le code de règle, le fichier, la ligne et un extrait :

    text
    src/app.py:3:1: F401 [*] `os` imported but unused
    src/app.py:8:80: E501 Line too long (95 > 88)
    Found 2 errors.
    [*] 1 fixable with the `--fix` option.
  3. Formater le code

    Ruff embarque un formatter compatible Black (mêmes règles, mêmes choix), mais beaucoup plus rapide :

    bash
    uv run ruff format .            # formate en place
    uv run ruff format --check .    # vérifie sans modifier (CI)
    uv run ruff format --diff .     # affiche les changements proposés
  4. Configurer dans pyproject.toml

    Toute la config tient dans une section [tool.ruff] de ton pyproject.toml. Bonne base de départ pour un projet débutant :

    toml (pyproject.toml)
    [tool.ruff]
    line-length = 100
    target-version = "py312"
    
    [tool.ruff.lint]
    # Familles de règles à activer
    select = [
        "E",    # pycodestyle errors
        "F",    # pyflakes
        "I",    # isort
        "UP",   # pyupgrade
        "B",    # bugbear
        "SIM",  # simplify
    ]
    ignore = ["E501"]       # laisser le formatter gérer la longueur
    
    [tool.ruff.format]
    quote-style = "double"    # comme black

    On peut commencer avec select = ["E", "F"] seulement, puis ajouter des familles au fil du temps quand on est à l'aise.

  5. Ignorer une règle ponctuellement

    Sur une ligne précise, on ajoute un commentaire # noqa: CODE :

    python
    import os  # noqa: F401  <- import volontairement non utilisé
    
    password = "hardcoded"  # noqa: S105

    Pour ignorer une règle dans un fichier entier, on utilise la section per-file-ignores du pyproject.toml — pratique pour exempter par exemple les tests d'une règle.

  6. Intégrer dans VS Code

    L'extension officielle Ruff ajoute le lint et le format au save. Réglages clés dans .vscode/settings.json :

    json
    {
      "[python]": {
        "editor.defaultFormatter": "charliermarsh.ruff",
        "editor.formatOnSave": true,
        "editor.codeActionsOnSave": {
          "source.fixAll.ruff": "explicit",
          "source.organizeImports.ruff": "explicit"
        }
      }
    }

    À chaque Ctrl+S : le code est formaté, les imports triés, les erreurs fixables corrigées. Boucle de feedback parfaite.

  7. Brancher Ruff en CI

    En CI, on lance ruff check (sans --fix !) et ruff format --check. Si l'un échoue, la build casse.

    yaml (.github/workflows/lint.yml)
    name: Lint
    on: [push, pull_request]
    
    jobs:
      lint:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - uses: astral-sh/setup-uv@v3
          - run: uv sync --dev
          - run: uv run ruff check .
          - run: uv run ruff format --check .
  8. Pre-commit (optionnel mais conseillé)

    pre-commit lance Ruff avant chaque commit local : impossible de commiter du code mal formaté. Plus de cassures de CI sur des broutilles.

    yaml (.pre-commit-config.yaml)
    repos:
      - repo: https://github.com/astral-sh/ruff-pre-commit
        rev: v0.7.0
        hooks:
          - id: ruff
            args: [--fix]
          - id: ruff-format
    bash
    uv add --dev pre-commit
    uv run pre-commit install

Aide-mémoire

bash (commandes essentielles)
ruff check .                  # linter tout le projet
ruff check --fix .            # corriger automatiquement
ruff check --watch .          # mode re-run au changement
ruff format .                 # formater
ruff format --check .         # CI : vérifier sans modifier
ruff format --diff .          # aperçu des changements
ruff rule F401                # doc d'une règle
ruff linter                   # lister toutes les familles
toml (config minimale)
[tool.ruff]
line-length = 100
target-version = "py312"

[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B"]
ignore = ["E501"]

[tool.ruff.lint.per-file-ignores]
"tests/*" = ["S101"]      # autoriser assert dans les tests
python (suppressions ponctuelles)
x = 1  # noqa: F841  (ignore une règle)
y = 2  # noqa          (ignore toutes les règles)

Ruff et le reste de l'écosystème

  • UV — même éditeur (Astral), même philosophie (Rust, vitesse extrême). Le combo uv add --dev ruff est standard sur tout projet Python moderne.
  • pytest — Ruff et pytest se complètent : Ruff vérifie le style et les anti-patterns, pytest vérifie le comportement. Les deux tournent ensemble en CI.
  • VS Code / IntelliJ — Ruff a des plugins officiels pour les deux. Format-on-save + fix-on-save = la config gagnante.
  • CI/CD — pattern classique : un job « lint » qui lance ruff check et ruff format --check sur chaque PR.
  • Docstring — la famille de règles D (pydocstyle) permet à Ruff de vérifier la présence et le format des docstrings selon Google, NumPy ou PEP 257.
Ruff ne remplace pas mypy

Ruff fait du linting (analyse statique légère) et du formatage. Il ne fait pas de vérification de types — pour ça il faut mypy (ou ty, en cours de développement par Astral). Les trois se complètent : Ruff pour le style, mypy/ty pour les types, pytest pour le comportement.

Pour aller plus loin