使用flask-restful RequestParser进行嵌套验证

时间:2013-10-07 21:03:09

标签: python rest flask flask-restful

使用flask-restful微框架,我在构建验证嵌套资源的RequestParser时遇到了问题。假设格式为:

的预期JSON资源格式
{
    'a_list': [
        {
            'obj1': 1,
            'obj2': 2,
            'obj3': 3
        },
        {
            'obj1': 1,
            'obj2': 2,
            'obj3': 3
        }
    ]
}

a_list中的每个项目都对应一个对象:

class MyObject(object):
    def __init__(self, obj1, obj2, obj3)
        self.obj1 = obj1
        self.obj2 = obj2
        self.obj3 = obj3

...然后可以使用以下形式创建RequestParser:

from flask.ext.restful import reqparse
parser = reqparse.RequestParser()
parser.add_argument('a_list', type=MyObject, action='append')

...但是如何验证MyObject内每个字典的嵌套a_list?或者,这是错误的方法吗?

这对应的API将每个MyObject视为一个对象文字,并且可能有一个或多个传递给服务;因此,扁平化资源格式对这种情况不起作用。

5 个答案:

答案 0 :(得分:22)

通过为嵌套对象创建RequestParser实例,我获得了成功。首先解析根对象,然后使用结果输入嵌套对象的解析器。

技巧是location方法的add_argument参数和req方法的parse_args参数。它们让您操纵RequestParser看到的内容。

以下是一个例子:

root_parser = reqparse.RequestParser()
root_parser.add_argument('id', type=int)
root_parser.add_argument('name', type=str)
root_parser.add_argument('nested_one', type=dict)
root_parser.add_argument('nested_two', type=dict)
root_args = root_parser.parse_args()

nested_one_parser = reqparse.RequestParser()
nested_one_parser.add_argument('id', type=int, location=('nested_one',))
nested_one_args = nested_one_parser.parse_args(req=root_args)

nested_two_parser = reqparse.RequestParser()
nested_two_parser.add_argument('id', type=int, location=('nested_two',))
nested_two_args = nested_two_parser.parse_args(req=root_args)

答案 1 :(得分:7)

我建议使用数据验证工具,例如cerberus。首先为对象定义验证模式(嵌套对象模式在this段落中介绍),然后使用验证器根据模式验证资源。验证失败时,您还会收到详细的错误消息。

在以下示例中,我想验证位置列表:

from cerberus import Validator
import json


def location_validator(value):
    LOCATION_SCHEMA = {
        'lat': {'required': True, 'type': 'float'},
        'lng': {'required': True, 'type': 'float'}
    }
    v = Validator(LOCATION_SCHEMA)
    if v.validate(value):
        return value
    else:
        raise ValueError(json.dumps(v.errors))

参数定义如下:

parser.add_argument('location', type=location_validator, action='append')

答案 2 :(得分:5)

由于这里的type参数只是一个可调用的,无论是返回解析的值还是在无效类型上引发ValueError,我建议为此创建自己的类型验证器。验证器可能类似于:

from flask.ext.restful import reqparse
def myobj(value):
    try:
        x = MyObj(**value)
    except TypeError:
        # Raise a ValueError, and maybe give it a good error string
        raise ValueError("Invalid object")
    except:
        # Just in case you get more errors
        raise ValueError 

    return x


#and now inside your views...
parser = reqparse.RequestParser()
parser.add_argument('a_list', type=myobj, action='append')

答案 3 :(得分:4)

我发现bbenne10s answer非常有用,但它对我来说并不适用。

我这样做的方式可能是错的,但它确实有效。我的问题是,我不明白action='append'做什么,因为它似乎做了 wrap 列表中收到的值,但它没有任何意义对我来说。有人可以在评论中解释一下这一点吗?

所以我最终做的是创建自己的listtype,获取value param中的列表,然后以这种方式遍历列表:

from flask.ext.restful import reqparse
def myobjlist(value):
    result = []
    try:
        for v in value:
            x = MyObj(**v)
            result.append(x)
    except TypeError:
        raise ValueError("Invalid object")
    except:
        raise ValueError

    return result


#and now inside views...
parser = reqparse.RequestParser()
parser.add_argument('a_list', type=myobjlist)

不是一个非常优雅的解决方案,但至少它可以完成工作。我希望有人可以指出我们正确的方向......

<强>更新

作为bbenne10 has said in the commentsaction='append'所做的是将所有相同的参数追加到一个列表中,因此对于OP来说,它似乎并不是非常有用。 / p>

我已经迭代了我的解决方案,因为我不喜欢reqparse没有解析/验证任何嵌套对象的事实所以我所做的就是使用{{1}在自定义对象类型reqparse内。

首先,我声明了一个新的myobjlist子类,在解析嵌套对象时将其作为请求传递:

Request

此类重写class NestedRequest(Request): def __init__(self, json=None, req=request): super(NestedRequest, self).__init__(req.environ, False, req.shallow) self.nested_json = json @property def json(self): return self.nested_json ,以便它使用新的json来解析对象。 然后,我向request.json添加了一个reqparse解析器来解析所有参数,并添加了一个except以捕获解析错误并传递myobjlist消息。

reqparse

这样,即使是嵌套对象也会通过reqparse进行解析并显示其错误

答案 4 :(得分:2)

评分最高的解决方案不支持'strict = True',要解决'strict = True'不支持问题,可以创建一个FakeRequest对象来欺骗RequestParser

class FakeRequest(dict):
    def __setattr__(self, name, value):
        object.__setattr__(self, name, value)

root_parser = reqparse.RequestParser()
root_parser.add_argument('id', type=int)
root_parser.add_argument('name', type=str)
root_parser.add_argument('nested_one', type=dict)
root_parser.add_argument('nested_two', type=dict)
root_args = root_parser.parse_args()

nested_one_parser = reqparse.RequestParser()
nested_one_parser.add_argument('id', type=int, location=('json',))

fake_request = FakeRequest()
setattr(fake_request, 'json', root_args['nested_one'])
setattr(fake_request, 'unparsed_arguments', {})

nested_one_args = nested_one_parser.parse_args(req=fake_request, strict=True)

fake_request = FakeRequest()
setattr(fake_request, 'json', root_args['nested_two'])
setattr(fake_request, 'unparsed_arguments', {})

nested_two_parser = reqparse.RequestParser()
nested_two_parser.add_argument('id', type=int, location=('json',))
nested_two_args = nested_two_parser.parse_args(req=fake_request, strict=True)

BTW:烧瓶restful会将RequestParser撕掉,并用Marshmallow替换它 Linkage