From 567aeed0a14c5b90b7ca0c47da56276fc8a7422b Mon Sep 17 00:00:00 2001 From: gwen Date: Wed, 5 Jul 2023 12:59:51 +0200 Subject: [PATCH] xmldataset --- actes-princiers/docs/source/pipeline.rst | 163 +----------------- .../pipelines/xml_processing/nodes.py | 6 +- actes-princiers/src/actesdataset.py | 87 ++++++++-- 3 files changed, 86 insertions(+), 170 deletions(-) diff --git a/actes-princiers/docs/source/pipeline.rst b/actes-princiers/docs/source/pipeline.rst index 92e4317..a8221f5 100644 --- a/actes-princiers/docs/source/pipeline.rst +++ b/actes-princiers/docs/source/pipeline.rst @@ -26,103 +26,6 @@ Ce type de référentiel de données a un schéma défini qui nécessite un *ali c'est-à-dire qu'il faut faire correspondre les colonnes et les types de données afin de mettre à jour les données existantes avec de nouvelles données. -Le trajet dans le pipeline de données ------------------------------------------ - -Qu'entend-t-on par "data processing" ? - -- La collecte ou l’extraction d’ensembles de données brutes. -- La gouvernance des données. Une fois les données collectées, il faut constituer - une discipline pour organiser les données. - Cette discipline s’appelle la gouvernance des données. - On commence par relier les données brutes au contexte pour qu’elles aient un sens. - On contrôle ensuite la qualité des données et la sécurité des données, - avant de les organiser pleinement en vue de leur consommation. - -Le pipeline ETL ----------------- - -.. glossary:: - - ETL - - ETL signifie **E**\ xtract, **T**\ ransform, **L**\ oad. - C'est historiquement le premier data pipeline, (pipeline sur les données). - - -.. rubric:: Exemple de YAML descriptif d'un pipeline - - -.. code-block:: yaml - - stage: - - prepared: - - name: bourbon_clean_up - depends: bourbon_xml_catalog - outputs: bourbon_data - - - - -Qu'est-ce que intégrer des données ? ---------------------------------------- - -.. glossary:: - - intégration des données - - L'intégration des données est le processus qui consiste à combiner des données - provenant de différentes sources dans une vue unifiée : de l'importation au nettoyage - en passant par le mapping et la transformation dans un gisement cible, - pour finalement rendre les données plus exploitables et plus utiles. - -.. rubric:: exemple - -Les métadatas contenues dans un fichier CSV et les datas contenues dans un fichier XML. - -Qu’est-ce que l'étiquetage des données ? -------------------------------------------------------- - -Dans le cas de processus tels que l’intégration des données, la migration des données, -l’automatisation des entrepôts de données, la synchronisation des données, -l’extraction automatique de données ou d’autres projets de gestion des données, -la qualité de l'étiquetage conditionnera la qualité des données qui seront analysées -pour en tirer des informations exploitables. - -.. glossary:: - - étiquetage des données - - Un label est associé à une donnée pour savoir par exemple quelle est - sa provenance. - -**E**\ xtraire les données ------------------------------ - - -- **Extraire les données** c'est, dans ce cas, charger depuis le système de fichiers en local, - les fichiers CSV et XML. - -.. note:: Pas besoin de collecte et agrégation des données. - Les Sources de datas sont présentes en local dans l'application. - -.. admonition:: TODO - - Il faudra les enlever de l'arborescence flask pour les mettre dans - un :term:`référentiel de données`. - -Datasource CSV:: - - ./app/static/csv/corpus-agnes-bourgogne.csv - ./app/static/csv/corpus-charles-i.csv - ./app/static/csv/actors.csv - -Datasource XML : - -Dans `./app/static/xml/` : - -.. image:: img/xml.png Le référentiel de données -------------------------------- @@ -215,68 +118,16 @@ La **T**\ ransformation des données La transformation des données consiste à convertir les données d’un format source dans un format cible. Cela peut inclure un nettoyage des données par une modification des types de données, une suppression des données invalides ou des doublons, une agrégation des données, un enrichissement des données ou d’autres transformations. -- La transformation des données - - - normalisation - - dédoublonnage - - vérification - - classement - - partage des données - -Autres types de transformation : - -- Cleaning -- Filtering -- Joining -- Sorting -- Splitting -- Deduplication -- Summarization - -"The data cleaning and organization stage is the transformation stage." - -.. rubric:: Quel type de transformation des données et pourquoi ? +La visualisation des pipelines +---------------------------------- -Il s'agit surtout de les structurer, ce que faisait, avant, le storage -dans une base de données relationnelle. +Utiliser l'outil fournit avec kedro, lancer la commande:: -Aujourd'hui, l'organisation des datas ne relève plus de la responsabilité du storage -(`cohérence de type ACID `_), -le storage n'ayant que la responsabilité de type `BASE `_ + kedro viz + +On obtient une interface de ce type : -Que signifie le data reshaping ? --------------------------------------- +.. image:: img/kedro-viz.png -On parle de data shape shifting ou data reshaping. - -- data shape shifting (changement de forme des datas) -- data reshaping avec pandas -- data reshaping avec les schémas, que ce soient les schémas d'une database - ou bien les librairies de schémas d'une structure de données. - -Avant, on utilisait uniquement les databases pour faire du data reshaping. -La database etait vue comme cela : - -- PostgreSQL as a data processing engine, -- sqlite as a data processing engine. - -Aujourd'hui, on utilise plutôt des librairies de validation de schémas (pydantic, JSON schema, -XML schema...) - -Le chargement des données (**L**\ oad) ----------------------------------------- - -- mettre ces données dans un storage (dans ce contexte, une database nosql - de type mongodb). - -.. note:: concernant le storage, un serveur mongo n'est pas gourmand en ressources - et reste dans le cadre d'outil "low tech" à mon avis. - - On peut aussi utiliser une base nosql qui ne soit pas un serveur - (comme pour sqlite dans le monde des databases relationnelles). -.. note:: Chacune de ces étapes fait aujourd'hui l'objet d'un pipeline - indépendant. - Par exemple, dans le "data understanding", la question de l'extraction - d'information représente de nombreux pipelines. diff --git a/actes-princiers/src/actes_princiers/pipelines/xml_processing/nodes.py b/actes-princiers/src/actes_princiers/pipelines/xml_processing/nodes.py index 2cb74b2..20ceda8 100755 --- a/actes-princiers/src/actes_princiers/pipelines/xml_processing/nodes.py +++ b/actes-princiers/src/actes_princiers/pipelines/xml_processing/nodes.py @@ -4,7 +4,7 @@ from typing import Dict from lxml import etree -from actesdataset import XMLDataSet +from actesdataset import EtreeXMLDataSet logger = logging.getLogger(__name__) @@ -16,7 +16,7 @@ def transform(source_doc: etree._ElementTree, xlststylesheet: str) -> str: return str(xslt_transformer(source_doc)) -def parse_xml_collection(datasets: Dict[str, XMLDataSet], param: str) -> Dict[str, XMLDataSet]: +def parse_xml_collection(datasets: Dict[str, EtreeXMLDataSet], param: str) -> Dict[str, EtreeXMLDataSet]: "node function entry point, performs batch processing" output_datasets = dict() for dataset_filenamestem, dataset in datasets.items(): @@ -26,7 +26,7 @@ def parse_xml_collection(datasets: Dict[str, XMLDataSet], param: str) -> Dict[st output_source_doc = transform(dataset.get_source_doc(), param) # set dataset's output filepath output_filepath = dataset.get_filepath().replace("01_raw", "02_intermediate") - output_xmldataset = XMLDataSet(output_filepath) + output_xmldataset = EtreeXMLDataSet(output_filepath) output_xmldataset.set_source_doc(output_source_doc) output_datasets[dataset_filenamestem] = output_xmldataset # let's create subfolders now, if they don't exist diff --git a/actes-princiers/src/actesdataset.py b/actes-princiers/src/actesdataset.py index 62e8c0c..213c1d7 100644 --- a/actes-princiers/src/actesdataset.py +++ b/actes-princiers/src/actesdataset.py @@ -4,14 +4,17 @@ from typing import Dict, Any from pathlib import Path from lxml import etree +from bs4 import BeautifulSoup from kedro.io import AbstractDataSet, DataSetError from kedro.framework.session import KedroSession logger = logging.getLogger(__name__) - + + +# FIXME hériter de abc (cf dans le code de kedro) class XMLDataSet: - "lxml.etree._ElementTree loader" + "Abstract base class for an XML dataset loader" def __init__(self, filepath: str) -> None: self._filepath = filepath @@ -31,9 +34,18 @@ class XMLDataSet: def set_source_doc(self, source_doc: str) -> None: "XML source_doc (xml as a string) setter" self.source_doc = source_doc + + def _describe(self) -> Dict[str, Any]: + "kedro's API-like repr()" + return dict(filepath=self._filepath) + +class EtreeXMLDataSet(XMLDataSet): + "XMLDataSet loader with lxml.etree (lxml.etree._ElementTree)" + def _transform_source_doc(self) -> etree._ElementTree: "xml transformer (with element tree)" + self.source_doc = etree.parse(self._filepath) # removing namespace query = "descendant-or-self::*[namespace-uri()!='']" for element in self.source_doc.xpath(query): @@ -44,20 +56,73 @@ class XMLDataSet: def _load(self) -> etree._ElementTree: "kedro's API-like loader" - self.source_doc = etree.parse(self._filepath) self._transform_source_doc() return self.source_doc - + def _save(self, data:str) -> None: "kedro's API-like saver" with open(self._filepath, 'w') as fhandle: fhandle.write(data) - - def _describe(self) -> Dict[str, Any]: - "kedro's API-like repr()" - return dict(filepath=self._filepath) +class BsXMLDataSet(XMLDataSet): + "XMLDataSet loaded with BeautifulSoup" + + def _load(self) -> etree._ElementTree: + "kedro's API-like loader" + self._transform_source_doc() + return self.source_doc + + def _load_soup(self): + """open a xml file and return a BeautifulSoup object""" + with open(self._filepath, 'r', encoding="utf-8") as opening: + xml = BeautifulSoup(opening, 'xml') + return xml + + def _save(self, data:str) -> None: + "kedro's API-like saver" + raise NotImplementedError("This DataSet shall not be saved...") + + def _extract_data(self): + # FIXME -> traitement à déplacer dans le nodes.py + # make_soup -> _load_soup -> soup est déjà chargé + #soup = make_soup(os.path.join(folder, acte)) + # 1.1/ Get all data from XML (9). counter is the id (= numb_acte) + numb = soup.TEI["xml:id"] # /TEI[@xml:id] is always the acte's ID + date_time = soup.msItem.docDate["when"] # YYYY-MM-DD or YYYY-MM date + date = soup.msItem.docDate.text # verbose date + analyse = soup.abstract.p.text # acte's short analysis + ref = soup.msIdentifier.find_all("idno", {"n": "2"}) + # //sourceDesc//msIdentifier/idno[@n='2'] is the doc id inside the + # archive box or the page number inside a manuscript (see _create_doc) + # warning: the analysis may not have been written yet, + # which would result in List Index Out of Range Error. Hence : + if len(ref) > 0: # there is an analysis + ref_acte = ref[0].text + else: # there is no analysis + ref_acte = "NS" + prod_place = soup.find_all("placeName", {"type": "production_place"})[0].text + # //sourceDesc//msIdentifier/idno[@n='1'] is always the + # archive box or manuscript collection id + doc = soup.msIdentifier.find_all("idno", {"n": "1"})[0] + type_diplo = soup.body.div["subtype"] + diplo_state = soup.body.div["type"] + + # 2/ Make the data list + actes.append({ + "num_acte": counter, + "filename": numb, + "date_time": date_time, + "date": date, + "prod_place_acte": place_query[0], + "analysis": analyse, + "doc_acte": doc_query[0], + "ref_acte": ref_acte, + "state_doc": state_query[0], + "diplo_type_acte": diplo_query[0] + }) + + class XMLDataSetCollection(AbstractDataSet): """Stores instances of ``XMLDataSet`` implementations to provide ``_load`` and ``_save`` capabilities. @@ -76,15 +141,15 @@ class XMLDataSetCollection(AbstractDataSet): attr_error_msg = str(self._describe()) raise AttributeError(f"Object {attr_error_msg} has no attribute named : 'datasets'") - def _load(self) -> dict[str, XMLDataSet]: + def _load(self) -> dict[str, EtreeXMLDataSet]: "kedro's API loader" self.datasets = dict() for filepath in sorted(self._folderpath.glob("*.xml")): - self.datasets[filepath.stem] = XMLDataSet( + self.datasets[filepath.stem] = EtreeXMLDataSet( filepath=str(filepath)) return self.datasets - def _save(self, datasets: dict[str, XMLDataSet]) -> None: + def _save(self, datasets: dict[str, EtreeXMLDataSet]) -> None: "kedro's API saver" for stemfilename, dataset in datasets.items(): dataset._save(dataset.get_source_doc())