将生成的 Flask 应用程序代码 (Swagger-Codegen) 粘合到后端实现的最干净的方法
cleanest way to glue generated Flask app code (Swagger-Codegen) to backend implementation
我有:
- 一个库[东西]
- 一个 swagger API 定义,它大致是 #1,有细微差别以干净地映射到 REST 服务
- 一个烧瓶应用程序使用 Swagger-Codegen 生成了 #2 - 例如导致 python 控制器功能与 #1 大致一对一。
我的意图是 flask 应用程序(所有生成的代码)应该只处理实际 REST api 的映射和参数解析以匹配以 swagger 编码的 API 规范。在任何参数解析(同样是生成的代码)之后,它应该直接调用我的(非生成的)后端。
我的问题是,如何在不手动编辑生成的 python/flask 代码的情况下最好地将它们连接起来? (关于我的设计的反馈,或实现此目的的正式设计模式的详细信息也很棒;我是新手 space)。
刚从生成器中取出,我最终得到了 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(比如,add/refine MyTaskTypeFromSwagger
class,或者跳过检查 git 生成的代码完全没有),如果我必须手动编辑生成的 API 代码,那么每次重新生成时所有这些编辑都会被吹走。
当然,我可以用 ~ 简单的语法编写脚本,例如。解析;虽然这是我第一次遇到这个问题,但它似乎已经得到广泛解决!
我很想用 swagger-codegen
和 运行 进入同一个难题。在您更新规范之前,一切都很好。尽管您可以使用自定义模板,但这似乎需要大量开销和维护,而我首先想要的是设计 API。
我最终改用了 connexion,它使用 swagger 规范来自动处理路由、封送处理、验证等。Connexion 是基于 flask 构建的,因此您无需担心切换框架或任何事情,您将受益于 swagger 自动处理应用程序的某些部分,而不必维护自动生成的代码。
现在我正在通过按照这些步骤进行构建来解决这个问题
- 运行 代码生成器
- sed-script 生成的代码来修复命名空间等琐碎的东西
- 手动编辑文件,这样他们就不会 returning
'do some magic'
(即所有生成的控制器端点 return 的字符串),而是简单地在我的 'backend'
- 使用
git format-patch
为前面的更改打补丁,这样当我重新生成代码时,构建可以自动应用更改。
因此,我可以添加新的端点,而且我只需要手动编写一次对后端的调用。我可以通过为生成的代码编写 py-parsing 语法并使用解析的生成代码创建对我的后端的调用来直接执行此操作,而不是使用补丁文件......这会花费更长的时间所以我快速完成了这一切破解。
这远非最佳,我不会将其标记为已接受,因为我希望有人能提供真正的解决方案。
以下方法对我有用:
创建了三个目录:
src
- 对于我的代码,
src-gen
为 swagger 生成的代码,
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}}({{#allParams}}{{paramName}}{{^required}}=None{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}})
- 脚本:
src
有一个 server_impl
目录。
- 它创建了一个符号 link 以便
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
我得出的工作流程。
思路是生成代码,然后解压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.py
和controller.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
按照@MrName 的建议使用connexion。
我首先开始将它与 codegen 一起使用。
openapi-generator generate -i ../myapi.yaml -g python-flask -o .
这会生成一个带有打开api 服务器的目录。
|- openapi_server\
|--controllers\
|--mytag._controller.py\
|--openapi\
|--my-api.yaml\
如果您将标签添加到 api 规范中的路径,则会为每个标签创建一个单独的标签名-controller.py。对于每个 operationId,都会生成一个函数。
但是,一旦设置完成,connexion 就可以处理对 api 规范的更新。
如果我使用 operationId=new_func 添加到 openapi/my-api.yaml 的新路径,那么我可以将 new_func() 添加到现有控制器。我不会丢失现有的服务器逻辑(但我仍然会在以防万一之前对其进行备份)。我还没有尝试对现有路径进行彻底的更改。
我有:
- 一个库[东西]
- 一个 swagger API 定义,它大致是 #1,有细微差别以干净地映射到 REST 服务
- 一个烧瓶应用程序使用 Swagger-Codegen 生成了 #2 - 例如导致 python 控制器功能与 #1 大致一对一。
我的意图是 flask 应用程序(所有生成的代码)应该只处理实际 REST api 的映射和参数解析以匹配以 swagger 编码的 API 规范。在任何参数解析(同样是生成的代码)之后,它应该直接调用我的(非生成的)后端。
我的问题是,如何在不手动编辑生成的 python/flask 代码的情况下最好地将它们连接起来? (关于我的设计的反馈,或实现此目的的正式设计模式的详细信息也很棒;我是新手 space)。
刚从生成器中取出,我最终得到了 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(比如,add/refine MyTaskTypeFromSwagger
class,或者跳过检查 git 生成的代码完全没有),如果我必须手动编辑生成的 API 代码,那么每次重新生成时所有这些编辑都会被吹走。
当然,我可以用 ~ 简单的语法编写脚本,例如。解析;虽然这是我第一次遇到这个问题,但它似乎已经得到广泛解决!
我很想用 swagger-codegen
和 运行 进入同一个难题。在您更新规范之前,一切都很好。尽管您可以使用自定义模板,但这似乎需要大量开销和维护,而我首先想要的是设计 API。
我最终改用了 connexion,它使用 swagger 规范来自动处理路由、封送处理、验证等。Connexion 是基于 flask 构建的,因此您无需担心切换框架或任何事情,您将受益于 swagger 自动处理应用程序的某些部分,而不必维护自动生成的代码。
现在我正在通过按照这些步骤进行构建来解决这个问题
- 运行 代码生成器
- sed-script 生成的代码来修复命名空间等琐碎的东西
- 手动编辑文件,这样他们就不会 returning
'do some magic'
(即所有生成的控制器端点 return 的字符串),而是简单地在我的 'backend' - 使用
git format-patch
为前面的更改打补丁,这样当我重新生成代码时,构建可以自动应用更改。
因此,我可以添加新的端点,而且我只需要手动编写一次对后端的调用。我可以通过为生成的代码编写 py-parsing 语法并使用解析的生成代码创建对我的后端的调用来直接执行此操作,而不是使用补丁文件......这会花费更长的时间所以我快速完成了这一切破解。
这远非最佳,我不会将其标记为已接受,因为我希望有人能提供真正的解决方案。
以下方法对我有用:
创建了三个目录:
src
- 对于我的代码,src-gen
为 swagger 生成的代码,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}}({{#allParams}}{{paramName}}{{^required}}=None{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}})
- 脚本:
src
有一个server_impl
目录。- 它创建了一个符号 link 以便
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
我得出的工作流程。
思路是生成代码,然后解压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
到你的项目目录,完全覆盖已有的 - 从项目根 备份
git merge-file controller.py controller.py.common swagger_server/controllers/controller.py
- 创建
swagger_server/controllers/controller.py
新的共同祖先,因此将其复制到controller.py.common
,覆盖现有的
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
按照@MrName 的建议使用connexion。
我首先开始将它与 codegen 一起使用。
openapi-generator generate -i ../myapi.yaml -g python-flask -o .
这会生成一个带有打开api 服务器的目录。
|- openapi_server\
|--controllers\
|--mytag._controller.py\
|--openapi\
|--my-api.yaml\
如果您将标签添加到 api 规范中的路径,则会为每个标签创建一个单独的标签名-controller.py。对于每个 operationId,都会生成一个函数。
但是,一旦设置完成,connexion 就可以处理对 api 规范的更新。 如果我使用 operationId=new_func 添加到 openapi/my-api.yaml 的新路径,那么我可以将 new_func() 添加到现有控制器。我不会丢失现有的服务器逻辑(但我仍然会在以防万一之前对其进行备份)。我还没有尝试对现有路径进行彻底的更改。