marshmallowで相互排他的なfieldを定義する方法
はじめに
こういうJSONを許したい。
{ "left": { "name": "foo", "value": 100, } }
あるいはこう。
{ "right": { "name": "foo", "value": 100.0, } }
left,rightというfieldの内どちらか1つだけ値が入るデータをvalidとしたい。例えば以下はダメ。
{}
これもだめ。
{ "left": { "name": "foo", "value": 100, }, "right": { "name": "foo", "value": 100.0, } }
2つのleft,rightというfieldの構造がテキトウ過ぎるけれど。2つ(Nつ)あるfieldの内1つだけに値が入るという状態にしたい。
方法
field自体にこのような機能をつけるのは無理で。validate_schemasというschemaレベルのvalidationを使う。
import marshmallow as ma class Item(ma.Schema): name = ma.fields.String(required=True) value = ma.fields.Integer(required=True) class Item2(ma.Schema): name = ma.fields.String(required=True) value = ma.fields.Number(required=True) class S(ma.Schema): left = ma.fields.Nested(Item) right = ma.fields.Nested(Item2) @ma.validates_schema def mutual(self, data): items = [item for item in [data.get("left"), data.get("right")] if item] if len(items) != 1: raise ma.ValidationError("items0 or items1") print(S().load({})) print(S().load({"left": {"name": "foo", "value": 10}})) print(S().load({"right": {"name": "foo", "value": 10}})) print(S().load({"left": {"name": "foo", "value": 10}, "right": {"name": "foo", "value": 10}})) # UnmarshalResult(data={}, errors={'_schema': ['items0 or items1']}) # UnmarshalResult(data={'left': {'value': 10, 'name': 'foo'}}, errors={}) # UnmarshalResult(data={'right': {'value': 10.0, 'name': 'foo'}}, errors={}) # UnmarshalResult(data={'right': {'value': 10.0, 'name': 'foo'}, 'left': {'value': 10, 'name': 'foo'}}, errors={'_schema': ['items0 or items1']})