Merge pull request #25 from p1c2u/feature/restructure

Restructure
This commit is contained in:
A 2018-05-25 12:37:44 +02:00 committed by GitHub
commit 5d9a671ca1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
83 changed files with 1236 additions and 1070 deletions

View file

@ -1,77 +0,0 @@
"""OpenAPI core exceptions module"""
class OpenAPIError(Exception):
pass
class OpenAPIMappingError(OpenAPIError):
pass
class OpenAPIServerError(OpenAPIMappingError):
pass
class OpenAPIOperationError(OpenAPIMappingError):
pass
class InvalidValueType(OpenAPIMappingError):
pass
class OpenAPIParameterError(OpenAPIMappingError):
pass
class OpenAPIBodyError(OpenAPIMappingError):
pass
class InvalidServer(OpenAPIServerError):
pass
class InvalidOperation(OpenAPIOperationError):
pass
class EmptyValue(OpenAPIParameterError):
pass
class MissingParameter(OpenAPIParameterError):
pass
class InvalidParameterValue(OpenAPIParameterError):
pass
class MissingBody(OpenAPIBodyError):
pass
class InvalidMediaTypeValue(OpenAPIBodyError):
pass
class UndefinedSchemaProperty(OpenAPIBodyError):
pass
class MissingProperty(OpenAPIBodyError):
pass
class InvalidContentType(OpenAPIBodyError):
pass
class InvalidResponse(OpenAPIMappingError):
pass
class InvalidValue(OpenAPIMappingError):
pass

View file

@ -0,0 +1 @@
"""OpenAPI extensions package"""

View file

@ -0,0 +1 @@
"""OpenAPI X-Model extension package"""

View file

@ -0,0 +1,12 @@
"""OpenAPI X-Model extension factories module"""
from openapi_core.extensions.models.models import BaseModel
class ModelFactory(object):
def create(self, properties, name=None):
model = BaseModel
if name is not None:
model = type(name, (BaseModel, ), {})
return model(**properties)

View file

