FastAPI : construire une interface web rapide en Python

FastAPI expose des fonctions Python sur le réseau et tire la validation des données comme la documentation directement des annotations de type.
7 min de lecture
Believemy logo

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.

PYTHON
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 signatureCe que FastAPI en fait
facture_id: int dans le cheminConvertit le morceau d'URL en entier, refuse le reste avec une erreur 422
limite: int = 20Paramètre de requête optionnel, valeur par défaut affichée dans la documentation
facture: FactureLit 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.

Bon à savoir

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.

BesoinFlaskDjangoFastAPI
Servir des pages HTMLAvec un moteur de gabaritsC'est son terrainPossible, mais hors sujet
Valider les données entrantesÀ écrire ou à grefferFormulaires et sérialiseursComprise, tirée des annotations
Documentation interactiveExtension à installerExtension à installerGénérée sans une ligne
Base de données, administration, comptesÀ assemblerFournisÀ assembler
Appels lents en parallèleUn fil par appelUn fil par appelBoucle 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.

PYTHON
# À é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.

Attention

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.

BASH
pip install fastapi uvicorn
uvicorn main:app --reload


Quand 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

Question

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.

Question

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.

Question

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.

Termes connexes

Découvrez notre glossaire Python

Parcourez les termes et définitions les plus couramment utilisés dans le domaine du développement avec Python.

Partager cet article

Tu veux nous aider ? Fais un lien vers cet article sur tes réseaux ou encore mieux : sur ton site, dans un article ou dans ta newsletter.