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