Quand ton script lit du JSON, un CSV ou une saisie utilisateur, tu ne sais jamais vraiment ce qui t'arrive. Le champ « age » peut contenir un entier, la chaîne "34", ou carrément rien du tout. Python, lui, s'en fiche royalement : il exécute ton code jusqu'au moment où tout explose, trois fonctions plus loin, avec une erreur illisible.
Pydantic règle le problème de manière radicale : tu déclares la forme de tes données avec des types Python, et la bibliothèque valide, convertit et lève des erreurs claires à ta place. C'est la brique cachée sous FastAPI, sous Instructor et sous des milliers de projets d'IA. Aujourd'hui, on l'apprivoise.
Imagine un script qui envoie des emails à partir d'un fichier CSV. Une ligne a un email vide, une autre un nombre à la place du nom. Sans validation, ton script tourne, envoie des emails foireux, et tu découvres le carnage une heure plus tard. Avec Pydantic, la première ligne corrompue arrête tout, avec un message qui pointe du doigt la cellule exacte.
Étape 1 — Installer Pydantic et créer ton premier modèle
Si tu connais déjà les annotations de type (def f(x: int) -> str), tu connais 90 % de la syntaxe. Pydantic les recycle pour en faire un outil de validation à l'exécution : les types ne sont plus décoratifs, ils deviennent contraignants.
Installe Pydantic (la version 2, moderne et plus rapide) :
pip install pydanticUn modèle Pydantic, c'est une classe qui hérite de BaseModel. Chaque champ est déclaré avec un type, et c'est tout :
from pydantic import BaseModel
class User(BaseModel):
nom: str
age: int
email: str
# Des données brutes qui arrivent d'une API : tout est en chaîne
brut = {"nom": "Alice", "age": "34", "email": "alice@example.com"}
user = User(**brut)
print(user) # nom='Alice' age=34 email='alice@example.com'
print(user.age, type(user.age)) # 34 <class 'int'>Regarde bien : « 34 » est une chaîne dans le dict d'entrée, mais user.age est devenu un vrai int. Pydantic a converti automatiquement, sans que tu lèves le petit doigt. C'est ce qu'on appelle la coercition de types.
Étape 2 — Des erreurs claires au lieu d'un crash mystérieux
Le vrai super-pouvoir de Pydantic, c'est ce qui se passe quand les données sont fausses. Au lieu de planter silencieusement au milieu de ton pipeline, tu reçois une ValidationError qui te dit exactement quoi, où et pourquoi :
from pydantic import BaseModel, ValidationError
class User(BaseModel):
nom: str
age: int
try:
User(nom="Bob", age="pas-un-nombre")
except ValidationError as e:
print(e)
# age
# Input should be a valid integer, unable to parse string as an integerLe message indique le champ fautif, la valeur reçue et le type attendu. Fini les NoneType object has no attribute à deux heures du matin : tu sais immédiatement quelle donnée d'entrée est corrompue.
Étape 3 — Ajouter tes propres règles avec les validateurs
La validation des types ne suffit pas toujours. Tu veux aussi vérifier qu'un email est plausible, qu'un âge est réaliste. Les validateurs de champ font ça en une poignée de lignes :
from pydantic import BaseModel, field_validator
class User(BaseModel):
email: str
age: int
@field_validator("email")
@classmethod
def email_propre(cls, v: str) -> str:
v = v.strip().lower()
if "@" not in v:
raise ValueError("email invalide")
return v
@field_validator("age")
@classmethod
def age_realiste(cls, v: int) -> int:
if not 0 <= v <= 130:
raise ValueError("age hors limites")
return v
print(User(email=" Alice@Example.COM ", age=34))
# email='alice@example.com' age=34Note le @classmethod, une particularité de Pydantic v2. Le validateur nettoie la valeur et la renvoie ; s'il lève une ValueError, Pydantic la transforme en une ValidationError propre et lisible.
Étape 4 — Parser une vraie réponse d'API avec des modèles imbriqués
Dans la vraie vie, tes données sont des objets dans des objets : un client a une adresse, une commande a une liste d'articles. Pydantic compose les modèles comme des briques Lego :
from pydantic import BaseModel
from typing import List
class Adresse(BaseModel):
rue: str
ville: str
code_postal: str
class Commande(BaseModel):
id: int
client: str
adresse: Adresse
articles: List[str]
# Réponse brute d'une API e-commerce
brut = {
"id": 1042,
"client": "Alice",
"adresse": {"rue": "12 rue des Lilas", "ville": "Lyon", "code_postal": "69003"},
"articles": ["clavier", "souris"],
}
cmd = Commande(**brut)
print(cmd.adresse.ville) # Lyon
print(cmd.model_dump()) # dict propre, prêt à être réutiliséSi l'API renvoie une adresse mal formée, l'erreur pointera précisément vers adresse.code_postal. Et model_dump() te redonne un dict propre, parfait pour repartir vers une base de données ou un fichier JSON.
Et si l'API renvoie une liste de commandes plutôt qu'une seule ? Un simple List[Commande] fait l'affaire, aucune boucle de validation manuelle :
brut_liste = [
{"id": 1, "client": "Alice",
"adresse": {"rue": "a", "ville": "Lyon", "code_postal": "69003"},
"articles": ["clavier"]},
{"id": 2, "client": "Bob",
"adresse": {"rue": "b", "ville": "Paris", "code_postal": "75001"},
"articles": ["écran"]},
]
commandes = [Commande(**c) for c in brut_liste]
print(len(commandes)) # 2Étape 5 — Les types du quotidien : dates, listes et valeurs par défaut
Un modèle réaliste ne se contente pas de str et int. Tu veux des dates valides, des listes typées, des champs facultatifs. Pydantic gère tout ça nativement, sans bibliothèque supplémentaire :
from pydantic import BaseModel
from typing import Optional, List
from datetime import datetime
class Article(BaseModel):
titre: str
tags: List[str] = []
publie_le: Optional[datetime] = None
vues: int = 0
a = Article(
titre="Un super tutoriel",
tags=["python", "ia"],
publie_le="2026-08-23T10:00:00",
)
print(a.publie_le) # 2026-08-23 10:00:00 (un vrai datetime)
print(a.model_dump_json()) # JSON propre, dates comprises, prêt à envoyerLa chaîne "2026-08-23T10:00:00" est devenue un vrai objet datetime. tags et vues ont des valeurs par défaut, publie_le est facultatif. Et model_dump_json() sérialise le tout en JSON, dates incluses, sans effort.
Étape 6 — Bonus : gérer ta configuration avec BaseSettings
Dernière astuce qui te servira dans tous tes projets : pydantic-settings lit tes variables d'environnement dans un modèle. Plus besoin de parser os.getenv à la main.
pip install pydantic-settingsfrom pydantic_settings import BaseSettings
class Settings(BaseSettings):
api_key: str
timeout: int = 30
debug: bool = False
model_config = {"env_prefix": "APP_"}
# Lit APP_API_KEY, APP_TIMEOUT et APP_DEBUG depuis l'environnement
settings = Settings()
print(settings.timeout) # 30Pose tes clés dans un fichier .env, et Pydantic les charge avec les bons types, convertis au passage. debug=false devient un vrai booléen, pas la chaîne "false" qui piège la moitié des débutants.
Un mot sur la performance : Pydantic v2 tourne en Rust sous le capot et valide des milliers d'objets par seconde. Tu ne paies donc pas la robustesse avec de la lenteur, même sur de gros volumes de données.
Tu viens de gagner des heures de debugging et de la robustesse gratuite. Avec Pydantic, tes données sont validées à l'entrée de ton code, là où c'est le plus facile à corriger. Résultat : moins de bugs silencieux, des erreurs qui te parlent et des modèles réutilisables partout.
La suite logique ? FastAPI s'appuie sur Pydantic pour ses schémas de requêtes, et Instructor l'utilise pour forcer les LLM à cracher du JSON structuré — deux sujets déjà couverts sur iancr. Et si tu veux creuser, les modèles imbriqués et les validateurs couvrent 80 % de ce que tu feras au quotidien.






