Skip to content

Commit 1739828

Browse files
committed
AllOf support
1 parent cffa0b4 commit 1739828

File tree

3 files changed

+78
-17
lines changed

3 files changed

+78
-17
lines changed

openapi_core/schemas.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,32 @@ class Schema(object):
2828

2929
def __init__(
3030
self, schema_type, model=None, properties=None, items=None,
31-
spec_format=None, required=False, default=None, nullable=False,
32-
enum=None, deprecated=False):
31+
spec_format=None, required=None, default=None, nullable=False,
32+
enum=None, deprecated=False, all_of=None):
3333
self.type = schema_type
3434
self.model = model
3535
self.properties = properties and dict(properties) or {}
3636
self.items = items
3737
self.format = spec_format
38-
self.required = required
38+
self.required = required or []
3939
self.default = default
4040
self.nullable = nullable
4141
self.enum = enum
4242
self.deprecated = deprecated
43+
self.all_of = all_of and list(all_of) or []
4344

4445
def __getitem__(self, name):
4546
return self.properties[name]
4647

48+
def get_all_properties(self):
49+
properties = self.properties.copy()
50+
51+
for subschema in self.all_of:
52+
subschema_props = subschema.get_all_properties()
53+
properties.update(subschema_props)
54+
55+
return properties
56+
4757
def get_cast_mapping(self):
4858
mapping = DEFAULT_CAST_CALLABLE_GETTER.copy()
4959
mapping.update({
@@ -101,17 +111,18 @@ def _unmarshal_object(self, value):
101111
if isinstance(value, (str, bytes)):
102112
value = loads(value)
103113

104-
properties_keys = self.properties.keys()
114+
all_properties = self.get_all_properties()
115+
all_properties_keys = all_properties.keys()
105116
value_keys = value.keys()
106117

107-
extra_props = set(value_keys) - set(properties_keys)
118+
extra_props = set(value_keys) - set(all_properties_keys)
108119

109120
if extra_props:
110121
raise UndefinedSchemaProperty(
111122
"Undefined properties in schema: {0}".format(extra_props))
112123

113124
properties = {}
114-
for prop_name, prop in iteritems(self.properties):
125+
for prop_name, prop in iteritems(all_properties):
115126
try:
116127
prop_value = value[prop_name]
117128
except KeyError:
@@ -156,19 +167,24 @@ def create(self, schema_spec):
156167
nullable = schema_deref.get('nullable', False)
157168
enum = schema_deref.get('enum', None)
158169
deprecated = schema_deref.get('deprecated', False)
170+
all_of_spec = schema_deref.get('allOf', None)
159171

160172
properties = None
161173
if properties_spec:
162174
properties = self.properties_generator.generate(properties_spec)
163175

176+
all_of = []
177+
if all_of_spec:
178+
all_of = map(self.create, all_of_spec)
179+
164180
items = None
165181
if items_spec:
166182
items = self._create_items(items_spec)
167183

168184
return Schema(
169185
schema_type, model=model, properties=properties, items=items,
170186
required=required, default=default, nullable=nullable, enum=enum,
171-
deprecated=deprecated,
187+
deprecated=deprecated, all_of=all_of,
172188
)
173189

174190
@property

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

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ paths:
6060
content:
6161
application/json:
6262
schema:
63-
$ref: "#/components/schemas/Pets"
63+
$ref: "#/components/schemas/PetsData"
6464
post:
6565
summary: Create a pet
6666
operationId: createPets
@@ -101,7 +101,7 @@ paths:
101101
content:
102102
application/json:
103103
schema:
104-
$ref: "#/components/schemas/Pets"
104+
$ref: "#/components/schemas/PetData"
105105
default:
106106
$ref: "#/components/responses/ErrorResponse"
107107
components:
@@ -154,12 +154,23 @@ components:
154154
position:
155155
$ref: "#/components/schemas/Position"
156156
Pets:
157+
type: array
158+
items:
159+
$ref: "#/components/schemas/Pet"
160+
PetsData:
157161
type: object
162+
required:
163+
- data
164+
properties:
165+
data:
166+
$ref: "#/components/schemas/Pets"
167+
PetData:
168+
type: object
169+
required:
170+
- data
158171
properties:
159172
data:
160-
type: array
161-
items:
162-
$ref: "#/components/schemas/Pet"
173+
$ref: "#/components/schemas/Pet"
163174
Error:
164175
type: object
165176
required:

tests/integration/test_petstore.py

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
from openapi_core.schemas import Schema
1717
from openapi_core.servers import Server, ServerVariable
1818
from openapi_core.shortcuts import create_spec
19-
from openapi_core.wrappers import MockRequest
19+
from openapi_core.validators import RequestValidator, ResponseValidator
20+
from openapi_core.wrappers import MockRequest, MockResponse
2021

2122

2223
class TestPetstore(object):
@@ -29,6 +30,14 @@ def spec_dict(self, factory):
2930
def spec(self, spec_dict):
3031
return create_spec(spec_dict)
3132

33+
@pytest.fixture
34+
def request_validator(self, spec):
35+
return RequestValidator(spec)
36+
37+
@pytest.fixture
38+
def response_validator(self, spec):
39+
return ResponseValidator(spec)
40+
3241
def test_spec(self, spec, spec_dict):
3342
url = 'http://petstore.swagger.io/v1'
3443
assert spec.info.title == spec_dict['info']['title']
@@ -99,7 +108,7 @@ def test_spec(self, spec, spec_dict):
99108
assert type(media_type.schema) == Schema
100109
assert media_type.schema.type == schema_spec['type']
101110
assert media_type.schema.required == schema_spec.get(
102-
'required', False)
111+
'required', [])
103112

104113
for parameter_name, parameter in iteritems(
105114
response.headers):
@@ -121,7 +130,7 @@ def test_spec(self, spec, spec_dict):
121130
assert type(parameter.schema) == Schema
122131
assert parameter.schema.type == schema_spec['type']
123132
assert parameter.schema.required == schema_spec.get(
124-
'required', False)
133+
'required', [])
125134

