简体   繁体   English

将生成的Flask应用程序代码(Swagger-Codegen)粘合到后端实现的最简洁方法

[英]cleanest way to glue generated Flask app code (Swagger-Codegen) to backend implementation

I have: 我有:

  1. a library that does [Stuff] 有[资料]的图书馆
  2. a swagger API definition, which is roughly #1 with minor differences to map cleanly to a REST service 醒目的API定义,大约是#1,有一些细微差别,可以清晰地映射到REST服务
  3. a flask app generated #2 using Swagger-Codegen - eg results in python controller functions roughly one-to-one with #1. 一个使用Swagger-Codegen生成的#2烧瓶应用程序-例如,导致#1大致与Python控制器功能一对一。

My intent is that the flask app (all generated code) should only handle mapping that actual REST api and parameter parsing to match the API spec coded in swagger. 我的意图是,flask应用程序(所有生成的代码)应仅处理实际REST api和参数解析的映射,以匹配以大方的方式编码的API规范。 After any parameter parsing (again, generated code) it should call directly over to my (non-generated) backend. 在解析完任何参数(再次生成的代码)之后,它应该直接调用我的(非生成的)后端。

My question is, how best to hook these up withOUT hand-editing the generated python/flask code? 我的问题是,如何最好地不手工编辑生成的python / flask代码? (Feedback on my design, or details of a formal design pattern that accomplishes this would be great too; I'm new to this space). (反馈我的设计,或完成此工作的正式设计模式的细节也很棒;我是这个领域的新手)。

Fresh from the generator, I end up with python functions like: 从生成器开始,我最终得到了python函数,例如:

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 right way to get create_task() to call create_task_backend() ? 什么是使create_task()调用create_task_backend()的正确方法?

Of course if I make breaking changes to my swagger spec I will have to hand-update the non-generated code regardless; 当然,如果我对摇摇欲坠的规格进行了重大更改,则无论如何我都必须手动更新未生成的代码。 however there are many reasons I may want to re-generate my API (say, add/refine the MyTaskTypeFromSwagger class, or skip checking into git the generated code at all) and if I have to hand-edit the generated API code, then all those edits are blown away with each re-generation. 但是,出于多种原因,我可能想重新生成我的API(例如,添加/优化MyTaskTypeFromSwagger类,或者完全跳过git生成的代码),如果我必须手动编辑生成的API代码,则所有每次重新生成时,这些编辑都会被删除。

Of course I could script this with a ~simple grammar in eg. 当然,我可以使用例如〜的简单语法编写脚本。 pyparsing; 剖析 but while this is my first time with this issue, it seems likely it's been widely solved already! 但是,尽管这是我第一次遇到此问题,但似乎已经被广泛解决了!

The following approach worked for me: 以下方法对我有用:

  • created three directories: 创建了三个目录:

    • src - for my code, src对于我的代码,
    • src-gen for the swagger generated code, src-gen生成的代码
    • codegen in which I have put a script that generate the server along with a few tricks. codegen中,我已经把那有几个窍门生成服务器的脚本。
  • I copied all the templates (available in the swagger build) to codegen/templates and edited the controller.mustache to refer to src/server_impl , so it can use my own code. 我将所有模板(可在swagger构建中使用)复制到codegen/templates并编辑controller.mustache以引用src/server_impl ,因此它可以使用我自己的代码。 The editing uses the template language so it is generic. 编辑使用模板语言,因此它是通用的。 Still it is not perfect (I would change a few naming conventions) but it does the job. 它仍然不是完美的(我会更改一些命名约定),但是确实可以。 So, first add to controller.mustache : 因此,首先添加到controller.mustache

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

then add instead of return 'do some magic!' 然后添加而不是return 'do some magic!' the following: 下列:

return {{classname}}_impl.{{operationId}}({{#allParams}}{{paramName}}{{^required}}=None{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}})
  • Script: 脚本:
    • The src has a server_impl directory. src有一个server_impl目录。
    • It creates a symobolic link so that server_impl can be imported as a python module 它创建一个符号链接,以便server_impl可以作为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

I was tempted to use swagger-codegen before and ran into the same conundrum. 以前我很想使用swagger-codegen并遇到相同的难题。 Everything is fine until you update the spec. 一切正常,直到您更新规格。 Although you can use custom templates, this just seemed like a lot of overhead and maintenance, when all I want is a design first API. 尽管您可以使用自定义模板,但是当我想要的只是设计优先的API时,这似乎需要大量的开销和维护。

I ended up using connexion instead, which uses the swagger specification to automatically handle routing, marshaling, validation, etc. Connexion is built on flask, so you would not need to worry about switching frameworks or anything, you will just get the benefit of portions of your application being automatically handled from swagger instead of having to maintain auto-generated code. 我最终使用了connexion ,它使用了swagger规范来自动处理路由,封送处理,验证等。Connexion构建在flask上,因此您不必担心切换框架或其他任何事情,您将获得部分收益可以自动处理所有应用程序,而不必维护自动生成的代码。

For now I am working around this by doing the build in these steps 现在,我正在按以下步骤进行构建,以解决此问题

  1. run the codegen 运行代码
  2. sed-script the generated code to fix trivial stuff like namespaces sed-script生成的代码可修复诸如名称空间之类的琐碎内容
  3. hand-edit the files, so that instead of returning 'do some magic' (thats the string all the generated controller endpoints return) they simply call a corresponding function in my 'backend' 手动编辑文件,以使它们无需返回'do some magic' (即所有生成的控制器端点返回的字符串),而只需在我的“后端”中调用相应的函数
  4. use git format-patch to make a patch of the preceeding changes, so that when i re-generated code the build can automatically apply the changes. 使用git format-patch对先前的更改进行修补,以便在我重新生成代码时,构建可以自动应用更改。

Thus, i can add new endpoints and I only have to hand-code the calls to my backend ~once. 因此,我可以添加新的端点,并且只需要手动将调用编码到后端即可。 Instead of using patch files, i could do this directly by writing a py-parsing grammar for the generated code and using the parsed generated code to create the calls to my backend ... that would take longer so I did this all as a quick hack. 无需使用补丁文件,我可以直接通过为生成的代码编写py解析语法并使用解析的生成的代码来创建对我的后端的调用来直接执行此操作……这将花费更长的时间,因此我很快就做了所有这些骇客。

This is far from optimal, i'm not going to mark this as accepted as I'm hoping someone will offer a real solution. 这远非最佳,我不会将其标记为已接受,因为我希望有人会提供真正的解决方案。

The workflow I came to. 我参加的工作流程。

The idea is to generate the code, then extract swagger_server package to the project directory. 这个想法是生成代码,然后将swagger_server包解压缩到项目目录。 But separately, keep controllers your are coding in the separate directory or (as I do) in the project root and merge them with generated ones after each generations using git merge-files . 但是要分开使用,将您正在编写的控制器保留在单独的目录中,或者(如我所做的那样)在项目根目录中,并在每代之后使用git merge-files 它们与生成的控制器git merge-files Then you need to inject your fresh controllers code into swagger_server/controllers , ie before starting server. 然后,您需要新的控制器代码注入swagger_server/controllers ,即在启动服务器之前。

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 the following: 因此,工作流程如下:

  1. Generate code, copy swagger_server to your project directory, completely overwrite existing 生成代码,将swagger_server复制到您的项目目录,完全覆盖现有文件
  2. Backup controller.py and controller.py.common from project root 从项目根目录备份controller.pycontroller.py.common
  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 existing swagger_server/controllers/controller.py设为新的共同祖先,然后将其复制到controller.py.common ,覆盖现有的

Feel free to automate all of this with shell script, ie 随意使用shell脚本自动化所有这些操作,即

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

Use connexion as @MrName suggested. 建议使用connexion作为@MrName。

I first started using this together with codegen. 我首先开始将其与Codegen一起使用。

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

This generates a directory with the openapi server. 这将使用openapi服务器生成目录。

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

If you add tags to your paths in the api spec, then a separate tagname-controller.py is created for each tag. 如果您将标签添加到api规范中的路径中,则会为每个标签创建一个单独的tagname-controller.py。 For each operationId a function is generated. 对于每个operationId,都会生成一个函数。

However, once this is set up, connexion can handle updates to the api spec. 但是,一旦设置完成,connexion就可以处理对api规范的更新。 If I add a new path to openapi/my-api.yaml, with an operationId=new_func, then I can add new_func() to the existing controller. 如果我使用operationId = new_func将新路径添加到openapi / my-api.yaml,则可以将new_func()添加到现有控制器。 I don't lose the existing server logic (but I would still back it up before just in case). 我不会丢失现有的服务器逻辑(但是为了以防万一,我还是会备份它)。 I haven't tried radical changes to existing paths yet. 我还没有尝试对现有路径进行重大更改。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM