Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Synthèse - API post #736

Open
camillemonchicourt opened this issue Sep 18, 2019 · 23 comments
Open

Synthèse - API post #736

camillemonchicourt opened this issue Sep 18, 2019 · 23 comments

Comments

@camillemonchicourt
Copy link
Member

Il est souhaité ajouter la possibilité de poster directement dans la synthèse depuis une application tierce grâce à une API post sécurisée et authentifiée.

@gildeluermoz
Copy link
Contributor

Ceci existait en V1
Il y aura qq sujets à traiter en parrallèle avec des choix à faire comme
faut-il créer un enregistrement dans gn_commons.t_modules générique du genre 'import direct depuis l'API'. Idem pour la source et le dataset.
En gros ces données seront orphelines et ne seront généralement pas rattachées à des métadonnées; Toutes les intégrités (FK), il y en a beaucoup, devront être soit vérifiées soit utilisées des valeurs par défaut, soit rester à null (dataset, source, module, nomenclatures, observateurs)
règles pour l'uuid ?
Les comportements retenus pour remplir (ou pas) ces FK doivent-ils être paramétrables pour adapter le fonctionnement de l'API à chacun des contextes.

@camillemonchicourt
Copy link
Member Author

Oui exact.
Comme pour tous les autres imports de données externes, il y a différents pré-requis à respecter au niveau des métadonnées notamment.

@joelclems
Copy link
Contributor

Avec amandine on propose une function SQL qui permet d'inserer/actualiser des lignes dans la synthèse à partir d'une vue ou d'une table

voir
dc0e3ef

gn_synthese.import_row_from_table(
  select_col_name varchar,
  select_col_val varchar,
  table_name varchar
)

où l'on actualise les lignes de la synthèse correspond aux lignes de la selection de table_name filtrée par col_name = col_val

Si une ligne de la selection est présente dans la synthèse c'est une mise à jour, sinon c'est un ajout.

La fonction python import_from_table permet l'appel de la fonction sql depuis python.

Il reste à faire une fonction pour gérer les suppressions.

Testé avec le module monitoring (où les delete se font en trigger)

@camillemonchicourt
Copy link
Member Author

Version 2.4.0 : Ajout de fonctions SQL d'insertion de données dans la Synthèse (gn_synthese.import_json_row() et gn_synthese.import_row_from_table()) et de la fonction Python associée (import_from_table(schema_name, table_name, field_name, value)) pour l'API permettant de poster dans la Synthèse.

@joelclems
Copy link
Contributor

joelclems commented Sep 30, 2020

Je propose une réflexion sur l'api POST synthèse. C'est un peu long...

L'api POST synthèse

L'api synthèse serait conçue pour un import ponctuel et dynamique de données, en création ou modification et pour 1 ligne à la fois.

Le but ici est de donner les moyens de synchroniser les données d'une application tierce avec la synthèse de GéoNature (et de rendre possible l'intégration des données dans la synthèse quand on a pas d'accès à la base de GN en écriture)

Les Prérequis

Il faut satisfaire certains prérequis pour pouvoir envoyer des données depuis une application tierce vers la synthèse.

Module

  • On laisse id_module à NULL

Utilisateur de l'api POST

  • donner des droits en écriture pour le module synthèse

Medias ???

  • on prend l'id_table_location correspondant à la table gn_synthese.synthese
  • à faire dans un second temps
  • envoyer les médias dans une requête séparée (comme ce qui est fait avec les modules occtax ou monitoring):
    • création du ou des medias
    • association par le champ uuid_attached_row

UUIDs

  • unique_id_sinp uuid et unique_id_sinp_grp uuid
  • l'application tierce doit être en mesure de fournir un identifiant unique pour ses données
    • le but ici est d'éviter de créer ces identifiants dans GéoNature et d'éviter tout problème de duplication
  • unique_id_sinp_grp uuid peut être laissé à NULL s'il n'y a pas de notion de regroupement (données à plats par exemple)

JDD

  • une application peux avoir plusieurs JDD, déjà existant ou non
    • Avoir les JDD correspondants déjà présent dans la table gn_meta.t_datasets.
    • Au besoin, créer le JDD avec le module métadonnées.
    • JDD communiqué à l'api par UUID

