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

Posted

技术标签:

【中文标题】将生成的 Flask 应用程序代码(Swagger-Codegen)粘合到后端实现的最简洁方法【英文标题】:cleanest way to glue generated Flask app code (Swagger-Codegen) to backend implementation 【发布时间】:2018-01-22 15:12:08 【问题描述】:

我有:

    做 [Stuff] 的库 一个大张旗鼓的 API 定义,大致是 #1,与干净地映射到 REST 服务的细微差别 使用 Swagger-Codegen 生成 #2 的烧瓶应用程序 - 例如导致 python 控制器功能与 #1 大致一对一。

我的意图是烧瓶应用程序(所有生成的代码)应该只处理实际 REST api 的映射和参数解析,以匹配以 swagger 编码的 API 规范。在任何参数解析(同样,生成的代码)之后,它应该直接调用我的(非生成的)后端。

我的问题是,如何在不手动编辑生成的 python/flask 代码的情况下最好地连接这些? (关于我的设计的反馈,或者实现这一点的正式设计模式的细节也很棒;我是这个领域的新手)。

刚从生成器中获得,我最终得到了以下 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 :)

在后端我有我的实际逻辑:

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

create_task() 调用create_task_backend() 的正确方法是什么?

当然,如果我对我的 swagger 规范进行重大更改,无论如何我都必须手动更新非生成的代码;但是有很多原因我可能想要重新生成我的 API(例如,添加/优化 MyTaskTypeFromSwagger 类,或者根本跳过将生成的代码签入 git),如果我必须手动编辑生成的 API 代码,然后所有这些编辑都会随着每次重新生成而被吹走。

当然,我可以用 ~ 简单的语法编写这个脚本,例如。解析;但是虽然这是我第一次遇到这个问题,但它似乎已经被广泛解决了!

【问题讨论】:

注意,我确实看到了***.com/questions/29252817/…,但是似乎通过简单地从生成中免除一些文件,我会丢失自动化方法 cmets、参数反序列化等内容。我想保证 python (生成的)控制器中的入口点始终与 swagger 同步。 【参考方案1】:

以下方法对我有用:

创建了三个目录:

src - 对于我的代码, src-gen 为招摇生成的代码, codegen 我在其中放置了一个生成服务器的脚本以及一些技巧。

我将所有模板(在 swagger 构建中可用)复制到 codegen/templates 并编辑 controller.mustache 以引用 src/server_impl,因此它可以使用我自己的代码。编辑使用模板语言,因此它是通用的。它仍然不是完美的(我会更改一些命名约定),但它可以完成工作。所以,先加controller.mustache

from packageName.server_impl.controllers_impl import classname_impl

然后添加以下内容而不是 return 'do some magic!'

