openapi-core/openapi_core/schemas.py

184 lines
5.3 KiB
Python
Raw Normal View History

2017-09-21 11:51:37 +00:00
"""OpenAPI core schemas module"""
import logging
from collections import defaultdict
2017-09-22 08:54:37 +00:00
from distutils.util import strtobool
from functools import lru_cache
2017-09-21 11:51:37 +00:00
from json import loads
from six import iteritems
2017-09-25 14:15:00 +00:00
from openapi_core.exceptions import (
InvalidValueType, UndefinedSchemaProperty, MissingPropertyError,
)
from openapi_core.models import ModelFactory
2017-09-21 11:51:37 +00:00
log = logging.getLogger(__name__)
DEFAULT_CAST_CALLABLE_GETTER = {
'integer': int,
'number': float,
'boolean': lambda x: bool(strtobool(x)),
}
class Schema(object):
"""Represents an OpenAPI Schema."""
def __init__(
self, schema_type, model=None, properties=None, items=None,
2017-09-25 14:15:00 +00:00
spec_format=None, required=False, default=None):
2017-09-21 11:51:37 +00:00
self.type = schema_type
self.model = model
2017-09-21 11:51:37 +00:00
self.properties = properties and dict(properties) or {}
self.items = items
self.format = spec_format
self.required = required
2017-09-25 14:15:00 +00:00
self.default = default
2017-09-21 11:51:37 +00:00
def __getitem__(self, name):
return self.properties[name]
def get_cast_mapping(self):
mapping = DEFAULT_CAST_CALLABLE_GETTER.copy()
mapping.update({
'array': self._unmarshal_collection,
'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:
return None
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:
2017-09-25 14:15:00 +00:00
raise InvalidValueType(
2017-09-21 11:51:37 +00:00
"Failed to cast value of %s to %s", value, self.type,
)
def unmarshal(self, value):
"""Unmarshal parameter from the value."""
casted = self.cast(value)
if casted is None and not self.required:
return None
return casted
def _unmarshal_collection(self, value):
return list(map(self.items.unmarshal, value))
def _unmarshal_object(self, value):
if isinstance(value, (str, bytes)):
value = loads(value)
2017-09-25 14:15:00 +00:00
properties_keys = self.properties.keys()
value_keys = value.keys()
extra_props = set(value_keys) - set(properties_keys)
if extra_props:
raise UndefinedSchemaProperty(
"Undefined properties in schema: {0}".format(extra_props))
properties = {}
for prop_name, prop in iteritems(self.properties):
2017-09-25 14:15:00 +00:00
try:
prop_value = value[prop_name]
except KeyError:
if prop_name in self.required:
raise MissingPropertyError(
"Missing schema property {0}".format(prop_name))
prop_value = prop.default
properties[prop_name] = prop.unmarshal(prop_value)
return ModelFactory().create(properties, name=self.model)
2017-09-21 11:51:37 +00:00
class PropertiesGenerator(object):
def __init__(self, dereferencer):
self.dereferencer = dereferencer
def generate(self, properties):
for property_name, schema_spec in iteritems(properties):
schema = self._create_schema(schema_spec)
yield property_name, schema
def _create_schema(self, schema_spec):
return SchemaFactory(self.dereferencer).create(schema_spec)
class SchemaFactory(object):
def __init__(self, dereferencer):
self.dereferencer = dereferencer
def create(self, schema_spec):
schema_deref = self.dereferencer.dereference(schema_spec)
2017-09-21 11:51:37 +00:00
schema_type = schema_deref['type']
model = schema_deref.get('x-model', None)
2017-09-21 11:51:37 +00:00
required = schema_deref.get('required', False)
properties_spec = schema_deref.get('properties', None)
items_spec = schema_deref.get('items', None)
properties = None
if properties_spec:
2017-09-22 08:54:37 +00:00
properties = self.properties_generator.generate(properties_spec)
2017-09-21 11:51:37 +00:00
items = None
if items_spec:
items = self._create_items(items_spec)
return Schema(
schema_type, model=model, properties=properties, items=items,
required=required,
)
2017-09-21 11:51:37 +00:00
2017-09-22 08:54:37 +00:00
@property
@lru_cache()
def properties_generator(self):
return PropertiesGenerator(self.dereferencer)
2017-09-21 11:51:37 +00:00
def _create_items(self, items_spec):
return self.create(items_spec)
class SchemaRegistry(SchemaFactory):
def __init__(self, dereferencer):
super(SchemaRegistry, self).__init__(dereferencer)
self._schemas = {}
def get_or_create(self, schema_spec):
schema_deref = self.dereferencer.dereference(schema_spec)
model = schema_deref.get('x-model', None)
if model and model in self._schemas:
return self._schemas[model], False
return self.create(schema_deref), True
class SchemasGenerator(object):
2017-09-22 08:54:37 +00:00
def __init__(self, dereferencer, schemas_registry):
self.dereferencer = dereferencer
2017-09-22 08:54:37 +00:00
self.schemas_registry = schemas_registry
def generate(self, schemas_spec):
schemas_deref = self.dereferencer.dereference(schemas_spec)
for schema_name, schema_spec in iteritems(schemas_deref):
2017-09-22 08:54:37 +00:00
schema, _ = self.schemas_registry.get_or_create(schema_spec)
yield schema_name, schema