Skip to content

Commit

Permalink
Add experimental import tool and documentation (#504)
Browse files Browse the repository at this point in the history
  • Loading branch information
florimondmanca authored Oct 26, 2022
1 parent 52298c5 commit 8033c92
Show file tree
Hide file tree
Showing 6 changed files with 513 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/fr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ Table des matières :

- [Démarrage](./demarrage.md) - Un guide pour vous mettre en route.
- [Outils de développement](./outils.md) - Description des outils de développement à disposition.
- [Outils de gestion des organisations et catalogues](./outils-orgas.md) - Description de l'outillage permettant de gérer les organisations et catalogues enregistrés dans l'outil (dépôt de config, import, etc).
- [Opérations](./ops.md) - Un guide concernant l'infrastructure, le déploiement, les environnements, etc.
- [Trucs et astuces](./trucs-et-astuces.md) - Quelques "conseils de pro" qui peuvent vous simplifier la vie.
87 changes: 87 additions & 0 deletions docs/fr/outils-orgas.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Outils de gestion des organisations et catalogues

## Dépôt de configuration

_Aussi appelé "repo de config"_

Lien GitHub : [etalab/catalogage-donnees-config](https://github.com/etalab/catalogage-donnees-config)

Ce dépôt sert à stocker les organisations enregistrées sur l'instance de production [catalogue.data.gouv.fr](https://catalogue.data.gouv.fr) ainsi que leur catalogue au format [TableSchema](https://specs.frictionlessdata.io/table-schema/).

Pour en savoir plus sur son utilisation, consulter le README du dépôt de configuration.

## Importer un catalogue

Un outil expérimental _[lire : testé sommairement, et à modifier ou adapter selon les besoins]_ est disponible pour importer un catalogue.

### Utilisation

L'organisation et le catalogue doivent avoir été créés au préalable.

1. Écrire un fichier de configuration pour l'import à partir de l'exemple [import.config.example.yml](https://github.com/etalab/catalogage-donnees/blob/master/tools/import.config.example.yml).

1. Générer un fichier d'initdata grâce à l'outil :

```bash
python -m tools.import_catalog <CONFIG_PATH> <OUT_PATH>
```

`<CONFIG_PATH>` est le chemin du fichier de configuration écrit à l'instant, et `<OUT_PATH>` un chemin de sortie pour le fichier d'initdata résultant.

1. Tester l'import en local :
* Partir d'une base de données vierge. Par exemple :

```
dropdb catalogage
createdb catalogage
make migrate
```

* Importer les organisations et catalogues à partir du dépôt de configuration. Pour ce faire :
* Dans le `.env`, définir :
```dotenv
APP_CONFIG_REPO_API_KEY=abcd1234 # N'importe quelle valeur
```
* Démarrer le serveur d'API local avec `make serve`.
* Dans le `.env` du dépôt de configuration, définir :
```dotenv
CATALOGAGE_API_URL=abcd1234 # La même valeur que côté catalogage-donnees
```
Puis lancer `make upload`.
* Lancer l'initdata :

```bash
python -m tools.initdata <OUT_PATH>
```

* Démarrer le serveur et vérifier que l'import s'est correctement déroulé.

1. Si et seulement si le comportement local est validé, procéder à l'import en production :
* Remplacer temporairement (sans faire de commit) le fichier d'initdata de la prod `ops/ansible/prod/assets/initdata.yml` par le fichier d'initdata généré.
* Lancer `make ops-initdata env=prod`.
* Remettre le fichier d'initdata de la prod dans son état d'origine (le git diff doit être vierge).
* Vérifier le comportement en production.
### Options notables
* _(Requis)_ `organization_siret` - `str`
SIRET de l'organisation dont il faut peupler le catalogue.

* _(Requis)_ `input_file.path` - `str`

Chemin vers le fichier CSV contenant le catalogue.

Les colonnes du CSV doivent refléter le schéma du catalogue tel que défini dans le dépôt de configuration :

* Champs du schéma communs (`titre`, `description`, etc) ;
* Champs complémentaires.

Les colonnes ne correspondant ni au schéma commun, ni aux champs complémentaires, doivent être supprimées du fichier CSV ou, à défaut, explicitement indiquées dans `ignore_fields` (voir ci-dessous). Toute incohérence avec les champs complémentaires du catalogue en base de données entraînera une erreur.

* `ignore_fields` - `List[str]`

La liste des colonnes ne correspondant ni au schéma commun, ni aux champs complémentaires.
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ addopts =
-rxXs
--cov=server
--cov=tests
--cov=tools
--cov-report=term-missing
--cov-config=.coveragerc
--cov-fail-under=90
Expand Down
93 changes: 93 additions & 0 deletions tests/tools/test_import_catalog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
from pathlib import Path
from textwrap import dedent

import pytest
import yaml

from server.application.catalogs.commands import CreateCatalog
from server.config.di import resolve
from server.domain.catalogs.entities import BoolExtraField
from server.seedwork.application.messages import MessageBus
from tools import import_catalog

from ..factories import CreateOrganizationFactory, CreateTagFactory


@pytest.mark.asyncio
async def test_import_catalog_example(tmp_path: Path) -> None:
csv_path = tmp_path / "catalog.csv"
example_config_path = Path() / "tools" / "import.config.example.yml"
out_path = tmp_path / "initdata.yml"

bus = resolve(MessageBus)

# Simulate an existing organization and catalog
siret = await bus.execute(
CreateOrganizationFactory.build(name="Ministère 1", siret="11004601800013")
)
await bus.execute(
CreateCatalog(
organization_siret=siret,
extra_fields=[
BoolExtraField(
organization_siret=siret,
name="donnees_geoloc",
title="Données géolocalisées",
hint_text="Les données sont-elles géolocalisées ?",
data={"true_value": "Oui", "false_value": "Non"},
)
],
)
)

# Simulate a pre-existing tag.
await bus.execute(CreateTagFactory.build(name="Tag 1"))

csv_path.write_text(
dedent(
"""\
titre;description;mots_cles;nom_orga;siret_orga;id_alt_orga;service;si;contact_service;contact_personne;date_pub;date_maj;freq_maj;couv_geo;url;format;licence;donnees_geoloc
Titre1;Description1;"Tag 1,Tag 2";Ministère 1;11004601800013;;Direction1;SI1;[email protected];[email protected];;2022-10-06;annuelle;aquitaine;;geojson, xls, oracle et shp;etalab-2.0;oui
Titre2;Description2;"Tag 1,Tag 3";Ministère 1;11004601800013;;Direction1;SI2;[email protected];[email protected];;;Invalid;NSP;;Information manquante;etalab-2.0;oui
""" # noqa
)
)

config_path = tmp_path / "catalogue.csv"
config_path.write_text(
example_config_path.read_text().replace("REPLACE_ME", str(csv_path))
)

code = await import_catalog.main(config_path, out_path)
assert code == 0

with out_path.open() as f:
initdata = yaml.safe_load(f)

assert not initdata["organizations"]
assert not initdata["catalogs"]
assert not initdata["users"]
assert [t["params"] for t in initdata["tags"]] == [
# "Tag 1" already existed
{"name": "Tag 2"},
{"name": "Tag 3"},
]

assert len(initdata["datasets"]) == 2

assert all(d["params"]["organization_siret"] == siret for d in initdata["datasets"])

d0 = initdata["datasets"][0]["params"]
assert sorted(d0["formats"]) == ["database", "file_gis", "file_tabular"]
assert d0["geographical_coverage"] == "aquitaine"
assert d0["update_frequency"] == "yearly"
assert "[[ Notes d'import automatique ]]" not in d0["description"]

d1 = initdata["datasets"][1]["params"]
assert d1["geographical_coverage"] == "(Information manquante)"
assert d1["formats"] == ["other"]
assert d1["update_frequency"] is None
assert d1["url"] is None
assert "[[ Notes d'import automatique ]]" in d1["description"]
assert "Format : (Information manquante)" in d1["description"]
assert "Fréquence de mise à jour (valeur originale) : Invalid" in d1["description"]
30 changes: 30 additions & 0 deletions tools/import.config.example.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
input_csv:
path: REPLACE_ME
encoding: utf-8
delimiter: ";"
na_values:
- "Information manquante"
- "NSP"

organization_siret: "11004601800013"

ignore_fields:
- id_alt_orga
- date_pub

formats:
list_map:
"oracle et shp": [database, file_gis]

map:
"xls": file_tabular
"geojson": file_gis

update_frequency:
map:
"aucune": never
"en continu": realtime
"annuelle": yearly

last_updated_at:
format: "%Y-%M-%d" # See: https://docs.python.org/fr/3/library/datetime.html#strftime-and-strptime-format-codes
Loading

0 comments on commit 8033c92

Please sign in to comment.