.. _validation: ********************* Validation de données ********************* La validation est un processus permettant de garantir l'adéquation, la précision et la cohérence des données collectées. Elle est essentielle pour garantir la qualité des données et la fiabilité des analyses qui en découlent. La validation des données peut être effectuée à différents niveaux, notamment au niveau de l'interface utilisateur et du serveur pour les données récupérées via un formulaire web. Mais également avant insertion dans la base de données ou lors de la récupération de données depuis une source externe. Validation côté client ====================== La validation côté client est effectuée dans le navigateur de l'utilisateur avant que les données ne soient envoyées au serveur. Elle permet de garantir que les données saisies par l'utilisateur sont correctes et cohérentes. La validation côté client est généralement effectuée à l'aide de JavaScript et de bibliothèques de validation telles que jQuery Validation. Voici un exemple de validation côté client à l'aide de jQuery Validation : .. code-block:: html
Dans cet exemple, jQuery Validation est utilisé pour valider un formulaire contenant un champ de texte et un champ d'e-mail. Le champ de texte est requis, tandis que le champ d'e-mail doit contenir une adresse e-mail valide. Validation côté serveur ======================= La validation côté serveur est effectuée sur le serveur avant de traiter les données. Elle permet de garantir que les données reçues du client sont correctes et cohérentes. La validation côté serveur est généralement effectuée à l'aide de frameworks de développement web tels que Django. Voici un exemple de validation côté serveur à l'aide de Django : .. code-block:: python from django import forms class MyForm(forms.Form): name = forms.CharField(max_length=100) email = forms.EmailField() Dans cet exemple, un formulaire Django est défini avec deux champs : un champ de texte pour le nom et un champ d'e-mail pour l'adresse e-mail. La validation est effectuée automatiquement par Django lors de la soumission du formulaire. Validation back end =================== La validation back end est effectuée avant l'insertion des données dans la base de données. Elle permet de garantir que les données sont correctes et cohérentes avant d'être stockées. La validation back end est généralement effectuée à l'aide de bibliothèques de validation telles que `Pydantic `_ pour Python. Voici un exemple de validation back end à l'aide de `Pydantic `_ : .. code-block:: python from pydantic import BaseModel class User(BaseModel): name: str email: str user_data = {"name": "John Doe", "email": " [email protected]"} user = User(**user_data) # Validation is performed here Dans cet exemple, une classe `Pydantic `_ est définie avec deux champs : un champ de texte pour le nom et un champ d'e-mail pour l'adresse e-mail. Lorsqu'une instance de la classe est créée avec des données utilisateur, la validation est effectuée automatiquement pour garantir que les données sont correctes. `Pydantic `_ sera vu plus en détail ci dessous. Types de validation =================== La validation des données regroupe diverses techniques visant à garantir que les données sont exactes, cohérentes et exploitables pour l'analyse. Voici les principaux types de validation des données : Validation Syntaxique --------------------- La validation syntaxique vérifie que les données respectent une structure et un format spécifiques. Elle impose des règles de représentation des données. Par exemple : - Vérification de format : s'assurer que les données suivent un format précis (ex. : une date au format YYYY-MM-DD, une adresse e-mail valide, etc.) ; - Vérification de longueur : confirmer que la donnée respecte une longueur définie (ex. : un numéro de téléphone doit comporter 10 chiffres). Validation Sémantique --------------------- La validation sémantique vérifie que les données ont un sens logique et respectent les règles métier. Par exemple : - Vérification logique : la date de fin d'un projet doit être postérieure à la date de début ; - Contrôle de domaine : l'âge d'un client doit être compris entre 0 et 120 ans. Validation de Plage (Range Validation) -------------------------------------- La validation de plage vérifie que les valeurs numériques appartiennent à une plage définie. Par exemple : - Une température doit être comprise entre -50°C et 50°C ; - Le prix d'un produit ne peut pas être négatif. Validation de Type ------------------ La validation de type vérifie que la donnée est du type attendu (nombre, texte, booléen, etc.). Par exemple : - Un champ prévu pour un nombre ne doit contenir que des chiffres ; - Une case à cocher Oui/Non doit être un booléen (true / false). Validation par Code ------------------- La validation par code vérifie que les valeurs saisies correspondent à des codes prédéfinis. Par exemple : - Un code pays doit être valide selon la norme ISO 3166 ; - Une catégorie de produit doit appartenir à une liste prédéfinie. Validation d'Unicité -------------------- La validation d'unicité empêche la duplication de valeurs pour préserver l'intégrité des identifiants uniques. Par exemple : - Un e-mail ou un identifiant utilisateur doit être unique dans la base de données ; - Une clé primaire dans une table ne doit pas être dupliquée. Validation de Cohérence ----------------------- La validation de cohérence vérifie que les données liées entre elles sont cohérentes. Par exemple : - L'adresse de facturation et l'adresse de livraison doivent être identiques si aucune autre adresse n'est fournie ; - Les dates de commande et d'expédition doivent être dans un ordre logique. Validation de Présence (Null/Not Null Validation) ------------------------------------------------- La validation de présence vérifie que les champs obligatoires sont remplis et que les valeurs facultatives sont bien gérées. Par exemple : - Le nom, l'adresse et l'e-mail doivent être obligatoires ; - Un champ optionnel peut être NULL sans provoquer d'erreur. Validation Croisée (Cross-Validation) ------------------------------------- La validation croisée compare les données entre plusieurs sources pour assurer leur exactitude et leur cohérence. Par exemple : - Vérifier que les données clients dans la base de ventes correspondent aux données du CRM ; - Comparer les prix des produits entre plusieurs fournisseurs. Validation avec Expressions Régulières (Regex Validation) --------------------------------------------------------- La validation avec expressions régulières (regex) permet de définir des règles de validation complexes sur les chaînes de caractères. Par exemple : - Valider une adresse e-mail avec un motif spécifique ; - Vérifier un mot de passe pour qu'il contienne au moins 8 caractères, des lettres, des chiffres et des symboles. .. admonition:: Lecture :download:`Data Validation and Data Cleaning <../files/data-validation-and-data-cleaning.pdf>` [Sebastian Schelter, Moore-Sloan Fellow, Center for Data Science, New York University] Conclusion ---------- L'utilisation de techniques variées de validation des données est essentielle pour assurer leur intégrité et leur qualité. En combinant plusieurs de ces méthodes, on peut minimiser les erreurs. Les expressions régulières ========================== Les `expressions régulières `_ (regex) sont très utiles pour valider les données en fonction de leur format. Elles permettent de définir des patterns (motifs) de caractères qui correspondent à des chaînes de texte spécifiques. Il y existe plusieurs syntaxes pour définir un pattern, la plus utilisée étant la syntaxe `Perl (Practical Extraction Report Language) `_, adoptée par Python. Analyser attentivement `la syntaxe `_ utilisée en Python. .. admonition:: Tutoriels - `Regular Expressions: Regexes in Python (Part 1) `_. - `Regular Expressions: Regexes in Python (Part 2) `_. Vous êtes maintenant en mesure de répondre aux questions suivantes. .. quiz:: quizz-01 :title: Syntaxe élémentaire regex #. :quiz:`{"type":"FB","answer":".", "size":9}` correspond à n'importe quel caractère excepté un retour à la ligne #. :quiz:`{"type":"FB","answer":"?", "size":9}` correspond à 0 ou 1 occurrence du caractère précédent #. :quiz:`{"type":"FB","answer":"*", "size":9}` correspond à 0 ou plusieurs occurrences du caractère précédent #. :quiz:`{"type":"FB","answer":"+", "size":9}` correspond à 1 ou plusieurs occurrences du caractère précédent #. :quiz:`{"type":"FB","answer":"?", "size":9}` doit être ajouté après l'un des trois quantifiers précédents pour les rendre non-greedy #. :quiz:`{"type":"FB","answer":"\\d", "size":9}` correspond à n'importe quel chiffre #. :quiz:`{"type":"FB","answer":"\\D", "size":9}` correspond à n'importe quel caractère qui n'est pas un chiffre #. :quiz:`{"type":"FB","answer":"\\w", "size":9}` correspond à n'importe quelle lettre ou chiffre #. :quiz:`{"type":"FB","answer":"\\W", "size":9}` correspond à n'importe quel caractère qui n'est pas une lettre ou un chiffre #. :quiz:`{"type":"FB","answer":"\\s", "size":9}` correspond à n'importe quel espace blanc #. :quiz:`{"type":"FB","answer":"\\S", "size":9}` correspond à n'importe quel caractère qui n'est pas un espace blanc #. :quiz:`{"type":"FB","answer":"{m}", "size":9}` signifie exactement ``m`` occurrences du motif précédent #. :quiz:`{"type":"FB","answer":"{m,n}", "size":9}` signifie entre ``m`` et ``n`` occurrences du motif précédent #. :quiz:`{"type":"FB","answer":"\\", "size":9}` permet d'échapper le caractère spécial suivant #. :quiz:`{"type":"FB","answer":"^", "size":9}` correspond au début de la chaîne #. :quiz:`{"type":"FB","answer":"$", "size":9}` correspond à la fin de la chaîne #. :quiz:`{"type":"FB","answer":"[]", "size":9}` permet de définir un ensemble de caractères #. :quiz:`{"type":"FB","answer":"[^]", "size":9}` permet de définir un ensemble de caractères à exclure #. :quiz:`{"type":"FB","answer":"|", "size":9}` permet de définir une alternative entre deux motifs #. :quiz:`{"type":"FB","answer":"()", "size":9}` permet de grouper des motifs #. :quiz:`{"type":"FB","answer":"[a-z]", "size":9}` correspond à n'importe quelle lettre minuscule #. :quiz:`{"type":"FB","answer":"[A-Z]", "size":9}` correspond à n'importe quelle lettre majuscule #. :quiz:`{"type":"FB","answer":"[0-9]", "size":9}` correspond à n'importe quel chiffre #. :quiz:`{"type":"FB","answer":"[a-zA-Z0-9]", "size":9}` correspond à n'importe quelle lettre ou chiffre #. :quiz:`{"type":"FB","answer":"[a-zA-Z0-9_]", "size":9}` correspond à n'importe quelle lettre, chiffre ou souligné La syntaxe des expressions régulières est très puissante et permet de réaliser des opérations complexes. Utiliser `regex101 `_ pour observer comment sont construites les expressions régulières permettant de valider les données suivantes : - `un mot de passe fort `_ - `une date `_ au format `RFC3339 `_ - `un email `_ au format `RFC2822 `_ - `une adresse IP `_ au format `RFC791 `_ .. admonition:: Exercice Ecrire une expression régulière pour valider chaque type de nom de variable en Python : - b (single lowercase letter) - B (single uppercase letter) - lowercase - lower_case_with_underscores - UPPERCASE - UPPER_CASE_WITH_UNDERSCORES - CapitalizedWords - mixedCase - Capitalized_Words_With_Underscores - _single_leading_underscore - single_trailing_underscore\_ - __double_leading_underscore - __double_leading_and_trailing_underscore__ Pour chaque règle vérifier avec `regex101 `_ si elle est valide ou non avec les noms de variable contenus dans la liste ``list_of_names``. .. code-block:: python list_of_names = [ "__privateVar", "myVariableName", "VARIABLE", "_var_", "_privateVar", "My_Variable_Name", "my_variable_name", \ "var_", "XY", "myVariableName", "Variable", "x", "privateVar", "__magic", "MyVariableName", "__magic__", \ "MY_VARIABLE_NAME", "variable", "My_Variable_Name", "my_variable_name", "MY_VARIABLE_NAME", "My_Variable_Name", \ "my_variable_name", "__privateVar", "Variable", "__magic", "__magic__", "MyVariableName", "privateVar", "_privateVar", \ "XY", "VARIABLE", "x", "var_", "_var_"] Pydantic ======== `Pydantic `_ est une bibliothèque de validation de données pour Python. Elle permet de définir des modèles de données avec des types de champs et des règles de validation, puis de valider les données par rapport à ces modèles. Pydantic est utile pour garantir la qualité et la cohérence des données dans une application. L'un des principaux attraits de Python est qu'il s'agit d'un langage à typage dynamique. Le typage dynamique signifie que les types des variables sont déterminés à l'exécution, contrairement aux langages à typage statique, où ils sont explicitement déclarés au moment de la compilation. Bien que le typage dynamique soit idéal pour un développement rapide et une utilisation simplifiée, il est souvent nécessaire d'avoir une vérification des types et une validation des données plus robustes pour les applications en conditions réelles. C'est là que la bibliothèque Pydantic de Python entre en jeu. Pydantic a rapidement gagné en popularité et est aujourd'hui la bibliothèque de validation de données la plus utilisée en Python. Elle est largement utilisée dans les applications web, les services RESTful, les applications de traitement de données et les applications de machine learning. Pydantic est également intégré à de nombreuses autres bibliothèques Python populaires, telles que FastAPI, Tortoise-ORM et Typer. Pydantic exploite les annotations de type pour aider à valider et sérialiser facilement les schémas de données. Cela rend le code plus robuste, lisible, concis et facile à déboguer. Pydantic s'intègre également avec `mypy `_ et `VS Code `_, ce qui permet de détecter les erreurs de schéma avant d'exécuter le code. Principales fonctionnalités de Pydantic : - Personnalisation : on peut valider pratiquement n'importe quel type de données. Que ce soit des types primitifs de Python ou des structures de données profondément imbriquées, on peut valider et sérialiser presque tous les objets Python ; - Flexibilité : on peut choisir entre une validation stricte ou souple des données ; - Sérialisation : on peut convertir ses objets en dictionnaires et en chaînes JSON (et inversement). Cela facilite l'intégration avec des API et des outils utilisant des schémas JSON ; - Performance : grâce à son coeur écrit en Rust, la validation est très rapide ; - Adoption et écosystème : Pydantic est une dépendance clé dans des bibliothèques Python populaires comme FastAPI, LangChain et Polars. .. admonition:: Tutoriel Le tutoriel `Pydantic: Simplifying Data Validation in Python `_ présente les principaux aspects de l'utilisation de Pydantic pour la validation des données en Python. Il couvre les sujets suivants : - `Python's Pydantic Library `_ - `Using Models `_ - `Working With Validators `_ - `Managing Settings `_ Application =========== Ecrire un validateur pour les données utilisées dans le :ref:`test-python`. Utiliser ce validateur avec le fichier :download:`population-corrupted.csv <../data/population-corrupted.csv>` dans lequel 15 erreurs ont été introduites. Quelles sont les lignes concernées ? .. quiz:: quizz-02 :title: Erreurs de validation #. :quiz:`{"type":"FB","answer":"918", "size":6}` Erreur #1 #. :quiz:`{"type":"FB","answer":"1884", "size":6}` Erreur #2 #. :quiz:`{"type":"FB","answer":"3949", "size":6}` Erreur #3 #. :quiz:`{"type":"FB","answer":"8351", "size":6}` Erreur #4 #. :quiz:`{"type":"FB","answer":"12712", "size":6}` Erreur #5 #. :quiz:`{"type":"FB","answer":"17216", "size":6}` Erreur #6 #. :quiz:`{"type":"FB","answer":"29128", "size":6}` Erreur #7 #. :quiz:`{"type":"FB","answer":"42376", "size":6}` Erreur #8 #. :quiz:`{"type":"FB","answer":"52447", "size":6}` Erreur #9 #. :quiz:`{"type":"FB","answer":"61962", "size":6}` Erreur #10 #. :quiz:`{"type":"FB","answer":"73094", "size":6}` Erreur #11 #. :quiz:`{"type":"FB","answer":"87216", "size":6}` Erreur #12 #. :quiz:`{"type":"FB","answer":"110674", "size":6}` Erreur #13 #. :quiz:`{"type":"FB","answer":"123639", "size":6}` Erreur #14 #. :quiz:`{"type":"FB","answer":"137670", "size":6}` Erreur #15 .. Erreur sur les lignes : 918, 1884, 3949, 8351, 12712, 17216, 29128, 42376, 52447, 61962, 73094, 87216, 110674, 123639, 137670 .. import csv from pathlib import Path from pydantic import BaseModel, ValidationError, Field, field_validator, StringConstraints from typing import Optional, Annotated ConstrainedNoDigitsString = Annotated[str, StringConstraints(pattern=r"^[^\d]*$")] ConstrainedCodeOfficielRegion = Annotated[str, StringConstraints(min_length=2, max_length=3, pattern=r"^\d{2,3}$")] ConstrainedCodeOfficielDepartement = Annotated[str, StringConstraints(min_length=2, max_length=3, pattern=r"^\d[A-B0-9]\d{0,1}$")] ConstrainedCodeOfficielArrondissementDepartemental = Annotated[str, StringConstraints(min_length=3, max_length=3, pattern=r"^\d{3}$")] ConstrainedCodeOfficielCommuneArrondissementMunicipal = Annotated[str, StringConstraints(min_length=5, max_length=6, pattern=r"^\d[A-B0-9]\d{3,4}$")] ConstrainedAnnee = Annotated[str, StringConstraints(min_length=4, max_length=4, pattern=r"^\d{4}$")] ConstrainedCodeOfficielEPCI = Annotated[Optional[str], StringConstraints(min_length=0, max_length=9, pattern=r"^(\d{9})?$")] # le path du fichier de données FILEPATH = Path("population.csv") class Data(BaseModel): CodeOfficielRegion: ConstrainedCodeOfficielRegion = Field(...,validation_alias="Code Officiel Région", description="'Code Officiel Région' is required") # Required NomOfficielRegion: ConstrainedNoDigitsString = Field(...,validation_alias="Nom Officiel Région") # Required CodeOfficielDepartement: ConstrainedCodeOfficielDepartement = Field(...,validation_alias="Code Officiel Département") # Required CodeOfficielArrondissementDepartemental: ConstrainedCodeOfficielArrondissementDepartemental = Field(...,validation_alias="Code Officiel Arrondissement Départemental") CodeOfficielCommuneArrondissementMunicipal: ConstrainedCodeOfficielCommuneArrondissementMunicipal = Field(...,validation_alias="Code Officiel Commune / Arrondissement Municipal") NomOfficielCommuneArrondissementMunicipal: str = Field(...,validation_alias="Nom Officiel Commune / Arrondissement Municipal") PopulationMunicipale: float = Field(...,validation_alias="Population municipale") # Required PopulationCompteeAPart: float = Field(...,validation_alias="Population comptée à part") # Required PopulationTotale: float = Field(...,validation_alias="Population totale") # Required AnneeRecensement: ConstrainedAnnee = Field(...,validation_alias="Année de recensement") # Required AnneeEntreeEnVigueur: ConstrainedAnnee = Field(...,validation_alias="Année d’entrée en vigueur") # Required AnneeReferenceGeographique: ConstrainedAnnee = Field(...,validation_alias="Année de référence géographique") # Required NomOfficielEPCI: str = Field(...,validation_alias="Nom Officiel EPCI") # Required CodeOfficielEPCI: ConstrainedCodeOfficielEPCI = Field(...,validation_alias="Code Officiel EPCI") # Required NomOfficielDepartement: ConstrainedNoDigitsString = Field(...,validation_alias="Nom Officiel Département") # Required def read_and_validate(filepath: Path): with open(filepath, "r", encoding="utf-8") as f: reader = csv.DictReader(f, delimiter=";") valid_rows = [] print(reader.fieldnames) print() for row in reader: try: data = Data(**row) valid_rows.append(data.model_dump()) except (ValidationError, ValueError) as e: print(f"Erreur sur la ligne {row} : {e}\n") def main(): read_and_validate(FILEPATH) if __name__ == "__main__": main()