Python avec Claude Code : guide uv, pytest, Ruff et FastAPI
Construisez un projet Python avec Claude Code, uv, pyproject.toml, pytest, Ruff, FastAPI et une itération sûre.
Avec Claude Code, le vrai risque pour un débutant Python n’est pas l’absence de code. Le risque est d’obtenir un projet qui semble complet mais que personne ne sait installer, tester ou corriger. Un environnement virtuel différent, un import cassé, un pytest absent ou un formatage non configuré suffisent à bloquer l’apprentissage.
Ce guide propose une base courte et vérifiable. Nous allons créer l’environnement avec uv ou venv, centraliser la configuration dans pyproject.toml, ajouter une petite API FastAPI, proposer une CLI optionnelle, écrire des tests avec pytest et contrôler le style avec ruff. Claude Code devient utile quand chaque demande se termine par une preuve.
Gardez les références officielles ouvertes : Claude Code, le guide Python pour écrire pyproject.toml, l’installation de uv, pytest, Ruff et les premiers pas de FastAPI.
Commencer par une cible petite
Ne demandez pas dès le départ l’authentification, la base de données, Docker, CI et les tâches asynchrones. Pour apprendre, le premier jalon doit être facile à vérifier.
uv run pytestpasseuv run ruff check .etuv run ruff format .s’exécutent- FastAPI crée et liste une tâche
- La CLI ajoute une tâche depuis le terminal
- Claude Code indique les commandes exécutées
flowchart LR
A["Brief de travail"] --> B["uv ou venv"]
B --> C["pyproject.toml"]
C --> D["Code dans src"]
D --> E["pytest"]
E --> F["ruff"]
F --> G["Petite itération Claude Code"]
Le brief est le cadre de travail de l’agent. Il décrit l’objectif, les fichiers modifiables, la version de Python et les commandes de preuve. Pour rendre ces règles durables dans un dépôt, ajoutez aussi un CLAUDE.md bien structuré. Pour aller plus loin côté backend, lisez développer une API avec Claude Code et les stratégies de test.
Prompt initial pour Claude Code
Voici un prompt court mais exploitable.
Ajoute à ce dépôt une petite API de gestion de tâches pour Python 3.12.
Contraintes:
- Utilise uv pour les dépendances et centralise la configuration dans pyproject.toml.
- Place le code dans src/task_api/ et les tests dans tests/.
- Implémente POST /tasks et GET /tasks avec FastAPI.
- Ajoute des tests pytest pour le chemin nominal et un cas 404.
- ruff check et ruff format doivent passer.
- Avant modification, donne le plan. Après modification, liste les commandes exécutées.
Fichiers autorisés:
- pyproject.toml
- src/task_api/**
- tests/**
Cette demande évite le flou. Si Claude Code modifie un dossier sans rapport ou oublie les tests, le problème est visible immédiatement. Pour un débutant, c’est beaucoup plus utile qu’un grand bloc de code sans contexte.
Créer l’environnement avec uv ou venv
uv est pratique pour un nouveau projet car il est rapide et rend les commandes courtes. Si votre entreprise n’autorise pas son installation, utilisez venv. Le point essentiel est de choisir une méthode et de l’écrire dans le brief.
macOS ou Linux :
mkdir task-api
cd task-api
uv init --app --python 3.12
uv add "fastapi[standard]"
uv add --dev pytest ruff
mkdir -p src/task_api tests
touch src/task_api/__init__.py
Windows PowerShell :
mkdir task-api
cd task-api
uv init --app --python 3.12
uv add "fastapi[standard]"
uv add --dev pytest ruff
New-Item -ItemType Directory -Force src/task_api, tests
New-Item -ItemType File -Force src/task_api/__init__.py
Alternative venv :
python -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install "fastapi[standard]" pytest ruff
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install "fastapi[standard]" pytest ruff
Centraliser pyproject.toml
Pour une base pédagogique, évitez de disperser les règles entre plusieurs fichiers. Ce pyproject.toml suffit pour les dépendances, le script de console, pytest et Ruff.
[project]
name = "task-api"
version = "0.1.0"
description = "Small FastAPI and CLI sample for Claude Code practice"
requires-python = ">=3.11"
dependencies = [
"fastapi[standard]>=0.115.0",
]
[project.scripts]
task-api = "task_api.cli:main"
[dependency-groups]
dev = [
"pytest>=8.0.0",
"ruff>=0.8.0",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["src/task_api"]
[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["src"]
[tool.ruff]
line-length = 100
target-version = "py311"
[tool.ruff.lint]
select = ["E", "F", "I", "B", "UP"]
Ajouter FastAPI et une CLI minimale
Placez ce code dans src/task_api/main.py. Il utilise la mémoire locale, donc ce n’est pas une base de production. C’est volontaire : avant la base de données, il faut comprendre les routes, les modèles et les erreurs.
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
app = FastAPI(title="Task API")
class TaskCreate(BaseModel):
title: str = Field(min_length=1, max_length=80)
class Task(BaseModel):
id: int
title: str
done: bool = False
_tasks: dict[int, Task] = {}
_next_id = 1
@app.post("/tasks", response_model=Task, status_code=201)
def create_task(payload: TaskCreate) -> Task:
global _next_id
task = Task(id=_next_id, title=payload.title)
_tasks[task.id] = task
_next_id += 1
return task
@app.get("/tasks", response_model=list[Task])
def list_tasks() -> list[Task]:
return list(_tasks.values())
@app.patch("/tasks/{task_id}/done", response_model=Task)
def mark_done(task_id: int) -> Task:
task = _tasks.get(task_id)
if task is None:
raise HTTPException(status_code=404, detail="Task not found")
updated = task.model_copy(update={"done": True})
_tasks[task_id] = updated
return updated
uv run fastapi dev src/task_api/main.py
Pour une CLI, créez src/task_api/cli.py.
import argparse
import json
from pathlib import Path
DB_PATH = Path("tasks.json")
def load_tasks() -> list[dict[str, object]]:
if not DB_PATH.exists():
return []
return json.loads(DB_PATH.read_text(encoding="utf-8"))
def save_tasks(tasks: list[dict[str, object]]) -> None:
DB_PATH.write_text(json.dumps(tasks, ensure_ascii=False, indent=2), encoding="utf-8")
def add_task(title: str) -> dict[str, object]:
tasks = load_tasks()
task = {"id": len(tasks) + 1, "title": title, "done": False}
tasks.append(task)
save_tasks(tasks)
return task
def main() -> None:
parser = argparse.ArgumentParser(description="Task CLI")
subparsers = parser.add_subparsers(dest="command", required=True)
add_parser = subparsers.add_parser("add")
add_parser.add_argument("title")
args = parser.parse_args()
if args.command == "add":
task = add_task(args.title)
print(f"Added #{task['id']}: {task['title']}")
if __name__ == "__main__":
main()
uv run task-api add "write pytest"
Sécuriser avec pytest et Ruff
Ajoutez tests/test_main.py, puis gardez les mêmes commandes à chaque itération.
import pytest
from fastapi.testclient import TestClient
from task_api import main
from task_api.main import app
@pytest.fixture(autouse=True)
def clean_tasks() -> None:
main._tasks.clear()
main._next_id = 1
def test_create_and_list_tasks() -> None:
client = TestClient(app)
response = client.post("/tasks", json={"title": "Write pytest"})
assert response.status_code == 201
assert response.json()["title"] == "Write pytest"
list_response = client.get("/tasks")
assert list_response.status_code == 200
assert len(list_response.json()) == 1
def test_mark_done_returns_404_for_missing_task() -> None:
client = TestClient(app)
response = client.patch("/tasks/999/done")
assert response.status_code == 404
assert response.json()["detail"] == "Task not found"
uv run pytest
uv run ruff check .
uv run ruff format .
Cas d’usage concrets
Premier cas : une API d’apprentissage. Elle montre les routes, Pydantic, OpenAPI, pytest et Ruff dans un périmètre court. Deuxième cas : une CLI d’automatisation pour nettoyer des CSV, renommer des fichiers ou préparer un rapport. Troisième cas : ajouter des tests à un script Python existant avant de le refactorer. Quatrième cas : construire un support de formation commun pour une équipe.
Ces usages créent aussi une transition naturelle vers les produits Claude Code ou la formation et consultation. Le lecteur reçoit d’abord du code utilisable, puis voit clairement quand un accompagnement devient pertinent.
Pièges fréquents
Ne laissez pas le gestionnaire de paquets implicite, sinon pip, poetry, uv et requirements.txt peuvent se mélanger. Ne démarrez pas par la base de données et JWT. Vérifiez les diffs Ruff avant de les accepter. Corrigez les imports avec pythonpath = ["src"] ou une installation correcte du paquet. Et ne collez jamais de secrets, données client ou URLs de production dans le prompt.
Résultat vérifié
Pour cette mise à jour, j’ai suivi l’ordre utilisé par Masa dans les supports débutants : environnement, pyproject.toml, petite API FastAPI, tests, puis Ruff. Le résultat pratique est que chaque erreur devient localisable : environnement, import, comportement testé ou formatage. C’est ce cadre qui rend Claude Code fiable pour apprendre, former une équipe et améliorer du code Python réel.
PDF gratuit: cheatsheet Claude Code
Saisissez votre email et téléchargez une page avec commandes, habitudes de review et workflow sûr.
Nous protégeons vos données et n'envoyons pas de spam.
À propos de l'auteur
Masa
Ingénieur spécialisé dans les workflows pratiques avec Claude Code.
Articles liés
Workflow Obsidian vers CLAUDE.md avec Claude Code
Transformer des notes Obsidian en notes CLAUDE.md concises pour reprendre les sessions sans réexpliquer.
Claude Code Revenue CTA Routing : relier articles, PDF, Gumroad et consultation
Un workflow Claude Code pour orienter les lecteurs vers PDF gratuit, Gumroad ou consultation selon l'intention.
Règles de handoff Claude Code en équipe: preuves, permissions, rollback et revenus
Un format concret pour transmettre un travail Claude Code avec preuves, permissions, rollback, PDF gratuit, Gumroad et consultation.