Skip to content

Commit 3179018

Browse files
committed
WIP Add any-of
1 parent 8c3b23f commit 3179018

File tree

4 files changed

+126
-3
lines changed

4 files changed

+126
-3
lines changed

openapi_core/schema/schemas/factories.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ def create(self, schema_spec):
3131
deprecated = schema_deref.get('deprecated', False)
3232
all_of_spec = schema_deref.get('allOf', None)
3333
one_of_spec = schema_deref.get('oneOf', None)
34+
any_of_spec = schema_deref.get('anyOf', None)
3435
additional_properties_spec = schema_deref.get('additionalProperties',
3536
True)
3637
min_items = schema_deref.get('minItems', None)
@@ -63,6 +64,10 @@ def create(self, schema_spec):
6364
if one_of_spec:
6465
one_of = list(map(self.create, one_of_spec))
6566

67+
any_of = []
68+
if any_of_spec:
69+
any_of = list(map(self.create, any_of_spec))
70+
6671
items = None
6772
if items_spec:
6873
items = self._create_items(items_spec)
@@ -75,7 +80,7 @@ def create(self, schema_spec):
7580
schema_type=schema_type, properties=properties,
7681
items=items, schema_format=schema_format, required=required,
7782
default=default, nullable=nullable, enum=enum,
78-
deprecated=deprecated, all_of=all_of, one_of=one_of,
83+
deprecated=deprecated, all_of=all_of, one_of=one_of, any_of=any_of,
7984
additional_properties=additional_properties,
8085
min_items=min_items, max_items=max_items, min_length=min_length,
8186
max_length=max_length, pattern=pattern, unique_items=unique_items,
@@ -118,6 +123,10 @@ class SchemaDictFactory(object):
118123
'one_of',
119124
dest_prop_name='oneOf', is_list=True, dest_default=[],
120125
),
126+
Contribution(
127+
'any_of',
128+
dest_prop_name='anyOf', is_list=True, dest_default=[],
129+
),
121130
Contribution(
122131
'additional_properties',
123132
dest_prop_name='additionalProperties', dest_default=True,

openapi_core/schema/schemas/models.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class Schema(object):
2121
def __init__(
2222
self, schema_type=None, properties=None, items=None,
2323
schema_format=None, required=None, default=NoValue, nullable=False,
24-
enum=None, deprecated=False, all_of=None, one_of=None,
24+
enum=None, deprecated=False, all_of=None, one_of=None, any_of=None,
2525
additional_properties=True, min_items=None, max_items=None,
2626
min_length=None, max_length=None, pattern=None, unique_items=False,
2727
minimum=None, maximum=None, multiple_of=None,
@@ -40,6 +40,7 @@ def __init__(
4040
self.deprecated = deprecated
4141
self.all_of = all_of and list(all_of) or []
4242
self.one_of = one_of and list(one_of) or []
43+
self.any_of = any_of and list(any_of) or []
4344
self.additional_properties = additional_properties
4445
self.min_items = int(min_items) if min_items is not None else None
4546
self.max_items = int(max_items) if max_items is not None else None

openapi_core/unmarshalling/schemas/unmarshallers.py

+40-1
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,23 @@ def _unmarshal_object(self, value=NoValue):
187187
if properties is None:
188188
log.warning("valid oneOf schema not found")
189189

190+
if self.schema.any_of:
191+
properties = None
192+
for any_of_schema in self.schema.any_of:
193+
try:
194+
unmarshalled = self._unmarshal_properties(
195+
value, any_of_schema)
196+
except (UnmarshalError, ValueError):
197+
pass
198+
else:
199+
if properties is not None:
200+
log.warning("multiple valid anyOf schemas found")
201+
continue
202+
properties = unmarshalled
203+
204+
if properties is None:
205+
log.warning("valid anyOf schema not found")
206+
190207
else:
191208
properties = self._unmarshal_properties(value)
192209

@@ -196,7 +213,8 @@ def _unmarshal_object(self, value=NoValue):
196213

197214
return properties
198215

199-
def _unmarshal_properties(self, value=NoValue, one_of_schema=None):
216+
def _unmarshal_properties(self, value=NoValue, one_of_schema=None,
217+
any_of_schema=None):
200218
all_props = self.schema.get_all_properties()
201219
all_props_names = self.schema.get_all_properties_names()
202220

@@ -205,6 +223,11 @@ def _unmarshal_properties(self, value=NoValue, one_of_schema=None):
205223
all_props_names |= one_of_schema.\
206224
get_all_properties_names()
207225

226+
if any_of_schema is not None:
227+
all_props.update(any_of_schema.get_all_properties())
228+
all_props_names |= any_of_schema.\
229+
get_all_properties_names()
230+
208231
value_props_names = value.keys()
209232
extra_props = set(value_props_names) - set(all_props_names)
210233

@@ -253,6 +276,10 @@ def __call__(self, value=NoValue):
253276
if one_of_schema:
254277
return self.unmarshallers_factory.create(one_of_schema)(value)
255278

279+
any_of_schema = self._get_any_of_schema(value)
280+
if any_of_schema:
281+
return self.unmarshallers_factory.create(any_of_schema)(value)
282+
256283
all_of_schema = self._get_all_of_schema(value)
257284
if all_of_schema:
258285
return self.unmarshallers_factory.create(all_of_schema)(value)
@@ -283,6 +310,18 @@ def _get_one_of_schema(self, value):
283310
else:
284311
return subschema
285312

313+
def _get_any_of_schema(self, value):
314+
if not self.schema.any_of:
315+
return
316+
for subschema in self.schema.any_of:
317+
unmarshaller = self.unmarshallers_factory.create(subschema)
318+
try:
319+
unmarshaller.validate(value)
320+
except ValidateError:
321+
continue
322+
else:
323+
return subschema
324+
286325
def _get_all_of_schema(self, value):
287326
if not self.schema.all_of:
288327
return

tests/unit/unmarshalling/test_validate.py

+74
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,80 @@ def test_unambiguous_one_of(self, value, validator_factory):
529529

530530
assert result is None
531531

532+
@pytest.mark.parametrize('value', [Model(), ])
533+
def test_object_multiple_any_of(self, value, validator_factory):
534+
any_of = [
535+
Schema('object'), Schema('object'),
536+
]
537+
schema = Schema('object', any_of=any_of)
538+
539+
with pytest.raises(InvalidSchemaValue):
540+
validator_factory(schema).validate(value)
541+
542+
@pytest.mark.parametrize('value', [{}, ])
543+
def test_object_different_type_any_of(self, value, validator_factory):
544+
any_of = [
545+
Schema('integer'), Schema('string'),
546+
]
547+
schema = Schema('object', any_of=any_of)
548+
549+
with pytest.raises(InvalidSchemaValue):
550+
validator_factory(schema).validate(value)
551+
552+
@pytest.mark.parametrize('value', [{}, ])
553+
def test_object_no_any_of(self, value, validator_factory):
554+
any_of = [
555+
Schema(
556+
'object',
557+
properties={'test1': Schema('string')},
558+
required=['test1', ],
559+
),
560+
Schema(
561+
'object',
562+
properties={'test2': Schema('string')},
563+
required=['test2', ],
564+
),
565+
]
566+
schema = Schema('object', any_of=any_of)
567+
568+
with pytest.raises(InvalidSchemaValue):
569+
validator_factory(schema).validate(value)
570+
571+
@pytest.mark.parametrize('value', [
572+
{
573+
'foo': u("FOO"),
574+
},
575+
{
576+
'foo': u("FOO"),
577+
'bar': u("BAR"),
578+
},
579+
])
580+
def test_unambiguous_any_of(self, value, validator_factory):
581+
any_of = [
582+
Schema(
583+
'object',
584+
properties={
585+
'foo': Schema('string'),
586+
},
587+
additional_properties=False,
588+
required=['foo'],
589+
),
590+
Schema(
591+
'object',
592+
properties={
593+
'foo': Schema('string'),
594+
'bar': Schema('string'),
595+
},
596+
additional_properties=False,
597+
required=['foo', 'bar'],
598+
),
599+
]
600+
schema = Schema('object', any_of=any_of)
601+
602+
result = validator_factory(schema).validate(value)
603+
604+
assert result is None
605+
532606
@pytest.mark.parametrize('value', [{}, ])
533607
def test_object_default_property(self, value, validator_factory):
534608
schema = Schema('object', default='value1')

0 commit comments

Comments
 (0)