Utilisation du micro-cadre Flacon-reposondre , j'ai du mal à construire un RequestParser
qui validera les ressources imbriquées. En supposant un format de ressource JSON attendu du formulaire:
{
'a_list': [
{
'obj1': 1,
'obj2': 2,
'obj3': 3
},
{
'obj1': 1,
'obj2': 2,
'obj3': 3
}
]
}
Chaque article 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 une demande de demande en utilisant un formulaire quelque chose comme:
from flask.ext.restful import reqparse
parser = reqparse.RequestParser()
parser.add_argument('a_list', type=MyObject, action='append')
... mais comment valideriez-vous la nichée MyObject
s de chaque dictionnaire à l'intérieur de a_list
? Ou, alternativement, est-ce la mauvaise approche?
L'API ceci correspond à traiter chaque MyObject
comme, essentiellement 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 du format de ressources ne fonctionnera pas pour cette circonstance.
J'ai eu du succès en créant RequestParser
instances pour les objets imbriqués. Analyser l'objet racine d'abord comme vous le feriez normalement, puis utilisez les résultats pour alimenter les analyseurs pour les objets imbriqués.
L'astuce est le location
argument de la add_argument
méthode et req
argument de la parse_args
méthode. Ils vous permettent de 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)
Je suggérerais d'utiliser un outil de validation de données telle que Cerberus . Vous commencez par définir un schéma de validation pour votre objet (le schéma d'objet imbriqué est couvert dans this paragraphe), puis utilisez un validateur pour valider la ressource contre le schéma. Vous obtenez é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')
Étant donné que l'argument type
n'est rien d'autre qu'une appelable qui renvoie une valeur analysée ou élever ValueError sur un type non valide, 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é la BBenne10S Réponse Vraiment utile, mais cela n'a pas fonctionné pour moi tel quel.
La façon dont je l'ai fait est probablement faux, mais ça marche. Mon problème est que je ne comprends pas ce que action='append'
fait comme ce qu'il semble faire est Enveloppe la valeur reçue dans une liste, mais cela n'a aucun sens pour moi. Quelqu'un peut-il s'il vous plaît expliquer quel est le point de cela dans les commentaires?
Donc, ce que j'ai fini par faire, c'est créer ma propre listtype
, obtenez la liste à l'intérieur du paramètre value
, puis itérer dans 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 très élégante, mais au moins cela fait le travail. J'espère que quelqu'un peut nous signaler dans la bonne direction ...
Mise à jour
Comme BBenne10 a dit dans les commentaires , quels action='append'
Est fait ajouter tous les arguments nommés la même chose dans une liste, donc dans le cas de l'OP, il ne semble pas être très utile.
J'ai itérisé sur ma solution parce que je n'ai pas aimé le fait que reqparse
n'a pas analysé/valider aucun des objets imbriqués pour que je ce que j'ai fait est d'utiliser reqparse
à l'intérieur de l'objet personnalisé. Tapez myobjlist
.
Premièrement, j'ai déclaré une nouvelle sous-classe de Request
, de la transmettre 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 le request.json
Pour qu'il utilise un nouveau JSON avec l'objet à analyser. Ensuite, j'ai ajouté un analyseur reqparse
à myobjlist
pour analyser tous les arguments et ajouté une sauf pour attraper l'erreur d'analyse et transmettre 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
La solution nominale la plus élevée ne prend pas en charge "strict = vrai", pour résoudre le problème "strict = vrai" non de support, vous pouvez créer un objet Fakerequest à tricherie
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 RESTFULAIRE DIFFICATEUR DIRECTPARSER OUT et remplacez-le par Marshmallow Linkage