AllOf support

This commit is contained in:
Artur Maciag 2017-11-06 16:50:00 +00:00
parent cffa0b4528
commit 1739828559
3 changed files with 78 additions and 17 deletions

View file

@ -28,22 +28,32 @@ class Schema(object):
def __init__( def __init__(
self, schema_type, model=None, properties=None, items=None, self, schema_type, model=None, properties=None, items=None,
spec_format=None, required=False, default=None, nullable=False, spec_format=None, required=None, default=None, nullable=False,
enum=None, deprecated=False): enum=None, deprecated=False, all_of=None):
self.type = schema_type self.type = schema_type
self.model = model self.model = model
self.properties = properties and dict(properties) or {} self.properties = properties and dict(properties) or {}
self.items = items self.items = items
self.format = spec_format self.format = spec_format
self.required = required self.required = required or []
self.default = default self.default = default
self.nullable = nullable self.nullable = nullable
self.enum = enum self.enum = enum
self.deprecated = deprecated self.deprecated = deprecated
self.all_of = all_of and list(all_of) or []
def __getitem__(self, name): def __getitem__(self, name):
return self.properties[name] return self.properties[name]
def get_all_properties(self):
properties = self.properties.copy()
for subschema in self.all_of:
subschema_props = subschema.get_all_properties()
properties.update(subschema_props)
return properties
def get_cast_mapping(self): def get_cast_mapping(self):
mapping = DEFAULT_CAST_CALLABLE_GETTER.copy() mapping = DEFAULT_CAST_CALLABLE_GETTER.copy()
mapping.update({ mapping.update({
@ -101,17 +111,18 @@ class Schema(object):
if isinstance(value, (str, bytes)): if isinstance(value, (str, bytes)):
value = loads(value) value = loads(value)
properties_keys = self.properties.keys() all_properties = self.get_all_properties()
all_properties_keys = all_properties.keys()
value_keys = value.keys() value_keys = value.keys()
extra_props = set(value_keys) - set(properties_keys) extra_props = set(value_keys) - set(all_properties_keys)
if extra_props: if extra_props:
raise UndefinedSchemaProperty( raise UndefinedSchemaProperty(
"Undefined properties in schema: {0}".format(extra_props)) "Undefined properties in schema: {0}".format(extra_props))
properties = {} properties = {}
for prop_name, prop in iteritems(self.properties): for prop_name, prop in iteritems(all_properties):
try: try:
prop_value = value[prop_name] prop_value = value[prop_name]
except KeyError: except KeyError:
@ -156,11 +167,16 @@ class SchemaFactory(object):
nullable = schema_deref.get('nullable', False) nullable = schema_deref.get('nullable', False)
enum = schema_deref.get('enum', None) enum = schema_deref.get('enum', None)
deprecated = schema_deref.get('deprecated', False) deprecated = schema_deref.get('deprecated', False)
all_of_spec = schema_deref.get('allOf', None)
properties = None properties = None
if properties_spec: if properties_spec:
properties = self.properties_generator.generate(properties_spec) properties = self.properties_generator.generate(properties_spec)
all_of = []
if all_of_spec:
all_of = map(self.create, all_of_spec)
items = None items = None
if items_spec: if items_spec:
items = self._create_items(items_spec) items = self._create_items(items_spec)
@ -168,7 +184,7 @@ class SchemaFactory(object):
return Schema( return Schema(
schema_type, model=model, properties=properties, items=items, schema_type, model=model, properties=properties, items=items,
required=required, default=default, nullable=nullable, enum=enum, required=required, default=default, nullable=nullable, enum=enum,
deprecated=deprecated, deprecated=deprecated, all_of=all_of,
) )
@property @property

View file

@ -60,7 +60,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/Pets" $ref: "#/components/schemas/PetsData"
post: post:
summary: Create a pet summary: Create a pet
operationId: createPets operationId: createPets
@ -101,7 +101,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/Pets" $ref: "#/components/schemas/PetData"
default: default:
$ref: "#/components/responses/ErrorResponse" $ref: "#/components/responses/ErrorResponse"
components: components:
@ -154,12 +154,23 @@ components:
position: position:
$ref: "#/components/schemas/Position" $ref: "#/components/schemas/Position"
Pets: Pets:
type: array
items:
$ref: "#/components/schemas/Pet"
PetsData:
type: object type: object
required:
- data
properties: properties:
data: data:
type: array $ref: "#/components/schemas/Pets"
items: PetData:
$ref: "#/components/schemas/Pet" type: object
required:
- data
properties:
data:
$ref: "#/components/schemas/Pet"
Error: Error:
type: object type: object
required: required:

View file

@ -16,7 +16,8 @@ from openapi_core.responses import Response
from openapi_core.schemas import Schema from openapi_core.schemas import Schema
from openapi_core.servers import Server, ServerVariable from openapi_core.servers import Server, ServerVariable
from openapi_core.shortcuts import create_spec from openapi_core.shortcuts import create_spec
from openapi_core.wrappers import MockRequest from openapi_core.validators import RequestValidator, ResponseValidator
from openapi_core.wrappers import MockRequest, MockResponse
class TestPetstore(object): class TestPetstore(object):
@ -29,6 +30,14 @@ class TestPetstore(object):
def spec(self, spec_dict): def spec(self, spec_dict):
return create_spec(spec_dict) return create_spec(spec_dict)
@pytest.fixture
def request_validator(self, spec):
return RequestValidator(spec)
@pytest.fixture
def response_validator(self, spec):
return ResponseValidator(spec)
def test_spec(self, spec, spec_dict): def test_spec(self, spec, spec_dict):
url = 'http://petstore.swagger.io/v1' url = 'http://petstore.swagger.io/v1'
assert spec.info.title == spec_dict['info']['title'] assert spec.info.title == spec_dict['info']['title']
@ -99,7 +108,7 @@ class TestPetstore(object):
assert type(media_type.schema) == Schema assert type(media_type.schema) == Schema
assert media_type.schema.type == schema_spec['type'] assert media_type.schema.type == schema_spec['type']
assert media_type.schema.required == schema_spec.get( assert media_type.schema.required == schema_spec.get(
'required', False) 'required', [])
for parameter_name, parameter in iteritems( for parameter_name, parameter in iteritems(
response.headers): response.headers):
@ -121,7 +130,7 @@ class TestPetstore(object):
assert type(parameter.schema) == Schema assert type(parameter.schema) == Schema
assert parameter.schema.type == schema_spec['type'] assert parameter.schema.type == schema_spec['type']
assert parameter.schema.required == schema_spec.get( assert parameter.schema.required == schema_spec.get(
'required', False) 'required', [])
request_body_spec = operation_spec.get('requestBody') request_body_spec = operation_spec.get('requestBody')
@ -161,7 +170,7 @@ class TestPetstore(object):
for schema_name, schema in iteritems(spec.components.schemas): for schema_name, schema in iteritems(spec.components.schemas):
assert type(schema) == Schema assert type(schema) == Schema
def test_get_pets(self, spec): def test_get_pets(self, spec, response_validator):
host_url = 'http://petstore.swagger.io/v1' host_url = 'http://petstore.swagger.io/v1'
path_pattern = '/v1/pets' path_pattern = '/v1/pets'
query_params = { query_params = {
@ -187,6 +196,17 @@ class TestPetstore(object):
} }
assert body is None assert body is None
data_json = {
'data': [],
}
data = json.dumps(data_json)
response = MockResponse(data)
response_result = response_validator.validate(request, response)
assert response_result.errors == []
assert response_result.data == data_json
def test_get_pets_wrong_parameter_type(self, spec): def test_get_pets_wrong_parameter_type(self, spec):
host_url = 'http://petstore.swagger.io/v1' host_url = 'http://petstore.swagger.io/v1'
path_pattern = '/v1/pets' path_pattern = '/v1/pets'
@ -439,7 +459,7 @@ class TestPetstore(object):
with pytest.raises(InvalidServer): with pytest.raises(InvalidServer):
request.get_body(spec) request.get_body(spec)
def test_get_pet(self, spec): def test_get_pet(self, spec, response_validator):
host_url = 'http://petstore.swagger.io/v1' host_url = 'http://petstore.swagger.io/v1'
path_pattern = '/v1/pets/{petId}' path_pattern = '/v1/pets/{petId}'
view_args = { view_args = {
@ -461,3 +481,17 @@ class TestPetstore(object):
body = request.get_body(spec) body = request.get_body(spec)
assert body is None assert body is None
data_json = {
'data': {
'id': 1,
'name': 'test',
},
}
data = json.dumps(data_json)
response = MockResponse(data)
response_result = response_validator.validate(request, response)
assert response_result.errors == []
assert response_result.data == data_json