Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions openapi_core/components.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from openapi_core.schemas import SchemasGenerator


class Components(object):
"""Represents an OpenAPI Components in a service."""

def __init__(
self, schemas=None, responses=None, parameters=None,
request_bodies=None):
self.schemas = schemas and dict(schemas) or {}
self.responses = responses and dict(responses) or {}
self.parameters = parameters and dict(parameters) or {}
self.request_bodies = request_bodies and dict(request_bodies) or {}


class ComponentsFactory(object):

def __init__(self, dereferencer):
self.dereferencer = dereferencer

def create(self, components_spec):
components_deref = self.dereferencer.dereference(components_spec)

schemas_spec = components_deref.get('schemas', [])
responses_spec = components_deref.get('responses', [])
parameters_spec = components_deref.get('parameters', [])
request_bodies_spec = components_deref.get('request_bodies', [])

schemas = self._generate_schemas(schemas_spec)
responses = self._generate_response(responses_spec)
parameters = self._generate_parameters(parameters_spec)
request_bodies = self._generate_request_bodies(request_bodies_spec)
return Components(
schemas=list(schemas), responses=responses, parameters=parameters,
request_bodies=request_bodies,
)

def _generate_schemas(self, schemas_spec):
return SchemasGenerator(self.dereferencer).generate(schemas_spec)

def _generate_response(self, responses_spec):
return responses_spec

def _generate_parameters(self, parameters_spec):
return parameters_spec

def _generate_request_bodies(self, request_bodies_spec):
return request_bodies_spec
17 changes: 17 additions & 0 deletions openapi_core/infos.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class Info(object):

def __init__(self, title, version):
self.title = title
self.version = version


class InfoFactory(object):

def __init__(self, dereferencer):
self.dereferencer = dereferencer

def create(self, info_spec):
info_deref = self.dereferencer.dereference(info_spec)
title = info_deref['title']
version = info_deref['version']
return Info(title, version)
6 changes: 4 additions & 2 deletions openapi_core/media_types.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""OpenAPI core mediaTypes module"""
from six import iteritems

from openapi_core.schemas import SchemaFactory
from openapi_core.schemas import SchemaRegistry


class MediaType(object):
Expand Down Expand Up @@ -34,4 +34,6 @@ def generate(self, content):
yield content_type, MediaType(content_type, schema)

def _create_schema(self, schema_spec):
return SchemaFactory(self.dereferencer).create(schema_spec)
schema, _ = SchemaRegistry(self.dereferencer).get_or_create(
schema_spec)
return schema
27 changes: 27 additions & 0 deletions openapi_core/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""OpenAPI core models module"""


class BaseModel(dict):
"""Base class for OpenAPI models."""

def __getattr__(self, attr_name):
"""Only search through properties if attribute not found normally.
:type attr_name: str
"""
try:
return self[attr_name]
except KeyError:
raise AttributeError(
'type object {0!r} has no attribute {1!r}'
.format(type(self).__name__, attr_name)
)


class ModelFactory(object):

def create(self, properties, name=None):
model = BaseModel
if name is not None:
model = type(name, (BaseModel, ), {})

