-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add experimental import tool and documentation (#504)
- Loading branch information
1 parent
52298c5
commit 8033c92
Showing
6 changed files
with
513 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
``` | ||
|
||
Où `<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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.