web-dev-qa-db-ja.com

Flask-restful RequestParserによるネストされた検証

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

...しかし、a_list内の各辞書のネストされたMyObjectsをどのように検証しますか?あるいは、これは間違ったアプローチですか?

これに対応するAPIは、各MyObjectを基本的にオブジェクトリテラルとして扱い、それらの1つ以上がサービスに渡される場合があります。したがって、リソースの形式をフラット化しても、この状況では機能しません。

29
Daniel Naab

ネストされたオブジェクトのRequestParserインスタンスを作成することで成功しました。通常どおり最初にルートオブジェクトを解析し、次にその結果を使用して、ネストされたオブジェクトのパーサーにフィードします。

トリックは、add_argumentメソッドのlocation引数とparse_argsメソッドのreq引数です。 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)
23
barqshasbite

cerberus などのデータ検証ツールを使用することをお勧めします。まず、オブジェクトの検証スキーマを定義し(ネストされたオブジェクトスキーマは this 段落で説明されています)、次にバリデーターを使用して、スキーマに対してリソースを検証します。検証が失敗した場合も、詳細なエラーメッセージが表示されます。

次の例では、場所のリストを検証します。

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

ここでの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')
5
bbenne10

bbenne10sの回答 は本当に便利だと思いましたが、そのままではうまくいきませんでした。

私がこれをした方法はおそらく間違っていますが、それはうまくいきます。私の問題は、リストで受け取った値がwrapであるように、action='append'が何をするのか理解できないことですが、何もしません私に感覚。誰かがコメントでこれの要点を説明できますか?

したがって、私がやったことは、自分のlisttypeを作成し、value param内のリストを取得して、この方法でリストを反復処理することです。

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に追加してすべての引数を解析し、exceptを追加して解析エラーをキャッチし、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を通じて解析され、そのエラーを表示します

4
josebama

最高評価のソリューションは '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)

ところで:flask RESTfulはRequestParserをリッピングし、Marshmallowで置き換えます Linkage

2
Matthewgao