Skip to content

Commit 050d368

Browse files
committed
WIP Add any-off
1 parent 8c3b23f commit 050d368

File tree

4 files changed

+138
-15
lines changed

4 files changed

+138
-15
lines changed

openapi_core/schema/schemas/factories.py

+11-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,11 @@ 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+
71+
6672
items = None
6773
if items_spec:
6874
items = self._create_items(items_spec)
@@ -75,7 +81,7 @@ def create(self, schema_spec):
7581
schema_type=schema_type, properties=properties,
7682
items=items, schema_format=schema_format, required=required,
7783
default=default, nullable=nullable, enum=enum,
78-
deprecated=deprecated, all_of=all_of, one_of=one_of,
84+
deprecated=deprecated, all_of=all_of, one_of=one_of, any_of=any_of,
7985
additional_properties=additional_properties,
8086
min_items=min_items, max_items=max_items, min_length=min_length,
8187
max_length=max_length, pattern=pattern, unique_items=unique_items,
@@ -118,6 +124,10 @@ class SchemaDictFactory(object):
118124
'one_of',
119125
dest_prop_name='oneOf', is_list=True, dest_default=[],
120126
),
127+
Contribution(
128+
'any_of',
129+
dest_prop_name='anyOf', is_list=True, dest_default=[],
130+
),
121131
Contribution(
122132
'additional_properties',
123133
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

+39-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,7 @@ 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, any_of_schema=None):
200217
all_props = self.schema.get_all_properties()
201218
all_props_names = self.schema.get_all_properties_names()
202219

@@ -205,6 +222,11 @@ def _unmarshal_properties(self, value=NoValue, one_of_schema=None):
205222
all_props_names |= one_of_schema.\
206223
get_all_properties_names()
207224

225+
if any_of_schema is not None:
226+
all_props.update(any_of_schema.get_all_properties())
227+
all_props_names |= any_of_schema.\
228+
get_all_properties_names()
229+
208230
value_props_names = value.keys()
209231
extra_props = set(value_props_names) - set(all_props_names)
210232

@@ -253,6 +275,10 @@ def __call__(self, value=NoValue):
253275
if one_of_schema:
254276
return self.unmarshallers_factory.create(one_of_schema)(value)
255277

278+
any_of_schema = self._get_any_of_schema(value)
279+
if any_of_schema:
280+
return self.unmarshallers_factory.create(any_of_schema)(value)
281+
256282
all_of_schema = self._get_all_of_schema(value)
257283
if all_of_schema:
258284
return self.unmarshallers_factory.create(all_of_schema)(value)
@@ -283,6 +309,18 @@ def _get_one_of_schema(self, value):
283309
else:
284310
return subschema
285311

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

tests/unit/unmarshalling/test_validate.py

+86-12
Original file line numberDiff line numberDiff line change
@@ -456,28 +456,28 @@ def test_object_not_an_object(self, value, validator_factory):
456456
validator_factory(schema).validate(value)
457457

458458
@pytest.mark.parametrize('value', [Model(), ])
459-
def test_object_multiple_one_of(self, value, validator_factory):
460-
one_of = [
459+
def test_object_multiple_any_of(self, value, validator_factory):
460+
any_of = [
461461
Schema('object'), Schema('object'),
462462
]
463-
schema = Schema('object', one_of=one_of)
463+
schema = Schema('object', any_of=any_of)
464464

465465
with pytest.raises(InvalidSchemaValue):
466466
validator_factory(schema).validate(value)
467467

468468
@pytest.mark.parametrize('value', [{}, ])
469-
def test_object_different_type_one_of(self, value, validator_factory):
470-
one_of = [
469+
def test_object_different_type_any_of(self, value, validator_factory):
470+
any_of = [
471471
Schema('integer'), Schema('string'),
472472
]
473-
schema = Schema('object', one_of=one_of)
473+
schema = Schema('object', any_of=any_of)
474474

475475
with pytest.raises(InvalidSchemaValue):
476476
validator_factory(schema).validate(value)
477477

478478
@pytest.mark.parametrize('value', [{}, ])
479-
def test_object_no_one_of(self, value, validator_factory):
480-
one_of = [
479+
def test_object_no_any_of(self, value, validator_factory):
480+
any_of = [
481481
Schema(
482482
'object',
483483
properties={'test1': Schema('string')},
@@ -489,7 +489,7 @@ def test_object_no_one_of(self, value, validator_factory):
489489
required=['test2', ],
490490
),
491491
]
492-
schema = Schema('object', one_of=one_of)
492+
schema = Schema('object', any_of=any_of)
493493

494494
with pytest.raises(InvalidSchemaValue):
495495
validator_factory(schema).validate(value)
@@ -503,8 +503,8 @@ def test_object_no_one_of(self, value, validator_factory):
503503
'bar': u("BAR"),
504504
},
505505
])
506-
def test_unambiguous_one_of(self, value, validator_factory):
507-
one_of = [
506+
def test_unambiguous_any_of(self, value, validator_factory):
507+
any_of = [
508508
Schema(
509509
'object',
510510
properties={
@@ -523,7 +523,81 @@ def test_unambiguous_one_of(self, value, validator_factory):
523523
required=['foo', 'bar'],
524524
),
525525
]
526-
schema = Schema('object', one_of=one_of)
526+
schema = Schema('object', any_of=any_of)
527+
528+
result = validator_factory(schema).validate(value)
529+
530+
assert result is None
531+
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)
527601

528602
result = validator_factory(schema).validate(value)
529603

0 commit comments

Comments
 (0)