[JOUR 23] II. Python, the right way : Validation de données avec Pydantic
Ne passez pas à côté des avantages cruciaux que peux apporter Pydantic à vos projets Python !
Sommaire :
I.Introduction
II.Installation
III.Les modèles Pydantic
IV.Validation basique
V.Field
VI.AnnotatedTypes
VII.Mypy plugin
Article précédent sur comment gérer les exceptions en Python :
I. Introduction
Pydantic est une librairie pour la validation de données, largement utilisée dans la communauté Python.
Qu'est-ce que la validation de données ? La validation de données englobe toutes les vérifications que nous voulons appliquer à nos données. Il y a évidemment le type attendu, mais aussi des règles plus spécifiques au métier, par exemple :
Si nous sommes censés recevoir un mail, la chaîne de caractères contient-elle bien un "@" ?
Pour une valeur numérique, est-elle dans l'intervalle fixé ?
Etc.
Pydantic est très utilisé pour valider les données utilisées pour créer des objets.
La validation de données avec Pydantic est le sujet central de cet article. Nous verrons ensemble des exemples d'utilisations pratiques de Pydantic.
II. Installation
Tout d'abord, créons un nouveau projet avec uv et installons Pydantic.
mkdir mon_projet
cd mon_projet
uv init
uv venv
source .venv/bin/activate
uv add pydantic
À noter que lors de son installation, Pydantic arrive avec son lot de dépendances installées d'office.
Mais il est possible d'ajouter deux dépendances optionnelles : email-validator pour la validation d'emails et tzdata pour gérer les fuseaux horaires lorsqu'on manipule des temps et des dates.
Nous allons aussi installer email-validator pour la suite :
uv add 'pydantic[email]'
La logique fondamentale de validation de Pydantic est mise en œuvre dans un paquet distinct (pydantic-core), où la validation pour la majorité des types est effectuée en Rust.
Ainsi, Pydantic se classe parmi les bibliothèques de validation de données les plus performantes pour Python.
III. Les modèles Pydantic
Pydantic permet de définir des modèles, qui sont des classes héritant de BaseModel
et utilisant des attributs annotés pour définir les champs.
Bien que similaires aux dataclasses de Python, les modèles de Pydantic offrent des fonctionnalités optimisées pour la validation, la sérialisation et la création de schémas JSON, détaillées dans la documentation de Pydantic. Nous reparlerons plus en détail de la partie schéma et sérialisation plus tard dans la formation, notamment lors de l'étude des webapis.
En outre, Pydantic assure que les données en entrée, une fois traitées et validées par le modèle, respecteront les types de champs spécifiés.
from pydantic import BaseModel
class User(BaseModel): # définition d'un modèle simple
age: int
name: str
first_user = User(age=30, name="Pedro Samba") # création d'un utilisateur
second_user = User(age="30", name=b"Pedro Samba") # conversion automatique vers le modèle
print(first_user)
print(second_user)
Il est également possible de passer des données sous forme de JSON et de les valider de la manière suivante afin de créer un objet :
User.model_validate({"age":30, "name":"Pedro Samba"})
IV. Validation basique
Créons une nouvelle fois un modèle de données en créant une classe qui hérite de BaseModel
:
from pydantic import BaseModel, ValidationError
class User(BaseModel):
username: str
email: str
age: int
Pydantic validera automatiquement les types de données et convertira les valeurs si nécessaire, comme vu précédemment. Si les données sont invalides, Pydantic lèvera une ValidationError
.
Exemple :
try:
user = User(username=b"john_doe", email="john@example.com", age="hello")
except ValidationError as e:
print(e)
Pour une validation plus avancée, vous pouvez utiliser des validateurs personnalisés grâce aux décorateurs validator
:
from pydantic import BaseModel, validator
class User(BaseModel):
username: str
email: str
age: int
@validator('age') # Spécification du champ ciblé par le validateur
def check_age(cls, v):
if v < 18:
raise ValueError('Must be 18 or older')
return v
Cette approche vous permet de définir des règles de validation spécifiques pour vos champs.
Validons également l'email fourni, simplement avec EmailStr
un type Pydantic fournit spécialement pour ce besoin :
from pydantic import BaseModel, validator, EmailStr
class User(BaseModel):
username: str
email: EmailStr #utilisation du type spécial
age: int
@validator('age')
def check_age(cls, v):
if v < 18:
raise ValueError('Must be 18 or older')
return v
if __name__ == "__main__":
try:
user = User(username="john_doe", email="john@example.com", age=25) # correction de la syntaxe pour le username
except ValidationError as e:
print(e)
À la place d'utiliser des validateurs basiques pour des vérifications simples sur des chaînes de caractères ou des entiers (comme celui défini ci-dessus), nous pouvons utiliser les Fields.
V. Field
Un Field est un champ destiné à être configuré pour des objets.
Ils sont utilisés pour fournir des informations supplémentaires sur un champ, que ce soit pour le schéma du modèle ou pour une validation complexe. Certains arguments s'appliquent uniquement aux champs numériques (int, float, Decimal) et d'autres uniquement aux chaînes de caractères (str).
Par exemple, pour des types numériques :
from typing_extensions import Annotated
from pydantic import BaseModel, Field, EmailStr
class User(BaseModel):
username: str
email: EmailStr
age: int = Field(gt=18) # accepte seulement les valeurs supérieures à 18.
Voici la liste des autres contraintes applicables aux valeurs numériques :
gt - greater than (supérieur à)
lt - less than (inférieur à)
ge - greater than or equal to (supérieur ou égal à)
le - less than or equal to (inférieur ou égal à)
multiple_of - a multiple of the given number (un multiple du nombre donné)
allow_inf_nan - allow 'inf', '-inf', 'nan' values (autorise les valeurs 'inf', '-inf', 'nan')
Vous pouvez trouvez une liste équivalente pour les autres types str, float, … dans la doc Pydantic.
Autres options intéressantes configurables dans les Fields :
default : pour ajouter une valeur par défaut.
strict : active le mode strict sur cette variable. Lorsqu'il est activé, Pydantic génère une erreur si les données ne sont pas du type correct au lieu de tenter de les convertir automatiquement.
frozen : rend la variable immutable, ce qui signifie qu’elle ne pourra pas être changée après son initialisation, un peu comme une constante.
VI. Type Annotated
Pour créer des types personnalisés embarquant toutes nos conditions, nous pouvons utiliser le type Annotated comme ici :
AdultAge = Annotated[int, Field(ge=18)]
class User(BaseModel):
username: str
email: EmailStr
age: AdultAge
Le type Annotated nous permet également d’ajouter des metadatas à nos types.
Nous pouvons utiliser les classes précédemment définies dans d’autres classes. Pydantic se chargera d’effectuer la validation également.
from pydantic import BaseModel, EmailStr, validator
from typing import List, Optional
class Item(BaseModel):
id: int
name: str
brand: str
class Customer(BaseModel):
email: EmailStr
name: str
age: int|None # Utilisation de Optional au lieu de int|None pour plus de clarté
items: List[Item]
VII. Mypy plugin
Nous pouvons ajouter une plugin spécial à notre projet uv pour que Mypy puisse checker les modèles Pydantic.
Configuration pour Mypy en mode strict :
[tool.mypy]
strict = "True"
plugins = [
"pydantic.mypy",
]
Avec l'ajout de Mypy en mode strict, les types non correspondants seront signalés, malgré que Pydantic puisse s'occuper de la conversion.
Pydantic offre également des fonctionnalités pour la sérialisation des données, la gestion des valeurs par défaut, et bien plus encore, ce qui en fait un outil polyvalent pour la préparation et la validation des données dans vos projets Python.
Nous reparlerons de Pydantic lors de la partie sur les webAPIs.
Merci de votre lecture !