đ FastAPI â Installation, Syntaxe & DĂ©ploiement
Guide complet IDEOâLab pour le framework Python haute performance (Pydantic, ASGI, DI).
Vue d'ensemble
Framework Python moderne, performance, auto-docs.
Python 3.7+ ASGI PerformantArchitecture
Basé sur Starlette (ASGI) & Pydantic (Typage).
Starlette Pydantic ASGIInstallation & Serveur
pip install fastapi, uvicorn.
"Hello World"
FastAPI(), décorateur @app.get.
ParamĂštres (Path & Query)
ParamĂštres de chemin (URL) et de requĂȘte (query).
Path Params Query ParamsPydantic (BaseModel)
Validation des données via BaseModel.
Request Body (POST)
Recevoir du JSON (POST, PUT) avec Pydantic.
Documentation Auto
Swagger UI (/docs) et ReDoc (/redoc).
Validation Avancée
Query, Path, Body, Field (gt, lt, regex).
Gestion des Erreurs
HTTPException, gestion des erreurs 404.
Async (async def)
Opérations asynchrones (async, await).
Injection de Dépendances
Depends (DI), gestion de BDD.
Sécurité (OAuth2)
OAuth2PasswordBearer, dépendances de sécurité.
Middleware (CORS)
app.add_middleware(CORSMiddleware).
Déploiement (Prod)
Uvicorn (serveur), Gunicorn (manager).
Uvicorn Gunicorn ProdCheat-sheet FastAPI
Décorateurs, commandes, Pydantic.
cheat @appLe framework Python haute performance
FastAPI est un framework web Python 3.7+ moderne et haute performance pour la création d'APIs.
Il est conçu pour ĂȘtre "Fast" (rapide) Ă deux niveaux :
- Rapide à exécuter : Performances trÚs élevées, comparables à Node.js et Go (grùce à Starlette et Pydantic).
- Rapide à coder : Augmente la vitesse de développement de 200% à 300%.
Caractéristiques Clés
- Basé sur les standards : Utilise OpenAPI (ex-Swagger) et JSON Schema.
- Docs Automatiques : GénÚre une documentation API interactive (Swagger UI & ReDoc) sans effort.
- Typage (Pydantic) : Utilise les "type hints" Python pour la validation, la sérialisation et la documentation. C'est sa plus grande force.
- Asynchrone : Supporte
async/awaitnativement (grùce à ASGI). - Injection de Dépendances : Un systÚme simple et puissant (
Depends).
Philosophie
"FastAPI vous donne le meilleur de Flask (micro-framework, simple) et de Django (validation, docs) avec les performances de Node.js."
FastAPI vs. Flask vs. Django
| CritĂšre | FastAPI | Flask | Django |
|---|---|---|---|
| Type | API-first (ASGI) | Micro-framework (WSGI) | Full-stack "batteries incluses" (WSGI) |
| Performance | ExtrĂȘme (Asynchrone) | Moyenne (Synchrone) | Moyenne (Synchrone) |
| Validation | Natif (Pydantic) | Externe (ex: Marshmallow) | Natif (Django Forms/Serializers) |
| Docs API | Automatique (Swagger/ReDoc) | Externe (ex: Flasgger) | Externe (ex: DRF-Swagger) |
| Admin UI | Non | Non | Oui (Auto-générée) |
| Cas d'usage | APIs REST, Microservices | Petits projets, APIs simples | Projets Full-stack, Admin, ORM puissant |
FastAPI = Starlette + Pydantic
FastAPI n'a pas réinventé la roue. Il combine intelligemment deux librairies :
- Starlette (Le Moteur) : Un micro-framework ASGI (Asynchronous Server Gateway Interface) lĂ©ger et trĂšs rapide. Il gĂšre le routage, les middlewares, et les requĂȘtes/rĂ©ponses asynchrones.
- Pydantic (La Validation) : Une librairie qui utilise les "type hints" Python (ex:
name: str) pour valider, sérialiser et parser les données (ex: JSON).
Uvicorn (Le Serveur)
Un serveur web ASGI (comme Gunicorn pour WSGI). C'est lui qui exécute l'application FastAPI.
Schéma de la Stack
[Image d'une architecture ASGI]
+--------------------------------------+
| Internet (RequĂȘte HTTP) |
+--------------------------------------+
|
âŒ
+--------------------------------------+
| Serveur ASGI (ex: Uvicorn, Gunicorn) |
| (GĂšre les connexions) |
+--------------------------------------+
|
âŒ
+--------------------------------------+
| Starlette (Routage, Async) | <-- (FastAPI)
+--------------------------------------+
|
âŒ
+--------------------------------------+
| Pydantic (Validation des données) | <-- (FastAPI)
+--------------------------------------+
|
âŒ
+--------------------------------------+
| Votre Code Applicatif |
| (ex: main.py, @app.get(...)) |
+--------------------------------------+
1. Créer un Environnement Virtuel (Requis)
# (Linux / macOS) python3 -m venv .venv source .venv/bin/activate # (Windows) py -m venv .venv .\.venv\Scripts\activate
2. Installer FastAPI et Uvicorn
Installez FastAPI et un serveur ASGI comme Uvicorn.
# Installe FastAPI (qui installe Starlette et Pydantic)
pip install fastapi
# Installe le serveur
pip install "uvicorn[standard]"
# ('standard' ajoute des dépendances pour de meilleures perfs)3. Fichier requirements.txt
pip freeze > requirements.txt # (Pour geler les dépendances)
Lancer le serveur de développement (Uvicorn)
Uvicorn est le serveur qui exécute votre application.
Créez un fichier main.py (voir 2.1).
# Lancer uvicorn uvicorn main:app --reload
| Argument | Description |
|---|---|
main | Le fichier main.py (sans .py). |
app | L'objet FastAPI() créé dans main.py (app = FastAPI()). |
--reload | (Dev) Redémarre le serveur à chaque modification du code. |
--host | (Optionnel) --host 0.0.0.0 pour le rendre accessible sur le réseau. |
--port | (Optionnel) --port 8080 (défaut: 8000). |
Une fois lancé, votre API est accessible sur http://127.0.0.1:8000.
Structure de Projet (Simple)
mon_projet/ âââ .venv/ (Environnement virtuel) âââ main.py (Fichier principal de l'app) âââ requirements.txt (DĂ©pendances)
Structure de Projet (Scalable - Recommandée)
mon_projet/ âââ .venv/ âââ app/ â âââ __init__.py â âââ main.py (CrĂ©ation de l'app FastAPI) â âââ routers/ (Fichiers de routes, ex: items.py) â âââ models/ (ModĂšles SQLAlchemy) â âââ schemas/ (ModĂšles Pydantic) â âââ dependencies.py (Injection de dĂ©pendances) âââ tests/ âââ requirements.txt
main.py
from fastapi import FastAPI
# 1. Créer l'instance de l'application
app = FastAPI()
# 2. Définir une "route" avec un "décorateur"
# @app.get -> Opération (GET)
# "/" -> Path (URL)
@app.get("/")
def read_root():
# 3. Ce que retourne la fonction (dict, list, str, int)
# est automatiquement converti en JSON.
return {"message": "Hello, IDEO-Lab!"}
@app.get("/test")
def read_test():
return {"status": "ok"}Lancement
uvicorn main:app --reload
Résultat (via curl ou navigateur)
$ curl http://127.0.0.1:8000/
{"message":"Hello, IDEO-Lab!"}
$ curl http://127.0.0.1:8000/test
{"status":"ok"}1. Path Parameters (ParamĂštres de Chemin)
Variables dans l'URL. Définies avec {}.
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/{item_id}")
def read_item(item_id: int): # 1. Type hint (int)
# FastAPI valide que item_id est un entier.
# Si vous appelez /items/foo, vous recevez une erreur JSON 422.
return {"item_id": item_id, "type": "entier"}
@app.get("/users/{username}")
def read_user(username: str):
return {"username": username, "type": "string"}Appel
$ curl http://127.0.0.1:8000/items/123
{"item_id":123,"type":"entier"}
$ curl http://127.0.0.1:8000/items/abc
{"detail":[{"loc":["path","item_id"],"msg":"Input should be a valid integer"...2. Query Parameters (ParamĂštres de RequĂȘte)
ParamÚtres (clé/valeur) aprÚs le ? dans l'URL. Définis comme arguments de fonction (non présents dans le path).
from fastapi import FastAPI
from typing import Optional # Pour les champs optionnels
app = FastAPI()
# URL: /items/?skip=0&limit=10&q=test
@app.get("/items/")
def read_items(
skip: int = 0, # Valeur par défaut (0)
limit: int = 10, # Valeur par défaut (10)
q: Optional[str] = None # Optionnel (par défaut: None)
):
results = {"skip": skip, "limit": limit}
if q:
results.update({"q": q})
return resultsAppel
$ curl http://127.0.0.1:8000/items/?skip=5&q=mon-test
{"skip":5,"limit":10,"q":"mon-test"}
$ curl http://127.0.0.1:8000/items/
{"skip":0,"limit":10}La validation par les types
Pydantic est la librairie de validation de donnĂ©es au cĆur de FastAPI. Vous dĂ©finissez la "forme" (shape) de vos donnĂ©es en crĂ©ant une classe qui hĂ©rite de BaseModel.
FastAPI utilise ces modĂšles pour :
- Valider les requĂȘtes entrantes (Request Body).
- Sérialiser les réponses sortantes (Response Model).
- Générer la documentation OpenAPI (Swagger/ReDoc).
Exemple : schemas.py
from pydantic import BaseModel
from typing import Optional, List
from datetime import datetime
class Tag(BaseModel):
id: int
name: str
class Article(BaseModel):
# Champ requis
titre: str
# Champ requis (type complexe)
auteur_id: int
# Champ optionnel (défaut: None)
description: Optional[str] = None
# Champ avec valeur par défaut
publie: bool = False
# Champ avec type complexe (Liste)
tags: List[str] = []
# Champ avec type imbriqué
# (Pydantic gÚre les modÚles imbriqués)
tag_objets: List[Tag] = []
# Exemple de config (ex: pour l'ORM)
class Config:
orm_mode = True
Recevoir du JSON (POST)
Pour recevoir un corps de requĂȘte (ex: JSON), dĂ©clarez-le comme argument de fonction avec un type hint Pydantic (item: Article).
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Optional
app = FastAPI()
# 1. Définir le modÚle Pydantic (le "schema")
class Article(BaseModel):
titre: str
description: Optional[str] = None
publie: bool = False
# 2. Utiliser le modÚle dans le décorateur 'post'
@app.post("/articles/")
async def create_article(article: Article):
# Si le JSON reçu ne correspond pas au modÚle Article,
# FastAPI retourne une erreur 422 (Unprocessable Entity)
# Le 'article' ici est un objet Pydantic
# (Logique pour sauver en BDD...)
return {"message": "Article créé", "data": article}
# 'PUT' (mise Ă jour) et 'PATCH' (partielle) fonctionnent pareil
@app.put("/articles/{article_id}")
async def update_article(article_id: int, article: Article):
return {"id": article_id, "data": article}response_model (ContrĂŽler la sortie)
Parfois, vous ne voulez pas retourner *tout* ce que votre fonction manipule (ex: un mot de passe hashé). response_model filtre la sortie selon un modÚle Pydantic.
class ArticleInput(BaseModel):
titre: str
class ArticleDB(BaseModel):
id: int
titre: str
date_creation: datetime
class Config:
orm_mode = True # Permet de lire depuis un objet ORM
# 'response_model' garantit que la sortie
# correspondra au modĂšle 'ArticleDB'
@app.post("/articles/", response_model=ArticleDB)
async def create_article(article_in: ArticleInput):
# (Logique BDD...)
# db_article = BDD.create(titre=article_in.titre)
# db_article contient {id: 1, titre: '...', date_creation: ...}
return db_article # FastAPI va filtrer la sortieGénération automatique
C'est la fonctionnalité "magique" de FastAPI. En se basant sur vos décorateurs (@app.get), vos "type hints" (item_id: int) et vos modÚles Pydantic (item: Article), FastAPI génÚre un schéma OpenAPI 3.
Il expose ensuite ce schéma via deux interfaces web :
1. Swagger UI (/docs)
URL : http://127.0.0.1:8000/docs
Une interface interactive. Vous pouvez voir tous vos endpoints, leurs paramĂštres, leurs rĂ©ponses, et mĂȘme les tester directement depuis le navigateur.
2. ReDoc (/redoc)
URL : http://127.0.0.1:8000/redoc
Une interface de documentation statique (non-interactive), plus propre pour la lecture seule.
Personnalisation
app = FastAPI(
title="IDEO-Lab API",
description="L'API officielle pour IDEO-Lab",
version="1.0.0"
)
@app.post(
"/articles/",
response_model=ArticleDB,
tags=["Articles"], // Regroupe dans Swagger
summary="Crée un nouvel article"
)
async def create_article(article_in: ArticleInput):
...
Validation (Query, Path)
Permet d'ajouter des contraintes (longueur, min/max) aux paramĂštres.
from fastapi import FastAPI, Query, Path
from typing import Optional, List
app = FastAPI()
@app.get("/items/")
async def read_items(
# Valide un paramĂštre Query
q: Optional[str] = Query(
None, # Valeur par défaut
min_length=3,
max_length=50,
regex="^fixedquery$" // Accepte que "fixedquery"
),
# Valide une liste
tags: List[str] = Query([])
):
...
@app.get("/users/{user_id}")
async def read_user(
# Valide un paramĂštre Path
user_id: int = Path(
..., # '...' signifie Requis
title="L'ID de l'utilisateur",
gt=0, # Greater than 0
le=1000 // Less than or equal to 1000
)
):
...Validation Pydantic (Field)
Permet d'ajouter les mĂȘmes contraintes aux modĂšles Pydantic.
from pydantic import BaseModel, Field, EmailStr
from typing import Optional
class Article(BaseModel):
titre: str = Field(
...,
min_length=5,
max_length=100,
example="Mon Super Titre"
)
description: Optional[str] = Field(None)
prix: float = Field(
...,
gt=0, // > 0
le=999.99 // <= 999.99
)
auteur_email: EmailStr // Validation de format email
class Commande(BaseModel):
article: Article
quantite: int = Field(1, gt=0)
HTTPException
Pour retourner une réponse d'erreur HTTP (4xx, 5xx), le plus simple est de lever (raise) une HTTPException.
FastAPI l'interceptera et la convertira en une réponse JSON propre.
from fastapi import FastAPI, HTTPException
app = FastAPI()
items_db = {1: "Item A", 2: "Item B"}
@app.get("/items/{item_id}")
async def read_item(item_id: int):
if item_id not in items_db:
# Lever l'exception
raise HTTPException(
status_code=404,
detail="Article non trouvé",
headers={"X-Error": "ID Inconnu"}
)
return {"item": items_db[item_id]}Appel
$ curl http://127.0.0.1:8000/items/99
{
"detail": "Article non trouvé"
}
# (Le statut HTTP sera 404)Gestionnaires d'exceptions (Avancé)
Vous pouvez intercepter des erreurs spécifiques (ex: erreur BDD) pour retourner une réponse 500 propre.
from fastapi.responses import JSONResponse
from fastapi import Request
class MaSuperErreur(Exception):
pass
@app.exception_handler(MaSuperErreur)
async def mon_gestionnaire_erreur(req: Request, exc: MaSuperErreur):
return JSONResponse(
status_code=418,
content={"message": "J'ai attrapé une erreur custom !"}
)
async def
FastAPI est basé sur ASGI, il supporte donc nativement les fonctions "coroutines" (async def).
Quand l'utiliser ?
Utilisez async def si votre fonction contient une opération d'I/O (Entrée/Sortie) lente et asynchrone.
Ex: await (attendre) une requĂȘte BDD (async), await une API externe (async), await asyncio.sleep().
Quand ne PAS l'utiliser ?
Si votre fonction fait juste du calcul CPU (ex: crunching de nombres) ou utilise une librairie I/O *bloquante* (ex: requests standard), utilisez def normal.
Exemple
import asyncio
from fastapi import FastAPI
app = FastAPI()
# Fonction 'def' (Synchrone)
# (Bloque le worker pendant 5s)
@app.get("/sync")
def route_sync():
time.sleep(5) # Bloquant !
return {"message": "Sync terminé"}
# Fonction 'async def' (Asynchrone)
# (LibĂšre le worker pendant 5s)
@app.get("/async")
async def route_async():
# 'await' cĂšde le contrĂŽle au serveur,
# qui peut traiter d'autres requĂȘtes.
await asyncio.sleep(5) # Non-bloquant !
return {"message": "Async terminé"}Depends (DI)
L'injection de dépendances est un moyen de déclarer que votre route "dépend" d'autre chose (ex: une session BDD, l'utilisateur courant).
Vous utilisez Depends() comme valeur par défaut d'un paramÚtre de fonction.
FastAPI va :
- Exécuter la fonction dans
Depends()(la "dépendance"). - Prendre la valeur retournée (
return). - Injecter cette valeur dans votre paramĂštre de route.
Exemple Simple (Dépendance partagée)
from fastapi import FastAPI, Depends
from typing import Optional
app = FastAPI()
# 1. La Dépendance (une fonction simple)
def get_common_params(
q: Optional[str] = None,
skip: int = 0,
limit: int = 10
):
# Retourne un dictionnaire
return {"q": q, "skip": skip, "limit": limit}
# 2. Injection
@app.get("/items/")
async def read_items(commons: dict = Depends(get_common_params)):
return {"message": "Items", "params": commons}
@app.get("/users/")
async def read_users(commons: dict = Depends(get_common_params)):
return {"message": "Users", "params": commons}Cas d'usage N°1 : Gestion de Session BDD
Le cas d'usage le plus courant est de gérer une session BDD (ex: SQLAlchemy).
# (En supposant que 'SessionLocal' est configuré)
# 1. La Dépendance (générateur 'yield')
# (Ouvre la session, la 'yield', puis la ferme)
def get_db_session():
db = SessionLocal()
try:
yield db # 3. Injecte la session
finally:
db.close() # 5. Ferme la session (mĂȘme en cas d'erreur)
# 2. L'Endpoint (la route)
@app.post("/users/")
async def create_user(
user: UserSchema,
# 2. FastAPI appelle get_db_session()
db: Session = Depends(get_db_session)
):
# 4. db est la session BDD
db_user = models.User(nom=user.nom)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_userFastAPI intÚgre des outils (basés sur Depends) pour gérer l'authentification.
Exemple (Protection de route)
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
app = FastAPI()
# 1. Définit le "schéma" de sécurité.
# Il dit Ă FastAPI: "Va chercher un 'Authorization: Bearer [TOKEN]'
# dans le header de la requĂȘte sur l'URL '/token'".
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token")
# 2. Une dépendance qui valide le token
# (C'est votre logique métier)
async def get_current_user(token: str = Depends(oauth2_scheme)):
# (Ici, vous devriez décoder le token JWT,
# vérifier sa validité, et chercher l'utilisateur en BDD)
if token != "fake-token": # Logique de validation bidon
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Token invalide"
)
return {"username": "admin", "id": 1}
# 3. La route protégée
@app.get("/users/me")
async def read_users_me(
# Si le client n'a pas de token, ou si get_current_user
# lĂšve une HTTPException, FastAPI gĂšre l'erreur 401.
current_user: dict = Depends(get_current_user)
):
# Si le code arrive ici, le token est valide.
return current_userLa route /docs affichera un bouton "Authorize" pour tester.
Middleware
Un Middleware est une fonction qui s'exĂ©cute sur **chaque requĂȘte** *avant* qu'elle n'atteigne votre route (et sur chaque rĂ©ponse *aprĂšs*).
Utile pour : Logs, CORS, Gzip, gestion d'erreurs globales.
CORS (Cross-Origin Resource Sharing)
ProblÚme : Par défaut, un navigateur n'autorise pas un frontend (http://mon-site-react.com) à appeler une API (http://mon-api.com).
Solution : L'API doit envoyer des headers HTTP (ex: Access-Control-Allow-Origin) pour autoriser le frontend. FastAPI gĂšre cela avec CORSMiddleware.
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
# Liste des "origines" (frontends) autorisées
origins = [
"http://localhost:3000", // React local
"http://localhost:8080", // Vue local
"https://mon-site-react.com",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins, // Les sites autorisés
allow_credentials=True,
allow_methods=["*"], // (GET, POST, etc.)
allow_headers=["*"],
)
@app.get("/")
def root():
return {"message": "ok"}Serveur ASGI : Uvicorn
Uvicorn est un serveur ASGI. Il peut ĂȘtre utilisĂ© seul en production (contrairement au serveur de dev Flask/Django).
# Installer uvicorn pip install "uvicorn[standard]" # Lancer en production (sans --reload) # Ăcoute sur 0.0.0.0:8000 uvicorn main:app --host 0.0.0.0
Process Manager : Gunicorn (Recommandé en Prod)
Uvicorn ne gĂšre qu'un seul processus. Pour exploiter plusieurs cĆurs de CPU (scaling vertical), on utilise Gunicorn (serveur WSGI mature) comme "Process Manager" qui lance et supervise des "Uvicorn Workers".
# 1. Installer Gunicorn pip install gunicorn
2. Lancer Gunicorn + Uvicorn
# Syntaxe gunicorn -w [WORKERS] -k [CLASSE_WORKER] [MODULE:APP] # Exemple : 4 workers (pour 2-4 coeurs CPU) gunicorn -w 4 -k uvicorn.workers.UvicornWorker main:app --bind 0.0.0.0:8000
| Argument | Description |
|---|---|
-w 4 | Nombre de "workers" (processus). RĂšgle: (2 * Nb CĆurs) + 1. |
-k uvicorn.workers.UvicornWorker | (ClĂ©) Dit Ă Gunicorn d'utiliser Uvicorn pour gĂ©rer les requĂȘtes (ASGI). |
main:app | Module Python et objet App. |
--bind 0.0.0.0:8000 | L'interface et le port d'écoute. |
On place Nginx en "reverse proxy" devant Gunicorn (comme pour Django).
Commandes
# Installation pip install fastapi "uvicorn[standard]" # Lancement (Dev) uvicorn main:app --reload # Lancement (Prod) gunicorn -w 4 -k uvicorn.workers.UvicornWorker main:app
Décorateurs
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
@app.post("/")
@app.put("/{item_id}")
@app.delete("/{item_id}")
@app.patch("/{item_id}")ParamĂštres (from fastapi import ...)
# Path (ex: /items/{item_id})
def f(item_id: int): ...
# Query (ex: /items/?q=test)
def f(q: Optional[str] = None): ...
# Body (ex: POST /items)
from pydantic import BaseModel
class Item(BaseModel):
name: str
def f(item: Item): ...
# Validation
from fastapi import Path, Query
def f(
item_id: int = Path(..., gt=0),
q: str = Query(None, max_length=10)
): ...
# Dépendances
from fastapi import Depends
def get_db(): ...
def f(db: Session = Depends(get_db)): ...
# Erreurs
from fastapi import HTTPException
raise HTTPException(status_code=404, detail="Not found")