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.

23
demandé sur Daniel Naab 2013-10-08 01:03:09

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)
21
répondu barqshasbite 2015-01-07 21:55:27

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')
5
répondu bbenne10 2014-01-17 18:41:00

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

4
répondu josebama 2017-05-23 12:34:19

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')
4
répondu kardaj 2016-06-15 20:29:49

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

2
répondu Matthewgao 2016-05-12 09:50:05