@ -1,8 +1,8 @@
"""OpenAPI core models module"""
"""OpenAPI X-Model extension models module"""
class BaseModel(dict):
"""Base class for OpenAPI models."""
"""Base class for OpenAPI X-Model."""
def __getattr__(self, attr_name):
"""Only search through properties if attribute not found normally.
@ -15,13 +15,3 @@ class BaseModel(dict):
'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)

View file

@ -1,149 +0,0 @@
"""OpenAPI core parameters module"""
import logging
import warnings
from functools import lru_cache
from six import iteritems
from openapi_core.enums import ParameterLocation, ParameterStyle, SchemaType
from openapi_core.exceptions import (
EmptyValue, InvalidValueType, InvalidParameterValue,
)
log = logging.getLogger(__name__)
PARAMETER_STYLE_DESERIALIZERS = {
ParameterStyle.FORM: lambda x: x.split(','),
ParameterStyle.SIMPLE: lambda x: x.split(','),
ParameterStyle.SPACE_DELIMITED: lambda x: x.split(' '),
ParameterStyle.PIPE_DELIMITED: lambda x: x.split('|'),
}
class Parameter(object):
"""Represents an OpenAPI operation Parameter."""
def __init__(
self, name, location, schema=None, required=False,
deprecated=False, allow_empty_value=False,
items=None, style=None, explode=None):
self.name = name
self.location = ParameterLocation(location)
self.schema = schema
self.required = (
True if self.location == ParameterLocation.PATH else required
)
self.deprecated = deprecated
self.allow_empty_value = (
allow_empty_value if self.location == ParameterLocation.QUERY
else False
)
self.items = items
self.style = ParameterStyle(style or self.default_style)
self.explode = self.default_explode if explode is None else explode
@property
def aslist(self):
return (
self.schema and
self.schema.type in [SchemaType.ARRAY, SchemaType.OBJECT]
)
@property
def default_style(self):
simple_locations = [ParameterLocation.PATH, ParameterLocation.HEADER]
return (
'simple' if self.location in simple_locations else "form"
)
@property
def default_explode(self):
return self.style == ParameterStyle.FORM
def get_dererializer(self):
return PARAMETER_STYLE_DESERIALIZERS[self.style]
def deserialize(self, value):
if not self.aslist or self.explode:
return value
deserializer = self.get_dererializer()
return deserializer(value)
def unmarshal(self, value):
if self.deprecated:
warnings.warn(
"{0} parameter is deprecated".format(self.name),
DeprecationWarning,
)
if (self.location == ParameterLocation.QUERY and value == "" and
not self.allow_empty_value):
raise EmptyValue(
"Value of {0} parameter cannot be empty".format(self.name))
if not self.schema:
return value
deserialized = self.deserialize(value)
try:
return self.schema.unmarshal(deserialized)
except InvalidValueType as exc:
raise InvalidParameterValue(str(exc))
class ParameterFactory(object):
def __init__(self, dereferencer, schemas_registry):
self.dereferencer = dereferencer
self.schemas_registry = schemas_registry
def create(self, parameter_spec, parameter_name=None):
parameter_deref = self.dereferencer.dereference(parameter_spec)
parameter_name = parameter_name or parameter_deref['name']
parameter_in = parameter_deref.get('in', 'header')
allow_empty_value = parameter_deref.get('allowEmptyValue')
required = parameter_deref.get('required', False)
style = parameter_deref.get('style')
explode = parameter_deref.get('explode')
schema_spec = parameter_deref.get('schema', None)
schema = None
if schema_spec:
schema, _ = self.schemas_registry.get_or_create(schema_spec)
return Parameter(
parameter_name, parameter_in,
schema=schema, required=required,
allow_empty_value=allow_empty_value,
style=style, explode=explode,
)
class ParametersGenerator(object):
def __init__(self, dereferencer, schemas_registry):
self.dereferencer = dereferencer
self.schemas_registry = schemas_registry
def generate(self, parameters):
for parameter_name, parameter_spec in iteritems(parameters):
parameter = self.parameter_factory.create(
parameter_spec, parameter_name=parameter_name)
yield (parameter_name, parameter)
def generate_from_list(self, parameters_list):
for parameter_spec in parameters_list:
parameter = self.parameter_factory.create(parameter_spec)
yield (parameter.name, parameter)
@property
@lru_cache()
def parameter_factory(self):
return ParameterFactory(self.dereferencer, self.schemas_registry)

View file

View file

@ -1,18 +1,7 @@
from functools import lru_cache
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 {}
from openapi_core.schema.components.models import Components
from openapi_core.schema.schemas.generators import SchemasGenerator
class ComponentsFactory(object):

View file

@ -0,0 +1,10 @@
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 {}

View file

@ -0,0 +1,9 @@
"""OpenAPI core schema exceptions module"""
class OpenAPIError(Exception):
pass
class OpenAPIMappingError(OpenAPIError):
pass

View file

View file

@ -1,8 +1,5 @@
class Info(object):
def __init__(self, title, version):
self.title = title
self.version = version
"""OpenAPI core infos factories module"""
from openapi_core.schema.infos.models import Info
class InfoFactory(object):

View file

@ -0,0 +1,8 @@
"""OpenAPI core infos models module"""
class Info(object):
def __init__(self, title, version):
self.title = title
self.version = version

View file

@ -0,0 +1,13 @@
from openapi_core.schema.exceptions import OpenAPIMappingError
class OpenAPIMediaTypeError(OpenAPIMappingError):
pass
class InvalidMediaTypeValue(OpenAPIMediaTypeError):
pass
class InvalidContentType(OpenAPIMediaTypeError):
pass

View file

@ -0,0 +1,21 @@
"""OpenAPI core media types generators module"""
from six import iteritems
from openapi_core.schema.media_types.models import MediaType
class MediaTypeGenerator(object):
def __init__(self, dereferencer, schemas_registry):
self.dereferencer = dereferencer
self.schemas_registry = schemas_registry
def generate(self, content):
for mimetype, media_type in iteritems(content):
schema_spec = media_type.get('schema')
schema = None
if schema_spec:
schema, _ = self.schemas_registry.get_or_create(schema_spec)
yield mimetype, MediaType(mimetype, schema)

View file

@ -1,10 +1,10 @@
"""OpenAPI core mediaTypes module"""
"""OpenAPI core media types models module"""
from collections import defaultdict
from json import loads
from six import iteritems
from openapi_core.exceptions import InvalidValueType, InvalidMediaTypeValue
from openapi_core.schema.media_types.exceptions import InvalidMediaTypeValue
from openapi_core.schema.schemas.exceptions import InvalidSchemaValue
MEDIA_TYPE_DESERIALIZERS = {
@ -42,22 +42,5 @@ class MediaType(object):
try:
return self.schema.unmarshal(deserialized)
except InvalidValueType as exc:
except InvalidSchemaValue as exc:
raise InvalidMediaTypeValue(str(exc))
class MediaTypeGenerator(object):
def __init__(self, dereferencer, schemas_registry):
self.dereferencer = dereferencer
self.schemas_registry = schemas_registry
def generate(self, content):
for mimetype, media_type in iteritems(content):
schema_spec = media_type.get('schema')
schema = None
if schema_spec:
schema, _ = self.schemas_registry.get_or_create(schema_spec)
yield mimetype, MediaType(mimetype, schema)

View file

@ -0,0 +1,9 @@
from openapi_core.schema.exceptions import OpenAPIMappingError
class OpenAPIOperationError(OpenAPIMappingError):
pass
class InvalidOperation(OpenAPIOperationError):
pass

View file

@ -1,50 +1,14 @@
# -*- coding: utf-8 -*-
"""OpenAPI core operations module"""
import logging
"""OpenAPI core operations models module"""
from functools import lru_cache
from six import iteritems
from openapi_spec_validator.validators import PathItemValidator
from openapi_core.exceptions import InvalidResponse
from openapi_core.parameters import ParametersGenerator
from openapi_core.request_bodies import RequestBodyFactory
from openapi_core.responses import ResponsesGenerator
log = logging.getLogger(__name__)
class Operation(object):
"""Represents an OpenAPI Operation."""
def __init__(
self, http_method, path_name, responses, parameters,
request_body=None, deprecated=False, operation_id=None):
self.http_method = http_method
self.path_name = path_name
self.responses = dict(responses)
self.parameters = dict(parameters)
self.request_body = request_body
self.deprecated = deprecated
self.operation_id = operation_id
def __getitem__(self, name):
return self.parameters[name]
def get_response(self, http_status='default'):
try:
return self.responses[http_status]
except KeyError:
# try range
http_status_range = '{0}XX'.format(http_status[0])
if http_status_range in self.responses:
return self.responses[http_status_range]
if 'default' not in self.responses:
raise InvalidResponse(
"Unknown response http status {0}".format(http_status))
return self.responses['default']
from openapi_core.schema.operations.models import Operation
from openapi_core.schema.parameters.generators import ParametersGenerator
from openapi_core.schema.request_bodies.factories import RequestBodyFactory
from openapi_core.schema.responses.generators import ResponsesGenerator
class OperationsGenerator(object):

View file

@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
"""OpenAPI core operations models module"""
from openapi_core.schema.responses.exceptions import InvalidResponse
class Operation(object):
"""Represents an OpenAPI Operation."""
def __init__(
self, http_method, path_name, responses, parameters,
request_body=None, deprecated=False, operation_id=None):
self.http_method = http_method
self.path_name = path_name
self.responses = dict(responses)
self.parameters = dict(parameters)
self.request_body = request_body
self.deprecated = deprecated
self.operation_id = operation_id
def __getitem__(self, name):
return self.parameters[name]
def get_response(self, http_status='default'):
# @todo: move to Responses object
try:
return self.responses[http_status]
except KeyError:
# try range
http_status_range = '{0}XX'.format(http_status[0])
if http_status_range in self.responses:
return self.responses[http_status_range]
if 'default' not in self.responses:
raise InvalidResponse(
"Unknown response http status {0}".format(http_status))
return self.responses['default']

View file

@ -1,3 +1,4 @@
"""OpenAPI core parameters enums module"""
from enum import Enum
@ -22,27 +23,3 @@ class ParameterStyle(Enum):
SPACE_DELIMITED = 'spaceDelimited'
PIPE_DELIMITED = 'pipeDelimited'
DEEP_OBJECT = 'deepObject'
class SchemaType(Enum):
INTEGER = 'integer'
NUMBER = 'number'
STRING = 'string'
BOOLEAN = 'boolean'
ARRAY = 'array'
OBJECT = 'object'
class SchemaFormat(Enum):
NONE = None
INT32 = 'int32'
INT64 = 'int64'
FLOAT = 'float'
DOUBLE = 'double'
BYTE = 'byte'
BINARY = 'binary'
DATE = 'date'
DATETIME = 'date-time'
PASSWORD = 'password'

View file

@ -0,0 +1,21 @@
from openapi_core.schema.exceptions import OpenAPIMappingError
class OpenAPIParameterError(OpenAPIMappingError):
pass
class MissingParameter(OpenAPIParameterError):
pass
class MissingRequiredParameter(OpenAPIParameterError):
pass
class EmptyParameterValue(OpenAPIParameterError):
pass
class InvalidParameterValue(OpenAPIParameterError):
pass

View file

@ -0,0 +1,33 @@
"""OpenAPI core parameters factories module"""
from openapi_core.schema.parameters.models import Parameter
class ParameterFactory(object):
def __init__(self, dereferencer, schemas_registry):
self.dereferencer = dereferencer
self.schemas_registry = schemas_registry
def create(self, parameter_spec, parameter_name=None):
parameter_deref = self.dereferencer.dereference(parameter_spec)
parameter_name = parameter_name or parameter_deref['name']
parameter_in = parameter_deref.get('in', 'header')
allow_empty_value = parameter_deref.get('allowEmptyValue')
required = parameter_deref.get('required', False)
style = parameter_deref.get('style')
explode = parameter_deref.get('explode')
schema_spec = parameter_deref.get('schema', None)
schema = None
if schema_spec:
schema, _ = self.schemas_registry.get_or_create(schema_spec)
return Parameter(
parameter_name, parameter_in,
schema=schema, required=required,
allow_empty_value=allow_empty_value,
style=style, explode=explode,
)

View file

@ -0,0 +1,31 @@
"""OpenAPI core parameters generators module"""
from functools import lru_cache
from six import iteritems
from openapi_core.schema.parameters.factories import ParameterFactory
class ParametersGenerator(object):
def __init__(self, dereferencer, schemas_registry):
self.dereferencer = dereferencer
self.schemas_registry = schemas_registry
def generate(self, parameters):
for parameter_name, parameter_spec in iteritems(parameters):
parameter = self.parameter_factory.create(
parameter_spec, parameter_name=parameter_name)
yield (parameter_name, parameter)
def generate_from_list(self, parameters_list):
for parameter_spec in parameters_list:
parameter = self.parameter_factory.create(parameter_spec)
yield (parameter.name, parameter)
@property
@lru_cache()
def parameter_factory(self):
return ParameterFactory(self.dereferencer, self.schemas_registry)

View file

@ -0,0 +1,116 @@
"""OpenAPI core parameters models module"""
import logging
import warnings
from openapi_core.schema.parameters.enums import (
ParameterLocation, ParameterStyle,
)
from openapi_core.schema.parameters.exceptions import (
MissingRequiredParameter, MissingParameter, InvalidParameterValue,
EmptyParameterValue,
)
from openapi_core.schema.schemas.enums import SchemaType
from openapi_core.schema.schemas.exceptions import InvalidSchemaValue
log = logging.getLogger(__name__)
class Parameter(object):
"""Represents an OpenAPI operation Parameter."""
PARAMETER_STYLE_DESERIALIZERS = {
ParameterStyle.FORM: lambda x: x.split(','),
ParameterStyle.SIMPLE: lambda x: x.split(','),
ParameterStyle.SPACE_DELIMITED: lambda x: x.split(' '),
ParameterStyle.PIPE_DELIMITED: lambda x: x.split('|'),
}
def __init__(
self, name, location, schema=None, required=False,
deprecated=False, allow_empty_value=False,
items=None, style=None, explode=None):
self.name = name
self.location = ParameterLocation(location)
self.schema = schema
self.required = (
True if self.location == ParameterLocation.PATH else required
)
self.deprecated = deprecated
self.allow_empty_value = (
allow_empty_value if self.location == ParameterLocation.QUERY
else False
)
self.items = items
self.style = ParameterStyle(style or self.default_style)
self.explode = self.default_explode if explode is None else explode
@property
def aslist(self):
return (
self.schema and
self.schema.type in [SchemaType.ARRAY, SchemaType.OBJECT]
)
@property
def default_style(self):
simple_locations = [ParameterLocation.PATH, ParameterLocation.HEADER]
return (
'simple' if self.location in simple_locations else "form"
)
@property
def default_explode(self):
return self.style == ParameterStyle.FORM
def get_dererializer(self):
return self.PARAMETER_STYLE_DESERIALIZERS[self.style]
def deserialize(self, value):
if not self.aslist or self.explode:
return value
deserializer = self.get_dererializer()
return deserializer(value)
def get_value(self, request):
location = request.parameters[self.location.value]
try:
raw = location[self.name]
except KeyError:
if self.required:
raise MissingRequiredParameter(
"Missing required `{0}` parameter".format(self.name))
if not self.schema or self.schema.default is None:
raise MissingParameter(
"Missing `{0}` parameter".format(self.name))
raw = self.schema.default
if self.aslist and self.explode:
return location.getlist(self.name)
return raw
def unmarshal(self, value):
if self.deprecated:
warnings.warn(
"{0} parameter is deprecated".format(self.name),
DeprecationWarning,
)
if (self.location == ParameterLocation.QUERY and value == "" and
not self.allow_empty_value):
raise EmptyParameterValue(
"Value of {0} parameter cannot be empty".format(self.name))
if not self.schema:
return value
deserialized = self.deserialize(value)
try:
return self.schema.unmarshal(deserialized)
except InvalidSchemaValue as exc:
raise InvalidParameterValue(str(exc))

View file

View file

@ -1,20 +1,10 @@
"""OpenAPI core paths module"""
"""OpenAPI core paths generators module"""
from functools import lru_cache
from six import iteritems
from openapi_core.operations import OperationsGenerator
class Path(object):
"""Represents an OpenAPI Path."""
def __init__(self, name, operations):
self.name = name
self.operations = dict(operations)
def __getitem__(self, http_method):
return self.operations[http_method]
from openapi_core.schema.operations.generators import OperationsGenerator
from openapi_core.schema.paths.models import Path
class PathsGenerator(object):

View file

@ -0,0 +1,12 @@
"""OpenAPI core paths models module"""
class Path(object):
"""Represents an OpenAPI Path."""
def __init__(self, name, operations):
self.name = name
self.operations = dict(operations)
def __getitem__(self, http_method):
return self.operations[http_method]

View file

@ -0,0 +1,17 @@
"""OpenAPI core properties generators module"""
from six import iteritems
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):
from openapi_core.schema.schemas.factories import SchemaFactory
return SchemaFactory(self.dereferencer).create(schema_spec)

View file

@ -0,0 +1,9 @@
from openapi_core.schema.exceptions import OpenAPIMappingError
class OpenAPIRequestBodyError(OpenAPIMappingError):
pass
class MissingRequestBody(OpenAPIRequestBodyError):
pass

View file

@ -1,23 +1,8 @@
"""OpenAPI core requestBodies module"""
"""OpenAPI core request bodies factories module"""
from functools import lru_cache
from openapi_core.exceptions import InvalidContentType
from openapi_core.media_types import MediaTypeGenerator
class RequestBody(object):
"""Represents an OpenAPI RequestBody."""
def __init__(self, content, required=False):
self.content = dict(content)
self.required = required
def __getitem__(self, mimetype):
try:
return self.content[mimetype]
except KeyError:
raise InvalidContentType(
"Invalid mime type `{0}`".format(mimetype))
from openapi_core.schema.media_types.generators import MediaTypeGenerator
from openapi_core.schema.request_bodies.models import RequestBody
class RequestBodyFactory(object):

View file

@ -0,0 +1,25 @@
"""OpenAPI core request bodies models module"""
from openapi_core.schema.media_types.exceptions import InvalidContentType
from openapi_core.schema.request_bodies.exceptions import MissingRequestBody
class RequestBody(object):
"""Represents an OpenAPI RequestBody."""
def __init__(self, content, required=False):
self.content = dict(content)
self.required = required
def __getitem__(self, mimetype):
try:
return self.content[mimetype]
except KeyError:
raise InvalidContentType(
"Invalid mime type `{0}`".format(mimetype))
def get_value(self, request):
if not request.body and self.required:
raise MissingRequestBody("Missing required request body")
return request.body

View file

@ -0,0 +1,13 @@
from openapi_core.schema.exceptions import OpenAPIMappingError
class OpenAPIResponseError(OpenAPIMappingError):
pass
class InvalidResponse(OpenAPIResponseError):
pass
class MissingResponseContent(OpenAPIResponseError):
pass

View file

@ -1,30 +1,11 @@
"""OpenAPI core responses module"""
"""OpenAPI core responses generators module"""
from functools import lru_cache
from six import iteritems
from openapi_core.exceptions import InvalidContentType
from openapi_core.media_types import MediaTypeGenerator
from openapi_core.parameters import ParametersGenerator
class Response(object):
def __init__(
self, http_status, description, headers=None, content=None,
links=None):
self.http_status = http_status
self.description = description
self.headers = headers and dict(headers) or {}
self.content = content and dict(content) or {}
self.links = links and dict(links) or {}
def __getitem__(self, mimetype):
try:
return self.content[mimetype]
except KeyError:
raise InvalidContentType(
"Invalid mime type `{0}`".format(mimetype))
from openapi_core.schema.media_types.generators import MediaTypeGenerator
from openapi_core.schema.parameters.generators import ParametersGenerator
from openapi_core.schema.responses.models import Response
class ResponsesGenerator(object):

View file

@ -0,0 +1,28 @@
"""OpenAPI core responses models module"""
from openapi_core.schema.media_types.exceptions import InvalidContentType
from openapi_core.schema.responses.exceptions import MissingResponseContent
class Response(object):
def __init__(
self, http_status, description, headers=None, content=None,
links=None):
self.http_status = http_status
self.description = description
self.headers = headers and dict(headers) or {}
self.content = content and dict(content) or {}
self.links = links and dict(links) or {}
def __getitem__(self, mimetype):
try:
return self.content[mimetype]
except KeyError:
raise InvalidContentType(
"Invalid mime type `{0}`".format(mimetype))
def get_value(self, response):
if not response.data:
raise MissingResponseContent("Missing response content")
return response.data

View file

View file

@ -0,0 +1,26 @@
"""OpenAPI core schemas enums module"""
from enum import Enum
class SchemaType(Enum):
INTEGER = 'integer'
NUMBER = 'number'
STRING = 'string'
BOOLEAN = 'boolean'
ARRAY = 'array'
OBJECT = 'object'
class SchemaFormat(Enum):
NONE = None
INT32 = 'int32'
INT64 = 'int64'
FLOAT = 'float'
DOUBLE = 'double'
BYTE = 'byte'
BINARY = 'binary'
DATE = 'date'
DATETIME = 'date-time'
PASSWORD = 'password'

View file

@ -0,0 +1,17 @@
from openapi_core.schema.exceptions import OpenAPIMappingError
class OpenAPISchemaError(OpenAPIMappingError):
pass
class InvalidSchemaValue(OpenAPISchemaError):
pass
class UndefinedSchemaProperty(OpenAPISchemaError):
pass
class MissingSchemaProperty(OpenAPISchemaError):
pass

View file

@ -0,0 +1,56 @@
"""OpenAPI core schemas factories module"""
import logging
from functools import lru_cache
from openapi_core.schema.properties.generators import PropertiesGenerator
from openapi_core.schema.schemas.models import Schema
log = logging.getLogger(__name__)
class SchemaFactory(object):
def __init__(self, dereferencer):
self.dereferencer = dereferencer
def create(self, schema_spec):
schema_deref = self.dereferencer.dereference(schema_spec)
schema_type = schema_deref.get('type', 'object')
schema_format = schema_deref.get('format')
model = schema_deref.get('x-model', None)
required = schema_deref.get('required', False)
default = schema_deref.get('default', None)
properties_spec = schema_deref.get('properties', None)
items_spec = schema_deref.get('items', None)
nullable = schema_deref.get('nullable', False)
enum = schema_deref.get('enum', None)
deprecated = schema_deref.get('deprecated', False)
all_of_spec = schema_deref.get('allOf', None)
properties = None
if 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
if items_spec:
items = self._create_items(items_spec)
return Schema(
schema_type=schema_type, model=model, properties=properties,
items=items, schema_format=schema_format, required=required,
default=default, nullable=nullable, enum=enum,
deprecated=deprecated, all_of=all_of,
)
@property
@lru_cache()
def properties_generator(self):
return PropertiesGenerator(self.dereferencer)
def _create_items(self, items_spec):
return self.create(items_spec)

View file

@ -0,0 +1,20 @@
"""OpenAPI core schemas generators module"""
import logging
from six import iteritems
log = logging.getLogger(__name__)
class SchemasGenerator(object):
def __init__(self, dereferencer, schemas_registry):
self.dereferencer = dereferencer
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):
schema, _ = self.schemas_registry.get_or_create(schema_spec)
yield schema_name, schema

View file

@ -1,39 +1,29 @@
"""OpenAPI core schemas module"""
"""OpenAPI core schemas models module"""
import logging
from collections import defaultdict
import warnings
from distutils.util import strtobool
from functools import lru_cache
from six import iteritems
from openapi_core.enums import SchemaType, SchemaFormat
from openapi_core.exceptions import (
InvalidValueType, UndefinedSchemaProperty, MissingProperty, InvalidValue,
from openapi_core.extensions.models.factories import ModelFactory
from openapi_core.schema.schemas.enums import SchemaType, SchemaFormat
from openapi_core.schema.schemas.exceptions import (
InvalidSchemaValue, UndefinedSchemaProperty, MissingSchemaProperty,
)
from openapi_core.models import ModelFactory
from openapi_core.schema.schemas.util import forcebool
log = logging.getLogger(__name__)
def forcebool(val):
if isinstance(val, str):
val = strtobool(val)
return bool(val)
DEFAULT_CAST_CALLABLE_GETTER = {
SchemaType.INTEGER: int,
SchemaType.NUMBER: float,
SchemaType.BOOLEAN: forcebool,
}
class Schema(object):
"""Represents an OpenAPI Schema."""
DEFAULT_CAST_CALLABLE_GETTER = {
SchemaType.INTEGER: int,
SchemaType.NUMBER: float,
SchemaType.BOOLEAN: forcebool,
}
def __init__(
self, schema_type=None, model=None, properties=None, items=None,
schema_format=None, required=None, default=None, nullable=False,
@ -72,7 +62,7 @@ class Schema(object):
return required
def get_cast_mapping(self):
mapping = DEFAULT_CAST_CALLABLE_GETTER.copy()
mapping = self.DEFAULT_CAST_CALLABLE_GETTER.copy()
mapping.update({
SchemaType.ARRAY: self._unmarshal_collection,
SchemaType.OBJECT: self._unmarshal_object,
@ -84,7 +74,7 @@ class Schema(object):
"""Cast value to schema type"""
if value is None:
if not self.nullable:
raise InvalidValueType("Null value for non-nullable schema")
raise InvalidSchemaValue("Null value for non-nullable schema")
return self.default
if self.type is None:
@ -99,7 +89,7 @@ class Schema(object):
try:
return cast_callable(value)
except ValueError:
raise InvalidValueType(
raise InvalidSchemaValue(
"Failed to cast value of {0} to {1}".format(value, self.type)
)
@ -114,7 +104,7 @@ class Schema(object):
return None
if self.enum and casted not in self.enum:
raise InvalidValue(
raise InvalidSchemaValue(
"Value of {0} not in enum choices: {1}".format(
value, self.enum)
)
@ -126,7 +116,8 @@ class Schema(object):
def _unmarshal_object(self, value):
if not isinstance(value, (dict, )):
raise InvalidValueType("Value of {0} not an object".format(value))
raise InvalidSchemaValue(
"Value of {0} not an object".format(value))
all_properties = self.get_all_properties()
all_required_properties = self.get_all_required_properties()
@ -145,102 +136,10 @@ class Schema(object):
prop_value = value[prop_name]
except KeyError:
if prop_name in all_required_properties:
raise MissingProperty(
raise MissingSchemaProperty(
"Missing schema property {0}".format(prop_name))
if not prop.nullable and not prop.default:
continue
prop_value = prop.default
properties[prop_name] = prop.unmarshal(prop_value)
return ModelFactory().create(properties, name=self.model)
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)
schema_type = schema_deref.get('type', 'object')
schema_format = schema_deref.get('format')
model = schema_deref.get('x-model', None)
required = schema_deref.get('required', False)
default = schema_deref.get('default', None)
properties_spec = schema_deref.get('properties', None)
items_spec = schema_deref.get('items', None)
nullable = schema_deref.get('nullable', False)
enum = schema_deref.get('enum', None)
deprecated = schema_deref.get('deprecated', False)
all_of_spec = schema_deref.get('allOf', None)
properties = None
if 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
if items_spec:
items = self._create_items(items_spec)
return Schema(
schema_type=schema_type, model=model, properties=properties,
items=items, schema_format=schema_format, required=required,
default=default, nullable=nullable, enum=enum,
deprecated=deprecated, all_of=all_of,
)
@property
@lru_cache()
def properties_generator(self):
return PropertiesGenerator(self.dereferencer)
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):
def __init__(self, dereferencer, schemas_registry):
self.dereferencer = dereferencer
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):
schema, _ = self.schemas_registry.get_or_create(schema_spec)
yield schema_name, schema

View file

@ -0,0 +1,22 @@
"""OpenAPI core schemas registries module"""
import logging
from openapi_core.schema.schemas.factories import SchemaFactory
log = logging.getLogger(__name__)
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

View file

@ -0,0 +1,9 @@
"""OpenAPI core schemas util module"""
from distutils.util import strtobool
def forcebool(val):
if isinstance(val, str):
val = strtobool(val)
return bool(val)

View file

View file

@ -0,0 +1,9 @@
from openapi_core.schema.exceptions import OpenAPIMappingError
class OpenAPIServerError(OpenAPIMappingError):
pass
class InvalidServer(OpenAPIServerError):
pass

View file

@ -1,37 +1,9 @@
"""OpenAPI core servers generators module"""
from functools import lru_cache
from six import iteritems
class Server(object):
def __init__(self, url, variables=None):
self.url = url
self.variables = variables and dict(variables) or {}
@property
def default_url(self):
return self.get_url()
@property
def default_variables(self):
defaults = {}
for name, variable in iteritems(self.variables):
defaults[name] = variable.default
return defaults
def get_url(self, **variables):
if not variables:
variables = self.default_variables
return self.url.format(**variables)
class ServerVariable(object):
def __init__(self, name, default, enum=None):
self.name = name
self.default = default
self.enum = enum and list(enum) or []
from openapi_core.schema.servers.models import Server, ServerVariable
class ServersGenerator(object):

View file

@ -0,0 +1,33 @@
"""OpenAPI core servers models module"""
from six import iteritems
class Server(object):
def __init__(self, url, variables=None):
self.url = url
self.variables = variables and dict(variables) or {}
@property
def default_url(self):
return self.get_url()
@property
def default_variables(self):
defaults = {}
for name, variable in iteritems(self.variables):
defaults[name] = variable.default
return defaults
def get_url(self, **variables):
if not variables:
variables = self.default_variables
return self.url.format(**variables)
class ServerVariable(object):
def __init__(self, name, default, enum=None):
self.name = name
self.default = default
self.enum = enum and list(enum) or []

View file

View file

@ -0,0 +1,63 @@
# -*- coding: utf-8 -*-
"""OpenAPI core specs factories module"""
from functools import lru_cache
from openapi_spec_validator import openapi_v3_spec_validator
from openapi_core.schema.components.factories import ComponentsFactory
from openapi_core.schema.infos.factories import InfoFactory
from openapi_core.schema.paths.generators import PathsGenerator
from openapi_core.schema.schemas.registries import SchemaRegistry
from openapi_core.schema.servers.generators import ServersGenerator
from openapi_core.schema.specs.models import Spec
class SpecFactory(object):
def __init__(self, dereferencer, config=None):
self.dereferencer = dereferencer
self.config = config or {}
def create(self, spec_dict, spec_url=''):
if self.config.get('validate_spec', True):
openapi_v3_spec_validator.validate(spec_dict, spec_url=spec_url)
spec_dict_deref = self.dereferencer.dereference(spec_dict)
info_spec = spec_dict_deref.get('info', {})
servers_spec = spec_dict_deref.get('servers', [])
paths = spec_dict_deref.get('paths', {})
components_spec = spec_dict_deref.get('components', {})
info = self.info_factory.create(info_spec)
servers = self.servers_generator.generate(servers_spec)
paths = self.paths_generator.generate(paths)
components = self.components_factory.create(components_spec)
spec = Spec(
info, list(paths), servers=list(servers), components=components)
return spec
@property
@lru_cache()
def schemas_registry(self):
return SchemaRegistry(self.dereferencer)
@property
@lru_cache()
def info_factory(self):
return InfoFactory(self.dereferencer)
@property
@lru_cache()
def servers_generator(self):
return ServersGenerator(self.dereferencer)
@property
@lru_cache()
def paths_generator(self):
return PathsGenerator(self.dereferencer, self.schemas_registry)
@property
@lru_cache()
def components_factory(self):
return ComponentsFactory(self.dereferencer, self.schemas_registry)

View file

@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
"""OpenAPI core specs models module"""
import logging
from functools import partialmethod
from openapi_core.schema.operations.exceptions import InvalidOperation
from openapi_core.schema.servers.exceptions import InvalidServer
log = logging.getLogger(__name__)
class Spec(object):
"""Represents an OpenAPI Specification for a service."""
def __init__(self, info, paths, servers=None, components=None):
self.info = info
self.paths = paths and dict(paths)
self.servers = servers or []
self.components = components
def __getitem__(self, path_name):
return self.paths[path_name]
@property
def default_url(self):
return self.servers[0].default_url
def get_server(self, full_url_pattern):
for spec_server in self.servers:
if spec_server.default_url in full_url_pattern:
return spec_server
raise InvalidServer(
"Invalid request server {0}".format(full_url_pattern))
def get_server_url(self, index=0):
return self.servers[index].default_url
def get_operation(self, path_pattern, http_method):
try:
return self.paths[path_pattern].operations[http_method]
except KeyError:
raise InvalidOperation(
"Unknown operation path {0} with method {1}".format(
path_pattern, http_method))
def get_schema(self, name):
return self.components.schemas[name]
# operations shortcuts
get = partialmethod(get_operation, http_method='get')
put = partialmethod(get_operation, http_method='put')
post = partialmethod(get_operation, http_method='post')
delete = partialmethod(get_operation, http_method='delete')
options = partialmethod(get_operation, http_method='options')
head = partialmethod(get_operation, http_method='head')
patch = partialmethod(get_operation, http_method='patch')

View file

@ -3,9 +3,15 @@ from jsonschema.validators import RefResolver
from openapi_spec_validator.validators import Dereferencer
from openapi_spec_validator import default_handlers
from openapi_core.exceptions import OpenAPIParameterError, OpenAPIBodyError
from openapi_core.specs import SpecFactory
from openapi_core.validators import RequestValidator, ResponseValidator
from openapi_core.schema.media_types.exceptions import OpenAPIMediaTypeError
from openapi_core.schema.parameters.exceptions import OpenAPIParameterError
from openapi_core.schema.request_bodies.exceptions import (
OpenAPIRequestBodyError,
)
from openapi_core.schema.schemas.exceptions import OpenAPISchemaError
from openapi_core.schema.specs.factories import SpecFactory
from openapi_core.validation.request.validators import RequestValidator
from openapi_core.validation.response.validators import ResponseValidator
def create_spec(spec_dict, spec_url=''):
@ -25,7 +31,10 @@ def validate_parameters(spec, request, wrapper_class=None):
try:
result.raise_for_errors()
except OpenAPIBodyError:
except (
OpenAPIRequestBodyError, OpenAPIMediaTypeError,
OpenAPISchemaError,
):
return result.parameters
else:
return result.parameters

View file

@ -1,116 +0,0 @@
# -*- coding: utf-8 -*-
"""OpenAPI core specs module"""
import logging
from functools import partialmethod, lru_cache
from openapi_spec_validator import openapi_v3_spec_validator
from openapi_core.components import ComponentsFactory
from openapi_core.exceptions import InvalidOperation, InvalidServer
from openapi_core.infos import InfoFactory
from openapi_core.paths import PathsGenerator
from openapi_core.schemas import SchemaRegistry
from openapi_core.servers import ServersGenerator
log = logging.getLogger(__name__)
class Spec(object):
"""Represents an OpenAPI Specification for a service."""
def __init__(self, info, paths, servers=None, components=None):
self.info = info
self.paths = paths and dict(paths)
self.servers = servers or []
self.components = components
def __getitem__(self, path_name):
return self.paths[path_name]
@property
def default_url(self):
return self.servers[0].default_url
def get_server(self, full_url_pattern):
for spec_server in self.servers:
if spec_server.default_url in full_url_pattern:
return spec_server
raise InvalidServer(
"Invalid request server {0}".format(full_url_pattern))
def get_server_url(self, index=0):
return self.servers[index].default_url
def get_operation(self, path_pattern, http_method):
try:
return self.paths[path_pattern].operations[http_method]
except KeyError:
raise InvalidOperation(
"Unknown operation path {0} with method {1}".format(
path_pattern, http_method))
def get_schema(self, name):
return self.components.schemas[name]
# operations shortcuts
get = partialmethod(get_operation, http_method='get')
put = partialmethod(get_operation, http_method='put')
post = partialmethod(get_operation, http_method='post')
delete = partialmethod(get_operation, http_method='delete')
options = partialmethod(get_operation, http_method='options')
head = partialmethod(get_operation, http_method='head')
patch = partialmethod(get_operation, http_method='patch')
class SpecFactory(object):
def __init__(self, dereferencer, config=None):
self.dereferencer = dereferencer
self.config = config or {}
def create(self, spec_dict, spec_url=''):
if self.config.get('validate_spec', True):
openapi_v3_spec_validator.validate(spec_dict, spec_url=spec_url)
spec_dict_deref = self.dereferencer.dereference(spec_dict)
info_spec = spec_dict_deref.get('info', {})
servers_spec = spec_dict_deref.get('servers', [])
paths = spec_dict_deref.get('paths', {})
components_spec = spec_dict_deref.get('components', {})
info = self.info_factory.create(info_spec)
servers = self.servers_generator.generate(servers_spec)
paths = self.paths_generator.generate(paths)
components = self.components_factory.create(components_spec)
spec = Spec(
info, list(paths), servers=list(servers), components=components)
return spec
@property
@lru_cache()
def schemas_registry(self):
return SchemaRegistry(self.dereferencer)
@property
@lru_cache()
def info_factory(self):
return InfoFactory(self.dereferencer)
@property
@lru_cache()
def servers_generator(self):
return ServersGenerator(self.dereferencer)
@property
@lru_cache()
def paths_generator(self):
return PathsGenerator(self.dereferencer, self.schemas_registry)
@property
@lru_cache()
def components_factory(self):
return ComponentsFactory(self.dereferencer, self.schemas_registry)

View file

View file

@ -0,0 +1,11 @@
"""OpenAPI core validation models module"""
class BaseValidationResult(object):
def __init__(self, errors):
self.errors = errors
def raise_for_errors(self):
for error in self.errors:
raise error

View file

@ -0,0 +1,31 @@
"""OpenAPI core validation request models module"""
from openapi_core.schema.exceptions import OpenAPIMappingError
from openapi_core.validation.models import BaseValidationResult
class RequestParameters(dict):
valid_locations = ['path', 'query', 'headers', 'cookies']
def __getitem__(self, location):
self.validate_location(location)
return self.setdefault(location, {})
def __setitem__(self, location, value):
raise NotImplementedError
@classmethod
def validate_location(cls, location):
if location not in cls.valid_locations:
raise OpenAPIMappingError(
"Unknown parameter location: {0}".format(str(location)))
class RequestValidationResult(BaseValidationResult):
def __init__(self, errors, body=None, parameters=None):
super(RequestValidationResult, self).__init__(errors)
self.body = body
self.parameters = parameters or RequestParameters()

View file

@ -0,0 +1,85 @@
"""OpenAPI core validation request validators module"""
from six import iteritems
from openapi_core.schema.exceptions import OpenAPIMappingError
from openapi_core.schema.parameters.exceptions import MissingParameter
from openapi_core.validation.request.models import (
RequestParameters, RequestValidationResult,
)
from openapi_core.validation.util import get_operation_pattern
class RequestValidator(object):
def __init__(self, spec):
self.spec = spec
def validate(self, request):
try:
server = self.spec.get_server(request.full_url_pattern)
# don't process if server errors
except OpenAPIMappingError as exc:
return RequestValidationResult([exc, ], None, None)
operation_pattern = get_operation_pattern(
server.default_url, request.full_url_pattern
)
try:
operation = self.spec.get_operation(
operation_pattern, request.method)
# don't process if operation errors
except OpenAPIMappingError as exc:
return RequestValidationResult([exc, ], None, None)
params, params_errors = self._get_parameters(request, operation)
body, body_errors = self._get_body(request, operation)
errors = params_errors + body_errors
return RequestValidationResult(errors, body, params)
def _get_parameters(self, request, operation):
errors = []
parameters = RequestParameters()
for param_name, param in iteritems(operation.parameters):
try:
raw_value = param.get_value(request)
except MissingParameter:
continue
except OpenAPIMappingError as exc:
errors.append(exc)
continue
try:
value = param.unmarshal(raw_value)
except OpenAPIMappingError as exc:
errors.append(exc)
else:
parameters[param.location.value][param_name] = value
return parameters, errors
def _get_body(self, request, operation):
errors = []
if operation.request_body is None:
return None, errors
body = None
try:
media_type = operation.request_body[request.mimetype]
except OpenAPIMappingError as exc:
errors.append(exc)
else:
try:
raw_body = operation.request_body.get_value(request)
except OpenAPIMappingError as exc:
errors.append(exc)
else:
try:
body = media_type.unmarshal(raw_body)
except OpenAPIMappingError as exc:
errors.append(exc)
return body, errors

View file

@ -0,0 +1,10 @@
"""OpenAPI core validation response models module"""
from openapi_core.validation.models import BaseValidationResult
class ResponseValidationResult(BaseValidationResult):
def __init__(self, errors, data=None, headers=None):
super(ResponseValidationResult, self).__init__(errors)
self.data = data
self.headers = headers

View file

@ -0,0 +1,75 @@
"""OpenAPI core validation response validators module"""
from openapi_core.schema.exceptions import OpenAPIMappingError
from openapi_core.validation.response.models import ResponseValidationResult
from openapi_core.validation.util import get_operation_pattern
class ResponseValidator(object):
def __init__(self, spec):
self.spec = spec
def validate(self, request, response):
try:
server = self.spec.get_server(request.full_url_pattern)
# don't process if server errors
except OpenAPIMappingError as exc:
return ResponseValidationResult([exc, ], None, None)
operation_pattern = get_operation_pattern(
server.default_url, request.full_url_pattern
)
try:
operation = self.spec.get_operation(
operation_pattern, request.method)
# don't process if operation errors
except OpenAPIMappingError as exc:
return ResponseValidationResult([exc, ], None, None)
try:
operation_response = operation.get_response(
str(response.status_code))
# don't process if operation response errors
except OpenAPIMappingError as exc:
return ResponseValidationResult([exc, ], None, None)
data, data_errors = self._get_data(response, operation_response)
headers, headers_errors = self._get_headers(
response, operation_response)
errors = data_errors + headers_errors
return ResponseValidationResult(errors, data, headers)
def _get_data(self, response, operation_response):
errors = []
if not operation_response.content:
return None, errors
data = None
try:
media_type = operation_response[response.mimetype]
except OpenAPIMappingError as exc:
errors.append(exc)
else:
try:
raw_data = operation_response.get_value(response)
except OpenAPIMappingError as exc:
errors.append(exc)
else:
try:
data = media_type.unmarshal(raw_data)
except OpenAPIMappingError as exc:
errors.append(exc)
return data, errors
def _get_headers(self, response, operation_response):
errors = []
# @todo: implement
headers = {}
return headers, errors

View file

@ -0,0 +1,12 @@
"""OpenAPI core validation util module"""
from yarl import URL
def get_operation_pattern(server_url, request_url_pattern):
"""Return an updated request URL pattern with the server URL removed."""
if server_url[-1] == "/":
# operations have to start with a slash, so do not remove it
server_url = server_url[:-1]
if URL(server_url).is_absolute():
return request_url_pattern.replace(server_url, "", 1)
return URL(request_url_pattern).path_qs.replace(server_url, "", 1)

View file

@ -1,211 +0,0 @@
"""OpenAPI core validators module"""
from six import iteritems
from yarl import URL
from openapi_core.exceptions import (
OpenAPIMappingError, MissingParameter, MissingBody, InvalidResponse,
)
class RequestParameters(dict):
valid_locations = ['path', 'query', 'headers', 'cookies']
def __getitem__(self, location):
self.validate_location(location)
return self.setdefault(location, {})
def __setitem__(self, location, value):
raise NotImplementedError
@classmethod
def validate_location(cls, location):
if location not in cls.valid_locations:
raise OpenAPIMappingError(
"Unknown parameter location: {0}".format(str(location)))
class BaseValidationResult(object):
def __init__(self, errors):
self.errors = errors
def raise_for_errors(self):
for error in self.errors:
raise error
class RequestValidationResult(BaseValidationResult):
def __init__(self, errors, body=None, parameters=None):
super(RequestValidationResult, self).__init__(errors)
self.body = body
self.parameters = parameters or RequestParameters()
class ResponseValidationResult(BaseValidationResult):
def __init__(self, errors, data=None, headers=None):
super(ResponseValidationResult, self).__init__(errors)
self.data = data
self.headers = headers
def get_operation_pattern(server_url, request_url_pattern):
"""Return an updated request URL pattern with the server URL removed."""
if server_url[-1] == "/":
# operations have to start with a slash, so do not remove it
server_url = server_url[:-1]
if URL(server_url).is_absolute():
return request_url_pattern.replace(server_url, "", 1)
return URL(request_url_pattern).path_qs.replace(server_url, "", 1)
class RequestValidator(object):
def __init__(self, spec):
self.spec = spec
def validate(self, request):
errors = []
body = None
parameters = RequestParameters()
try:
server = self.spec.get_server(request.full_url_pattern)
# don't process if server errors
except OpenAPIMappingError as exc:
errors.append(exc)
return RequestValidationResult(errors, body, parameters)
operation_pattern = get_operation_pattern(
server.default_url, request.full_url_pattern
)
try:
operation = self.spec.get_operation(
operation_pattern, request.method)
# don't process if operation errors
except OpenAPIMappingError as exc:
errors.append(exc)
return RequestValidationResult(errors, body, parameters)
for param_name, param in iteritems(operation.parameters):
try:
raw_value = self._get_raw_value(request, param)
except MissingParameter as exc:
if param.required:
errors.append(exc)
if not param.schema or param.schema.default is None:
continue
raw_value = param.schema.default
try:
value = param.unmarshal(raw_value)
except OpenAPIMappingError as exc:
errors.append(exc)
else:
parameters[param.location.value][param_name] = value
if operation.request_body is not None:
try:
media_type = operation.request_body[request.mimetype]
except OpenAPIMappingError as exc:
errors.append(exc)
else:
try:
raw_body = self._get_raw_body(request)
except MissingBody as exc:
if operation.request_body.required:
errors.append(exc)
else:
try:
body = media_type.unmarshal(raw_body)
except OpenAPIMappingError as exc:
errors.append(exc)
return RequestValidationResult(errors, body, parameters)
def _get_raw_value(self, request, param):
location = request.parameters[param.location.value]
try:
raw = location[param.name]
except KeyError:
raise MissingParameter(
"Missing required `{0}` parameter".format(param.name))
if param.aslist and param.explode:
return location.getlist(param.name)
return raw
def _get_raw_body(self, request):
if not request.body:
raise MissingBody("Missing required request body")
return request.body
class ResponseValidator(object):
def __init__(self, spec):
self.spec = spec
def validate(self, request, response):
errors = []
data = None
headers = {}
try:
server = self.spec.get_server(request.full_url_pattern)
# don't process if server errors
except OpenAPIMappingError as exc:
errors.append(exc)
return ResponseValidationResult(errors, data, headers)
operation_pattern = get_operation_pattern(
server.default_url, request.full_url_pattern
)
try:
operation = self.spec.get_operation(
operation_pattern, request.method)
# don't process if operation errors
except OpenAPIMappingError as exc:
errors.append(exc)
return ResponseValidationResult(errors, data, headers)
try:
operation_response = operation.get_response(
str(response.status_code))
# don't process if invalid response status code
except InvalidResponse as exc:
errors.append(exc)
return ResponseValidationResult(errors, data, headers)
if operation_response.content:
try:
media_type = operation_response[response.mimetype]
except OpenAPIMappingError as exc:
errors.append(exc)
else:
try:
raw_data = self._get_raw_data(response)
except MissingBody as exc:
errors.append(exc)
else:
try:
data = media_type.unmarshal(raw_data)
except OpenAPIMappingError as exc:
errors.append(exc)
return ResponseValidationResult(errors, data, headers)
def _get_raw_data(self, response):
if not response.data:
raise MissingBody("Missing required response data")
return response.data

View file

@ -1,142 +0,0 @@
"""OpenAPI core wrappers module"""
import warnings
from six.moves.urllib.parse import urljoin
from werkzeug.datastructures import ImmutableMultiDict
class BaseOpenAPIRequest(object):
host_url = NotImplemented
path = NotImplemented
path_pattern = NotImplemented
method = NotImplemented
parameters = NotImplemented
body = NotImplemented
mimetype = NotImplemented
@property
def full_url_pattern(self):
return urljoin(self.host_url, self.path_pattern)
def get_body(self, spec):
warnings.warn(
"`get_body` method is deprecated. "
"Use RequestValidator instead.",
DeprecationWarning,
)
# backward compatibility
from openapi_core.shortcuts import validate_body
return validate_body(spec, self, wrapper_class=None)
def get_parameters(self, spec):
warnings.warn(
"`get_parameters` method is deprecated. "
"Use RequestValidator instead.",
DeprecationWarning,
)
# backward compatibility
from openapi_core.shortcuts import validate_parameters
return validate_parameters(spec, self, wrapper_class=None)
class MockRequest(BaseOpenAPIRequest):
def __init__(
self, host_url, method, path, path_pattern=None, args=None,
view_args=None, headers=None, cookies=None, data=None,
mimetype='application/json'):
self.host_url = host_url
self.path = path
self.path_pattern = path_pattern or path
self.method = method.lower()
self.parameters = {
'path': view_args or {},
'query': ImmutableMultiDict(args or []),
'header': headers or {},
'cookie': cookies or {},
}
self.body = data or ''
self.mimetype = mimetype
class FlaskOpenAPIRequest(BaseOpenAPIRequest):
def __init__(self, request):
self.request = request
@property
def host_url(self):
return self.request.host_url
@property
def path(self):
return self.request.path
@property
def method(self):
return self.request.method.lower()
@property
def path_pattern(self):
if self.request.url_rule is None:
return self.path
return self.request.url_rule.rule
@property
def parameters(self):
return {
'path': self.request.view_args,
'query': self.request.args,
'headers': self.request.headers,
'cookies': self.request.cookies,
}
@property
def body(self):
return self.request.data
@property
def mimetype(self):
return self.request.mimetype
class BaseOpenAPIResponse(object):
body = NotImplemented
status_code = NotImplemented
mimetype = NotImplemented
class MockResponse(BaseOpenAPIRequest):
def __init__(self, data, status_code=200, mimetype='application/json'):
self.data = data
self.status_code = status_code
self.mimetype = mimetype
class FlaskOpenAPIResponse(BaseOpenAPIResponse):
def __init__(self, response):
self.response = response
@property
def data(self):
return self.response.data
@property
def status_code(self):
return self.response._status_code
@property
def mimetype(self):
return self.response.mimetype

View file

View file

@ -0,0 +1,49 @@
"""OpenAPI core wrappers module"""
import warnings
from six.moves.urllib.parse import urljoin
class BaseOpenAPIRequest(object):
host_url = NotImplemented
path = NotImplemented
path_pattern = NotImplemented
method = NotImplemented
parameters = NotImplemented
body = NotImplemented
mimetype = NotImplemented
@property
def full_url_pattern(self):
return urljoin(self.host_url, self.path_pattern)
def get_body(self, spec):
warnings.warn(
"`get_body` method is deprecated. "
"Use RequestValidator instead.",
DeprecationWarning,
)
# backward compatibility
from openapi_core.shortcuts import validate_body
return validate_body(spec, self, wrapper_class=None)
def get_parameters(self, spec):
warnings.warn(
"`get_parameters` method is deprecated. "
"Use RequestValidator instead.",
DeprecationWarning,
)
# backward compatibility
from openapi_core.shortcuts import validate_parameters
return validate_parameters(spec, self, wrapper_class=None)
class BaseOpenAPIResponse(object):
body = NotImplemented
status_code = NotImplemented
mimetype = NotImplemented

View file

@ -0,0 +1,62 @@
"""OpenAPI core wrappers module"""
from openapi_core.wrappers.base import BaseOpenAPIRequest, BaseOpenAPIResponse
class FlaskOpenAPIRequest(BaseOpenAPIRequest):
def __init__(self, request):
self.request = request
@property
def host_url(self):
return self.request.host_url
@property
def path(self):
return self.request.path
@property
def method(self):
return self.request.method.lower()
@property
def path_pattern(self):
if self.request.url_rule is None:
return self.path
return self.request.url_rule.rule
@property
def parameters(self):
return {
'path': self.request.view_args,
'query': self.request.args,
'headers': self.request.headers,
'cookies': self.request.cookies,
}
@property
def body(self):
return self.request.data
@property
def mimetype(self):
return self.request.mimetype
class FlaskOpenAPIResponse(BaseOpenAPIResponse):
def __init__(self, response):
self.response = response
@property
def data(self):
return self.response.data
@property
def status_code(self):
return self.response._status_code
@property
def mimetype(self):
return self.response.mimetype

View file

@ -0,0 +1,36 @@
"""OpenAPI core wrappers module"""
from werkzeug.datastructures import ImmutableMultiDict
from openapi_core.wrappers.base import BaseOpenAPIRequest, BaseOpenAPIResponse
class MockRequest(BaseOpenAPIRequest):
def __init__(
self, host_url, method, path, path_pattern=None, args=None,
view_args=None, headers=None, cookies=None, data=None,
mimetype='application/json'):
self.host_url = host_url
self.path = path
self.path_pattern = path_pattern or path
self.method = method.lower()
self.parameters = {
'path': view_args or {},
'query': ImmutableMultiDict(args or []),
'header': headers or {},
'cookie': cookies or {},
}
self.body = data or ''
self.mimetype = mimetype
class MockResponse(BaseOpenAPIResponse):
def __init__(self, data, status_code=200, mimetype='application/json'):
self.data = data
self.status_code = status_code
self.mimetype = mimetype

View file

@ -1,9 +1,9 @@
import pytest
from openapi_core.exceptions import InvalidOperation
from openapi_core.schema.operations.exceptions import InvalidOperation
from openapi_core.shortcuts import create_spec
from openapi_core.validators import RequestValidator
from openapi_core.wrappers import MockRequest
from openapi_core.validation.request.validators import RequestValidator
from openapi_core.wrappers.mock import MockRequest
class TestMinimal(object):

View file

@ -2,22 +2,28 @@ import json
import pytest
from six import iteritems
from openapi_core.exceptions import (
MissingParameter, InvalidContentType, InvalidServer,
UndefinedSchemaProperty, MissingProperty,
EmptyValue, InvalidMediaTypeValue, InvalidParameterValue,
from openapi_core.schema.media_types.exceptions import (
InvalidContentType, InvalidMediaTypeValue,
)
from openapi_core.media_types import MediaType
from openapi_core.operations import Operation
from openapi_core.parameters import Parameter
from openapi_core.paths import Path
from openapi_core.request_bodies import RequestBody
from openapi_core.responses import Response
from openapi_core.schemas import Schema
from openapi_core.servers import Server, ServerVariable
from openapi_core.schema.media_types.models import MediaType
from openapi_core.schema.operations.models import Operation
from openapi_core.schema.parameters.exceptions import (
MissingRequiredParameter, InvalidParameterValue, EmptyParameterValue,
)
from openapi_core.schema.parameters.models import Parameter
from openapi_core.schema.paths.models import Path
from openapi_core.schema.request_bodies.models import RequestBody
from openapi_core.schema.responses.models import Response
from openapi_core.schema.schemas.exceptions import (
UndefinedSchemaProperty, MissingSchemaProperty,
)
from openapi_core.schema.schemas.models import Schema
from openapi_core.schema.servers.exceptions import InvalidServer
from openapi_core.schema.servers.models import Server, ServerVariable
from openapi_core.shortcuts import create_spec
from openapi_core.validators import RequestValidator, ResponseValidator
from openapi_core.wrappers import MockRequest, MockResponse
from openapi_core.validation.request.validators import RequestValidator
from openapi_core.validation.response.validators import ResponseValidator
from openapi_core.wrappers.mock import MockRequest, MockResponse
class TestPetstore(object):
@ -312,7 +318,7 @@ class TestPetstore(object):
path_pattern=path_pattern,
)
with pytest.raises(MissingParameter):
with pytest.raises(MissingRequiredParameter):
request.get_parameters(spec)
body = request.get_body(spec)
@ -331,7 +337,7 @@ class TestPetstore(object):
path_pattern=path_pattern, args=query_params,
)
with pytest.raises(EmptyValue):
with pytest.raises(EmptyParameterValue):
request.get_parameters(spec)
body = request.get_body(spec)
@ -464,7 +470,7 @@ class TestPetstore(object):
assert parameters == {}
with pytest.raises(MissingProperty):
with pytest.raises(MissingSchemaProperty):
request.get_body(spec)
def test_post_pets_extra_body_properties(self, spec, spec_dict):

View file

@ -1,14 +1,20 @@
import json
import pytest
from openapi_core.exceptions import (
InvalidServer, InvalidOperation, MissingParameter,
MissingBody, InvalidContentType, InvalidResponse, InvalidMediaTypeValue,
InvalidValue,
from openapi_core.schema.media_types.exceptions import (
InvalidContentType, InvalidMediaTypeValue,
)
from openapi_core.schema.operations.exceptions import InvalidOperation
from openapi_core.schema.parameters.exceptions import MissingRequiredParameter
from openapi_core.schema.request_bodies.exceptions import MissingRequestBody
from openapi_core.schema.responses.exceptions import (
MissingResponseContent, InvalidResponse,
)
from openapi_core.schema.servers.exceptions import InvalidServer
from openapi_core.shortcuts import create_spec
from openapi_core.validators import RequestValidator, ResponseValidator
from openapi_core.wrappers import MockRequest, MockResponse
from openapi_core.validation.request.validators import RequestValidator
from openapi_core.validation.response.validators import ResponseValidator
from openapi_core.wrappers.mock import MockRequest, MockResponse
class TestRequestValidator(object):
@ -52,7 +58,7 @@ class TestRequestValidator(object):
result = validator.validate(request)
assert type(result.errors[0]) == MissingParameter
assert type(result.errors[0]) == MissingRequiredParameter
assert result.body is None
assert result.parameters == {
'query': {
@ -88,7 +94,7 @@ class TestRequestValidator(object):
result = validator.validate(request)
assert len(result.errors) == 1
assert type(result.errors[0]) == MissingBody
assert type(result.errors[0]) == MissingRequestBody
assert result.body is None
assert result.parameters == {}
@ -183,7 +189,7 @@ class TestResponseValidator(object):
assert len(result.errors) == 1
assert type(result.errors[0]) == InvalidServer
assert result.data is None
assert result.headers == {}
assert result.headers is None
def test_invalid_operation(self, validator):
request = MockRequest(self.host_url, 'get', '/v1')
@ -194,7 +200,7 @@ class TestResponseValidator(object):
assert len(result.errors) == 1
assert type(result.errors[0]) == InvalidOperation
assert result.data is None
assert result.headers == {}
assert result.headers is None
def test_invalid_response(self, validator):
request = MockRequest(self.host_url, 'get', '/v1/pets')
@ -205,7 +211,7 @@ class TestResponseValidator(object):
assert len(result.errors) == 1
assert type(result.errors[0]) == InvalidResponse
assert result.data is None
assert result.headers == {}
assert result.headers is None
def test_invalid_content_type(self, validator):
request = MockRequest(self.host_url, 'get', '/v1/pets')
@ -225,7 +231,7 @@ class TestResponseValidator(object):
result = validator.validate(request, response)
assert len(result.errors) == 1
assert type(result.errors[0]) == MissingBody
assert type(result.errors[0]) == MissingResponseContent
assert result.data is None
assert result.headers == {}
@ -256,7 +262,7 @@ class TestResponseValidator(object):
result = validator.validate(request, response)
assert len(result.errors) == 1
assert type(result.errors[0]) == InvalidValue
assert type(result.errors[0]) == InvalidMediaTypeValue
assert result.data is None
assert result.headers == {}

View file

@ -5,7 +5,9 @@ from werkzeug.datastructures import EnvironHeaders, ImmutableMultiDict
from werkzeug.routing import Map, Rule, Subdomain
from werkzeug.test import create_environ
from openapi_core.wrappers import FlaskOpenAPIRequest, FlaskOpenAPIResponse
from openapi_core.wrappers.flask import (
FlaskOpenAPIRequest, FlaskOpenAPIResponse,
)
class TestFlaskOpenAPIRequest(object):

View file

@ -1,7 +1,7 @@
import mock
import pytest
from openapi_core.operations import Operation
from openapi_core.schema.operations.models import Operation
class TestSchemas(object):

View file

@ -1,8 +1,8 @@
import pytest
from openapi_core.enums import ParameterStyle
from openapi_core.exceptions import EmptyValue
from openapi_core.parameters import Parameter
from openapi_core.schema.parameters.exceptions import EmptyParameterValue
from openapi_core.schema.parameters.enums import ParameterStyle
from openapi_core.schema.parameters.models import Parameter
class TestParameterInit(object):
@ -59,7 +59,7 @@ class TestParameterUnmarshal(object):
param = Parameter('param', 'query')
value = ''
with pytest.raises(EmptyValue):
with pytest.raises(EmptyParameterValue):
param.unmarshal(value)
def test_query_allow_empty_value(self):

View file

@ -1,7 +1,7 @@
import mock
import pytest
from openapi_core.paths import Path
from openapi_core.schema.paths.models import Path
class TestPaths(object):

View file

@ -1,7 +1,7 @@
import mock
import pytest
from openapi_core.request_bodies import RequestBody
from openapi_core.schema.request_bodies.models import RequestBody
class TestRequestBodies(object):

View file

@ -1,8 +1,8 @@
import mock
import pytest
from openapi_core.exceptions import InvalidValueType, InvalidValue
from openapi_core.schemas import Schema
from openapi_core.schema.schemas.exceptions import InvalidSchemaValue
from openapi_core.schema.schemas.models import Schema
class TestSchemaIteritems(object):
@ -44,7 +44,7 @@ class TestSchemaUnmarshal(object):
schema = Schema('string')
value = None
with pytest.raises(InvalidValueType):
with pytest.raises(InvalidSchemaValue):
schema.unmarshal(value)
def test_string_default(self):
@ -52,7 +52,7 @@ class TestSchemaUnmarshal(object):
schema = Schema('string', default=default_value)
value = None
with pytest.raises(InvalidValueType):
with pytest.raises(InvalidSchemaValue):
schema.unmarshal(value)
def test_string_default_nullable(self):
@ -76,7 +76,7 @@ class TestSchemaUnmarshal(object):
schema = Schema('integer', enum=[1, 2, 3])
value = '123'
with pytest.raises(InvalidValue):
with pytest.raises(InvalidSchemaValue):
schema.unmarshal(value)
def test_integer_enum(self):
@ -92,7 +92,7 @@ class TestSchemaUnmarshal(object):
schema = Schema('integer', default=default_value)
value = None
with pytest.raises(InvalidValueType):
with pytest.raises(InvalidSchemaValue):
schema.unmarshal(value)
def test_integer_default_nullable(self):
@ -108,5 +108,5 @@ class TestSchemaUnmarshal(object):
schema = Schema('integer')
value = 'abc'
with pytest.raises(InvalidValueType):
with pytest.raises(InvalidSchemaValue):
schema.unmarshal(value)

View file

@ -1,9 +1,9 @@
import mock
import pytest
from openapi_core.exceptions import InvalidOperation
from openapi_core.paths import Path
from openapi_core.specs import Spec
from openapi_core.schema.operations.exceptions import InvalidOperation
from openapi_core.schema.paths.models import Path
from openapi_core.schema.specs.models import Spec
class TestSpecs(object):