Définition
Vous avez écrit une fonction Python qui fait bien son travail : elle calcule un montant, elle enregistre une facture. Le jour où quelqu'un d'autre doit s'en servir, une interface web ou un service voisin, la difficulté n'est plus le calcul. Il faut choisir une adresse, lire ce qui arrive par le réseau, vérifier que les données reçues ont la forme attendue, renvoyer une réponse lisible, puis expliquer tout cela à celui qui appellera. Cette plomberie prend souvent plus de lignes que la fonction elle-même.
FastAPI est la bibliothèque qui s'en charge. Elle rend des fonctions Python appelables depuis l'extérieur sans que vous écriviez à la main la couche HTTP qui les sépare du monde. Votre code métier ne bouge pas et reste testable sans qu'aucun serveur ne démarre.
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
# La forme attendue des données entrantes, décrite une seule fois
class Facture(BaseModel):
client: str
montant: float
@app.post("/factures")
def enregistrer(facture: Facture):
return {"client": facture.client, "ttc": round(facture.montant * 1.2, 2)}Trois choses se produisent ici sans que rien ne les réclame. L'adresse /factures existe et accepte un envoi de données. Un montant écrit en toutes lettres est refusé avant même d'entrer dans la fonction, avec un message qui nomme le champ fautif. Et une documentation interactive apparaît sur /docs, où chaque route se lit et s'essaie depuis le navigateur.
Il n'a fallu pour cela qu'une fonction ordinaire, un décorateur au-dessus et une réponse renvoyée en json. Cette documentation n'est pas un gadget : c'est le contrat que lira celui qui consomme votre interface, et personne n'a eu à l'écrire ni à le tenir à jour.
Les annotations cessent d'être décoratives
Reste à comprendre d'où l'outil tire tout cela, puisque vous n'avez rien déclaré d'autre que des types. C'est la seule idée vraiment neuve de la bibliothèque.
Partout ailleurs en Python, une annotation de type documente et alimente un vérificateur comme mypy, mais elle n'a aucun effet pendant l'exécution : vous pouvez annoncer montant: float, passer une chaîne de caractères, et rien ne proteste. FastAPI fait de l'annotation une instruction. Ce que vous écriviez pour vos collègues devient ce qui pilote le travail.
Voici, ligne par ligne, ce que la bibliothèque déduit d'une signature de fonction.
| Ce que déclare la signature | Ce que FastAPI en fait |
|---|---|
facture_id: int dans le chemin | Convertit le morceau d'URL en entier, refuse le reste avec une erreur 422 |
limite: int = 20 | Paramètre de requête optionnel, valeur par défaut affichée dans la documentation |
facture: Facture | Lit le corps de la requête, valide chaque champ, énumère ceux qui manquent |
-> list[Facture] | Filtre la réponse sur les seuls champs déclarés |
La validation elle-même ne vient pas de FastAPI mais de Pydantic, une bibliothèque séparée dont il a fait son moteur. Un modèle qui hérite de la classe de base décrit la forme attendue des données, et cette description sert trois fois : elle convertit, elle refuse, elle documente.
Le bénéfice se mesure au code qui disparaît plutôt qu'à celui que l'on ajoute : les quinze lignes de vérification manuelle en tête de chaque route, et le fichier de documentation tenu à part, celui qui mentait dès la deuxième semaine faute d'être rouvert.
Ces mêmes déclarations produisent une description au format OpenAPI, que d'autres outils savent lire. On génère à partir de là le code client dans un autre langage, sans écrire une ligne à la main.
Face à Flask et à Django
La question suivante est presque toujours la même : pourquoi celui-ci plutôt qu'un autre. Elle n'a pas de réponse absolue, parce que les trois outils ne visent pas le même objet. Cherchez dans le tableau la ligne qui décrit votre projet.
| Besoin | Flask | Django | FastAPI |
|---|---|---|---|
| Servir des pages HTML | Avec un moteur de gabarits | C'est son terrain | Possible, mais hors sujet |
| Valider les données entrantes | À écrire ou à greffer | Formulaires et sérialiseurs | Comprise, tirée des annotations |
| Documentation interactive | Extension à installer | Extension à installer | Générée sans une ligne |
| Base de données, administration, comptes | À assembler | Fournis | À assembler |
| Appels lents en parallèle | Un fil par appel | Un fil par appel | Boucle d'événements |
Le partage se résume assez bien. Django convient à un site complet, avec ses pages, son administration et ses comptes, parce qu'il fournit tout cela dès le premier jour. Flask convient à un service de cinquante lignes dont personne ne lira jamais la documentation. FastAPI prend l'avantage dès que d'autres programmes consomment vos données et qu'il faut leur dire précisément ce que vous acceptez et ce que vous renvoyez.
L'erreur des premiers jours
Un piège attend à peu près tout le monde, et il coûte d'autant plus cher qu'il ne déclenche aucun message d'erreur. Il porte sur async.
Une route déclarée asynchrone ne part pas sur son propre fil d'exécution : elle s'exécute dans la boucle d'événements, et il n'y en a qu'une pour tout le serveur. Le moindre appel bloquant placé dedans gèle le service entier, pour tous les visiteurs à la fois. Seul devant votre machine, vous ne verrez jamais rien.
# À éviter : cet appel bloque, et la boucle d'événements est unique
@app.get("/taux")
async def taux():
return requests.get("https://api.exemple.fr/taux").json()
# Correct : sans async, la route part sur un fil d'exécution séparé
@app.get("/taux")
def taux():
return requests.get("https://api.exemple.fr/taux").json()La règle tient en deux temps. Une fonction écrite normalement, sans async, part sur un fil séparé et ne gêne personne : c'est le choix par défaut, et il est bon. Une fonction asynchrone ne doit appeler que du code prévu pour cela, avec await devant chaque attente.
Appeler requests depuis une route asynchrone est la faute la plus répandue. Tout marche sur votre machine, puis le serveur s'effondre à trois utilisateurs simultanés en production.
La deuxième surprise arrive le même jour : FastAPI n'est pas un serveur. Exécuter le fichier avec l'interpréteur ne lance rien et la page reste introuvable. Il faut un serveur ASGI pour porter l'application, uvicorn dans l'immense majorité des cas, installé dans le même environnement virtuel que la bibliothèque.
pip install fastapi uvicorn
uvicorn main:app --reloadQuand il ne sert à rien
Deux situations reviennent où il vaut mieux le laisser de côté.
La première est le script déclenché par une tâche planifiée, qui lit un fichier et écrit son résultat à côté. Lui ajouter une interface HTTP pour qu'il s'appelle lui-même revient à surveiller un port de plus, à redémarrer un processus de plus et à ouvrir une surface d'attaque, en échange de rien.
La seconde est le site de contenu, avec ses pages, son formulaire de contact et sa zone d'administration. Ce qu'un cadre complet fournit dès l'installation représenterait ici des semaines de reconstruction, pour un résultat moins solide.
Il reste à savoir ce que la bibliothèque ne contient pas, puisque la question revient une fois le tutoriel terminé : ni base de données, ni migrations, ni interface d'administration, ni gestion des comptes. Ce sont des briques séparées, à assembler soi-même. Le parti pris est assumé, chaque morceau se remplaçant sans toucher au reste, mais il déplace le travail plutôt qu'il ne le supprime.
Questions fréquentes
Faut-il écrire toutes ses routes en asynchrone ?
Non, et c'est le contresens le plus répandu. Une route ordinaire est parfaitement supportée : FastAPI l'exécute sur un fil séparé, sans aucun risque de blocage. Réservez la forme asynchrone aux routes qui appellent réellement des bibliothèques prévues pour, faute de quoi vous prenez tout le risque sans jamais toucher le bénéfice.
Est-il vraiment plus rapide que les autres ?
Sur une route qui calcule, non : c'est le même interpréteur Python, à la même vitesse. L'écart apparaît sur les routes qui attendent quelque chose d'extérieur, une base de données ou un service distant, car la boucle d'événements sert d'autres appels pendant l'attente au lieu d'immobiliser un fil. Un service qui ne fait que du calcul ne gagnera rien du tout.
La documentation générée est-elle utilisable telle quelle ?
Oui, et c'est souvent ce qui emporte la décision. Elle décrit chaque route, chaque champ et chaque code de réponse, elle s'essaie directement depuis le navigateur, et elle reste juste puisqu'elle est produite à partir du code lui-même. Vous l'enrichissez en écrivant la docstring de chaque fonction, qui devient sa description à l'écran.