Подтвердить что ты не робот

Вложенная проверка с помощью поддерживающего колбу 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 как, по существу, литерал объекта, и может быть один или несколько из них переданы службе; поэтому для этого обстоятельства сглаживание формата ресурса не будет работать.

4b9b3361

Ответ 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 Связь