restplus python rest flask flask-restful

python - restplus - Validación anidada con el RequestParser que descansa en el matraz



flask restplus (5)

Al usar el micro-marco con flask-restful , tengo problemas para construir un RequestParser que validará los recursos anidados. Asumiendo un formato de recurso JSON esperado de la forma:

{ ''a_list'': [ { ''obj1'': 1, ''obj2'': 2, ''obj3'': 3 }, { ''obj1'': 1, ''obj2'': 2, ''obj3'': 3 } ] }

Cada elemento en a_list corresponde a un objeto:

class MyObject(object): def __init__(self, obj1, obj2, obj3) self.obj1 = obj1 self.obj2 = obj2 self.obj3 = obj3

... y uno crearía un RequestParser utilizando un formulario parecido a:

from flask.ext.restful import reqparse parser = reqparse.RequestParser() parser.add_argument(''a_list'', type=MyObject, action=''append'')

... pero, ¿cómo validaría los MyObject anidados de cada diccionario dentro de a_list ? O, alternativamente, ¿es este el enfoque equivocado?

La API a la que corresponde corresponde a cada MyObject como, esencialmente, un objeto literal, y puede haber uno o más de ellos pasados ​​al servicio; por lo tanto, aplanar el formato del recurso no funcionará para esta circunstancia.


Dado que el argumento de type aquí no es más que un llamable que devuelve un valor analizado o aumenta ValueError en un tipo no válido, sugeriría crear su propio validador de tipos para esto. El validador podría verse algo como:

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


Encontré la respuesta de bbenne10 realmente útil, pero no funcionó para mí como es.

La forma en que lo hice es probablemente incorrecta, pero funciona. Mi problema es que no entiendo qué hace action=''append'' ya que lo que parece hacer es envolver el valor recibido en una lista, pero no tiene ningún sentido para mí. ¿Puede alguien explicar cuál es el punto de esto en los comentarios?

Entonces, lo que terminé haciendo es crear mi propio tipo de listtype , obtener la lista dentro del value param y luego iterar a través de la lista de esta manera:

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)

No es una solución realmente elegante, pero al menos hace el trabajo. Espero que alguien nos pueda orientar en la dirección correcta ...

Actualizar

Como bbenne10 ha dicho en los comentarios , lo que hace action=''append'' es agregar todos los argumentos llamados iguales en una lista, por lo que en el caso del OP, no parece ser muy útil.

He repetido sobre mi solución porque no me gustó el hecho de que reqparse no estuviera analizando / validando ninguno de los objetos anidados, así que lo que hice fue usar reqparse dentro del myobjlist tipo de objeto personalizado.

Primero, he declarado una nueva subclase de Request , para pasarla como la solicitud al analizar los objetos anidados:

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

Esta clase reemplaza el request.json para que use un nuevo json con el objeto a analizar. Luego, agregué un analizador reqparse a myobjlist para analizar todos los argumentos y agregué una excepción para detectar el error de análisis y pasar el mensaje 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 esta manera, incluso los objetos anidados se analizarán a través de reqparse y mostrarán sus errores


He tenido éxito al crear instancias de RequestParser para los objetos anidados. Analice el objeto raíz primero como lo haría normalmente, luego use los resultados para alimentar los analizadores de los objetos anidados.

El truco es el argumento de location del método add_argument y el argumento req del método parse_args . Te permiten manipular lo que ve el RequestParser .

Aquí hay un ejemplo:

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)


La solución con la calificación más alta no es compatible con ''strict = True''. Para resolver el problema ''strict = True'' no compatible, puede crear un objeto FakeRequest para engañar a 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)

Por cierto: el matraz de descanso eliminará el RequestParser y lo reemplazará con Marshmallow Linkage


Yo sugeriría usar una herramienta de validación de datos como cerberus . Comience por definir un esquema de validación para su objeto (el esquema de objeto anidado se trata en this párrafo), luego use un validador para validar el recurso contra el esquema. También recibe mensajes de error detallados cuando falla la validación.

En el siguiente ejemplo, quiero validar una lista de ubicaciones:

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

El argumento se define de la siguiente manera:

parser.add_argument(''location'', type=location_validator, action=''append'')