Validation imbriquée avec le flacon-restful RequestParser
à l'aide du flacon-restful micro-framework, j'ai de la difficulté à construire un RequestParser
qui permettra de valider les ressources imbriquées. En supposant un format de ressource JSON prévu de la forme:
{
'a_list': [
{
'obj1': 1,
'obj2': 2,
'obj3': 3
},
{
'obj1': 1,
'obj2': 2,
'obj3': 3
}
]
}
chaque élément de a_list
correspond à un objet:
class MyObject(object):
def __init__(self, obj1, obj2, obj3)
self.obj1 = obj1
self.obj2 = obj2
self.obj3 = obj3
... et on créerait alors un RequestParser en utilisant un formulaire du genre:
from flask.ext.restful import reqparse
parser = reqparse.RequestParser()
parser.add_argument('a_list', type=MyObject, action='append')
... mais comment valideriez-vous les MyObject
de chaque dictionnaire à l'intérieur de a_list
? Ou, alternativement, est-ce la bonne approche?
L'API, ce qui correspond à la traite chaque MyObject
que, pour l'essentiel, un objet littéral, et il peut y avoir un ou plusieurs d'entre eux sont passés au service; par conséquent, l'aplatissement de la ressource, le format ne fonctionnera pas pour cette circonstance.
5 réponses
j'ai eu du succès en créant des instances RequestParser
pour les objets imbriqués. Analyser l'objet racine d'abord comme vous le feriez normalement, puis utiliser les résultats pour nourrir les analyseurs pour les objets imbriqués.
l'astuce est l'argument location
de la méthode add_argument
et l'argument req
de la méthode parse_args
. Ils vous laissent manipuler ce que le RequestParser
regarde.
voici un exemple:
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)
puisque l'argument type
ici n'est rien d'autre qu'une callable qui renvoie soit une valeur parsée soit une augmentation de valeur sur un type invalide, je suggérerais de créer votre propre validateur de type pour cela. Le validateur pourrait ressembler à quelque chose comme:
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')
j'ai trouvé le bbenne10s answer vraiment utile, mais il n'a pas fonctionné pour moi comme tel.
la façon dont je l'ai fait est probablement mal, mais cela fonctionne. Mon problème est que je ne comprends pas ce que action='append'
fait comme ce qu'il semble faire est wrap la valeur reçue dans une liste, mais il n'a pas de sens pour moi. Quelqu'un peut-il expliquer ce que cela signifie dans les commentaires?
alors ce que je finalement faire est de créer mon propre listtype
, obtenir la liste à l'intérieur du value
param et puis itérer à travers la liste de cette façon:
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)
pas une solution vraiment élégante, mais au moins il fait le travail. J'espère que quelqu'un peut nous indiquer la bonne direction...
mise à Jour
Comme bbenne10 a dit dans les commentaires , ce action='append'
n'est ajouter tous les les arguments nommés de la même façon dans une liste, donc dans le cas de L'OP, il ne semble pas être très utile.
j'ai itéré au-dessus de ma solution parce que je n'ai pas aimé le fait que reqparse
n'analysait pas/validait aucun des objets imbriqués donc je ce que j'ai fait est d'utiliser reqparse
à l'intérieur du type d'objet personnalisé myobjlist
.
tout D'abord, j'ai déclaré une nouvelle sous-classe de Request
, à passer comme la demande lors de l'analyse des objets imbriqués:
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
cette classe remplace la request.json
de sorte qu'elle utilise un nouveau json ayant pour objet d'être interprété.
Ensuite, j'ai ajouté un parser reqparse
à myobjlist
pour analyser tous les arguments et j'ai ajouté un sauf pour attraper l'erreur d'analyse et passer le message reqparse
.
from flask.ext.restful import reqparse
from werkzeug.exceptions import ClientDisconnected
def myobjlist(value):
parser = reqparse.RequestParser()
parser.add_argument('obj1', type=int, required=True, help='No obj1 provided', location='json')
parser.add_argument('obj2', type=int, location='json')
parser.add_argument('obj3', type=int, location='json')
nested_request = NestedRequest()
result = []
try:
for v in value:
nested_request.nested_json = v
v = parser.parse_args(nested_request)
x = MyObj(**v)
result.append(x)
except TypeError:
raise ValueError("Invalid object")
except ClientDisconnected, e:
raise ValueError(e.data.get('message', "Parsing error") if e.data else "Parsing error")
except:
raise ValueError
return result
de cette façon, même les objets imbriqués seront analysés par reqparse et montreront ses erreurs
je suggère d'utiliser un outil de validation des données tel que cerberus . Vous commencez par définir un schéma de validation pour votre objet (le schéma D'objet imbriqué est couvert dans ce "paragraphe ), puis utilisez un validateur pour valider la ressource par rapport au schéma. Vous recevez également des messages d'erreur détaillés lorsque la validation échoue.
dans l'exemple suivant, je veux valider une liste d'emplacements:
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))
l'argument est défini comme suit:
parser.add_argument('location', type=location_validator, action='append')
la solution la mieux notée ne supporte pas "strict=True", pour résoudre le problème "strict=True" ne supporte pas, vous pouvez créer un objet FakeRequest pour tricher 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: flask restful déchirera RequestParser dehors, et le remplacera par Marshmallow Lien