Retour au cours

backend / fastapi

Paramètres path, query et body

Leçon 21 exercice

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, Path et Annotated
  • 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.

EmplacementDétection FastAPIExemple
PathNom présent entre accolades dans la route/items/{item_id}
QueryType simple non présent dans le chemin?page=2&in_stock=true
BodyModè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

python
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}
python
# 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}
python
# 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}
python
# 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}
python
# 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}
python
# 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}
SourceDétection FastAPI
PathNom présent dans le chemin de la route ({item_id})
QueryType simple (str, int, bool...) non présent dans le chemin
BodyModè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

1 disponible
1

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.

Résoudre l’exercice →