L’outillage Python a basculé en 2026. Astral, l’éditeur derrière Ruff, a livré uv en 2024 et stabilisé l’écosystème en une suite cohérente : uv remplace pip + virtualenv + pip-tools + pyenv + poetry par un seul binaire Rust, ruff remplace flake8 + black + isort + pydocstyle + pyupgrade par un seul linter+formatter Rust. Le gain est radical : un projet qui met 45 secondes à installer ses dépendances avec pip s’installe en 2 secondes avec uv. Un linter qui prend 8 secondes avec flake8 tourne en 200 ms avec Ruff. Ce tutoriel installe et configure les deux pour un workflow professionnel en 2026.
Prérequis
- Python 3.13.13 ou 3.14.5 disponible localement (cf. Installer Python 3) — facultatif, uv peut l’installer lui-même
- Un terminal (zsh, bash ou PowerShell)
- Un projet existant ou intention d’en créer un nouveau
- Temps estimé : 60 minutes
Étape 1 — Installer uv (Rust-based, autonome)
uv 0.11.14 est sorti le 12 mai 2026. L’installation passe par un script standalone qui n’a pas besoin de Python pré-installé (uv est un binaire Rust autonome). Sur Linux/macOS, une commande curl ; sur Windows, un script PowerShell.
# macOS / Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows PowerShell
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
# Vérification
uv --version
# uv 0.11.14
# Mise à jour
uv self update
Le binaire s’installe dans ~/.local/bin/uv (Linux/macOS) ou %LOCALAPPDATA%/uv/bin/uv.exe (Windows) et s’ajoute automatiquement au PATH après redémarrage du shell. À ce stade, uv est prêt à gérer Python lui-même : uv python install 3.14 télécharge l’interpréteur officiel sans toucher au système. Cette autonomie évite les conflits avec le Python du système (sur macOS notamment, où le Python d’Apple peut casser des installations).
Étape 2 — Créer un projet avec uv init
uv init bootstrappe un projet propre : pyproject.toml minimal, .python-version, environnement virtuel, structure de fichiers. Cela remplace les recettes magiques type python -m venv .venv et pip install -e . par une seule commande.
uv init mon-service --python 3.14
cd mon-service
# Ajouter des dépendances
uv add httpx pydantic
uv add --dev pytest pytest-cov ruff
# Installer toutes les dépendances (génère uv.lock)
uv sync
# Lancer un script dans l'environnement
uv run python -c "import httpx; print(httpx.__version__)"
# Lancer un outil installé
uv run pytest
Trois différences majeures avec poetry/pip-tools. Le uv.lock est généré automatiquement et déterministe : même sur Mac M2 et runner Linux x86_64, les versions résolues sont identiques. uv sync crée et synchronise .venv en quelques secondes (au lieu de minutes). uv run exécute une commande dans le venv sans qu’on ait à l’activer manuellement — fini les source .venv/bin/activate oubliés.
Étape 3 — Configurer pyproject.toml avec uv
uv suit la PEP 621 (project metadata standardisée) et étend avec ses propres options sous [tool.uv]. La configuration suivante couvre 90 % des besoins : groupes de dépendances de dev, sources alternatives, contraintes Python.
[project]
name = "mon-service"
version = "0.1.0"
description = "Service API en Python 3.14"
requires-python = ">=3.13,<3.15"
dependencies = [
"httpx>=0.27,<1.0",
"pydantic>=2.13,<3.0",
"fastapi>=0.136",
]
[project.optional-dependencies]
postgres = ["asyncpg>=0.30", "sqlalchemy[asyncio]>=2.0"]
redis = ["redis>=5.0"]
[dependency-groups]
dev = [
"pytest>=9.0",
"pytest-cov>=7.1",
"pytest-asyncio>=1.3",
"ruff>=0.13",
"mypy>=1.13",
]
[tool.uv]
package = true
default-groups = ["dev"]
managed = true
Le requires-python = ">=3.13,<3.15" est une discipline structurante : il fait échouer uv sync sur un Python incompatible plutôt que d’installer puis crasher à l’exécution. Les extras permettent d’installer le minimum (uv sync) ou enrichir (uv sync --extra postgres --extra redis). Les dependency-groups (PEP 735, supportés par uv) remplacent les extras dev abusifs et restent invisibles côté utilisateur du package.
Étape 4 — Installer et configurer Ruff
Ruff fusionne linter et formatter. Sa configuration vit dans pyproject.toml sous [tool.ruff]. La règle d’or pour débuter : activer un ensemble large de règles, puis désactiver explicitement celles qui ne conviennent pas à votre style.
[tool.ruff]
line-length = 100
target-version = "py314"
src = ["src", "tests"]
extend-exclude = ["migrations", "vendor"]
[tool.ruff.lint]
select = [
"E", "W", "F", "I", "B", "C4", "UP", "N", "S", "SIM", "RUF",
]
ignore = [
"E501", # line too long — géré par le formatter
"S101", # use of assert — OK en tests
]
[tool.ruff.lint.per-file-ignores]
"tests/**/*.py" = ["S101", "S105", "S106"]
"__init__.py" = ["F401"]
[tool.ruff.format]
quote-style = "double"
indent-style = "space"
docstring-code-format = true
line-ending = "lf"
Le target-version = "py314" permet à Ruff de proposer des modernisations automatiques (par exemple, typing.List → list, typing.Optional[X] → X | None). per-file-ignores autorise des exceptions ciblées sans polluer la config globale (un # noqa: S101 inline reste possible mais salit le code). Le docstring-code-format formate aussi les snippets dans les docstrings, ce qui maintient la doc cohérente avec le code.
Étape 5 — Workflow Ruff au quotidien
Ruff fournit deux verbes principaux : check (linter) et format (formatter). Au quotidien, on les enchaîne. Avec uv installé, on évite même d’installer Ruff globalement : uv run ruff utilise la version exacte du projet.
# Linter — affiche les violations
uv run ruff check .
# Linter avec auto-fix sur ce qui peut l'être
uv run ruff check . --fix
# Linter avec fix unsafe (ex. suppression d'imports inutilisés)
uv run ruff check . --fix --unsafe-fixes
# Formatter
uv run ruff format .
# Check si formaté sans modifier (utile en CI)
uv run ruff format --check .
# Lister les règles activées dans la config courante
uv run ruff linter
Le triptyque pratique : ruff format puis ruff check --fix puis ruff check pour voir ce qui reste à corriger à la main. Sur une base de code de 50 000 lignes, ces trois commandes tournent en moins de deux secondes au total — plus rien à voir avec les temps d’attente flake8/black/isort de l’ancien monde. Cette vitesse rend les hooks pre-commit utilisables sans pénaliser l’expérience développeur.
Étape 6 — Hook pre-commit avec pre-commit framework
Ajouter un hook pre-commit qui lance Ruff sur les fichiers modifiés évite que des violations atteignent la branche principale. Le framework pre-commit (Python, mais s’installe globalement) gère l’orchestration et le caching des hooks.
# .pre-commit-config.yaml
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.13
hooks:
- id: ruff-check
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format
- repo: https://github.com/astral-sh/uv-pre-commit
rev: 0.11.14
hooks:
- id: uv-lock # vérifie que uv.lock est à jour avec pyproject.toml
# Installation
uv tool install pre-commit
pre-commit install
pre-commit run --all-files # premier passage sur tout le repo
Le hook uv-lock empêche qu’un développeur commit des changements à pyproject.toml sans régénérer le uv.lock, ce qui causerait des installations divergentes entre développeurs. uv tool install pre-commit est l’équivalent de pipx install : installation isolée d’outils CLI sans polluer l’environnement projet.
Étape 7 — Intégrer Ruff et uv en CI GitHub Actions
Le workflow CI typique combine setup-uv (action officielle Astral, installation instantanée), check Ruff, check format, tests pytest. L’ensemble tourne en moins de 60 secondes sur une base raisonnable.
name: CI
on: [push, pull_request]
jobs:
qualite:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: astral-sh/setup-uv@v8.1.0
with:
version: "latest"
enable-cache: true
- run: uv python install 3.14
- run: uv sync --all-extras --dev
- name: Ruff lint
run: uv run ruff check .
- name: Ruff format
run: uv run ruff format --check .
- name: Tests
run: uv run pytest --cov=src --cov-report=xml
- uses: codecov/codecov-action@v6
L’option enable-cache: true de setup-uv cache automatiquement le résolveur et les wheels téléchargés. Au second run, l’installation passe de 5 secondes à 1 seconde. Sur 100 PRs par mois, c’est plusieurs heures de temps CI économisées.
Étape 8 — Migration depuis Poetry / pip-tools
Pour un projet existant en Poetry ou pip-tools, la migration vers uv est généralement triviale. uv comprend nativement le pyproject.toml PEP 621 standard ; il suffit de retirer les sections [tool.poetry] spécifiques et de regénérer le lock.
# Migration depuis Poetry
# 1. Sauvegarder l'ancien pyproject.toml
cp pyproject.toml pyproject.toml.poetry-backup
# 2. Convertir manuellement la section [tool.poetry.dependencies] en [project].dependencies
# et [tool.poetry.group.dev.dependencies] en [dependency-groups.dev]
# 3. Supprimer l'ancien lock et générer le nouveau
rm poetry.lock
uv lock
# 4. Sync et tester
uv sync
uv run pytest
# Migration depuis pip-tools (requirements.in / requirements.txt)
# Lister les dépendances actuelles
cat requirements.in | xargs uv add
cat requirements-dev.in | xargs uv add --dev
Pour les projets très anciens en setup.py + requirements.txt, l’outil migrate-to-uv automatise la conversion. Pour les projets neufs, partir directement avec uv init évite tout ce parcours migratoire.
Erreurs fréquentes
| Symptôme | Cause | Solution |
|---|---|---|
| uv non trouvé après installation | PATH non rechargé | Redémarrer le terminal ou source ~/.bashrc |
| uv.lock changé sur d’autres machines | Différences de plateforme (Linux vs macOS) | uv génère un lock universel par défaut ; vérifier la version d’uv identique |
| Ruff réécrit du code après formatter | Conflit de règles linter vs formatter | Désactiver E501 (line length, géré par formatter) |
| Règle S101 sur les tests | per-file-ignores manquant | Ajouter « tests/**/*.py » = [« S101 »] |
| Import inutilisé en __init__.py | F401 trop strict pour ré-exports | per-file-ignores [« F401 »] sur __init__.py |
| Tests qui ne trouvent pas le package | package = true manquant | Ajouter package = true dans [tool.uv] |
Foire aux questions
uv ou Poetry en 2026 ?
uv pour tout nouveau projet : 10-50× plus rapide, suit les PEPs standards, futur certain. Poetry reste viable pour les projets existants où la migration n’est pas prioritaire.
Ruff ou black + isort + flake8 ?
Ruff systématiquement : un seul binaire, 100× plus rapide, configuration unifiée. Black peut rester pour des contraintes de stabilité long terme, mais Ruff format est compatible Black.
Comment passer ruff progressivement sur une base existante ?
Activer un sous-ensemble de règles, faire ruff check --fix, commit, puis élargir le select progressivement. Éviter ruff check --add-noqa qui pollue le code de commentaires.
uv supporte-t-il les wheels privés ?
Oui via [[tool.uv.index]] avec URL, credentials par env var. Compatible Azure Artifacts, AWS CodeArtifact, JFrog.
Faut-il committer le .venv ?
Non. Ajouter .venv/ au .gitignore. uv recrée l’environnement en quelques secondes depuis le lock.
Pour aller plus loin
Le workflow qualité en place, l’étape suivante consiste à structurer la distribution avec pyproject.toml et le packaging. Pour la vue panoramique, voir le guide principal Python.