web-dev-qa-db-fra.com

Validation imbriquée avec le flacon-requier reposant

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 MyObjects 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.

29
Daniel Naab

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)
23
barqshasbite

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')
7
kardaj

É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')
5
bbenne10

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

4
josebama

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

2
Matthewgao