The cleanest way to glue the generated Flask application code (Swagger-Codegen) to implement the backend

I have:

  • library that makes [stuff]
  • swagger API definition, which is roughly equal to # 1 with slight differences, to clearly display the REST service
  • a jar application created using Swagger-Codegen, for example, leads to python controller functions about one to one C # 1.

My intention is that the flash application (all generated code) should only process the display of the actual REST api and parameter parsing in order to comply with the API specification encoded in swagger. After any parsing of the parameters (again, the generated code), it should go directly to my (not created) backend.

My question is, what is the best way to connect them by manually editing the generated python / flask code? (Feedback on my design or the details of the formal design template that will do this will be great too, I'm new to this space).

Fresh from the generator, I get python functions such as:

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 :) 

On the backend, I have my actual logic:

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

What is the correct way to get create_task() to call create_task_backend() ?

Of course, if I make changes to my swagger specification, I will have to manually update the non-generated code; however, there are many reasons why I can regenerate my API (say, add / refine MyTaskTypeFromSwagger class or skip checking for git generated code in general), and if I need to manually edit the generated API code, then all these changes are deleted with each recreation.

Of course, I could script using simple grammar, for example. Pyparsing; but so far this is my first time with this problem, it seems that it has already been widely resolved!

+13
source share
4 answers

The following approach worked for me:

  • created three directories:

    • src is for my code,
    • src-gen for the generated code,
    • codegen in which I put a script that generates a server along with a few tricks.
  • I copied all the templates (available in the swagger assembly) to codegen/templates and edited controller.mustache to reference src/server_impl so that it can use my own code. For editing, the template language is used, therefore it is common. However, it is not perfect (I would change a few naming conventions), but it does its job. So first add to controller.mustache :

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

then add return 'do some magic!' instead of return 'do some magic!' the following:

 return {{classname}}_impl.{{operationId}}({{#allParams}}{{paramName}}{{^required}}=None{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) 
  • Written by:
    • src has a server_impl directory.
    • It creates a symbolic link so server_impl can be imported as a Python module
 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 
+5
source

I was tempted to use swagger-codegen before and ran into the same riddle. Everything is fine until you update the specification. Although you can use custom templates, it just seemed like a lot of overhead and maintenance when all I want is the first design API.

I ended up using connexion , which uses the swagger specification to automatically handle routing, marshaling, validation, etc. Connexion is flask-based, so you don’t have to worry about switching frameworks or anything else, you just get the advantage that parts of your application will be automatically processed from swagger instead of supporting automatically generated code.

+4
source

While I'm working on this, doing the assembly in these steps

  • run codegen
  • sed-script generated code to fix trivial things like namespaces
  • manually edit the files so that instead of returning 'do some magic' (thats the string all returned endpoints of the controller) they simply call the corresponding function in my "backend"
  • use git format-patch to patch the previous changes, so when I generate the code again, the assembly can automatically apply the changes.

That way I can add new endpoints, and I only need to manually process calls on my server ~ once. Instead of using patch files, I could do it directly by writing a py-parsing grammar for the generated code and using the processed generated code to create calls on my backend ... it will take more time, so I did it all quickly hack.

This is far from optimal, I will not mark it as accepted, because I hope that someone will offer a real solution.

+1
source

The workflow I came to.

The idea is to generate the code and then extract the swagger_server package into the project directory. But keep the controllers that you code separately in a separate directory or (like me) in the root of the project and combine them with the ones generated after each generation using git merge-files . Then you need to inject your fresh code controllers into swagger_server/controllers , i.e. before starting the server.

 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 

So, the workflow is as follows:

  1. Generate code, copy swagger_server to your project directory, completely overwrite existing
  2. Backing up controller.py and controller.py.common from the project root
  3. git merge-file controller.py controller.py.common swagger_server/controllers/controller.py
  4. Make swagger_server/controllers/controller.py new common ancestor, so copy it to controller.py.common , overwrite the existing one

Feel free to automate all this with a shell script, i.e.

 #!/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 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 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 
0
source

Source: https://habr.com/ru/post/1270863/


All Articles