Nested validation with the flask-restful RequestParser
I have had success by creating RequestParser
instances for the nested objects. Parse the root object first as you normally would, then use the results to feed into the parsers for the nested objects.
The trick is the location
argument of the add_argument
method and the req
argument of the parse_args
method. They let you manipulate what the RequestParser
looks at.
Here's an example:
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)
I would suggest using a data validation tool such as cerberus. You start by defining a validation schema for your object (Nested object schema is covered in this paragraph), then use a validator to validate the resource against the schema. You also get detailed error messages when the validation fails.
In the following example, I want to validate a list of locations:
from cerberus import Validatorimport jsondef 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))
The argument is defined as follows:
parser.add_argument('location', type=location_validator, action='append')
Since the type
argument here is nothing but a callable that either returns a parsed value or raise ValueError on invalid type, I would suggest creating your own type validator for this. The validator could look something like:
from flask.ext.restful import reqparsedef 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')