J'ai:
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!
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}})
src
a un server_impl
répertoire.server_impl
peut être importé en tant que module pythoncd ../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
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.
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:
swagger_server
dans votre répertoire de projet, remplacez complètement les fichiers existantscontroller.py
et controller.py.common
depuis la racine du projetgit merge-file controller.py controller.py.common swagger_server/controllers/controller.py
swagger_server/controllers/controller.py
nouvel ancêtre commun, copiez-le dans controller.py.common
, écraser l'existantN'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
Pour l'instant, je travaille autour de cela en faisant la construction de ces étapes
'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'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.
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.