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.py avec la classe Report
    • Module errors.py avec la classe Error
    • Module task.py avec la classe Task
    • Module resource.py avec la classe Resource
  • Introduction de fonctions intermédiaires dans validata-core, module __init__.py:

    • format, ̀_create_resource et _create_validate_options utilisées par la fonction validate
    • complete_task_content_for_formatted_report utilisée par la fonction format

Correction des tests :

  • Adaptation liée à la representation des objets frictionless dans la v5 :
    • report["stats"] devient report.stats
    • report["tasks"] devient report.task
    • report["tasks"][i]["errors"] devient report.task.error
    • error["code"] n'existe plus, on utilise désormais error.type
    • error["name"] devient error.name
    • introduction de error.title avec la v5
    • error["message"] devient error.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.

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 classe from_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_Errors en Errors
  • Pour les classes mères validata_core.custom_checks.utils.CustomCheckMultipleColumns et validata_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, columns et othercolumns

Erreurs liées aux custom checks

  • Introduction des classes mères validata_core.custom_checks.utilsCustomErrorMultipleColumns et validata_core.custom_checks.utilsCustomErrorSingleColumn héritant des erreurs frictionless CellError
  • Modification des classes d'erreurs liées aux custom check pour hériter de ses nouvelles classes mères
  • Rajout des attributs de classe type et title
  • 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_metrics
      • compute_badge
    • Introduction de fonctions intermédiaires utilisées dans la fonction validate :
      • build_validation_checks_and_check_errors_from_schema
      • get_custom_checks_from_schema
      • translate_errors
  • 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_constraints elle 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_constraint et enum_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_error et missing_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_option en _control_option (lié à la notion de ExcelFormat.dialect de la v4 de frictionless-py remplacée par ExcelFormat.control dans la v5, cf doc pour plus d'information)

Validata API

  • Module route_hanlders.py :
    • Introduction de fonctions intermédiaires utilisées dans la fonction validate :
      • get_args
      • create_validata_resource

Validata UI

  • Module views.py :
    • Introduction de fonctions intermédiaires utilisées dans la fonction ̀create_validata_ui_report pour la génération de rapport de validation de l'interface utilisateur :

      • add_source_headers_and_source_dimensions
      • get_headers_and_fields_dict_among_case_sensitivity
      • add_header_titles_and_description
      • add_columns_alerts
      • add_warnings_and_errors_counts
      • group_errors_into_structure_and_body
      • group_body_errors_by_row_id
      • sort_by_error_names_in_statistics
    • Introduction de fonctions intermédiaires utilisées dans la fonction validate de validation des données :

      • handle_unviewable_errors
      • build_pdf_report_url

Tests

  • Module tests/test_validata_core/utils.py : ajout de fonctions utilitaires, par exemple ̀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
  • 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-error avec 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, frictionless lève une exception de type frictionless.exception.FrictionlessException, générant une erreur de validation de de type error dans le rapport de validation de frictionless avec le message suivant : '"schema_sync" requires unique labels in the header'

  • Le type GeneralError n'existe plus dans la v5.

  • Le type TaskError qui héritait de GeneralError n'existe plus dans la v5.

  • Introduction du type ̀InquiryTaskError :

  • 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 Resource non retranscrits en metadata, exemple data, ... cf note

  • La notion de ExcelFormat.dialect de la v4 de frictionless-py remplacée par ExcelFormat.control dans 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 resource frictionless.

  • Fonction custom_validator du module validata_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

Edited by Amélie Rondot

Merge request reports

Loading