Skip to content

Commit 4c75f0f

Browse files
committed
OneOf schema support
1 parent 5d9a671 commit 4c75f0f

File tree

6 files changed

+251
-48
lines changed

6 files changed

+251
-48
lines changed

openapi_core/schema/schemas/exceptions.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,11 @@ class UndefinedSchemaProperty(OpenAPISchemaError):
1515

1616
class MissingSchemaProperty(OpenAPISchemaError):
1717
pass
18+
19+
20+
class NoOneOfSchema(OpenAPISchemaError):
21+
pass
22+
23+
24+
class MultipleOneOfSchema(OpenAPISchemaError):
25+
pass

openapi_core/schema/schemas/factories.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def create(self, schema_spec):
2727
enum = schema_deref.get('enum', None)
2828
deprecated = schema_deref.get('deprecated', False)
2929
all_of_spec = schema_deref.get('allOf', None)
30+
one_of_spec = schema_deref.get('oneOf', None)
3031

3132
properties = None
3233
if properties_spec:
@@ -36,6 +37,10 @@ def create(self, schema_spec):
3637
if all_of_spec:
3738
all_of = map(self.create, all_of_spec)
3839

40+
one_of = []
41+
if one_of_spec:
42+
one_of = map(self.create, one_of_spec)
43+
3944
items = None
4045
if items_spec:
4146
items = self._create_items(items_spec)
@@ -44,7 +49,7 @@ def create(self, schema_spec):
4449
schema_type=schema_type, model=model, properties=properties,
4550
items=items, schema_format=schema_format, required=required,
4651
default=default, nullable=nullable, enum=enum,
47-
deprecated=deprecated, all_of=all_of,
52+
deprecated=deprecated, all_of=all_of, one_of=one_of,
4853
)
4954

5055
@property

openapi_core/schema/schemas/models.py

Lines changed: 67 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from openapi_core.schema.schemas.enums import SchemaType, SchemaFormat
1010
from openapi_core.schema.schemas.exceptions import (
1111
InvalidSchemaValue, UndefinedSchemaProperty, MissingSchemaProperty,
12+
OpenAPISchemaError, NoOneOfSchema, MultipleOneOfSchema,
1213
)
1314
from openapi_core.schema.schemas.util import forcebool
1415