return model(**properties)
6 changes: 4 additions & 2 deletions openapi_core/parameters.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""OpenAPI core parameters module"""
import logging

from openapi_core.schemas import SchemaFactory
from openapi_core.schemas import SchemaRegistry

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -56,4 +56,6 @@ def generate(self, paramters):
)

def _create_schema(self, schema_spec):
return SchemaFactory(self.dereferencer).create(schema_spec)
schema, _ = SchemaRegistry(self.dereferencer).get_or_create(
schema_spec)
return schema
75 changes: 66 additions & 9 deletions openapi_core/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,25 @@
from json import loads
from six import iteritems

from openapi_core.models import ModelFactory

log = logging.getLogger(__name__)

DEFAULT_CAST_CALLABLE_GETTER = {
'integer': int,
'number': float,
'boolean': lambda x: bool(strtobool(x)),
'object': loads,
}


class Schema(object):
"""Represents an OpenAPI Schema."""

def __init__(
self, schema_type, properties=None, items=None, spec_format=None,
required=False):
self, schema_type, model=None, properties=None, items=None,
spec_format=None, required=False):
self.type = schema_type
self.model = model
self.properties = properties and dict(properties) or {}
self.items = items
self.format = spec_format
Expand All @@ -33,10 +35,10 @@ def __getitem__(self, name):

def get_cast_mapping(self):
mapping = DEFAULT_CAST_CALLABLE_GETTER.copy()
if self.items:
mapping.update({
'array': lambda x: list(map(self.items.unmarshal, x)),
})
mapping.update({
'array': self._unmarshal_collection,
'object': self._unmarshal_object,
})

return defaultdict(lambda: lambda x: x, mapping)

Expand Down Expand Up @@ -68,6 +70,19 @@ def unmarshal(self, value):

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)

properties = {}
for prop_name, prop in iteritems(self.properties):
prop_value = value.get(prop_name)
properties[prop_name] = prop.unmarshal(prop_value)
return ModelFactory().create(properties, name=self.model)


class PropertiesGenerator(object):

Expand All @@ -90,7 +105,9 @@ def __init__(self, dereferencer):

def create(self, schema_spec):
schema_deref = self.dereferencer.dereference(schema_spec)

schema_type = schema_deref['type']
model = schema_deref.get('x-model', None)
required = schema_deref.get('required', False)
properties_spec = schema_deref.get('properties', None)
items_spec = schema_deref.get('items', None)
Expand All @@ -104,10 +121,50 @@ def create(self, schema_spec):
items = self._create_items(items_spec)

return Schema(
schema_type, properties=properties, items=items, required=required)
schema_type, model=model, properties=properties, items=items,
required=required,
)

def _generate_properties(self, properties_spec):
return PropertiesGenerator(self.dereferencer).generate(properties_spec)

def _create_items(self, items_spec):
return SchemaFactory(self.dereferencer).create(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

def _create_items(self, items_spec):
schema, _ = self.get_or_create(items_spec)
return schema


class SchemasGenerator(object):

def __init__(self, dereferencer):
self.dereferencer = dereferencer

def generate(self, schemas_spec):
schemas_deref = self.dereferencer.dereference(schemas_spec)

for schema_name, schema_spec in iteritems(schemas_deref):
schema = self._create_schema(schema_spec)
yield schema_name, schema

def _create_schema(self, schema_spec):
schema, _ = SchemaRegistry(self.dereferencer).get_or_create(
schema_spec)
return schema
26 changes: 22 additions & 4 deletions openapi_core/specs.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

from openapi_spec_validator import openapi_v3_spec_validator

from openapi_core.components import ComponentsFactory
from openapi_core.infos import InfoFactory
from openapi_core.paths import PathsGenerator


Expand All @@ -14,9 +16,11 @@
class Spec(object):
"""Represents an OpenAPI Specification for a service."""

def __init__(self, servers=None, paths=None):
def __init__(self, info, paths, servers=None, components=None):
self.info = info
self.paths = paths and dict(paths)
self.servers = servers or []
self.paths = paths and dict(paths) or {}
self.components = components

def __getitem__(self, path_name):
return self.paths[path_name]
Expand All @@ -27,6 +31,9 @@ def get_server_url(self, index=0):
def get_operation(self, path_pattern, http_method):
return self.paths[path_pattern].operations[http_method]

def get_schema(self, name):
return self.components.schemas[name]

# operations shortcuts

get = partialmethod(get_operation, http_method='get')
Expand All @@ -50,11 +57,22 @@ def create(self, spec_dict, spec_url=''):

spec_dict_deref = self.dereferencer.dereference(spec_dict)

info_spec = spec_dict_deref.get('info', [])
servers = spec_dict_deref.get('servers', [])

paths = spec_dict_deref.get('paths', [])
components_spec = spec_dict_deref.get('components', [])

info = self._create_info(info_spec)
paths = self._generate_paths(paths)
return Spec(servers=servers, paths=list(paths))
components = self._create_components(components_spec)
spec = Spec(info, list(paths), servers=servers, components=components)
return spec

def _create_info(self, info_spec):
return InfoFactory(self.dereferencer).create(info_spec)

def _generate_paths(self, paths):
return PathsGenerator(self.dereferencer).generate(paths)

def _create_components(self, components_spec):
return ComponentsFactory(self.dereferencer).create(components_spec)
21 changes: 16 additions & 5 deletions tests/integration/data/v3.0/petstore.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -95,28 +95,39 @@ paths:
$ref: "#/components/schemas/Error"
components:
schemas:
Address:
type: object
x-model: Address
required:
- city
properties:
street:
type: string
city:
type: string
Pet:
type: object
x-model: Pet
allOf:
- $ref: "#/components/schemas/PetCreate"
required:
- id
- name
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
PetCreate:
type: object
x-model: PetCreate
required:
- name
properties:
name:
type: string
tag:
type: string
address:
$ref: "#/components/schemas/Address"
Pets:
type: array
items:
Expand Down
Loading