Flask-RESTful 是一个 Flask 扩展,用于快速构建 REST API。本文基于 Flask-RESTful 0.3.6 版本。
安装
安装:
1
| pipenv install flask-restful
|
Minimal App:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| from flask import Flask
from flask_restful import Resource, Api
app = Flask(__name__)
api = Api(app)
class HelloWorld(Resource):
def get(self):
return {'hello': 'world'}
api.add_resource(HelloWorld, '/')
if __name__ == '__main__':
app.run(debug=True)
|
运行:
集成
与 Flask App 集成
1
2
3
4
5
| from flask import Flask
from flask_restful import Api
app = Flask(__name__)
api = Api(app)
|
使用 factory function 时,可调用 flask_restful.Api
的 init_app
方法:
1
2
3
4
5
6
7
8
9
10
| from flask import Flask
from flask_restful import Api
api = Api()
def create_app():
app = Flask(__name__)
api.init_app(app)
app = create_app()
|
与 Blueprint 集成
1
2
3
4
5
6
7
8
| from flask import Blueprint, Flask
from flask_restful import Api
app = Flask(__name__)
bp_api = Blueprint('bp_api', __name__, url_prefix='/api')
api = Api(bp_api)
app.register_blueprint(api_bp)
|
路由
Flask-RESTful 使用了 Flask 中的 即插视图(Pluggable Views),可参考董伟明老师《Python Web 开发实践》P40。
Flask-RESTful 中的路由以资源为中心,视图类继承自 flask_restful.Resource
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| from flask import Flask, request
from flask_restful import Resource, Api
app = Flask(__name__)
api = Api(app)
todos = {}
class TodoSimple(Resource):
def get(self, todo_id):
return {todo_id: todos[todo_id]}
def put(self, todo_id):
todos[todo_id] = request.form['data']
return {todo_id: todos[todo_id]}
api.add_resource(TodoSimple, '/<string:todo_id>')
if __name__ == '__main__':
app.run(debug=True)
|
解析请求参数
基本用法
Flask-RESTful 的请求解析器会在 2.0 版本被删除。
一个 demo 如下:
1
2
3
4
5
6
| from flask_restful import reqparse
parser = reqparse.RequestParser()
parser.add_argument('rate', type=int, help='Rate cannot be converted')
parser.add_argument('name')
args = parser.parse_args()
|
首先新建解析器 reqparse.RequestParser
对象,然后使用 add_argument
方法添加参数,最后在路由方法中使用 parse_args
方法解析参数。
默认情况下,参数都是可选参数,在解析器中声明但未传入的参数值为 None
。
Argument 常见参数
add_argument
接受 reqparse.Argument
实例,或传递给 reqparse.Argument
构造函数的参数。
构造函数常用参数有:
name
参数名称type
参数类型required
必传参数,默认非必传default
默认值dest
添加到返回对象中的参数名action
默认 “store”,设置为 “append” 即可将同名参数接收为一个列表choices
参数可选值列表trim
去掉参数头尾空格,默认不处理nullable
允许参数为 None,默认不允许help
参数的简短描述,在参数无效时替换默认的错误消息在响应中返回,替换默认的错误消息。可以使用 “{error_msg}” 来包含默认的错误消息。location
从何种 flask.Request
对象属性中获取参数,默认为 “json” 和 “value”。多个 location 的参数会被组成为 MultiDict 对象。
自定义参数检查
可以传递一个函数给 type
,在函数中对参数进行检查和处理。若参数不符合要求可抛出 ValueError
,ValueError
的错误消息将在响应中返回。
1
2
3
4
5
6
7
8
9
| parser = reqparse.RequestParser()
def odd_number(value):
if value % 2 == 0:
raise ValueError("Value is not odd")
return value
parser.add_argument('OddNumber', type=odd_number)
parser.parse_args()
|
函数也可接收两个参数——参数值和参数名。方便于在错误消息中引用参数名。
1
2
3
4
| def odd_number(value, name):
if value % 2 == 0:
raise ValueError(f"The parameter '{name}' is not odd. You gave us the value: {value}")
return value
|
定义输出字段
基本用法
首先定义输出格式 dict,dict 中字段类型从设置为 fields中类型(会自动转换输出值)。其键是要呈现的对象上的属性或键的名称,其值是将格式化并返回该字段的值的类。
然后使用 marshal_with
装饰器绑定到请求方法上。最后在请求方法中返回数据库对象或字典即可。
1
2
3
4
5
6
7
8
9
10
11
12
| from flask_restful import Resource, fields, marshal_with
resource_fields = {
'name': fields.String,
'address': fields.String,
'date_updated': fields.DateTime(dt_format='rfc822'),
}
class Todo(Resource):
@marshal_with(resource_fields, envelope='resource')
def get(self, **kwargs):
return db_get_todo() # 这里的对象可以是数据库对象,或者 dict
|
上面这个例子中,我们假定返回的自定义的数据库对象,有 name
、address
和 date_updated
属性。对象中的其他属性将不会被返回。envelope
参数指定可选的关键字参数以包装结果输出。
装饰器 marshal_with
相当于:
1
2
3
| class Todo(Resource):
def get(self, **kwargs):
return marshal(db_get_todo(), resource_fields), 200
|
fields 常用类型
String
(default=None, attribute=None) 字符串
Float
(default=None, attribute=None) 浮点数
Boolean
(default=None, attribute=None) 布尔型
Integer
(default=0, **kwargs) 整数
FormattedString
(src_str) 格式化字符串,自动替换 format 语法中的参数。例如:
1
2
3
4
5
6
7
8
| fields = {
'name': fields.String,
'greeting': fields.FormattedString("Hello {name}")
}
data = {
'name': 'Doug',
}
marshal(data, fields)
|
DateTime
(dt_format=‘rfc822’, **kwargs) UTC时间,可以输出为 ‘rfc822’ 或者 ‘iso8601’ 格式。
Url
(endpoint=None, absolute=False, scheme=None, **kwargs) url类型,类似 Flask 的 url_for 函数
Fixed
(decimals=5, **kwargs) 固定小数位数的浮点数,用于价格等场景
Price
即 Fixed
Arbitrary
(default=None, attribute=None) 具有任意精度的浮点数
Nested
(nested, allow_null=False, **kwargs) 嵌套类型
List
(cls_or_instance, **kwargs) 列表类型
Raw
(default=None, attribute=None) 提供了一个基本字段类,自定义字段类从该字段类继承。
重命名属性
如果内部字段名称和输出字段名称不同,可以使用 attribute
参数指定内部参数名称。
指定时,可以使用字符串:
1
2
3
4
| fields = {
'name': fields.String(attribute='private_name'),
'address': fields.String,
}
|
使用函数:
1
2
3
4
| fields = {
'name': fields.String(attribute=lambda x: x._private_name),
'address': fields.String,
}
|
嵌套属性:
1
2
3
4
| fields = {
'name': fields.String(attribute='people_list.0.person_dictionary.name'),
'address': fields.String,
}
|
增加默认值
使用 default
参数为输出增加默认值。仅有部分类型支持。
1
2
3
4
| fields = {
'name': fields.String(default='Anonymous User'),
'address': fields.String,
}
|
自定义字段值
自定义字段需要继承 fields.Raw
类并实现format
方法。这对于使用整数存储的标识位字段非常有用。
1
2
3
4
5
6
7
8
9
10
11
12
13
| class UrgentItem(fields.Raw):
def format(self, value):
return "Urgent" if value & 0x01 else "Normal"
class UnreadItem(fields.Raw):
def format(self, value):
return "Unread" if value & 0x02 else "Read"
fields = {
'name': fields.String,
'priority': UrgentItem(attribute='flags'),
'status': UnreadItem(attribute='flags'),
}
|
复杂结构
除了输出简单的一层 k-v 结构之外,也可以输出嵌套结构。
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
| resource_fields = {'name': fields.String}
resource_fields['address'] = {}
resource_fields['address']['line 1'] = fields.String(attribute='addr1')
resource_fields['address']['line 2'] = fields.String(attribute='addr2')
resource_fields['address']['city'] = fields.String
resource_fields['address']['state'] = fields.String
resource_fields['address']['zip'] = fields.String
data = {
'name': 'bob',
'addr1': '123 fake street',
'addr2': '',
'city': 'New York',
'state': 'NY',
'zip': '10468'
}
result = marshal(data, resource_fields)
# result
{
"name": "bob",
"address":{
"line 1": "123 fake street",
"line 2": "",
"state": "NY",
"zip": "10468",
"city": "New York"
}
}
|
地址字段实际上并不存在于数据对象上,但是任何子字段都可以直接从对象访问属性,就好像它们没有嵌套一样。
列表字段
使用 fields.List
输出列表字段。
1
2
3
4
5
6
7
| >>> from flask_restful import fields, marshal
>>> import json
>>>
>>> resource_fields = {'name': fields.String, 'first_names': fields.List(fields.String)}
>>> data = {'name': 'Bougnazal', 'first_names' : ['Emile', 'Raoul']}
>>> json.dumps(marshal(data, resource_fields))
>>> '{"first_names": ["Emile", "Raoul"], "name": "Bougnazal"}'
|
输出列表
有时,我们可能不想要输出 dict,而是要直接输出列表,可以利用 fields.List
和 fields.Nested
配合输出列表。
此时要使用 marshal_with_field
装饰器。
1
2
3
4
5
6
7
8
9
10
11
| resource_fields = {'name': fields.String, 'age': fields.Integer}
resource_list_fields = fields.List(fields.Nested(product_fields))
class ResourceList(Resource):
@marshal_with_field(resource_list_fields)
def get(self):
data = [
dict(name='aaa', age=14),
dict(name='bbb', age=15),
]
return data
|
参考文档