backend / fastapi
Paramètres path, query et body
Explication
Ce que vous allez apprendre
- Distinguer un path parameter, un query parameter et un body dans une requête HTTP
- Comprendre comment FastAPI détecte automatiquement où chercher chaque paramètre
- Ajouter des contraintes de validation avec
Query,PathetAnnotated - Recevoir un body JSON structuré via un modèle Pydantic
- Combiner path, query et body dans un même endpoint sans ambiguïté
Dans quel contexte ?
Sur la route PUT /products/{product_id} de app/routers/products.py, un développeur doit permettre de modifier un produit (via le body), tout en acceptant un paramètre optionnel notify=true pour déclencher un email de notification (via la query string), le tout identifié par product_id dans l'URL. Sans comprendre la convention de détection de FastAPI, il risque de déclarer notify: bool sans Query() explicite et de se retrouver avec un comportement correct mais mal documenté dans Swagger.
Trois façons pour une donnée d'entrer dans ton API
Une requête HTTP peut transporter des informations à trois endroits différents. Bien comprendre cette distinction est essentiel pour concevoir une API cohérente.
Le CHEMIN de l'URL identifie d'abord une ressource précise. /items/42 désigne directement l'item numéro 42.
La QUERY STRING, après le ?, exprime plutôt des options ou des filtres. Par exemple ?page=2&in_stock=true affine une recherche sans changer de ressource.
Le BODY, enfin, transporte des données structurées. Typiquement pour créer ou modifier quelque chose de plus riche qu'un simple identifiant.
Une fois ces trois zones connues, une question se pose : comment FastAPI sait-il où chercher chaque paramètre ? Ce n'est pas de la magie, mais une convention claire à connaître.
Si le nom apparaît entre accolades dans le chemin, c'est un path parameter. Si c'est un modèle Pydantic, c'est le body ; sinon, c'est une query string.
Comprendre cette règle évite bien des surprises quand un paramètre n'arrive pas là où on l'attendait. Une fois cette détection comprise, voyons comment ajouter des contraintes de validation.
On pourrait valider une contrainte avec un if manuel au début de la fonction. Mais Query(min_length=2) déplace cette validation en amont, AVANT même que le code de la fonction ne s'exécute.
Cette contrainte apparaît en plus automatiquement dans la documentation Swagger. Un simple if ne ferait jamais ça.
| Emplacement | Détection FastAPI | Exemple |
|---|---|---|
| Path | Nom présent entre accolades dans la route | /items/{item_id} |
| Query | Type simple non présent dans le chemin | ?page=2&in_stock=true |
| Body | Modèle Pydantic (BaseModel) | {"name": "...", "price": 12.5} |
Bonne pratique
Préfère toujours Annotated[str, Query(min_length=2)] à un if len(q) < 2: raise HTTPException(...) manuel : la contrainte apparaît automatiquement dans /docs, et l'erreur 422 générée est cohérente avec le reste de l'API sans code supplémentaire.
Il reste un dernier bénéfice à voir, propre au body : la garantie offerte par un modèle Pydantic. Déclarer un modèle comme type d'un paramètre body signifie que la fonction ne s'exécute JAMAIS avec des données invalides.
Si un champ requis manque ou a le mauvais type, FastAPI renvoie automatiquement une réponse 422 détaillée. Sans que tu aies à écrire une seule ligne de validation toi-même.
Le piège fréquent à connaître avant de pratiquer : un paramètre simple (str, int, bool) NON présent dans le chemin devient automatiquement un query parameter, ce qui surprend parfois. Pour forcer un paramètre simple à être lu dans le body, il faut explicitement utiliser Body(...).
Commandes & code
Paramètres path, query et body
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
# Path parameter — typé et validé automatiquement
@app.get("/items/{item_id}")
def get_item(item_id: int):
# item_id est garanti être un int, sinon FastAPI renvoie déjà une 422
return {"item_id": item_id}# Query parameters — paramètres optionnels avec valeurs par défaut
from typing import Optional
@app.get("/products")
def list_products(
q: Optional[str] = None, # ?q=laptop
page: int = 1, # ?page=2 (défaut : 1)
page_size: int = 20,
in_stock: bool = True, # ?in_stock=false
):
return {"query": q, "page": page, "page_size": page_size, "in_stock": in_stock}# Query params avec contraintes de validation (Query)
from fastapi import Query
from typing import Annotated
@app.get("/search")
def search(
q: Annotated[str, Query(min_length=2, max_length=100)],
limit: Annotated[int, Query(ge=1, le=100)] = 20,
tags: Annotated[list[str] | None, Query()] = None, # ?tags=a&tags=b -> ["a", "b"]
):
return {"q": q, "limit": limit, "tags": tags}# Body — un modèle Pydantic reçu en JSON
class ProductCreate(BaseModel):
name: str
price: float
description: str | None = None
@app.post("/products")
def create_product(product: ProductCreate):
# product est déjà validé : type, champs requis, etc.
return {"created": product}# Combinaison path + query + body dans un même endpoint
from fastapi import Path
@app.put("/products/{product_id}")
def update_product(
product_id: Annotated[int, Path(gt=0)],
notify: Annotated[bool, Query()] = False,
product: ProductCreate = ..., # body
):
return {"id": product_id, "notify": notify, "data": product}# Body multiple — plusieurs modèles dans le même payload JSON
class Address(BaseModel):
city: str
zip_code: str
@app.post("/orders")
def create_order(product: ProductCreate, address: Address, quantity: int):
# FastAPI attend automatiquement :
# { "product": {...}, "address": {...}, "quantity": 3 }
return {"product": product, "address": address, "quantity": quantity}| Source | Détection FastAPI |
|---|---|
| Path | Nom présent dans le chemin de la route ({item_id}) |
| Query | Type simple (str, int, bool...) non présent dans le chemin |
| Body | Modèle Pydantic (BaseModel) |
Résumé
- FastAPI déduit automatiquement la source d'un paramètre (path/query/body) selon sa déclaration.
Annotated[type, Query(...)]/Path(...)ajoutent des contraintes de validation fines (min/max, regex).- Plusieurs modèles Pydantic en paramètres de fonction = plusieurs clés attendues dans le body JSON.
- Toute validation échouée renvoie automatiquement une réponse 422 détaillée, sans code manuel.
Exercices pratiques
Mission : le endpoint de commande qui accepte tout
Objectif : Corriger un endpoint PUT /products/{product_id} dont les paramètres path, query et body sont mal détectés par FastAPI.
Contexte
Sur app/routers/products.py, la route PUT /products/{product_id} doit permettre de modifier un produit via le body, tout en acceptant un paramètre optionnel notify en query string pour déclencher un email. Un collègue a écrit une première version qui compile mais se comporte mal une fois testée dans /docs.