Utilisation de Pydantic pour valider les données d’un fichier Open Document Spreadsheet (ODS)

Lors de mes différents développements sur le traitement de données, je rencontre fréquemment des situations où j’ai construit des automatismes basés sur des données générées par l’utilisateur à partir d’un document Open Document Spreadsheet (ODS). La flexibilité de ce format ouvert de données permet d’être utilisé par une grande variété d’utilisateurs, mais malheureusement, cette flexibilité conduit à l’entrée de données invalides, obligeant à vérifier et filtrer les données afin de s’assurer qu’elles soient valides.

Qu’est-ce que Pydantic ?

Pydantic est une bibliothèque Python permettant de définir un modèle de données de manière « pythonique » et d’utiliser ce modèle pour effectuer de la validation de données. Fondamentalement, vous déclarez la forme de la donnée avec des classes et des attributs. Chaque attribut possédant un type. Ensuite, il suffit de créer une instance de cette classe avec certaines valeurs et Pydantic validera les valeurs, les convertira dans le type adéquat (si c’est nécessaire et possible).

Exemple de données.

Avant de commencer, voici notre échantillon de données contenu dans une feuille de calcul au format ODS:

DATEPRENOMNOMEMAILAGEVILLEGENRE
25/11/2022RobertLepingreRobert@exemple.com410ParisM
Ducouxjeanne@exemple.com32MarseilleF
28/09/2022PierreLenfantpierre@exemple.com23RennesNB
ChristianVilburvilbur@exemple60EvreuxM
17/02/2023FabienLaHayefabien.lahaye@exemple.com30RouenX

Dans le cas de cet exemple, les champs obligatoires étant PRENOM, NOM, EMAIL et AGE. Vous aurez surement remarqué que sur certains champs des valeurs sont absentes, erronées voir non autorisées.

Pré-requis.

Nous aurons besoin des deux bibliothèques Pythons suivantes :

  • pandas : Bibliothèque polyvalente permettant de réaliser des analyses complexes de données.
  • odfpy : Bibliothèque de lecture & écriture de fichier au format Open Document
  • pydantic : Bibliothèque permettant d’effectuer la validation de données.

Pour installer, ces bibliothèques, il suffit juste d’exécuter :

pip install pandas

pip install odfpy

pip install pydantic

Définition du modèle de donnée Pydantic.

Pydantic a un certain nombre de classes de base de départ pour un modèle de données, mais notre modèle étant assez simple, nous allons donc utiliser pydantic.BaseModel :

# Pydantic types: https://docs.pydantic.dev/usage/types/
class SampleDataModel(pydantic.BaseModel):
    DATE: datetime
    PRENOM: str = pydantic.Field(...)
    NOM: str = pydantic.Field(...)
    EMAIL: pydantic.networks.EmailStr  = pydantic.Field(...)
    AGE: int = pydantic.Field(..., ge=1, le=125)
    VILLE: str
    GENRE: GenreEnum

La syntaxe est assez simple. Après avoir défini la classe et hérité de notre modèle de base, nous entrons chacun de nos noms de champ et fournissons un indice de type.

Enums pour contrôler les champs de type chaîne.

Les indications de type dans Pydantic sont plus puissantes que celle des autres types de classes en Python. L’une de ces fonctionnalités puissantes est de pouvoir limiter les entrées de chaînes de caractères en définissant des Enums et en passant l’Enum comme un indice de type.

Ainsi, nous définissons ces choix comme des Enums pour la donnée « GENRE » de la manière suivante :

class GenreEnum(enum.Enum):
    M = 'M'
    F = 'F'
    NB = 'NB'

Field() pour une spécificité encore plus grande.

Lorsque nous définissons un champ dans notre modèle de données, nous pouvons appeler la fonction Field() pour spécifier des options supplémentaires, y compris si un champ est obligatoire ou non, et pour définir des limitations sur les entrées numériques.

pydantic.Field(...)

Passer comme premier argument à Field() indique que cette donnée est obligatoire.

pydantic.Field(..., ge=1, le=125)

ge signifie supérieur ou égal à
le signifie inférieur ou égal à

pydantic.networks.EmailStr = pydantic.Field(...)

EmailStr signifiant que cela doit être une adresse électronique valide.

Voilà ! Nous avons ainsi défini dans ce modèle les données obligatoires et sur certaines le type et ordre de valeurs acceptées.

Utilisation du modèle.

Maintenant que nous avons fait tout le travail pour définir le modèle, nous pouvons l’utiliser. Les modèles Pydantic s’attendent à recevoir des données de type JSON, donc toute donnée que nous passons à notre modèle pour validation doit être de type dictionnaire.

Pour la validation de nos données, nous devons faire ce qui suit :

  • Recevoir un DataFrame Pandas en entrée.
  • Convertir ce dataframe en une liste de dictionnaires (c.-à-d. Un dictionnaire par ligne)
  • Exécuter la validation des données pour chaque ligne
  • Ajouter les lignes validées avec succès à une liste (good_data)
  • Ajouter les lignes non validées à une autre liste (bad_data), avec le nom du champ/de la donnée et le message d’erreur.

Ainsi, avec cette fonction, nous pouvons traiter les bonnes lignes de données et renvoyer les mauvaises lignes de données pour un contrôle qualité, une modification et une nouvelle soumission.

Code Python

from datetime import datetime
import enum

# https://pandas.pydata.org/
import pandas as pd
# https://docs.pydantic.dev/
import pydantic

class GenreEnum(enum.Enum):
    M = 'M'
    F = 'F'
    NB = 'NB'

# Pydantic types: https://docs.pydantic.dev/usage/types/
class SampleDataModel(pydantic.BaseModel):
    DATE: datetime
    PRENOM: str = pydantic.Field(...)
    NOM: str = pydantic.Field(...)
    EMAIL: pydantic.networks.EmailStr = pydantic.Field(...)
    AGE: int = pydantic.Field(..., ge=1, le=125)
    VILLE: str
    GENRE: GenreEnum


def validate_df_data(df: pd.DataFrame, model: pydantic.BaseModel, index_offset: int = 2) -> tuple[list, list]:
    # Par défaut index_offset = 2 car l'index Python commence à 0, Open Document Spreadsheet (ODS) à 1, et 1 rangée pour l'en-tête.

    good_data = []  # Données correctes
    bad_data = []   # Données erronées
    df_rows = df.to_dict(orient='records')

    for index, row in enumerate(df_rows):
        try:
            model(**row)
            good_data.append(row)
        except pydantic.ValidationError as err:
            row['Errors'] = [f"{error_message['loc'][0]}: {error_message['msg']}" for error_message in err.errors()]
            row['Error_row_num'] = index + index_offset
            bad_data.append(row)

    return (good_data, bad_data)

df = pd.read_excel("Validate User-Generated Data Using Pydantic/sample_data.ods")
valid_data, invalid_data = validate_df_data(df, SampleDataModel)

print(valid_data)
print(invalid_data)

Conclusion

Bien qu’il s’agisse d’un exemple simple, Pydantic peut gérer des modèles imbriqués complexes. Cela permet vraiment une grande granularité dans la validation des données sans avoir à écrire beaucoup de lignes de code Python.

En outre, la modélisation des données vous aide à comprendre/à interpréter les données, au lieu de vous contenter de ce qui vous est présenté.

Bien que ce tutoriel se concentre sur Pandas, vous pouvez utiliser Pydantic pour valider la plupart des formes d’entrées de données avec Python.

Laisser un commentaire