web-dev-qa-db-fra.com

moyen le plus propre de coller le code d'application généré Flask (Swagger-Codegen) pour la mise en œuvre du backend

J'ai:

  1. une bibliothèque qui fait [Stuff]
  2. une définition d'API swagger, qui est à peu près n ° 1 avec des différences mineures pour mapper proprement à un service REST
  3. a flask application générée # 2 à l'aide de Swagger-Codegen - par exemple, les résultats dans python fonctionne à peu près en tête-à-tête avec # 1.

Mon intention est que l'application flask (tout le code généré) ne gère que le mappage de cette REST api et analyse des paramètres pour correspondre aux spécifications de l'API codées dans Swagger). Après toute analyse de paramètres (encore une fois, le code généré), il devrait appeler directement mon backend (non généré).

Ma question est, comment mieux les connecter sans éditer à la main le code python/flask généré? (Les commentaires sur ma conception ou les détails d'un modèle de conception formel qui accomplit cela seraient également très bien; je suis nouveau dans cet espace).

Fraîchement sorti du générateur, je me retrouve avec python fonctions comme:

def create_task(myTaskDefinition):
    """
    comment as specified in swagger.json
    :param myTaskDefinition: json blah blah blah
    :type myTaskDefinition: dict | bytes
    :rtype: ApiResponse
    """
    if connexion.request.is_json:
        myTaskDefinition = MyTaskTypeFromSwagger.from_dict(connexion.request.get_json())
    return 'do some magic!' # swagger codegen inserts this string :)

Sur le backend, j'ai ma logique actuelle:

def create_task_backend(myTaskDefinition):
    # hand-coded, checked into git: do all the things
    return APIResponse(...)

Quelle est la bonne façon d'amener create_task() à appeler create_task_backend()?

Bien sûr, si j'apporte des modifications de rupture à ma spécification de swagger, je devrai mettre à jour manuellement le code non généré; Cependant, il y a de nombreuses raisons pour lesquelles je veux recréer mon API (par exemple, ajouter/affiner la classe MyTaskTypeFromSwagger, ou ignorer la vérification dans git le code généré) et si je dois modifier manuellement le généré Code API, puis toutes ces modifications sont supprimées à chaque re-génération.

Bien sûr, je pourrais l'écrire avec une grammaire ~ simple, par exemple. pyparsing; mais bien que ce soit ma première fois avec ce problème, il semble probable qu'il ait déjà été largement résolu!

17
mike

L'approche suivante a fonctionné pour moi:

  • créé trois répertoires:

    • src - pour mon code,
    • src-gen pour le code généré par swagger,
    • codegen dans lequel j'ai mis un script qui génère le serveur avec quelques astuces.
  • J'ai copié tous les modèles (disponibles dans la version swagger) dans codegen/templates et édité le controller.mustache se référer à src/server_impl, afin qu'il puisse utiliser mon propre code. L'édition utilise le langage du modèle, il est donc générique. Ce n'est pas encore parfait (je changerais quelques conventions de dénomination) mais ça fait l'affaire. Donc, ajoutez d'abord à controller.mustache:

from {{packageName}}.server_impl.controllers_impl import {{classname}}_impl

puis ajoutez au lieu de return 'do some magic!' le suivant:

return {{classname}}_impl.{{operationId}}({{#allParams}}{{paramName}}{{^required}}=None{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}})
  • Scénario:
    • src a un server_impl répertoire.
    • Il crée un lien symobolique pour que server_impl peut être importé en tant que module python
cd ../src-gen/swagger_server/
ln -s ../../src/server_impl/
cd ../../codegen
Java -jar swagger-codegen-cli.jar generate  \
-i /path_to_your_swagger definition.yaml \
-l python-flask \
-o ../src-gen \
-t ./templates
cd ../src-gen/
python3 -m swagger_server
7
Dudi

J'ai été tenté d'utiliser swagger-codegen avant et a rencontré la même énigme. Tout va bien jusqu'à ce que vous mettiez à jour la spécification. Bien que vous puissiez utiliser des modèles personnalisés, cela semblait être beaucoup de frais généraux et de maintenance, alors que tout ce que je veux, c'est une première API de conception.

J'ai fini par utiliser connexion à la place, qui utilise la spécification swagger pour gérer automatiquement le routage, le marshaling, la validation, etc. bénéficiera simplement du traitement automatique de parties de votre application à partir de Swagger au lieu d'avoir à maintenir du code généré automatiquement.

5
MrName

Le workflow auquel je suis arrivé.

L'idée est de générer le code, puis d'extraire swagger_server package dans le répertoire du projet. Mais séparément, conservez les contrôleurs que vous codez dans le répertoire séparé ou (comme je le fais) dans la racine du projet et fusionnez avec les générés après chaque génération en utilisant git merge-files. Ensuite, vous devez injecter votre nouveau code de contrôleur dans swagger_server/controllers, c'est-à-dire avant de démarrer le serveur.

project
+-- swagger_server
|   +-- controllers
|       +-- controller.py <- this is generated
+-- controller.py <- this is you are typing your code in
+-- controller.py.common <- common ancestor, see below
+-- server.py <- your server code, if any

Le workflow est donc le suivant:

  1. Générez du code, copiez swagger_server dans votre répertoire de projet, remplacez complètement les fichiers existants
  2. Sauvegarde controller.py et controller.py.common depuis la racine du projet
  3. git merge-file controller.py controller.py.common swagger_server/controllers/controller.py
  4. Faire swagger_server/controllers/controller.py nouvel ancêtre commun, copiez-le dans controller.py.common, écraser l'existant

N'hésitez pas à automatiser tout cela avec le script Shell, c'est-à-dire.

#!/bin/bash
# Swagger generate server and client stub based on specification, them merge it into the project.
# Use carefully! Commit always before using this script!
# The following structure is assumed:
# .
# +-- my_client
# |   +-- swagger_client
# +-- my_server
# |   +-- swagger_server
# +-- merge.sh <- this script

read -p "Have you commited the project??? " -n 1 -r
if [[ ! $REPLY =~ ^[Yy]$ ]]; then echo 'Commit first!'; exit 1; fi

rm -rf swagger-python-client
rm -rf swagger-python-server

Java -jar swagger-codegen-cli.jar generate -i swagger.yaml -l python -o swagger-python-client 
Java -jar swagger-codegen-cli.jar generate -i swagger.yaml -l python-flask -o swagger-python-server

# Client - it's easy, just replace swagger_client package
rm -rf my_client/swagger_client
cp -rf swagger-python-client/swagger_client/ my_client

# Server - replace swagger_server package and merge with controllers
rm -rf my_server/.backup
mkdir -p my_server/.backup
cp -rf my_server/swagger_server my_server/.backup


rm -rf my_server/swagger_server
cp -rf swagger-python-server/swagger_server my_server


cd my_server/swagger_server/controllers/
files=$( ls * )
cd ../../..

for f in $files; do

    # skip __init__.py
    if [ -z "$flag" ]; then flag=1; continue; fi
    echo "======== $f"

    # initialization
    cp -n my_server/swagger_server/controllers/$f my_server/$f.common
    cp -n my_server/swagger_server/controllers/$f my_server/$f


    # real merge
    cp -f my_server/$f my_server/.backup/
    cp -f my_server/$f.common my_server/.backup/
    git merge-file my_server/$f my_server/$f.common my_server/swagger_server/controllers/$f
    cp -f my_server/swagger_server/controllers/$f otmini-repo/$f.common

done

rm -rf swagger-python-client
rm -rf swagger-python-server

1
Anton Ovsyannikov

Pour l'instant, je travaille autour de cela en faisant la construction de ces étapes

  1. exécuter le codegen
  2. sed-script le code généré pour corriger des choses triviales comme les espaces de noms
  3. éditez les fichiers à la main, de sorte qu'au lieu de retourner 'do some magic' (c'est la chaîne renvoyée par tous les points de terminaison du contrôleur généré), ils appellent simplement une fonction correspondante dans mon 'backend'
  4. utilisation git format-patch pour effectuer un patch des modifications précédentes, de sorte que lorsque j'ai recréé du code, la génération puisse appliquer automatiquement les modifications.

Ainsi, je peux ajouter de nouveaux points de terminaison et je n'ai qu'à coder manuellement les appels vers mon backend ~ une fois. Au lieu d'utiliser des fichiers correctifs, je pourrais le faire directement en écrivant une grammaire py-parsing pour le code généré et en utilisant le code généré analysé pour créer les appels vers mon backend ... cela prendrait plus de temps, donc j'ai fait tout cela comme un rapide pirater.

C'est loin d'être optimal, je ne vais pas marquer cela comme accepté car j'espère que quelqu'un offrira une vraie solution.

1
mike

Utilisez connexion comme l'a suggéré @MrName.

J'ai d'abord commencé à l'utiliser avec codegen.

openapi-generator generate -i ../myapi.yaml -g python-flask -o .

Cela génère un répertoire avec le serveur openapi.

  |- openapi_server\
      |--controllers\
           |--mytag._controller.py\
      |--openapi\
           |--my-api.yaml\

Si vous ajoutez des balises à vos chemins dans la spécification api, un tagname-controller.py distinct est créé pour chaque balise. Pour chaque operationId, une fonction est générée.

Cependant, une fois ceci configuré, la connexion peut gérer les mises à jour de la spécification api. Si j'ajoute un nouveau chemin à openapi/my-api.yaml, avec un operationId = new_func, alors je peux ajouter new_func () au contrôleur existant. Je ne perds pas la logique du serveur existant (mais je le sauvegarderais avant juste au cas où). Je n'ai pas encore essayé de modifications radicales des chemins existants.

0
intotecho