@@ -27,7 +28,7 @@ class Schema(object):
2728
def __init__(
2829
self, schema_type=None, model=None, properties=None, items=None,
2930
schema_format=None, required=None, default=None, nullable=False,
30-
enum=None, deprecated=False, all_of=None):
31+
enum=None, deprecated=False, all_of=None, one_of=None):
3132
self.type = schema_type and SchemaType(schema_type)
3233
self.model = model
3334
self.properties = properties and dict(properties) or {}
@@ -39,6 +40,10 @@ def __init__(
3940
self.enum = enum
4041
self.deprecated = deprecated
4142
self.all_of = all_of and list(all_of) or []
43+
self.one_of = one_of and list(one_of) or []
44+
45+
self._all_required_properties_cache = None
46+
self._all_optional_properties_cache = None
4247

4348
def __getitem__(self, name):
4449
return self.properties[name]
@@ -52,14 +57,35 @@ def get_all_properties(self):
5257

5358
return properties
5459

60+
def get_all_properties_names(self):
61+
all_properties = self.get_all_properties()
62+
return set(all_properties.keys())
63+
5564
def get_all_required_properties(self):
65+
if self._all_required_properties_cache is None:
66+
self._all_required_properties_cache =\
67+
self._get_all_required_properties()
68+
69+
return self._all_required_properties_cache
70+
71+
def _get_all_required_properties(self):
72+
all_properties = self.get_all_properties()
73+
required = self.get_all_required_properties_names()
74+
75+
return dict(
76+
(prop_name, val)
77+
for prop_name, val in all_properties.items()
78+
if prop_name in required
79+
)
80+
81+
def get_all_required_properties_names(self):
5682
required = self.required.copy()
5783

5884
for subschema in self.all_of:
5985
subschema_req = subschema.get_all_required_properties()
6086
required += subschema_req
6187

62-
return required
88+
return set(required)
6389

6490
def get_cast_mapping(self):
6591
mapping = self.DEFAULT_CAST_CALLABLE_GETTER.copy()
@@ -119,27 +145,58 @@ def _unmarshal_object(self, value):
119145
raise InvalidSchemaValue(
120146
"Value of {0} not an object".format(value))
121147

122-
all_properties = self.get_all_properties()
123-
all_required_properties = self.get_all_required_properties()
124-
all_properties_keys = all_properties.keys()
125-
value_keys = value.keys()
148+
if self.one_of:
149+
properties = None
150+
for one_of_schema in self.one_of:
151+
try:
152+
found_props = self._unmarshal_properties(
153+
value, one_of_schema)
154+
except OpenAPISchemaError:
155+
pass
156+
else:
157+
if properties is not None:
158+
raise MultipleOneOfSchema(
159+
"Exactly one schema should be valid,"
160+
"multiple found")
161+
properties = found_props
162+
163+
if properties is None:
164+
raise NoOneOfSchema(
165+
"Exactly one valid schema should be valid, None found.")
166+
167+
else:
168+
properties = self._unmarshal_properties(value)
169+
170+
return ModelFactory().create(properties, name=self.model)
126171

127-
extra_props = set(value_keys) - set(all_properties_keys)
172+
def _unmarshal_properties(self, value, one_of_schema=None):
173+
all_props = self.get_all_properties()
174+
all_props_names = self.get_all_properties_names()
175+
all_req_props_names = self.get_all_required_properties_names()
128176

177+
if one_of_schema is not None:
178+
all_props.update(one_of_schema.get_all_properties())
179+
all_props_names |= one_of_schema.\
180+
get_all_properties_names()
181+
all_req_props_names |= one_of_schema.\
182+
get_all_required_properties_names()
183+
184+
value_props_names = value.keys()
185+
extra_props = set(value_props_names) - set(all_props_names)
129186
if extra_props:
130187
raise UndefinedSchemaProperty(
131188
"Undefined properties in schema: {0}".format(extra_props))
132189

133190
properties = {}
134-
for prop_name, prop in iteritems(all_properties):
191+
for prop_name, prop in iteritems(all_props):
135192
try:
136193
prop_value = value[prop_name]
137194
except KeyError:
138-
if prop_name in all_required_properties:
195+
if prop_name in all_req_props_names:
139196
raise MissingSchemaProperty(
140197
"Missing schema property {0}".format(prop_name))
141198
if not prop.nullable and not prop.default:
142199
continue
143200
prop_value = prop.default
144201
properties[prop_name] = prop.unmarshal(prop_value)
145-
return ModelFactory().create(properties, name=self.model)
202+
return properties

tests/integration/data/v3.0/petstore.yaml

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,22 @@ paths:
123123
$ref: "#/components/schemas/TagList"
124124
default:
125125
$ref: "#/components/responses/ErrorResponse"
126+
post:
127+
summary: Create new tag
128+
operationId: createTag
129+
tags:
130+
- tags
131+
requestBody:
132+
required: true
133+
content:
134+
application/json:
135+
schema:
136+
$ref: '#/components/schemas/TagCreate'
137+
responses:
138+
'200':
139+
description: Null response
140+
default:
141+
$ref: "#/components/responses/ErrorResponse"
126142
components:
127143
schemas:
128144
Address:
@@ -163,6 +179,9 @@ components:
163179
allOf:
164180
- $ref: "#/components/schemas/PetCreatePartOne"
165181
- $ref: "#/components/schemas/PetCreatePartTwo"
182+
oneOf:
183+
- $ref: "#/components/schemas/Cat"
184+
- $ref: "#/components/schemas/Bird"
166185
PetCreatePartOne:
167186
type: object
168187
x-model: PetCreatePartOne
@@ -183,6 +202,38 @@ components:
183202
$ref: "#/components/schemas/Position"
184203
healthy:
185204
type: boolean
205+
Bird:
206+
type: object
207+
x-model: Bird
208+
required:
209+
- wings
210+
properties:
211+
wings:
212+
$ref: "#/components/schemas/Wings"
213+
Wings:
214+
type: object
215+
x-model: Wings
216+
required:
217+
- healthy
218+
properties:
219+
healthy:
220+
type: boolean
221+
Cat:
222+
type: object
223+
x-model: Cat
224+
required:
225+
- ears
226+
properties:
227+
ears:
228+
$ref: "#/components/schemas/Ears"
229+
Ears:
230+
type: object
231+
x-model: Ears
232+
required:
233+
- healthy
234+
properties:
235+
healthy:
236+
type: boolean
186237
Pets:
187238
type: array
188239
items:
@@ -201,6 +252,13 @@ components:
201252
properties:
202253
data:
203254
$ref: "#/components/schemas/Pet"
255+
TagCreate:
256+
type: object
257+
required:
258+
- name
259+
properties:
260+
name:
261+
type: string
204262
TagList:
205263
type: array
206264
items:

0 commit comments

Comments
 (0)