Вложенная проверка с помощью поддерживающего колбу RequestParser
Используя micro-framework flask-restful, возникает проблема с конструкцией RequestParser
, которая будет проверять вложенные ресурсы. Предполагая ожидаемый формат ресурса JSON формы:
{
'a_list': [
{
'obj1': 1,
'obj2': 2,
'obj3': 3
},
{
'obj1': 1,
'obj2': 2,
'obj3': 3
}
]
}
Каждый элемент в a_list
соответствует объекту:
class MyObject(object):
def __init__(self, obj1, obj2, obj3)
self.obj1 = obj1
self.obj2 = obj2
self.obj3 = obj3
... и тогда вы создадите RequestParser, используя форму, вроде:
from flask.ext.restful import reqparse
parser = reqparse.RequestParser()
parser.add_argument('a_list', type=MyObject, action='append')
... но как бы вы проверили вложенный MyObject
каждого словаря внутри a_list
? Или, альтернативно, это неправильный подход?
API, соответствующий этому, обрабатывает каждый MyObject
как, по существу, литерал объекта, и может быть один или несколько из них переданы службе; поэтому для этого обстоятельства сглаживание формата ресурса не будет работать.
Ответы
Ответ 1
У меня был успех, создав экземпляры RequestParser
для вложенных объектов. Сначала проанализируйте корневой объект, как обычно, и используйте результаты для подачи в парсеры для вложенных объектов.
Трюк - это аргумент location
метода add_argument
и аргумент req
метода parse_args
. Они позволяют вам манипулировать тем, что смотрит RequestParser
.
Вот пример:
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)
Ответ 2
Я бы предложил использовать инструмент проверки данных, такой как цербер. Вы начинаете с определения схемы проверки для вашего объекта (схема вложенного объекта рассматривается в этом параграфе), а затем используете средство проверки для проверки ресурса по этой схеме. Вы также получите подробные сообщения об ошибках при сбое проверки.
В следующем примере я хочу проверить список мест:
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))
Аргумент определяется следующим образом:
parser.add_argument('location', type=location_validator, action='append')
Ответ 3
Так как аргумент type
здесь не что иное, как вызываемый, который либо возвращает разобранное значение, либо повышает значение ValueError на недопустимый тип, я бы предложил создать для него свой собственный валидатор типа. Валидатор может выглядеть примерно так:
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')
Ответ 4
Я нашел bbenne10s answer действительно полезным, но это не сработало для меня, как есть.
То, как я это делал, вероятно, ошибочно, но оно работает. Моя проблема заключается в том, что я не понимаю, что делает action='append'
как то, что он, кажется, делает, - это переносить полученное значение в список, но для меня это не имеет никакого смысла. Может кто-нибудь, пожалуйста, объясните, в чем смысл этого в комментариях?
Итак, что я закончил делать, это создать свой собственный listtype
, получить список внутри параметра value
, а затем перебирать список таким образом:
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)
Не очень элегантное решение, но, по крайней мере, это делает работу. Надеюсь, кто-то может указать нам в правильном направлении...
Обновление
Как bbenne10 сказал в комментариях, то, что action='append'
делает, добавляет все аргументы, названные одинаковыми, в список, поэтому в случае OP, это не кажется очень полезным.
Я повторил свое решение, потому что мне не понравилось то, что reqparse
не анализировал/проверял какие-либо из вложенных объектов, поэтому я сделал то, что я сделал, это использование reqparse
внутри настраиваемого типа объекта myobjlist
.
Во-первых, я объявил новый подкласс Request
, чтобы передать его в качестве запроса при разборе вложенных объектов:
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
Этот класс переопределяет request.json
, так что он использует новый json с объектом для анализа.
Затем я добавил парсер reqparse
в myobjlist
, чтобы проанализировать все аргументы и добавить исключение, кроме как уловить ошибку синтаксического анализа и передать сообщение 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
Таким образом, даже вложенные объекты будут проанализированы с помощью reqparse и покажут свои ошибки
Ответ 5
Самое высокое рейтинговое решение не поддерживает "strict = True". Чтобы решить проблему "strict = True" не поддерживается, вы можете создать объект FakeRequest для обмана 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: колба успокаивает, вырывает RequestParser и заменяет его Marshmallow
Связь