Flask-Restful解析请求中嵌套字典结构
先展示结果,下面再一步步解释:
from flask import Flask
from flask_restful import Api, Resource
from flask_restful.reqparse import Argument, RequestParser
from werkzeug.exceptions import BadRequest
app = Flask(__name__)
api = Api(app)
def parse_args(args, req=None, trim=True, bundle_errors=True):
parser = RequestParser(trim=trim, bundle_errors=bundle_errors)
parser.args = args
return parser.parse_args(req=req)
class DictParseWrap(object):
def __init__(self, **kwargs):
self.unparsed_arguments = None
self.kwargs = kwargs
def json(self):
return self.kwargs
class DictParseType(object):
def __init__(self, args=None):
self.args = args
def __call__(self, value):
if isinstance(value, dict):
value = DictParseWrap(**value)
try:
return parse_args(self.args, req=value)
except BadRequest as e:
raise ValueError(getattr(e, 'data', {}).get('message') or e)
test_args = [
Argument('args', location=['args'], type=str),
Argument('complex_lvl_1', required=True, location=['json'], type=DictParseType([
Argument('test_list', default=[], action='append', type=str),
Argument('complex_lvl_2', type=DictParseType([
Argument('test_dict', type=dict),
Argument('test_str', type=str)
]))
]))
]
class Test(Resource):
def post(self):
req = parse_args(test_args)
return req
api.add_resource(Test, '/test')
if __name__ == '__main__':
app.run('0.0.0.0', 9000)
结果展示
reqparse使用简介
reqparse是flask框架下的一个拓展flask_restful提供的请求参数解析模块。 官方文档 基础的使用样例:
from flask import Flask
from flask_restful import Api, Resource
from flask_restful.reqparse import RequestParser
app = Flask(__name__)
api = Api(app)
test_args = RequestParser()
test_args.add_argument('test_str', type=str, required=True, location='json')
test_args.add_argument('test_inf', type=int, default=0, location='json')
test_args.add_argument('test_lst', action='append')
class Test(Resource):
def post(self):
args = test_args.parse_args()
return args
api.add_resource(Test, '/test')
if __name__ == '__main__':
app.run('0.0.0.0', 9000)
实现思路
从原api可以看出,reqparse只提供最基础的参数解析。想要实现嵌套结构解析,思路自然是递归思想。下面就来一步一步解决问题
1. 嵌套解析类实现
我们来看一下参数解析的源码实现,源码中Argument.convert 方法实现的类型转换,下面是源码片段:
def convert(self, value, op):
try:
return self.type(value, self.name, op)
except TypeError:
pass
因此我们实现的复杂结构类中需要重载__call__方法。 ReqParser是用列表存放Arguments的,因此我们也可以用列表存放下一层嵌套结构。 由此:
class DictParseType(object):
def __init__(self, args=None):
self.args = args
def __call__(self, value):
parser = ReqParser()
for arg in self.args:
parser.add_argument(arg)
return parser.parse_args(value)
然而这样运行会报错,原因是源码中传入的value并不是一个字典,而是request实例,因此需要封装。
2. 封装传入的字典
上面说到需要对value进行封装,代码中会用到属性request.unparsed_arguments,因此需要加入封装。
class DictParseWrap(object):
def __init__(self, **kwargs):
self.unparsed_arguments = None
self.kwargs = kwargs
def json(self):
return self.kwargs
3. 错误处理
上面封装过后仍然存在缺陷,问题在于参数解析出错后的信息提醒:封装后的结构如果解析出错并不会返回正确的错误提醒,而是直接报错无法解析。 原因就是下面代码:
def parse(self, request, bundle_errors=False):
...
try:
value = self.convert(value, operator)
except Exception as error:
if self.ignore:
continue
return self.handle_validation_error(error, bundle_errors)
...
def handle_validation_error(self, error, bundle_errors):
"""Called when an error is raised while parsing. Aborts the request
with a 400 status and an error message
:param error: the error that was raised
:param bundle_errors: do not abort when first error occurs, return a
dict with the name of the argument and the error message to be
bundled
"""
error_str = six.text_type(error)
error_msg = self.help.format(error_msg=error_str) if self.help else error_str
msg = {self.name: error_msg}
if current_app.config.get("BUNDLE_ERRORS", False) or bundle_errors:
return error, msg
flask_restful.abort(400, message=msg)
由于是嵌套结构的内层解析出错后会返回一个HttpException而上层无法对此解析,所以需要额外增加错误处理,详情见顶部代码__call__方法中的错误处理。
封装添加参数并解析过程
为了添加参数的方便和解耦接口逻辑与校验逻辑,在此额外对添加参数和解析进行封装,见顶部代码parse_args 函数
综上,经过源码分析,一步步地实现了文章开头的代码,使其能够解析请求中嵌套的字典结构。
|