126135
request_body_spec = operation_spec.get('requestBody')
127136

@@ -161,7 +170,7 @@ def test_spec(self, spec, spec_dict):
161170
for schema_name, schema in iteritems(spec.components.schemas):
162171
assert type(schema) == Schema
163172

164-
def test_get_pets(self, spec):
173+
def test_get_pets(self, spec, response_validator):
165174
host_url = 'http://petstore.swagger.io/v1'
166175
path_pattern = '/v1/pets'
167176
query_params = {
@@ -187,6 +196,17 @@ def test_get_pets(self, spec):
187196
}
188197
assert body is None
189198

199+
data_json = {
200+
'data': [],
201+
}
202+
data = json.dumps(data_json)
203+
response = MockResponse(data)
204+
205+
response_result = response_validator.validate(request, response)
206+
207+
assert response_result.errors == []
208+
assert response_result.data == data_json
209+
190210
def test_get_pets_wrong_parameter_type(self, spec):
191211
host_url = 'http://petstore.swagger.io/v1'
192212
path_pattern = '/v1/pets'
@@ -439,7 +459,7 @@ def test_post_pets_raises_invalid_server_error(self, spec):
439459
with pytest.raises(InvalidServer):
440460
request.get_body(spec)
441461

442-
def test_get_pet(self, spec):
462+
def test_get_pet(self, spec, response_validator):
443463
host_url = 'http://petstore.swagger.io/v1'
444464
path_pattern = '/v1/pets/{petId}'
445465
view_args = {
@@ -461,3 +481,17 @@ def test_get_pet(self, spec):
461481
body = request.get_body(spec)
462482

463483
assert body is None
484+
485+
data_json = {
486+
'data': {
487+
'id': 1,
488+
'name': 'test',
489+
},
490+
}
491+
data = json.dumps(data_json)
492+
response = MockResponse(data)
493+
494+
response_result = response_validator.validate(request, response)
495+
496+
assert response_result.errors == []
497+
assert response_result.data == data_json

0 commit comments

Comments
 (0)