Tout au long des travaux pratiques, vous travaillez sur une seule application : QuizOps, un quiz de dix questions sur Docker et Kubernetes. Une API FastAPI, une base SQLite, une interface dans le navigateur, une suite de tests, un dépôt Git.
Vous ne construisez pas cette application : elle vous est fournie, dans un dépôt Git prêt à l'emploi. C'est un choix délibéré. Dans la vraie vie, un agent de code passe l'essentiel de son temps sur du code qui existe déjà : le lire, le faire évoluer, le corriger, le tester, l'outiller. C'est exactement ce que vous allez lui faire faire. Personne n'a besoin de savoir écrire du FastAPI ; il faut savoir dire ce que l'on veut, relire ce que l'agent propose, et vérifier.
navigateur
│
│ HTTP (port 8000)
▼
┌─────────────────────────────────────────────┐
│ FastAPI (uvicorn) │
│ │
│ ┌───────────────┐ ┌────────────────┐ │
│ │ front static │ │ API │ │
│ │ static/ │ │ /api/... │ │
│ │ index.html │ │ questions │ │
│ │ style.css │ │ answers │ │
│ │ app.js │ │ scores │ │
│ └───────────────┘ └───────┬────────┘ │
└────────────────────────────────┼────────────┘
│
┌─────────────────┴─────────────────┐
▼ ▼
data/questions.json quizops.db
(les questions du quiz) (les scores, SQLite)
Un seul processus, un seul port. FastAPI sert l'interface statique et l'API. Les questions vivent dans un fichier JSON, les scores dans une base SQLite créée au premier lancement.
| Composant | Fichier | Rôle | Présent dès |
|---|---|---|---|
| API | app/main.py |
Les routes /api/... et le service des fichiers statiques |
TP 1 |
| Logique métier | app/quiz.py, app/donnees.py |
Tirage, filtrage, calcul du score, chargement du JSON | TP 1 |
| Persistance | app/database.py, quizops.db |
L'accès SQLite et la base des scores | TP 1 |
| Questions | data/questions.json |
Les questions, leurs propositions et la bonne réponse | TP 1 |
| Interface | static/index.html, static/style.css, static/app.js |
Le quiz dans le navigateur, sans framework | TP 1 |
| Tests | tests/, tests/a-venir/ |
La suite pytest, et les tests des lots pas encore livrés | TP 1 |
| Outillage du formateur | outils/ |
Point de départ, pannes, sabotages, données de test | TP 1 |
| Contexte projet | CLAUDE.md, .claude/settings.json |
Les conventions lues à chaque session, les permissions | TP 2 |
| Cahier des charges | PRD.md |
Objectif, fonctionnalités, critères d'acceptation, lots à venir | TP 3 |
| Skills | .claude/commands/revue.md, .claude/skills/nouvelle-question/ |
Les deux skills du projet : la revue et l'ajout de question | TP 7 |
| Chaîne d'intégration | .github/workflows/ci.yml |
Formatage, analyse et tests à chaque poussée | TP 8 |
| Hooks | .claude/settings.json, .claude/hooks/garde-fou.sh |
Formatage automatique et garde-fou, versionnés | TP 8 |
| Outils externes | .mcp.json |
Le serveur MCP Playwright du projet | TP 9 |
| Plugin d'équipe | quizops-kit/, .claude-plugin/marketplace.json |
Skills, hooks et serveur MCP empaquetés | TP 9 |
| Sous-agents | .claude/agents/relecteur.md, .claude/agents/test-runner.md |
Les agents à privilèges restreints | fin du TP 9 |
| Méthode | Route | Rôle | Présente dès |
|---|---|---|---|
GET |
/api/questions |
Dix questions au hasard, sans le champ reponse ; ?categorie=docker pour filtrer |
TP 1 (filtre au TP 4) |
GET |
/api/categories |
La liste triée des catégories | TP 4 |
POST |
/api/answers |
Reçoit les réponses d'une partie, enregistre et renvoie le score | TP 1 (validée au TP 6) |
GET |
/api/scores |
Les dix meilleurs scores | TP 5 |
GET |
/api/statistiques |
Le nombre de parties et le score moyen | TP 8 |
Une partie envoyée par le navigateur contient un pseudo et la liste des reponses, chacune avec question_id et choix (l'index de la proposition, ou null si la question a été passée).
Le dépôt https://github.com/chichi13/quizops contient QuizOps avec un tag par TP : tp1-depart, tp2-depart, … tp9-depart. Chaque tag est l'état d'entrée du TP correspondant : ce que le TP précédent a produit, rien de plus.
Vous le clonez une fois, au début du TP 1 :
git clone https://github.com/chichi13/quizops ~/projets/quizops
cd ~/projets/quizops
Puis chaque TP commence par la même commande, qui remet le dépôt dans l'état attendu, installe les dépendances, efface la base et lance les tests :
outils/depart.sh 4
.............. [100%]
14 passed in 0.62s
Le nombre de tests verts est la signature de l'état : s'il correspond, vous êtes au bon endroit. Votre travail du TP précédent n'est pas perdu, il reste dans la branche tpN que le script a créée ; mais tout le monde repart du même point, et un TP raté ne condamne pas les suivants.
Votre poste est jetable, le dépôt est la source de vérité. C'est la même leçon que les manifestes des TP Kubernetes : ce qui compte est versionné, le reste se reconstruit en une commande.
| Tag | Ce que le tag contient | Tests verts | Ce qui manque volontairement |
|---|---|---|---|
tp1-depart |
L'application complète et jouable, la suite de tests, l'outillage du formateur, un README de cinq lignes | 10 | CLAUDE.md, .claude/, PRD.md |
tp2-depart |
+ CLAUDE.md et .claude/settings.json |
10 | PRD.md |
tp3-depart |
+ PRD.md en dix sections |
10 | Le filtre par catégorie |
tp4-depart |
+ Le filtre ?categorie=, le 404, GET /api/categories, les boutons du front |
14 | Le classement ; le score des parties incomplètes est faux |
tp5-depart |
+ GET /api/scores, le score corrigé |
19 | La validation des entrées |
tp6-depart |
+ La validation Pydantic de POST /api/answers, les tests du tirage |
29 | .claude/commands/, .claude/skills/ |
tp7-depart |
+ /revue, le skill nouvelle-question, une treizième question |
29 | .github/, les hooks, le README complet |
tp8-depart |
+ ci.yml, les deux hooks dans .claude/settings.json, le README complet, GET /api/statistiques |
31 | .mcp.json, le plugin |
tp9-depart |
+ Le serveur MCP Playwright, le plugin quizops-kit, la place de marché locale |
31 | .claude/agents/, USAGE-IA.md |
La branche solution/tp9 porte l'état final : les deux sous-agents, l'export CSV du défi B, les règles d'usage de l'IA, 34 tests verts.
Le dossier outils/ est présent dès le premier tag. Vous n'y touchez pas, vous l'utilisez.
| Script | Ce qu'il fait |
|---|---|
outils/depart.sh N |
Remet le dépôt à l'état tpN-depart, installe, efface la base, lance les tests |
outils/preflight.sh |
Vérifie la veille que le poste a tout ce qu'il faut : claude, uv, python, node, npx, git, jq, le navigateur de Playwright |
uv run outils/panne.py N |
Casse quelque chose de connu (JSON invalide, import inexistant, score faux, colonne renommée). Retour en arrière : git checkout -- . |
uv run outils/saboter.py N |
Sabote le code pour vérifier que les tests protègent (doublons dans le tirage, score toujours à 100, 404 devenu 200, code dupliqué). Retour : git checkout -- app/ |
uv run outils/graine.py |
Insère douze scores connus dans la base |
outils/fuite.sh |
Committe un .env avec une fausse clé, pour reproduire l'erreur classique |
Le dossier tests/a-venir/ contient les tests d'acceptation des fonctionnalités que vous n'avez pas encore livrées. Ils sont ignorés par pytest tant qu'ils sont là. Quand un TP vous demande de livrer un lot, vous déplacez son fichier de test dans tests/, la suite rougit, et votre travail consiste à la faire revenir au vert :
mv tests/a-venir/test_categories_lot1.py tests/
uv run pytest -q
..........FF [100%]
2 failed, 10 passed in 0.31s
Ces tests vérifient des contrats (un code HTTP, une taille, un tri, un champ imposé par le cahier des charges), jamais la façon dont le code est écrit. Deux implémentations différentes les font passer toutes les deux. C'est ce qui rend les exercices prévisibles sans dicter la solution : la preuve attendue est un nombre de tests verts, pas le texte que l'agent produit.
Le tag tp4-depart embarque un bug que rien ne signale : une partie de dix questions dont cinq seulement reçoivent une réponse, toutes justes, affiche 100 % au lieu de 50. Rien ne plante, le serveur répond 200, le score est simplement faux. C'est le bug plausible décrit au module 01, c'est un critère d'acceptation du module 03, c'est un test dans tests/a-venir/test_score_partiel.py, et c'est l'exercice 2 du TP 4. Vous le rencontrerez plusieurs fois : c'est voulu.
Ces conventions sont fixées à l'avance pour garder les TP cohérents, et pour vous donner des contraintes concrètes à écrire dans vos prompts.
| Élément | Choix | Pourquoi |
|---|---|---|
| Langage | Python 3.13 | Lisible même sans être développeur Python |
| Cadriciel web | FastAPI + Uvicorn | Peu de code, documentation automatique sur /docs |
| Base de données | SQLite, fichier quizops.db |
Aucun serveur à installer ; jamais versionné |
| Interface | HTML, CSS et JavaScript natifs | Le code reste lisible par tous |
| Environnement | uv | Installation rapide et reproductible ; uv sync suffit |
| Qualité | ruff (formatage et analyse), pytest (tests) | Exécutés par uvx et uv run, rien à installer globalement |
| Port | 8000 | La valeur par défaut d'Uvicorn |
| Dossier | ~/projets/quizops |
Tous les TP supposent ce chemin |
Les modules portent des noms imposés : app/main.py (routes), app/quiz.py (tirage, filtrage, score), app/donnees.py (chargement du JSON), app/database.py (accès SQLite). Le nom utils.py est interdit. Ces règles sont dans le CLAUDE.md du projet à partir du TP 2 : l'agent les lit à chaque session.
uv run uvicorn app.main:app --reload
INFO: Will watch for changes in these directories: ['/Users/vous/projets/quizops']
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [<pid>] using WatchFiles
INFO: Started server process [<pid>]
INFO: Waiting for application startup.
INFO: Application startup complete.
L'interface répond sur http://127.0.0.1:8000, la documentation interactive de l'API sur http://127.0.0.1:8000/docs. La séquence de vérification complète, celle que la chaîne d'intégration exécute à partir du TP 8 :
uvx ruff format . && uvx ruff check . && uv run pytest -q
➡️ Prêt à commencer ? Rendez-vous sur la liste des travaux pratiques, ou directement au TP 1 : Prise en Main de Claude Code sur un Projet Existant.