openapi-core/openapi_core/schema/schemas/models.py

146 lines
4.7 KiB
Python
Raw Normal View History

2018-04-17 12:18:40 +00:00
"""OpenAPI core schemas models module"""
2017-09-21 11:51:37 +00:00
import logging
from collections import defaultdict
2017-10-17 13:23:26 +00:00
import warnings
2017-09-21 11:51:37 +00:00
from six import iteritems
2018-04-17 12:18:40 +00:00
from openapi_core.extensions.models.factories import ModelFactory
from openapi_core.schema.schemas.enums import SchemaType, SchemaFormat
2018-04-18 10:39:03 +00:00
from openapi_core.schema.schemas.exceptions import (
InvalidSchemaValue, UndefinedSchemaProperty, MissingSchemaProperty,
)
2018-04-17 12:18:40 +00:00
from openapi_core.schema.schemas.util import forcebool
2017-09-21 11:51:37 +00:00
log = logging.getLogger(__name__)
2018-02-28 12:01:05 +00:00
2017-09-21 11:51:37 +00:00
class Schema(object):
"""Represents an OpenAPI Schema."""
2018-04-17 12:18:40 +00:00
DEFAULT_CAST_CALLABLE_GETTER = {
SchemaType.INTEGER: int,
SchemaType.NUMBER: float,
SchemaType.BOOLEAN: forcebool,
}
2017-09-21 11:51:37 +00:00
def __init__(
2018-04-04 10:26:21 +00:00
self, schema_type=None, model=None, properties=None, items=None,
2017-11-14 13:36:05 +00:00
schema_format=None, required=None, default=None, nullable=False,
2017-11-06 16:50:00 +00:00
enum=None, deprecated=False, all_of=None):
2018-04-04 10:26:21 +00:00
self.type = schema_type and SchemaType(schema_type)
self.model = model
2017-09-21 11:51:37 +00:00
self.properties = properties and dict(properties) or {}
self.items = items
2017-11-14 13:36:05 +00:00
self.format = SchemaFormat(schema_format)
2017-11-06 16:50:00 +00:00
self.required = required or []
2017-09-25 14:15:00 +00:00
self.default = default
2017-10-17 13:02:21 +00:00
self.nullable = nullable
2017-10-17 13:23:26 +00:00
self.enum = enum
2017-10-17 13:33:46 +00:00
self.deprecated = deprecated
2017-11-06 16:50:00 +00:00
self.all_of = all_of and list(all_of) or []
2017-09-21 11:51:37 +00:00
def __getitem__(self, name):
return self.properties[name]
2017-11-06 16:50:00 +00:00
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_all_required_properties(self):
required = self.required.copy()
for subschema in self.all_of:
subschema_req = subschema.get_all_required_properties()
required += subschema_req
return required
2017-09-21 11:51:37 +00:00
def get_cast_mapping(self):
2018-04-17 12:18:40 +00:00
mapping = self.DEFAULT_CAST_CALLABLE_GETTER.copy()
mapping.update({
2017-11-14 13:36:05 +00:00
SchemaType.ARRAY: self._unmarshal_collection,
SchemaType.OBJECT: self._unmarshal_object,
})
2017-09-21 11:51:37 +00:00
return defaultdict(lambda: lambda x: x, mapping)
def cast(self, value):
"""Cast value to schema type"""
if value is None:
2017-10-17 13:02:21 +00:00
if not self.nullable:
2018-04-18 10:39:03 +00:00
raise InvalidSchemaValue("Null value for non-nullable schema")
2017-10-17 13:02:21 +00:00
return self.default
2017-09-21 11:51:37 +00:00
2018-04-04 10:26:21 +00:00
if self.type is None:
return value
2017-09-21 11:51:37 +00:00
cast_mapping = self.get_cast_mapping()
if self.type in cast_mapping and value == '':
return None
cast_callable = cast_mapping[self.type]
try:
return cast_callable(value)
except ValueError:
2018-04-18 10:39:03 +00:00
raise InvalidSchemaValue(
2017-11-03 11:18:48 +00:00
"Failed to cast value of {0} to {1}".format(value, self.type)
2017-09-21 11:51:37 +00:00
)
def unmarshal(self, value):
"""Unmarshal parameter from the value."""
2017-10-17 13:33:46 +00:00
if self.deprecated:
warnings.warn(
"The schema is deprecated", DeprecationWarning)
2017-09-21 11:51:37 +00:00
casted = self.cast(value)
if casted is None and not self.required:
return None
2017-10-17 13:23:26 +00:00
if self.enum and casted not in self.enum:
2018-04-18 10:39:03 +00:00
raise InvalidSchemaValue(
2017-11-03 11:18:48 +00:00
"Value of {0} not in enum choices: {1}".format(
value, self.enum)
2017-10-17 13:23:26 +00:00
)
2017-09-21 11:51:37 +00:00
return casted
def _unmarshal_collection(self, value):
return list(map(self.items.unmarshal, value))
def _unmarshal_object(self, value):
2018-04-23 18:50:29 +00:00
if not isinstance(value, (dict, )):
2018-04-18 10:39:03 +00:00
raise InvalidSchemaValue(
"Value of {0} not an object".format(value))
2017-11-06 16:50:00 +00:00
all_properties = self.get_all_properties()
all_required_properties = self.get_all_required_properties()
2017-11-06 16:50:00 +00:00
all_properties_keys = all_properties.keys()
2017-09-25 14:15:00 +00:00
value_keys = value.keys()
2017-11-06 16:50:00 +00:00
extra_props = set(value_keys) - set(all_properties_keys)
2017-09-25 14:15:00 +00:00
if extra_props:
raise UndefinedSchemaProperty(
"Undefined properties in schema: {0}".format(extra_props))
properties = {}
2017-11-06 16:50:00 +00:00
for prop_name, prop in iteritems(all_properties):
2017-09-25 14:15:00 +00:00
try:
prop_value = value[prop_name]
except KeyError:
if prop_name in all_required_properties:
2018-04-18 10:39:03 +00:00
raise MissingSchemaProperty(
2017-09-25 14:15:00 +00:00
"Missing schema property {0}".format(prop_name))
2017-10-17 13:02:21 +00:00
if not prop.nullable and not prop.default:
continue
2017-09-25 14:15:00 +00:00
prop_value = prop.default
properties[prop_name] = prop.unmarshal(prop_value)
return ModelFactory().create(properties, name=self.model)