Source

  • Avoir la source correspondante dans la table gn_synthese.t_sources (optionnel?)
    • pour pouvoir retrouver l'origine des données
    • et dans certain cas avoir le liens dans la données dans l'application source depuis la fiche de la synthèse GN.
    • comment saisir / modifier les source facilement ? api ? interface ?

Les données post data

Les données nécessaires pour la synthèse sont:

  • id_source :

  • uuid_dataset:
    on laisse au soin de l'utilisateur de l'api de bien renseigner ces données.

  • unique_id_sinp uuid : créé par l'application tierce

  • unique_id_sinp_grp uuid: ...

  • les champs de type nomenclatures :

    • renseigner ici les code de nomenclature:
      • l'application doit être en mesure de faire les correspondances entre la nomenclature GN et ses données.
      • comment pouvoir faciliter cette partie ???
    • Documenter à minima:
      • quels champs peuvent être optionnels et quels sont les valeurs par défaut
      • renvoyer vers la doc du SINP pour le reste
  • les observateurs:

    • passer par le champs texte
    • il y a peut être un moyen de faire le liens avec les utilisateurs de GN et de retrouver les id_role ??????
      • difficile à priori de trouver une solution robuste (test sur le nom_complet ???)
  • id_digitiser:

    • faire l'impasse
    • ou bien renseigner l'id_role de l'utilisateur qui effectue l'api POST synthèse
  • les données à calculer en fonction de la position (altitude) doivent elle être dans les post data ? ou calculée par trigger ?

  • et toutes les autres .... (pas de difficultées identifiées pour l'instant, mais il y a en aura sûrement)

Traitement des données en backend

  • vérifier et valider les données reçues par la requête

  • récupérer id_table_location correspondant à la synthese

  • utiliser la fonction existante gn_synthese.import_json_row qui va insérer / modifier les lignes dans la synthèse une par une

Documenter

Exemples d'utilisations

  • en python avec requests
  • en bash avec curl
  • autres...

@TheoLechemia
Copy link
Member

Salut Joel,
Merci pour ce défrichage. Voilà mes retours/propositions:

Module:

  • ne pas remplir. La donnée ne vient pas d'un module GeoNature

Authentification:

  • Oui il faut avoir un id/mdp pour poster dans l'API. On utilise déjà ce mécanisme pour intéragir avec l'API Userhub

Média:

  • peut être dans un second temps ?

UUID:

  • Oui ok pour que ce soit fourni pas l'appli tierce. Attention il n'y a pas de NOT NULL dans GN sur ce champs.
    Pour unique_id_sinp_grp je le remplirai que s'il est fourni. Selon les données source, il n'y a pas forcement de notion de regroupement.

JDD:

  • Oui, le créer en amont. Il faudra utilisé l'id_dataset dans le POST

Source:

  • Je ferais plutôt évoluer t_sources pour lui ajouter la possibilité de dire que ça vient d'une appli tierce, et que c'est intégré via une API, plutôt que de faire une table t_applications.

Table t_applications:

  • je ne suis pas sûr de voir son utilité. Mais peut être que je manque quelquechose

Nomenclatures
Je partirai du principe qu'il faut échanger des code nomenclatures (ceux du SINP). A l'API de GeoNature de faire recoller ses ID internes (fonction ref_nomenclature.get_id_nomenclature(<code_type>, <code_item>) ).

Observateurs

  • Oui en texte au moins dans un 1er temps. A voir si des applications tierces utilisent UsersHub, alors on pourrait imaginer un mécanisme plus évolué

Pour une API de modification des données là par contre je ne sais pas trop. Quid des données entrée via un module GeoNature qui pourraient être modifié dans le Synthese par l'API et pas dans leurs tables sources ?

@joelclems
Copy link
Contributor

joelclems commented Oct 1, 2020

Merci théo pour ces retours.

Pour ton dernier point concernant la modification.
Si l'uuid fournie par l'api existe déjà dans la synthèse, c'est un modification.
Sinon c'est une création.
Pour cela on a la fonction gn_synthese.import_row_from_table qui gère ça très bien (utilisée dans le module monitoring)

La table t_application permettrait de reguler l'id_source.
On ne renseignerai pas l'id_source directement dans les post_data de l'api.
Le post_data contiendraient un champs application_code qui permettrai de faire le lien avec l'id_source.
Si cette table est bien renseignée, il n'y a pas de risque de confusion.

@jpm-cbna
Copy link
Contributor

jpm-cbna commented Oct 1, 2020

@joelclems je suis assez d'accord avec @TheoLechemia concernant l'évolution de la table synthese.t_sources plutôt que l'ajout d'une nouvelle table t_applications. Si l'objectif de cette table est d'avoir une seule clé d'entrée (UUID ou code) a passer à l'API pour retrouver ensuite les valeurs id_source et id_dataset, peut être pourrait on plutôt lier entre elle les tables t_sources et t_datasets qui pour moi sont relativement liées ?

Sinon, j'ai travaillé sur un format d'échange de données qui permette d'importer de gros jeu de données dans GeoNature. Cela peut peut être te servir pour l'API.
Pour résoudre le problème de dépendance entre Synthese, Jeu de données, Cadre d'acquisition et Source, j'ai cherché à utiliser les champs "code" (Ex. gn_metat.t_datasets.dataset_shortname). Mais il n'y en a pas de disponible dans toutes les tables. Du coup, j'ai utilisé les champs "nom" ou "libellé" dans ce dernier cas.
Je n'ai pas utilisé les UUID car les fichiers doivent pouvoir être vérifié facilement par une personne.
Mais pour une API comme la tienne, qui devrait être utilisé par des scripts, je pense qu'il faut privilégier l'utilisation des champs de type UUID.

@joelclems
Copy link
Contributor

J'ai modifié le texte avec les remarques.
On part sur une application qui peut avoir plusieurs jdd (les échanges se font par l'uuid)
L'utilisateur de l'api doit être en mesure de fournir les bonne valeurs de l'uuid du dataset et de l'id_source

Ca a l'air très complet le liens sur l'échange de données.
Je réfléchit aussi à comment l'aplication pourrait envoyer ses JDD à GéoNature (voir même les element pour renseigner t_sources)

A voir comment blinder l'api pour ne pas écrire n'importe où
(par exemple si la ligne à un id_module, elle n'est pas sensée être modifiée par l'api)

@camillemonchicourt
Copy link
Member Author

Concernant l'API Post de la Synthèse, je pense qu'il faut s'appuyer sur les réflexions qui ont été faites sur le module IMPORT, ainsi que les réflexions initiées pour les échanges automatisés de données entre instances de GeoNature : #789

@camillemonchicourt
Copy link
Member Author

Un premier point général à considérer est le fait qu'en postant directement dans l'API, on déroge en partie à un principe de GeoNature où toute donnée dans la Synthèse a sa donnée brute par ailleurs dans GeoNature.
Dans notre cas où l'on va poster des données formatées pour la synthèse, cela n'a pas forcément de sens de les stocker brut par ailleurs, mais c'est à prendre en compte.
Car la Synthèse est censée être calculable et regénérable à tout moment.
Si on poste des données seulement dans la Synthèse, cela ne serait plus le cas.
Et cela est renforcé si certaines données sont calculées et stockées uniquement dans la Synthèse lors du POST.

Par exemple, si on s'appuie sur ce qui a été fait dans IMPORT, il serait pertinent de pouvoir paramétrer si l'on calcule ou pas les UUID_SINP et les altitudes si elles ne sont pas fournis lors du POST. Pour les UUID, si il ne sont pas fournis depuis la BDD source, il peut être souhaitable de les générer dans GeoNature. Sinon pas de validation possible notamment.
Voir aussi ce que l'on fait si une nomenclature n'est pas renseignée ou que l'on fournit une valeur de nomenclature qui n'existe pas dans GeoNature. On renseigne la valeur par défaut de la nomenclature définie dans la BDD GeoNature j'imagine ?

Je ne raisonnerai pas non plus en "applications" qui postent, c'est plus large. Source me semble mieux en effet.
La notion de "source" permet actuellement surtout d'indiquer dans quelle table est rangée dans GN la donnée source brute d'une donnée présente dans la synthèse.
Si on l'étend pour lister aussi des sources externes, à voir comment bien le préciser dans la table. Avec un booleen indiquant si c'est une source interne ou externe ?

Pour les nomenclatures, je suis OK pour privilégier les codes. Cependant si l'outil source n'en dispose pas, à voir si on ne permet pas de poster le libellé de la nomenclature.

Si on doit réconcilier des personnes ou des organismes, alors il faudrait passer par des UUID selon moi.
Je mettrai en effet de côté pour le moment de récupérer les id_role et id_digitiser.

Pour s'authentifier je me demande si on ne pourrait pas passer par une clé, plutôt qu'un token utilisateur ? Définir dans une table de GN des URL et leur clé. Si c'est la bonne URL avec la bonne clé qui poste, alors on accepte le POST. Je ne sais pas si c'est intéressant ou pas comme solution.

@lepontois
Copy link

Salut tous le monde,
Je vous apporte mon point de vue sans doute perfectible car encore très frais sur le sujet.

Avant tout, je voulais vous partager ma vision de cette API pour être sûr que l'on parle de la même chose:
L'API "Synthèse" doit permettre l'intégration de données externes dans GN de la façon la plus souple et ouverte possible. Libre au développeur utilisant l'API de s'approcher ou non du format de données. Toutes données supplémentaires doivent être formatées dans un jsonb (géré côté application tierce). Tout champ non renseigné aura sa valeur par défaut.
Le développeur peut s'appuyer intégralement sur la base de données GN ou avoir sa propre base en parallèle (au développeur de gérer une double écriture s'il le juge utile).
A minima, une donnée envoyée à l'API doit avoir une date, un observateur, une localisation, un taxon et être associé à un jeu de données.
Voyez-vous des choses à compléter ?

Sur ce constat, je trouve intéressante et rassurante cette notion d'une table synthèse recalculable.
Il faudrait alors que l'écriture via l'API se fasse sur une table dédiée (avec un champ identifiant la source) qui alimenterait dans un second temps la synthèse par des triggers (tel que c'est pour occtax). On ne serait donc plus sur une table temporaire qui serait intégrée dans la synthèse (via gn_synthese.import_json_row) puis supprimé. Fonctionnement qui, en première lecture, me pose question sur la gestion de la modification des données.

Concernant les nomenclatures, soit l'application tierce peuple ses listes en demandant les valeurs possibles à GN, soit c'est un autre champ qui doit alors être inscrit dans un jsonb.
Pour les JDD. Favoriser dans un premier temps de créer le JDD correspondant en amont sur GN (et/ou ajouter une notion de JDD par défaut). Peut-être voir dans une seconde version la possibilité de créer des JDD directement depuis des applis tierces (je ne pense pas que ce soit prioritaire).

Sur les médias, c'est peut-être encore à murir mais je pense que le besoin va vite émerger (surtout sur le volet app nomade).

Je trouve ce système de clé attribuée à une application intéressant pour autoriser la communication avec l'API. Ça force à "enregistrer" son application dans GN. Mais est-ce que ce ne doit pas être couplé avec l'authentification utilisateur ? (qui permettrait de récupérer des droits côté GN ?)
Il faut également s'assurer de son fonctionnement avec des applications autres que des applis web (app sans url - app nomade / plugin qgis ...).

Si je devais soulever des questions précédemment actées, n'hésitez pas à me le faire savoir. Je ne sais pas vraiment quel est le niveau d'avancement du projet et je ne tiens pas à remettre en cause des choix déjà faits.
Mon but est de vous apporter une vision utilisateur car au PNP nous serons sans aucun doute amenés à nous appuyer sur cette API. Nous y portons un fort intérêt.
Bien à vous !

@jpm-cbna
Copy link
Contributor

jpm-cbna commented Oct 2, 2020

Concernant les droits d'accès de l'API, comme le suggère @camillemonchicourt l'utilisation d'une clé ou jeton (à distinguer du couple login/mot de passe) est préférable. La solution JWT est parfaitement adaptée à ce cas d'usage. Il me semble d'ailleurs qu'il y a des pull-request sur le sujet #662. De plus, cela n'utilise pas de cookie et est donc compatible avec les applis mobiles et surement avec Qgis car le jeton peut être transmis dans les entête HTTP ou comme paramètre (query string) de la requête.

@joelclems
Copy link
Contributor

Merci Ludovic pour ces remarques. Le sujet est toujours en cours de réflexion, et toutes les contributions sont les bienvenues.

  • L'idée d'une table dédiée à l'import et qui alimenterai la synthèse dans un second temps me parait bonne, et répondrai aux questions problème signalés par @camillemonchicourt dans le post précédent.
    • la fonction synthese.import row from_table pourrait gérer les données en cretation / modification moyennant quelques modifications:
      • (prise en compte de la date pour ne pas gérer toutes les lignes de la table dédié à l'import)

@DonovanMaillard
Copy link
Contributor

DonovanMaillard commented Oct 2, 2020

Bonjour,

Ma première idée en lisant tout ça, même si je n'ai pas tous les tenants et aboutissants du projet, serait de faire passer ce mécanisme via le module d'import et pas en direct dans la synthèse.

La synthèse devrait être recalculable comme le dit camille, mais aussi et surtout, je pense que les données sont toujours destinées à être modifiées :

  • calcul des altitudes,
  • calcul ou non des uuid,
  • mise à jour des nomenclatures,
  • mises à jour du taxref

Stocker les données dans un format brut (comme le fait gn_imports dans son schéma archive) permettrait de garder systématiquement la donnée brute sans aucune info modifiée/recalculée. Ca permettrait de conserver l'idée d'une synthèse qui ne fait que synthétiser les données réparties ailleurs dans la base. Et de conserver le mécanisme de la t_source actuelle, à savoir la source=module d'import (+ id_import). Pour terminer sur la lisibilité, on saurait que les données de la synthèse proviennent soit d'un module de saisie, soit du module d'import pour les données venant de l'extérieur (que ca vienne en api, en fichier csv ou autre ne devrait pas forcement changer le cheminement de la donnée importée).

@TheoLechemia
Copy link
Member

Juste quelques remarques en mémo pour le prochain point au tel :

  • Synthese recalculable: oui sur le principe je comprend. Mais attention à l'usine à gaz... Duplication de la données, rajout de triggers supplémentaires etc... Même si on duplique la données, j'éviterais au max de rajouter de nouveaux triggers, qui comme on l'a vu nous pose des problème pour les insertions de gros volumes de données. A voir si l'API Synthese a cette vocation (je ne pense pas).
  • D'un point de vue technique @joelclems , pour la vérification des champs posté par l'appli tierce, je regarderai du côté de ce qu'a fat @jbrieuclp dans occtax, avec l'utilisation de Marshmallow, qui permet d'avoir un contrôle assez avancé des données entrantes.

@joelclems
Copy link
Contributor

En résumé de la réunion de tout à l'heure et pour définir la suite

Les grandes lignes

  • api pour un nombre restreint de données .
  • écriture directe dans la synthèse, pas de table intermédiaire, les données sont déjà dans les applis sources
  • une ligne dans gn_synthese.t_sources par source / application

Etape 1

  • API avec toutes les métadonnées renseignées au préalable,
  • utilisateurs en texte,
  • au plus simple pour démontrer le fonctionnement

SQL

  • ajout d'une table gn_synthese.t_source_complements (ou t_sources_api)

    • id_role (utilisateur qui peut écrire dans la synthèse pour cette source)
    • option générer uuid si null (false)
    • option generer altitude si null (false)
  • ? Comment définir les JDD utilisables par l'utilisateur de l'API pour la source?

    • champs dans .t_source_complements ?
    • table en plus ?
    • utilisation de l'existant ?
    • on laisse libre (ou à faire plus tard) ?

Backend

  • Création d'une api UPSERT

    • vérification des données entrantes (Marshmallow)
    • gestion / vérification des droits et conformité du trio (id_role, id_source, id_dataset)
    • update ou insert (Marshmallow, schema.load() ?)
    • renvoie l'uuid ou la ligne de la synthèse crée / modifiée
    • gestion des erreurs
  • Création d'une api DELETE

    • depuis uuid ou (entity_source_pk_value, id_source)
    • gestion des erreurs
      • données non conformes (préciser quels champs)
      • cd_nom non présent dans TaxRef

Améliorations

  • journalisation de la synthèse
  • pouvoir demander la dernière date d'action pour une source
    • pour que l'application puisse fournir seulement les dernières données à traiter
  • medias
  • JDD fournis par l'application (attention aux duplications) ?
  • Authentification JWT (au besoin pour les appli mobiles sans BDD qui veulent publier direct dans GN?)

les ++

  • premier import, gros volumes de données
    • ...

@DonovanMaillard
Copy link
Contributor

DonovanMaillard commented Oct 13, 2020

J'ai bien en tête que ca diffère un peu de l'idée vue cet aprem, mais pour répondre à la question :

    Comment définir les JDD utilisables par l'utilisateur de l'API pour la source?
    champs dans .t_source_complements ?
    table en plus ?
    utilisation de l'existant ?
    on laisse libre (ou à faire plus tard) ?

Vu qu'en quelque sorte, cette API a vocation "d'externaliser des modules de saisie" serait-il intéressant d'imaginer :

  • ne pas toucher du tout à t_sources, on garde l'actuel sans complément
  • créer une table (dans synthèse ou commons?) t_module_api ou t_module_complement
    -> qui liste les applis externes qui ont le droit de poster via l'API, définir ce qui est actif ou non etc
    -> permet de générer 1 source par appli externe (via un trigger sur t_module_api) dans la t_sources comme si elles constituaient des modules de saisie
    -> permet de définir le calcul des uuid, altitudes etc à ce niveau, ou toute info utile
    -> permet de récupérer ces "module_api" dans le module métadonnées pour l'association aux JDD existants, au même titre que si les applis externes étaient des modules de saisie internes de l'instance

@camillemonchicourt
Copy link
Member Author

Pour moi, en terme de concept et de vocabulaire ce ne sont pas des modules ni des applications qui vont poster dans la Synthèse mais bien des sources. Des sources externes.
Je suis pas trop emballé par l'idée de les lister comme des modules.

Une table t_sources_api me semble bien.

@lepontois
Copy link

Merci Joël pour ce résumé très clair,

J'aurais quelques questions pour ma compréhension,

  • Quelle est l'utilité de l'id_role dédié à une source ?
  • Ne peut-on pas seulement contrôler que la source est déclarée sur GN ?
    Cette question car si j'ai bien compris, une source aura forcément un rôle unique associé (genre de 1-1). Je ne suis pas certain que ça apporte beaucoup niveau sécurité.

Pour le lien source / jdd, j'y vois une table cor_source_dataset car plusieurs JDD pourraient être alimentés par une même source (en remplacement de cor_module_dataset -> élargissement de cette table à des sources plutôt que des modules ?).
Sélection du JDD gérée du coté de l'application externe -> Penser à permettre la récupération de la liste des JDD disponible pour une source à travers l'API (GET).

J'y pense que maintenant, mais la déclaration d'une source devrait se faire via une page d'admin. Car on n'est pas certains de nos droits d'écriture en base.
On y retrouverait le rattachement aux JDD, et la configuration pour la génération des UUID et du calcul d'altitude (et + ?).
Ça n'a pas été abordé hier mais c'était peut-être intuitif (je voulais m'en assurer).

@camillemonchicourt
Copy link
Member Author

Le fait de limiter les JDD par source ne me paraît pas forcément une nécessité, du moins pas dans un premier temps.

@joelclems
Copy link
Contributor

@ludovic l'id_role dans la table permettrai de faire le lien entre l'utilisateur et les sources (1-n)
c'est pour s'assurer que cet utilisateur n'intervienne que pour des source auxquelles il est rattaché.
(on peut imaginer le cas ou l'on aurait deux utilisateurs de l'api post et ou on voudrait cloisonner les sources)

pour les jdd je serai plutot pour une table cor_source_dataset ( le pendant de cor_module_dataset pour les sources)

on en a pas parlé mais effectivement il faudrait avoir une interface d'administration pour pouvoir créer / gérer les sources

@DonovanMaillard
Copy link
Contributor

Bonjour,

Est-ce que l'API Post vers la synthèse a été implémentée ou terminée ?

La question de pose d'utiliser cette solution pour poster les données via le module d'imports, en simplifiant son backend.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Development

No branches or pull requests

8 participants