return classname_impl.operationId(#allParamsparamName^required=None/required#hasMore, /hasMore/allParams)
脚本: src 有一个server_impl 目录。 它创建了一个符号链接,以便 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

【讨论】:

太棒了!只是在“packageName”中缺少一个左括号 @Dudi 感谢您的解决方案。对于不需要的参数,在调用operationId 时不应在参数中传递None,因此要添加到controller.mustache 中的第二行应为:return classname_impl.operationId(#allParamsparamName#hasMore, /hasMore/allParams) hasMore 已从 openapi-generator 中删除(请参阅github.com/OpenAPITools/openapi-generator/pull/7882),因此为了使此答案从今天开始起作用,您需要将#hasMore, /hasMore 替换为^-last, /-last跨度> 【参考方案2】:

我之前很想使用swagger-codegen,但遇到了同样的难题。在您更新规范之前,一切都很好。尽管您可以使用自定义模板,但这似乎需要大量开销和维护,而我想要的只是设计优先的 API。

我最终改用了connexion,它使用 swagger 规范来自动处理路由、编组、验证等。Connexion 是建立在烧瓶上的,所以你不需要担心切换框架或任何东西,你只需从 swagger 中自动处理应用程序的某些部分,而不必维护自动生成的代码。

【讨论】:

我看到 SwaggerCodegen 在后台使用 Connexion;似乎是一个非常简单的用法(x-swagger-router-controlleroperationId)。我认为使用 SwaggerCodegen 是有利的;知道它比“原始”连接有什么好处吗? 直接在 swagger 中连接我的手工编码函数看起来很简单(就像 swagger-codegen 所做的那样)。但是,我没有找到一个很好的例子来展示 connexion 为生成的参数验证提供了什么。您可以指出我的任何资源吗?谢谢! 别担心,它隐藏在文档中:connexion.readthedocs.io/en/latest/… 我玩过这个。它按预期工作,但是我放弃了很多,即生成所有样板控制器和响应。我在这里赞成你的评论,以感谢你的想法和时间来回应,但我真的觉得应该有更好的东西。 Swagger 代码生成器看起来很流行且功能强大;它真的有〜零的思想份额吗?【参考方案3】:

现在我正在通过这些步骤进行构建来解决这个问题

    运行代码生成 sed-script 生成的代码来修复一些琐碎的东西,比如命名空间 手动编辑文件,这样他们就无需返回'do some magic'(即所有生成的控制器端点返回的字符串),而是在我的“后端”中调用相应的函数 使用git format-patch 对前面的更改进行修补,这样当我重新生成代码时,构建可以自动应用更改。

因此,我可以添加新的端点,而我只需手动编写对后端的调用 ~ 一次。而不是使用补丁文件,我可以通过为生成的代码编写一个py解析语法并使用解析的生成代码来创建对我的后端的调用来直接做到这一点......这需要更长的时间,所以我很快就完成了这一切破解。

这远非最佳,我不会将其标记为已接受,因为我希望有人会提供真正的解决方案。

【讨论】:

【参考方案4】:

我来到的工作流程。

思路是生成代码,然后将swagger_server包解压到项目目录下。但单独地,将您正在编码的控制器保存在单独的目录中或(像我一样)在项目根目录中,并使用git merge-files 在每一代之后将它们与生成的控制器合并。然后您需要将新的控制器代码注入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

所以工作流程如下:

    生成代码,复制swagger_server到你的项目目录,完全覆盖现有的 从项目根目录备份 controller.pycontroller.py.common git merge-file controller.py controller.py.common swagger_server/controllers/controller.py 使 swagger_server/controllers/controller.py 成为新的共同祖先,因此将其复制到 controller.py.common,覆盖现有的

随意使用 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

【讨论】:

【参考方案5】:

按照@MrName 的建议使用connexion。

我第一次开始将它与 codegen 一起使用。

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

这会生成一个带有 openapi 服务器的目录。

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

如果您在 api 规范中为路径添加标签,则会为每个标签创建一个单独的 tagname-controller.py。为每个 operationId 生成一个函数。

但是,一旦设置好,connexion 就可以处理对 api 规范的更新。 如果我向 openapi/my-api.yaml 添加一个新路径,并使用 operationId=new_func,那么我可以将 new_func() 添加到现有控制器。我不会丢失现有的服务器逻辑(但我仍然会在之前备份它以防万一)。我还没有尝试对现有路径进行彻底的改变。

【讨论】:

以上是关于将生成的 Flask 应用程序代码(Swagger-Codegen)粘合到后端实现的最简洁方法的主要内容,如果未能解决你的问题,请参考以下文章

Flask 学习-51.Flask-RESTX 生成 Swagger 文档 详细教程

Flask 学习-52.Flask-RESTX 生成 Swagger 文档带上Authorization认证

Flask 学习-46.Flask-RESTX 生成 Swagger 文档入门教程

Flask API 文档管理与 Swagger 上手

Python Flask:从 Swagger YAML 到 Google App Engine?

Swagger-Codegen:如何将所有文件合并到一个文件中以进行客户端代码生成