Une documentation utilisable
- Leçon 11
- 45 min
- Module 4 : Documenter l'API avec OpenAPI et Swagger UI
Leçon 11 sur 21 · Créer et déployer votre première APIUne documentation utilisable
1. Comprendre la requête Web
2. Créer un petit service FastAPI
3. Concevoir un contrat utilisable
4. Documenter l'API avec OpenAPI et Swagger UI
5. Explorer et tester avec Postman
6. Tester et renforcer le service
7. Déployer et vérifier
Ce que vous saurez faire
Faites en sorte que /docs réponde aux questions d’une personne inconnue sans qu’elle ait à vous les poser. La documentation doit expliquer le rôle de chaque point de terminaison et le sens de chaque champ. Elle doit montrer une vraie requête et chaque erreur possible. Un développeur qui peut utiliser votre API avec la documentation seule n’a pas besoin d’une rencontre avec vous.
Avant de commencerCe qu’il faut savoir et avoir sous la main
Ce que vous devez savoir
- Les lacunes de la documentation générée, présentées dans OpenAPI et Swagger UI : exemple détaillé : la documentation dit-elle la vérité?.
Fieldet ses limites dans un modèle Pydantic, présentés dans Modèles de requête et de réponse : les modèles décrivent les données.
Ce qu’il vous faut sur votre ordinateur
ticket_api/models.py,routes.pyetmain.py, dans leur état à la fin de Accès depuis le navigateur et CORS : exercice guidé : autoriser la page de test. Cette leçon modifie les trois fichiers.- Le serveur de développement fonctionne et
/docss’ouvre, comme dans OpenAPI et Swagger UI : Swagger UI : essayer l’API dans le navigateur. - Si votre projet est manquant ou ne fonctionne plus, copiez l’instantané
end-of-m03. Il contient le projet à la fin du module 3, comme dans Utiliser un instantané. La leçon précédente ne modifie aucun fichier du projet. Vous pouvez donc continuer ici.
Testé avec FastAPI 0.142.2 et Pydantic 2.13 sur macOS.
D’où vient chaque partie de la documentation
Section intitulée « D’où vient chaque partie de la documentation »Tout dans Swagger UI vient de votre code. Pour améliorer la documentation, ajoutez l’information au bon endroit :
| Dans Swagger UI | Provient de | Où |
|---|---|---|
| Titre, version et texte sous ceux-ci | title, version et description de FastAPI(...) |
main.py |
| Noms des sections et leurs descriptions | tags sur les routes et openapi_tags dans FastAPI(...) |
routes.py, main.py |
| Ligne à côté du chemin | summary= sur la route |
routes.py |
| Texte affiché quand vous ouvrez un point de terminaison | description= sur la route |
routes.py |
| Chaque code d’état documenté | Type de retour et responses= sur la route |
routes.py |
| Descriptions et limites des champs | Field(description=..., ...) |
models.py |
| Corps donné en exemple | json_schema_extra={"examples": [...]} sur le modèle |
models.py |
Écrivez pour un développeur qui n’a jamais vu votre code. Expliquez ce qu’une valeur signifie, pas seulement son type. « 1 est le plus urgent, 3 le moins urgent » est utile; « la priorité » ne l’est pas.
Exemple détaillé : les modèles
Section intitulée « Exemple détaillé : les modèles »Amira ajoute une chaîne de documentation, des descriptions de champs et un exemple à chaque modèle. Voici le nouveau models.py :
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field
Category = Literal["billing", "login", "shipping", "account", "other"]
class TicketIn(BaseModel): """A support ticket to classify."""
model_config = ConfigDict( extra="forbid", json_schema_extra={ "examples": [ { "subject": "Charged twice for March", "body": "My card was charged two times for the same invoice.", } ] }, )
subject: str = Field( min_length=1, max_length=120, description="The short title of the ticket." ) body: str = Field( default="", max_length=5000, description="The customer's message. Optional." )
class Classification(BaseModel): """The predicted category and priority of a ticket."""
model_config = ConfigDict( json_schema_extra={ "examples": [ { "category": "billing", "priority": 1, "confidence": 0.9, "model_version": "keywords-1.0", } ] } )
category: Category = Field(description="The team that should handle the ticket.") priority: int = Field(ge=1, le=3, description="1 is the most urgent, 3 the least.") confidence: float = Field( ge=0, le=1, description="How sure the classifier is, from 0 to 1." ) model_version: str = Field(description="The classifier that made the prediction.")
class CategoryList(BaseModel): categories: list[Category] = Field( description="Every category that /v1/classify can return." )
class Health(BaseModel): status: Literal["ok"]
class ErrorDetail(BaseModel): code: str = Field(description="A stable code that a program can check.") message: str = Field(description="A short explanation for a person.") fields: list[str] = Field( default=[], description="The request fields that are not valid, if any." )
class ErrorResponse(BaseModel): model_config = ConfigDict( json_schema_extra={ "examples": [ { "error": { "code": "invalid_request", "message": "The request is not valid.", "fields": ["subject"], } } ] } )
error: ErrorDetail| Partie | Apparaît dans la documentation sous cette forme |
|---|---|
La chaîne de documentation """A support ticket to classify.""" (un billet de soutien à classer) |
La description du schéma. |
description="..." dans Field |
Une ligne sous chaque champ du schéma. |
json_schema_extra={"examples": [...]} |
Le corps donné en exemple. Swagger UI le place dans la zone de Try it out (essayer). Un développeur peut ainsi envoyer une vraie requête utile en un clic. |
Health |
Un modèle pour /health, afin de documenter sa réponse comme les autres. |
Choisissez des exemples qui fonctionnent et qui enseignent : l’exemple de TicketIn renvoie billing (facturation) avec la priorité 1. Il montre ce que fait l’API. Un exemple comme {"subject": "string"} renvoie other (autre), ce qui ne montre rien.
Exemple détaillé : les routes et l’application
Section intitulée « Exemple détaillé : les routes et l’application »Sur la route, un résumé, une description et la vraie réponse d’erreur :
from ticket_api.models import ( Category, CategoryList, Classification, ErrorResponse, TicketIn,)
@router.get("/categories", summary="List the ticket categories")def list_categories() -> CategoryList: return CategoryList(categories=list(get_args(Category)))
@router.post( "/classify", summary="Classify a support ticket", description=( "Predicts the category and priority of one ticket from its subject and body. " "The same text always gives the same result." ), response_description="The predicted category and priority", responses={ 422: { "model": ErrorResponse, "description": "A field is missing or not valid.", }, },)def classify( # ... the function does not changeresponses={422: {"model": ErrorResponse, ...}} remplace le schéma 422 par défaut de FastAPI par la structure réellement envoyée par l’API. Cela corrige la lacune la plus importante de la dernière leçon.
Dans main.py, l’application reçoit une version, une description et une description pour chaque étiquette. /health reçoit sa propre étiquette et un modèle :
from ticket_api.models import Health
DESCRIPTION = """Classifies support tickets into a category and a priority.
The classifier is a deterministic mock: it uses keywords, so the same textalways gives the same result. Every error has the same shape:`{"error": {"code": ..., "message": ..., "fields": [...]}}`."""
app = FastAPI( title="Ticket Classifier API", version="1.0.0", description=DESCRIPTION, openapi_tags=[ {"name": "tickets", "description": "Classify support tickets."}, {"name": "health", "description": "Check that the service runs."}, ],)
@app.get("/health", tags=["health"], summary="Check that the service runs")def health() -> Health: return Health(status="ok")La description est en Markdown, donc `...` s’affiche comme du code. C’est la première chose qu’un développeur lit. Elle présente donc les deux faits dont chaque client a besoin : les résultats sont déterministes et toutes les erreurs ont la même structure. version="1.0.0" est la version du contrat de l’API, pas celle du code. Modifiez-la quand le contrat change.
Le résultat
Section intitulée « Le résultat »

Exercice guidé : documenter l’API
Section intitulée « Exercice guidé : documenter l’API »- Remplacez
models.pypar l’exemple détaillé. - Modifiez
routes.pyetmain.py. Enregistrez, puis rechargez/docs. - Vérifiez la vue d’ensemble : la version est
1.0.0, la description se trouve sous le titre et/healtha sa propre section. - Ouvrez
POST /v1/classify, sélectionnez Try it out, puis Execute (exécuter) sans modifier le corps donné en exemple. La réponse estbilling, avec la priorité1. - Remplacez le corps par
{"subject": ""}, puis exécutez la requête. Comparez la réponse à l’exemple422. Ils ont maintenant la même structure. - Ouvrez Schemas › Classification (schémas › classification) et développez-le. Chaque champ a sa description.
À vous de jouer : documenter le point de terminaison des catégories
Section intitulée « À vous de jouer : documenter le point de terminaison des catégories »Un développeur d’interface doit créer un menu de catégories à partir de GET /v1/categories, avec seulement /docs. Améliorez sa documentation :
- Une description sur la route qui indique quand l’appeler, par exemple une fois au chargement de la page.
- Un
response_description. - Un exemple sur
CategoryList. - Une phrase dans la description indiquant que
othersignifie « aucune catégorie ne correspond ». Le menu peut ainsi l’omettre au besoin.
Rechargez ensuite /docs et vérifiez chaque modification comme le ferait une personne qui découvre l’API.
Afficher une réponse
@router.get( "/categories", summary="List the ticket categories", description=( "Returns every category that /v1/classify can return, in a fixed order. " "Call it once, for example when a page loads. " "`other` means that no category matched the ticket." ), response_description="The categories",)class CategoryList(BaseModel): model_config = ConfigDict( json_schema_extra={ "examples": [ {"categories": ["billing", "login", "shipping", "account", "other"]} ] } )
categories: list[Category] = Field( description="Every category that /v1/classify can return." )Notez que l’exemple doit rester exact quand les catégories changent. Dans la prochaine leçon, un test vérifie que les exemples documentés fonctionnent encore.
Erreur courante
Un exemple documenté que l'API rejette
Symptôme : un développeur sélectionne Try it out, envoie l’exemple de /docs sans modification et obtient 422. Il conclut que l’API ne fonctionne pas.
Cause : l’exemple ne correspond plus au modèle. Quelqu’un a renommé un champ (par exemple subject en title), réduit une limite ou ajouté un champ obligatoire sans mettre à jour json_schema_extra. Pydantic ne vérifie pas les exemples. Ce sont seulement des textes dans le document.
Correction :
- Envoyez vous-même chaque exemple après chaque modification d’un modèle.
- Mieux encore : faites-le avec un test, pour qu’un exemple incorrect fasse échouer la compilation. Vous écrirez ce test dans la prochaine leçon.
- Gardez les exemples près des champs qu’ils illustrent, dans le modèle. Une modification de l’un sera ainsi proche de l’autre.
Vérifiez votre compréhension
Pourquoi l'exemple d'erreur utilise-t-il un vrai code, invalid_request, plutôt que de laisser Swagger UI en générer un?
Afficher la réponse
Un exemple généré utilise seulement le type. Il affiche donc "code": "string", ce qui ne renseigne pas le développeur sur les valeurs attendues. Un vrai exemple montre le code à vérifier dans son programme et la vraie structure de fields. Les exemples sont souvent la première et la seule partie de la documentation que les développeurs lisent.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Vous modifiez le modèle de réponse et ajoutez un champ facultatif explanation. Quel numéro dans FastAPI(...) modifiez-vous, et comment?
Afficher la réponse
version. Ajouter un champ de réponse facultatif ne rompt pas la compatibilité. C’est donc une petite évolution, par exemple de 1.0.0 à 1.1.0. Une modification incompatible aurait une nouvelle version dans le chemin (/v2) et une nouvelle version majeure, 2.0.0. La version indique aux clients quel contrat ils consultent.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Récapitulatif
- Chaque partie de
/docsvient de votre code : l’application, les routes et les modèles. - Décrivez ce que les valeurs signifient, pas seulement leurs types.
- Donnez à chaque modèle un vrai exemple qui fonctionne et qui enseigne. Swagger UI l’utilise pour Try it out.
- Documentez chaque réponse d’erreur avec son vrai modèle :
responses={422: {"model": ErrorResponse, ...}}. - La
versionest la version du contrat.
Ensuite, vous ferez échouer un test quand la documentation et l’API ne correspondront plus.
Leçon suivante : Garder la documentation exacte