feat: Upgrade frictionless v5
Cette MR englobe les modifications de code suivantes liées à l'upgrade vers la version v5.16.1 de frictionless:
WARNING
Dans les requirements définis dans pyproject.toml la version de frictionless-py utilisée est un fork du dépôt officiel pour intégrer des fix bugs qui ne sont pas encore intégrés dans le dépôt officiel -> Il faudra changer les requirements une fois que les fix bugs auront été intégrés à la version officielle frictionless-py.
Formatage du rapport rendu par Validata
Afin d'assurer une rétrocompatibilité, le format du rapport de validation rendu par l'API doit contenir les informations contenues dans le rapport de validation qui était renvoyé par l'API lorsque l'on utilisait la version v4.38.0 de frictionless-py.
-
Introduction de nouveaux modules et de nouvelles classes dans
validata-core:- Module
report.pyavec la classeReport - Module
errors.pyavec la classeError - Module
task.pyavec la classeTask - Module
resource.pyavec la classeResource
- Module
-
Introduction de fonctions intermédiaires dans
validata-core, module__init__.py:-
format,̀_create_resourceet_create_validate_optionsutilisées par la fonctionvalidate -
complete_task_content_for_formatted_reportutilisée par la fonctionformat
-
Correction des tests :
- Adaptation liée à la representation des objets frictionless dans la v5 :
-
report["stats"]devientreport.stats -
report["tasks"]devientreport.task -
report["tasks"][i]["errors"]devientreport.task.error -
error["code"]n'existe plus, on utilise désormaiserror.type -
error["name"]devienterror.name - introduction de
error.titleavec la v5 -
error["message"]devienterror.message
-
Ajout de cas de tests :
Tests du custom check cohesive_column_values
- Complète le cas de test où une colonne relative à ce custom check requise dans le schéma est manquante dans les données en précisant que deux erreurs sont retranscrites dans le rapport de validation:
- une
missing-label-error - une erreur relative de type
check-error, précisant dans le message d'erreur que la colonne concernée par le custom check 'cohesive-columns-value' est manquante.
- une
Tests du format de représentation en dictionnaire du rapport de validation
- Rajout des cas de tests dans
test_validata_core/test_format_report_to_dict.py
Adaptation des custom checks
Initialisation des objets custom checks modifiée
-
Pour toutes les classes filles correspondant aux différents custom checks :
- Méthode
__init__remplacée par la méthode de classefrom_descriptor - Rajout de la méthode de classe ̀metadata_select_class`̀
- Suppression de l'attribut de classe
code - Rajout de l'attribut de classe
type - Modification de l'attribut
possible_ErrorsenErrors
- Méthode
-
Pour les classes mères
validata_core.custom_checks.utils.CustomCheckMultipleColumnsetvalidata_core.custom_checks.utils.CustomCheckSingleColumn:- Rajout du décorateur de classe
@attrs.define(kw_only=True, repr=False) - Suppression de la méthode
__init__ - Rajout des attributs de classe
column,column2,columnsetothercolumns
- Rajout du décorateur de classe
Erreurs liées aux custom checks
- Introduction des classes mères
validata_core.custom_checks.utilsCustomErrorMultipleColumnsetvalidata_core.custom_checks.utilsCustomErrorSingleColumnhéritant des erreurs frictionlessCellError - Modification des classes d'erreurs liées aux custom check pour hériter de ses nouvelles classes mères
- Rajout des attributs de classe
typeettitle - Suppression de l'attribut de classe
code
Correction de la validation en ligne de commande
Rappel de la syntaxe :
validata --schema <url_du_schema_de_données> <path/du/fichier/a/valider.csv>
Avec l'introduction de la v5 de frictionless, le rapport de validation rendu par validata en ligne de commande était faux, interprétant les données à valider comme étant vides.
Pour une raison qui me reste inexpliquée, l'objet frictionless.Resource issue de l'url (ou du path) de la donnée source construit en amont de l'étape de validation par frictionless, ne se construisait pas correctement (que l'on passe en argument un url de fichier à valider ou un path) et la valeur résultante de resource.data était None.
Ce problème n'était pas observé pour l'API ni pour l'UI, car toutes deux utilisent l'ensemble [en-tête + lignes] comme données dans la validation : elles effectuent un traitement de la donnée source en amont pour en extraire cet ensemble [en-tête + lignes], comme c'est le cas ici ou bien ici par l'appel à la fonction extract_tabular_data().
NB : l'appel à la fonction extract_tabular_data() appelle la fonction _extract_header_and_rows_from_frictionless_source() qui appelle elle-même la construction d'une resource frictionless, ce qui n'est pas satisfaisant d'un point de vue architecture du projet : On ne devrait pas avoir à faire de traitement de la donnée source via frictionless dans la partie Validata-API ou Validata-UI. Ceci pourra faire l'objet d'un refactoring à part entière.
Pour règler ce problème observé sur l'outil en lignes de commandes, j'ai choisi de suivre la même méthode que celle utilisée par Validata_UI et Validata_API : effectuer un traitement sur la donnée source passée en paramètre pour en extraire les en-têtes et lignes constituant la donnée à valider. Cette solution n'est pas très satisfaisante à mon sens, et mériterait un gros travail de refactoring que je n'ai pas pris le temps de faire dans cette MR.
Typage
- Correction des erreurs identifiées par
pyright
Refactoring effectués :
Refactoring général
- Amélioration générale de la docstring : rajout des signatures manquantes, et descriptions de fonctions
Validata Core
-
Module
__init__.py:- Suppression des fonctions liées à la fonctionnalité du 'badge' inutilisée :
compute_badge_metricscompute_badge
- Introduction de fonctions intermédiaires utilisées dans la fonction
validate:build_validation_checks_and_check_errors_from_schemaget_custom_checks_from_schematranslate_errors
- Suppression des fonctions liées à la fonctionnalité du 'badge' inutilisée :
-
Module
error_messages.py:-
Introduction de fonctions intermédiaires utilisées dans la fonction
constraint_name_and_message:extract_constraint_value_from_field_constraints-
all_constraintselle même éclatée en plusieurs nouvelles fonctions intermédiaires :required_constraint,unique_constraint,min_length_constraint,max_length_constraint,minimum_constraint,maximum_constraint,pattern_constraintetenum_constraint
-
Introduction de fonctions intermédiaires utilisées dans la fonction
error_translate:-
all_errors, elle-même décomposée en plusieurs nouvelles fonctions intermédiaires :encoding_error,blank_header_error,blank_row_error,extra_cell_error,type_error,constraint_erroretmissing_label_error
-
-
Introduction de fonctions intermédiaires utilisées dans la fonction
type_error:-
find_field_in_schema,date_type_error,number_type_error,string_type_error,boolean_type_error
-
-
-
Module
helpers.py:- Renommage de la fonction
_dialect_optionen_control_option(lié à la notion deExcelFormat.dialectde la v4 defrictionless-pyremplacée parExcelFormat.controldans la v5, cf doc pour plus d'information)
- Renommage de la fonction
Validata API
- Module
route_hanlders.py:- Introduction de fonctions intermédiaires utilisées dans la fonction
validate:get_argscreate_validata_resource
- Introduction de fonctions intermédiaires utilisées dans la fonction
Validata UI
- Module
views.py:-
Introduction de fonctions intermédiaires utilisées dans la fonction
̀create_validata_ui_reportpour la génération de rapport de validation de l'interface utilisateur :add_source_headers_and_source_dimensionsget_headers_and_fields_dict_among_case_sensitivityadd_header_titles_and_descriptionadd_columns_alertsadd_warnings_and_errors_countsgroup_errors_into_structure_and_bodygroup_body_errors_by_row_idsort_by_error_names_in_statistics
-
Introduction de fonctions intermédiaires utilisées dans la fonction
validatede validation des données :handle_unviewable_errorsbuild_pdf_report_url
-
Tests
- Module
tests/test_validata_core/utils.py: ajout de fonctions utilitaires, par exemplèassert_valid_report()
Tests fonctionnels réalisés :
-
Validata-UI : -
pour tous les schémas tester le comportement attendu pour les exemples de fichiers données et comparer avec le comportement observé en production -
pour les schémas délibérations et subventions (schémas présentant plusieurs custom checks) : tester le comportement attendu avec des exemples de fichiers invalides et comparer avec le comportement observé en production -
tester la fonctionnalité relative à la sensibilité à la casse
-
-
Validata API : - tester le comportement attendu pour les schémas délibérations et subventions avec :
-
cas d'une requête GET avec 2 cas de tests : sensible à la casse et insensible à la casse -
cas d'une requête POST avec 2 cas de tests : sensible à la casse et insensible à la casse
-
- tester le comportement attendu pour les schémas délibérations et subventions avec :
-
Validata en ligne de commandes : -
validation d'un fichier en donnant son url -
validation d'un fichier en donnant son path -
validation d'un fichier en fournissant un schéma invalide -
tester la sensibilité et l'insensibilité à la casse
-
PM : Modifications de comportement observées entre la v4 et la v5 de frictionless
-
Lorsque le nom d'une colonne apparaît plusieurs fois dans le fichier de données, dans la v4 cela générait une erreur de validation de type
general-erroravec le message suivant : 'Duplicate labels in header is not supported with "schema_sync"'. Cette erreur était retranscrite dans le rapport de validation de frictionless. Dans la v5, le nom d'une colonne apparaît plusieurs fois dans le fichier de données,frictionlesslève une exception de typefrictionless.exception.FrictionlessException, générant une erreur de validation de de typeerrordans le rapport de validation defrictionlessavec le message suivant : '"schema_sync" requires unique labels in the header' -
Le type
GeneralErrorn'existe plus dans la v5. -
Le type
TaskErrorqui héritait deGeneralErrorn'existe plus dans la v5. -
Introduction du type
̀InquiryTaskError:- Error levée dans le rapport de validation de frictionless si un 'path' utilisé n'est pas sécurisé
- Error levée dans le rapport de validation de frictionless si une des propriétés 'path', 'ressources' ou 'package' requises n'existe pas
-
Introduction du type
̀ReportTaskError, mais pas de cas d'usage actuellement identifié pour ce type d'erreur dans la v5 de frictionless-py -
Attributs d'un objet
Resourcenon retranscrits en metadata, exempledata, ... cf note -
La notion de
ExcelFormat.dialectde la v4 defrictionless-pyremplacée parExcelFormat.controldans la v5, cf doc pour plus d'information
Suggestions de refactoring restant à faire
-
Architecture hexagonale : comme évoqué plus haut, on ne devrait pas à avoir à faire de traitement de la donnée source via frictionless dans l'API, l'UI et l'outil en ligne de commandes en amont de la validation des données. Il faudrait pouvoir isoler le traitement de la donnée source utilisée en amont de la validation dans validata-core. Dans Validata-API, Validata-UI et l'outil en ligne de commandes, on ne devrait pas avoir à faire appel à frictionless comme c'est le cas ici, ici ou bien ici : l'appel à la fonction
extract_tabular_data()appelle la fonction_extract_header_and_rows_from_frictionless_source()qui appelle elle-même la construction d'une resourcefrictionless. -
Fonction
custom_validatordu modulevalidata_ui/views.py -
Module
validata_core/helpers.py: il reste peut-être du refacto à faire dans ce module -
'header_case' à remplacer plutôt pas 'case_sensitive' dans validata