前言
flask框架默认的路由和视图函数映射规则是通过在视图函数上直接添加路由装饰器来实现的,这使得路由和视图函数的对应关系变得清晰,但对于统一的API开发就变得不怎么美妙了,尤其是当路由接口足够多的时候,可读性会变差。flask_restful可以使我们像Django那样统一在一个地方设计所有的API规则。
flask_restful
安装
pip install flask_restful
初始化
# __init__.py
from flask import Flask, current_app
app = Flask(__name__)
app.config['SECRET_KEY'] = '123'
# rest/__init___.py
from flask import Blueprint
from flask_restful import Api
# 创建蓝图
rest = Blueprint("rest", __name__)
flask_api = Api(app=rest)
__all__ = ["rest","api"]
# 加载蓝图所对应的视图上下文
from .api import *
# 设计路由
flask_api.add_resource(ToDo1,'/restful')
flask_api.add_resource(Todo,"/restful1")
# rest/api.py
from flask_restful import Resource, fields, marshal_with, reqparse
parser = reqparse.RequestParser()
class ToDo1(Resource):
def get(self):
# 拷贝一个对象备用
new_parser = parser.copy()
# 添加请求参数和它的验证规则
new_parser.add_argument('n', type=int, help='Rate to charge for this resource,{error_msg}')
# 进行验证,得到通过验证后的字典或者抛出400错误
args = new_parser.parse_args(strict=True)
print(args)
return "OK"
说明:
-
Api类管理着视图函数的注册和相关资源,使用app或蓝图对象对其进行初始化;
-
我们定义一个类继承Resource,通过方法名指定post、get等请求方式的处理逻辑;
-
使用Api.add_resource方法设计api接口,指定路由和视图函数的映射关系;
-
使用RequestParser对象对参数进行验证;
-
核心的对象有Api和Resource,针对其一一分析;
APi对象分析
- 初始化
针对需要使用restful风格的蓝图进行初始化。
test = Blueprint("test", __name__)
api = Api(app=test, prefix='/rest1') # prefix添加url的前缀,如果蓝图也有前缀则拼接
- Api.add_resource
该方法对路由和视图函数进行设计设定。创建了RULE对象,并将其添加到Maps对象中,同时将resource添加到app的view_functions中;
# 参数
resource:视图类
urls:路由url,可以多个指向一个视图类
endpoint:视图函数的标志,默认用类名替代
api.add_resource(Todo,"/restful","/index", endpoint="restful")
请求数据验证
对于收到的每一个请求,如果它带了参数,后台是需要对参数进行验证的,这是一个十分枯燥的工作,RequestParser对象就是帮助我们对参数进行验证的,它可以实现验证方法对验证参数的解耦,是的代码结构趋向统一。
- RequestParser
# 创建一个RequestParser,其会拦截request上下文,但是其内部存储的参数列表对所有的请求都是同一个,因此针对每个请求都需要一个新的对象
parser = reqparse.RequestParser()
class ToDo1(Resource):
def get(self):
# 拷贝一个对象备用
new_parser = parser.copy()
# 向参数列表添加请求参数和它的验证规则,每个参数单独添加
new_parser.add_argument('n', type=int, help='Rate to charge for this resource,{error_msg}')
# 进行验证,得到通过验证后的字典或者抛出400错误
args = new_parser.parse_args(strict=True)
print(args)
return "OK"
# 使用 strict=True 调用 parse_args 能够确保当请求包含你的解析器中未定义的参数的时候会抛出一个异常。
- RequestParser.add_argument
该方法其实是创建了一个Argument对象,其参数对应了Argument的初始化参数
def __init__(self, name, default=None, dest=None, required=False,
ignore=False, type=text_type, location=('json', 'values',),
choices=(), action='store', help=None, operators=('=',),
case_sensitive=True, store_missing=True, trim=False,
nullable=True):
self.name = name # 参数的名字
self.default = default # 如果没有该参数,其默认的值
self.dest = dest # name的别名,解析后该参数的键编程别名
self.required = required # 参数是否可以省略,设置为True时,这个参数是必须的
self.ignore = ignore # 是否忽略参数类型转换失败的情况,默认不忽略
self.location = location # 从请求对象中获取的参数类型,默认从values和json中解析值,但是可以指定'args','form','json',''headers','files'等解析;或指定列表['args','form'],优先解析列表末尾的值
self.type = type # 转换的参数类型,默认转换成str,失败就报错
self.choices = choices # 参数的值的有限集合,不在其中就报错,如[1,2,3]
self.action = action # 如果要接受一个键有多个值的话,可以传入 action='append', 默认是禁止的
self.help = help # 参数无效时可以返回的信息,默认返回错误信息
self.case_sensitive = case_sensitive # 是否区分大小写,默认所有大写转换成小写
self.operators = operators
self.store_missing = store_missing # 缺少参数时是否加默认的值
self.trim = trim # 是否去除参数周围的空白
self.nullable = nullable # 是否允许参数使用空值
new_parser.add_argument('n', type=int, help='msgerr',location=('args',), required=True) # 从参数中获取n,该参数是必须的
- RequestParser.replace_argument和RequestParser.remove_argument
覆盖解析规则和删除解析规则,主要用在当多个api有相同的解析参数时,可以设置共享参数解析;
parser = RequestParser()
parser.add_argument('foo', type=int)
parser_copy = parser.copy()
parser_copy.add_argument('bar', type=int)
parser_copy.replace_argument('foo', type=str, required=True, location='json')
验证方法
Argument对象的type参数指定了参数的验证方法,除了我们python常用的数据类型外,inputs模块预定义了一些验证方法供我们验证。
- input模块
parser_copy.add_argument('bar', type=url) # 验证是否符合url格式
parser_copy.add_argument('bar', type=inputs.regex('^[0-9]+$')) # 自定义正则验证
date:转换成datatime.data对象;
natural:转换类型为自然数;
positive:转换类型为正整数
int_range:输入整数的范围,设置最大最小值
boolean:限制必须为布尔类型;
- 自定义验证方法
input模块提供的方法也是不够用的。很多时候我们需要自定义验证方法:
def ver_num(value, name):name表示参数名
if value % 2 == 0:
raise ValueError("Value is not odd")
return value
parser_copy.add_argument('bar', type=ver_num) # 验证是调用:ver_num("xxx","bar")
# 方法必须有两个参数,第一个value表示参数的值,name表示参数的名字,
# 如果验证不通过,我们就抛出一个ValueError错误。
- RequestParser.parse_args
当调用parse_args时就会按添加时的顺序依次验证请求参数,如果有验证不通过的参数,调用flask.abort(400);
如果设置了strict=True,请求包含解析器中未定义的参数也会抛出400状态码。
parse_args
#参数:
req:请求上下文,默认使用flask.request
strict:请求是否可以包含解析器中未定义的参数,默认false,即可以。
实际使用时如果我们想改变抛出异常的错误处理就最好捕捉一下异常:
class ToDo1(Resource):
try:
new_parser = parser.copy()
new_parser.add_argument('n', type=int, help='Rate to charge for this resource,{error_msg}')
args = new_parser.parse_args(strict=True)
except HTTPException as e:
do somthing...
raise e
return "OK"
小结
-
对于RequestParser来说,其主要的功能对请求的参数进行验证,验证通过反馈参数字典,否则抛出400异常;
-
我们可以设置自定义的验证类型来验证额外的需求;
格式化返回数据
通过marshal_with装饰器,我们可以对视图函数返回的数据进行格式化,使得呈现给前端的数据符合我们预期要返回的数据格式,而不需要去手动使用代码转化;
class Todo(Resource):
@marshal_with({'data':fields.String}, envelope='resource')
def get(self, **kwargs):
return {'data':'xiao'}
# 前端数据
{"resource": {"data": "xiao"}}
# 参数
fields:数据规则;
envelope:数据的键,如果定义了该值,数据就以该值为键返回。
使用方式
- 定义想要返回的数据格式,可以是复杂的嵌套结构
resource_dict = {"status_code": fields.Integer,
"message": {"count": fields.Integer, "newcount": fields.Integer}}
但是我们视图函数的返回值可以是扁平结构:
resource_fields = {"status_code":fields.Integer,
"data":{"count": fields.Integer, "newcount": fields.List(fields.Integer)}},
@app.route('/')
@marshal_with(resource_fields,
envelope='message')
def index(self):
return {"status_code":200, "count":10, "newcount": [5,6]}
marshal_with会将你返回的数据和预定义的数据进行比较,存在的就填充,不存在就用None替代,用envelope为键将结果包裹;装饰过后视图函数的返回结果是OrderedDict对象。
- 自定义返回的数据类型
restful模块默认对前端返回的是json格式的字符串数据,所有的Api都只支持json,即所有视图返回的数据都会默认尝试转化成json格式,不成功就会报错。我们可以指定APi默认输出的格式,如html,csv,json等;
# 设置输出html格式,所有视图返回的数据都会默认尝试转化成text/html格式
rest_api = Api(app)
@rest_api.representation('text/html')
def output_html(data, code, headers=None):
resp = make_response(data, code)
resp.headers.extend(headers or {})
return resp
#或者这样写:
rest_api.representations = OrdereDict({'text/html':output_html})
# 同理如果想设置其他格式的处理函数
@rest_api.representation('application/json')
def output_json(data, code, headers=None):
pass
@api.representation('text/csv')
def output_csv(data, code, headers=None):
pass
# 或者
rest_api.representations = OrdereDict({'application/json':output_json})
rest_api.representations = OrdereDict({'text/csv':output_csv})
fields模块
fields模块可以说是专为json数据交互准备的,被marshal_with装饰的视图函数必须返回字典对象,它定义了许多的针对视图函数对外的格式化处理方法。
resource_fields = {
'name': fields.String,
'address': fields.String,
'date_updated': fields.DateTime(dt_format='rfc822'),
}
# 相应的字段有:
"String":字符串,参数attribute指定别名,default指定默认值;
"FormattedString":格式化字符串,参数如"name{age}"这种形式
"Url":生成url,参数endpoint和url_for的参数类似,根据它找到对应的url,参数absolute=True则生成绝对url,参数scheme='https'则修改协议
"DateTime":转化日期的标准格式,如datetime.datetime(2018,11,23)--》"Fri, 23 Nov 2018 00:00:00 -0000"
"Float":小数;
"Integer":整形,默认为0;
"Arbitrary":任意精度的浮点数
"Nested":嵌套结构;
"List":列表类型,参数cls_or_instance指定列表元素的类型,如:List(String)
"Raw":基类,参数attribute指定别名,default指定默认值;
"Boolean":布尔值,返回True或False
"Fixed":固定精度的十进制数,参数decimals指定精度;
"Price":Fixed的引用;
- 如果内置的字段不满足我们的要求,我们可以自定义:
class UrgentItem(fields.Raw):
def format(self, value):
return "big" if value == 1 else "small"
# 只需要继承Raw并实现format函数即可