成熟的 Web API 框架总有一款 API 文档与之伴随,当前最知名的莫过于支持 Open API 的 Swagger 了。Python 的 Flask 框架支持 Swagger UI 也有几条路子
- Flasgger : 好像是 flask-swagger 的 fork
- flask-swagger: 许久未更新了,不用考虑
- flask-restful-swagger: 到目前也两年未更新了
- Flask-RESTPlus 的 Swagger 特性: 真需要用到 Flask-RESTPlus 就可以用它
单纯用 Flask 构建 API 的话,细数起来也就 Flasgger 比较合适,如果甩开 Flask 而用 FastAPI 的话,就不用操心 Swagger 了,因为 FastAPI 原生的支持 Swagger。
本文中我们将体验如何使用 Flasgger, 关于使用方法,在它源码的 README.md 已经描述的很清楚了。Flasgger 提供了以下几种主要的使用方式
- 用函数的 docstrings 注释
- 使用外部 YAML 文件
- 使用 YAML 文件的字典形式变量描述
- 用 Marshmallow Schemas
用 docstrings 的方式在 IDE 中无法得到语法高亮,写错了也不知道,而且大段的 docstrings 影响阅读有效的代码。用外部的 YAML 文件能实现 API 文档与有效代码的分离。字典形式描述的 YAML 和外部 YAML 文件并没有多大区别,字典变量可以声明到单独的 Python 文件中。
现在讨论使用哪种方式还为时过早,先来瞧瞧以上几种基本的方式的实例(Marshmallow 的方式对代码的侵入性较大, 此处跳过)
Flasgger 得到一个 Swagger UI
用 pip 安装 flasgger
$ pip install flasgger
然后写一个 Flask app main.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
from flask import Flask from flasgger import Swagger app = Flask(__name__) swagger = Swagger(app) @app.route('/', methods=['GET']) def hello(): return "hello world!" if __name__ == '__main__': app.run() |
运行该 main.py 后,就可以打开 http://localhost:5000/apidocs
Swagger UI 里目前是空的,只是启动了一个 Swagger UI 空壳而已。接下来开始看如何在其中加上 API
docstrings 注解 API
直接借用官方的例子,自己想来想去也没觉得有比官方更好的例子。在 main.py 中加一个 API
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
@app.route('/colors/<palette>/') def colors(palette): """Example endpoint returning a list of colors by palette This is using docstrings for specifications. --- parameters: - name: palette in: path type: string enum: ['all', 'rgb', 'cmyk'] required: true default: all definitions: Palette: type: object properties: palette_name: type: array items: $ref: '#/definitions/Color' Color: type: string responses: 200: description: A list of colors (may be filtered by palette) schema: $ref: '#/definitions/Palette' examples: rgb: ['red', 'green', 'blue'] """ all_colors = { 'cmyk': ['cyan', 'magenta', 'yellow', 'black'], 'rgb': ['red', 'green', 'blue'] } if palette == 'all': result = all_colors else: result = {palette: all_colors.get(palette)} return jsonify(result) |
运行它后,再次访问 http://localhost:5000/apidocs,看到下面的样子
可以 Try it out。有兴趣的请直接查看 http://localhost:5000/apispec_1.json 文档的内容
使用外部 YAML 文件
觉得在函数中加一大段注释影响阅读主体代码,或者会一不小心修改到 docstrings 内容的话,可使用外部 YAML 文件。要做的基本就是先把前面 docstring 的内容保存成一个 colors.yml(只对描述作修改) 文件
colors.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
Example endpoint returning a list of colors by palette In this example the specification is taken from external YAML file --- parameters: - name: palette in: path type: string enum: ['all', 'rgb', 'cmyk'] required: true default: all definitions: Palette: type: object properties: palette_name: type: array items: $ref: '#/definitions/Color' Color: type: string responses: 200: description: A list of colors (may be filtered by palette) schema: $ref: '#/definitions/Palette' examples: rgb: ['red', 'green', 'blue'] |
然后 colors 函数去掉 docstring 注释, 加上 @swag_from 装饰来引用 colors.yml 文件
1 2 3 4 5 6 |
from flasgger import swag_from @app.route('/colors/<palette>/') @swag_from('colors.yml') def colors(palette): ... |
重新运行 main.py 后得到一样的结果(描述部分不同)
不想用装饰器的话,还能 docstrings 和 yml 文件结合,写成
1 2 3 4 5 6 |
@app.route('/colors/<palette>/') def colors(palette): """ file: colors.yml """ ... |
但是,为何不用装饰器呢?
@swag_from
目前只支持 yaml 和 yml 文件,以后将会对 json, py 文件的支持。如果想支持用 JSON 文件格式,那需要第三方的包自动转换为 yml 格式。
再进入到字典格式 API 描述之前,我们先来考虑一个问题,如果有两个 API 返回了相同结构的数据,在新的 yml 文件中声明一次就重复了,问题就是多个 API 中如何重用 Swagger Model。
查看 JSON 文档 http://localhost:5000/apispec_1.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
{ "definitions": { "Color": { "type": "string" }, "Palette": { "properties": { "palette_name": { "items": { "$ref": "#/definitions/Color" }, "type": "array" } }, "type": "object" } }, "info": { "description": "powered by Flasgger", "termsOfService": "/tos", "title": "A swagger API", "version": "0.0.1" }, "paths": { "/colors/{palette}/": { "get": { "description": "In this example the specification is taken from external YAML file<br/>", "parameters": [ { "default": "all", "enum": [ "all", "rgb", "cmyk" ], "in": "path", "name": "palette", "required": true, "type": "string" } ], "responses": { "200": { "description": "A list of colors (may be filtered by palette)", "examples": { "rgb": [ "red", "green", "blue" ] }, "schema": { "$ref": "#/definitions/Palette" } } }, "summary": "Example endpoint returning a list of colors by palette" } } }, "swagger": "2.0" } |
请拿这个与 colors.yml 对比理解 yml 中的内容是如何映射到 apispec_1.josn 中的。如果再来一个新的 API 同时引用 colors.yml 会怎么样呢?在 main.py 中加上 color1 函数
1 2 3 4 |
@app.route('/colors1/<palette>/') @swag_from('colors.yml') def colors1(palette): pass |
这时时候 http://localhost:5000/apidocs 可看到两个 APIs
- GET /color/{palette}
- GET /color1/{palette}
再查看 http://localhost:5000/apispec_1.json, 除了 paths
下新增了 color1/{palette}
外, definitions
中的内容与原来一样,所以 Flasgger 是能正确处理 request/response 中用到的 schema 的定义的。可以想见
- 一个 yml 文件可以引用另一个 yml 文件中的
definitions
的 Model 定义 - 不同 API 引用 yml 中的
definitions
最终会被合并到一处
于是我们可以这么实现
- 被共享的 Model 定义放到同一个 yml 文件中,要保证该 yml 会被 @swag_from 引用
- 每个 API 定义自己专享的部分,所用公共 Model 以 schema: $ref: '#/definitions/Palette' 方式引用
如此,这般仍然无法避免产生众多的 yml 文件,于是下一种字典的方式或许是更好的选择
使用字典变量描述
由于字典是 Python 中的变量,也就有了更大的灵活性。比如现在仍然有两个 API, color 和 color1,它们有共享的 Model, 我们建立 apis.py 文件,内容为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
shared_definitions = \ { "Palette": { "type": "object", "properties": { "palette_name": { "type": "array", "items": { "$ref": "#/definitions/Color" } } } }, "Color": { "type": "string" } } color = { "definitions": shared_definitions, "parameters": [ { "name": "palette", "in": "path", "type": "string", "enum": [ "all", "rgb", "cmyk" ], "required": "true", "default": "all" } ], "responses": { "200": { "description": "A list of colors (may be filtered by palette)", "schema": { "$ref": "#/definitions/Palette" }, "examples": { "rgb": [ "red", "green", "blue" ] } } } } color1 = { "definitions": shared_definitions, "parameters": [ { "name": "palette", "in": "path", "type": "string" } ], "responses": { "200": { "schema": { "$ref": "#/definitions/Palette" } } } } |
因为是 Python 代码,可以把公共的代码轻松抽取出来放到一块,如上面的 shared_definitions
main.py 中引用它们
1 2 3 4 5 6 7 8 9 10 |
@app.route('/colors/<palette>/') @swag_from(color) def colors(palette): pass @app.route('/colors1/<palette>/') @swag_from(color1) def colors1(palette): pass |
用字典的方式避免了产生很多的 yml 文件,所有的 API spec 定义可以选择放到同一个或多个 py 中, 它们之间共享内容非常简单。
字典方式相比而言变成了更优的选择。
总结
通过对以上几种用法的体验,最后作一个总结,以供我们选择何种方式作参考
- docstrings 方式,大面积的注解侵入了代码,影响对业务代码的阅读,同时 docstrings 中的内容不能被 IDE 高亮显示
- yml 让 API 描述分离了出来,但 API 多的话会产生同样多的 yml 文件,而且 yml 间共享内容不那么容易
- 字典方式可以把 API spec 放在一个或多个 py 文件中,共享相同的 Model, request, response 都很便利
不管使用以上何种方,都要求我们对 Swagger API(或 Open API) 文档的语法有一定的熟悉。而在 API 函数中使用的 docstring 或 @swag_from 只是帮助我们获得了 URL Path 和 HTTP Method。我们最终想要得到的就是那个 http://localhost:5000/apispec_1.json, 这样的话,完全可以自己编辑 apispec_1.json, 只须 Flask 同时启动一个 Swagger UI 来浏览该 apispec。
Python 是无类型的语言,如果想要 Swagger 以某种机制来获得参数和返回类型的话,那应该是 type hints.
链接:
- FLASK PYTHON: CREATING REST APIS AND SWAGGER DOCUMENTATION
- Working with APIs using Flask, Flask-RESTPlus and Swagger UI
本文链接 https://yanbin.blog/flask-integrate-with-swagger-ui/, 来自 隔叶黄莺 Yanbin Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
[…] 本文中我们将体验如何使用 Flasgger, 关于使用方法,在它源码的 README.md 已经描述的很清楚了。Flasgger 提供了以下几种主要的使用方式 阅读全文 >> […]
[…] Flask 实现 Swagger UI 文档功能,基本上要让Flask配合 Flasgger, 所以写了篇Flask 应用集成 Swagger UI 。然而不断的 Google 过程中偶然间发现了FastAPI这么一款集成了 Swagger UI […]
[…] Flask 实现 Swagger UI 文档功能,基本上要让Flask配合 Flasgger, 所以写了篇Flask 应用集成 Swagger UI 。然而不断的 Google 过程中偶然间发现了FastAPI这么一款集成了 Swagger UI […]
[…] 实现 Swagger UI 文档功能,找到的实现方式基本上是 Flask + Flasgger, 记录在 Flask 应用集成 Swagger UI。然而不断的 Google 过程中偶然发现了一款集成了 Swagger UI 的比 Flask 还好的 […]
简明扼要
[…] Flask 实现 Swagger UI 文档功能,基本上要让 Flask 配合 Flasgger, 所以写了篇 Flask 应用集成 Swagger UI。然而不断的 Google 过程中偶然间发现了 FastAPI 这么一款集成了 Swagger UI […]