Exceptions restructure

This commit is contained in:
Artur Maciag 2018-04-18 11:39:03 +01:00
parent 9b05d7b271
commit 4e1a61aace
27 changed files with 287 additions and 209 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,9 @@
"""OpenAPI core schema exceptions module"""
class OpenAPIError(Exception):
pass
class OpenAPIMappingError(OpenAPIError):
pass

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

@ -2,9 +2,9 @@
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,5 +42,5 @@ class MediaType(object):
try:
return self.schema.unmarshal(deserialized)
except InvalidValueType as exc:
except InvalidSchemaValue as exc:
raise InvalidMediaTypeValue(str(exc))

View file

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

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""OpenAPI core operations models module"""
from openapi_core.exceptions import InvalidResponse
from openapi_core.schema.responses.exceptions import InvalidResponse
class Operation(object):
@ -21,6 +21,7 @@ class Operation(object):
return self.parameters[name]
def get_response(self, http_status='default'):
# @todo: move to Responses object
try:
return self.responses[http_status]
except KeyError:

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

@ -2,11 +2,13 @@
import logging
import warnings
from openapi_core.exceptions import (
EmptyValue, InvalidValueType, InvalidParameterValue,
)
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__)
@ -68,6 +70,27 @@ class Parameter(object):
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(
@ -77,7 +100,7 @@ class Parameter(object):
if (self.location == ParameterLocation.QUERY and value == "" and
not self.allow_empty_value):
raise EmptyValue(
raise EmptyParameterValue(
"Value of {0} parameter cannot be empty".format(self.name))
if not self.schema:
@ -87,5 +110,5 @@ class Parameter(object):
try:
return self.schema.unmarshal(deserialized)
except InvalidValueType as exc:
except InvalidSchemaValue as exc:
raise InvalidParameterValue(str(exc))

View file

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

View file

@ -1,5 +1,7 @@
"""OpenAPI core request bodies models module"""
from openapi_core.exceptions import InvalidContentType
from openapi_core.schema.media_types.exceptions import InvalidContentType
from openapi_core.schema.request_bodies.exceptions import MissingRequestBody
class RequestBody(object):
@ -15,3 +17,9 @@ class RequestBody(object):
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,5 +1,6 @@
"""OpenAPI core responses models module"""
from openapi_core.exceptions import InvalidContentType
from openapi_core.schema.media_types.exceptions import InvalidContentType
from openapi_core.schema.responses.exceptions import MissingResponseContent
class Response(object):
@ -19,3 +20,9 @@ class Response(object):
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

@ -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

@ -5,11 +5,11 @@ import warnings
from six import iteritems
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.schema.schemas.util import forcebool
log = logging.getLogger(__name__)
@ -74,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:
@ -89,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)
)
@ -104,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)
)
@ -116,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()
@ -135,7 +136,7 @@ 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

View file

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

View file

@ -4,7 +4,8 @@ from functools import lru_cache
from openapi_spec_validator import openapi_v3_spec_validator
from openapi_core.exceptions import InvalidOperation, InvalidServer
from openapi_core.schema.operations.exceptions import InvalidOperation
from openapi_core.schema.servers.exceptions import InvalidServer
from openapi_core.schema.components.factories import ComponentsFactory
from openapi_core.schema.infos.factories import InfoFactory
from openapi_core.schema.paths.generators import PathsGenerator

View file

@ -3,7 +3,8 @@
import logging
from functools import partialmethod
from openapi_core.exceptions import InvalidOperation, InvalidServer
from openapi_core.schema.operations.exceptions import InvalidOperation
from openapi_core.schema.servers.exceptions import InvalidServer
log = logging.getLogger(__name__)

View file

@ -3,7 +3,12 @@ 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.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
@ -26,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,5 +1,5 @@
"""OpenAPI core validation request models module"""
from openapi_core.exceptions import OpenAPIMappingError
from openapi_core.schema.exceptions import OpenAPIMappingError
from openapi_core.validation.models import BaseValidationResult

View file

@ -1,9 +1,8 @@
"""OpenAPI core validation request validators module"""
from six import iteritems
from openapi_core.exceptions import (
OpenAPIMappingError, MissingParameter, MissingBody,
)
from openapi_core.schema.exceptions import OpenAPIMappingError
from openapi_core.schema.parameters.exceptions import MissingParameter
from openapi_core.validation.request.models import (
RequestParameters, RequestValidationResult,
)
@ -16,16 +15,11 @@ class RequestValidator(object):
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)
return RequestValidationResult([exc, ], None, None)
operation_pattern = get_operation_pattern(
server.default_url, request.full_url_pattern
@ -36,19 +30,26 @@ class RequestValidator(object):
operation_pattern, request.method)
# don't process if operation errors
except OpenAPIMappingError as exc:
errors.append(exc)
return RequestValidationResult(errors, body, parameters)
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 = 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
raw_value = param.get_value(request)
except MissingParameter:
continue
except OpenAPIMappingError as exc:
errors.append(exc)
continue
try:
value = param.unmarshal(raw_value)
@ -57,41 +58,28 @@ class RequestValidator(object):
else:
parameters[param.location.value][param_name] = value
if operation.request_body is not None:
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:
media_type = operation.request_body[request.mimetype]
raw_body = operation.request_body.get_value(request)
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)
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
return body, errors

View file

@ -1,7 +1,5 @@
"""OpenAPI core validation response validators module"""
from openapi_core.exceptions import (
OpenAPIMappingError, MissingBody, InvalidResponse,
)
from openapi_core.schema.exceptions import OpenAPIMappingError
from openapi_core.validation.response.models import ResponseValidationResult
from openapi_core.validation.util import get_operation_pattern
@ -12,16 +10,11 @@ class ResponseValidator(object):
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)
return ResponseValidationResult([exc, ], None, None)
operation_pattern = get_operation_pattern(
server.default_url, request.full_url_pattern
@ -32,37 +25,51 @@ class ResponseValidator(object):
operation_pattern, request.method)
# don't process if operation errors
except OpenAPIMappingError as exc:
errors.append(exc)
return ResponseValidationResult(errors, data, headers)
return ResponseValidationResult([exc, ], None, None)
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)
# don't process if operation response errors
except OpenAPIMappingError as exc:
return ResponseValidationResult([exc, ], None, None)
if operation_response.content:
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:
media_type = operation_response[response.mimetype]
raw_data = operation_response.get_value(response)
except OpenAPIMappingError as exc:
errors.append(exc)
else:
try:
raw_data = self._get_raw_data(response)
except MissingBody as exc:
data = media_type.unmarshal(raw_data)
except OpenAPIMappingError 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)
return data, errors
def _get_raw_data(self, response):
if not response.data:
raise MissingBody("Missing required response data")
def _get_headers(self, response, operation_response):
errors = []
return response.data
# @todo: implement
headers = {}
return headers, errors

View file

@ -1,6 +1,6 @@
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.validation.request.validators import RequestValidator
from openapi_core.wrappers.mock import MockRequest

View file

@ -2,18 +2,23 @@ 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.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.validation.request.validators import RequestValidator
@ -313,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)
@ -332,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)
@ -465,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,11 +1,16 @@
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.validation.request.validators import RequestValidator
from openapi_core.validation.response.validators import ResponseValidator
@ -53,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': {
@ -89,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 == {}
@ -184,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')
@ -195,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')
@ -206,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')
@ -226,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 == {}
@ -257,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

@ -1,6 +1,6 @@
import pytest
from openapi_core.exceptions import EmptyValue
from openapi_core.schema.parameters.exceptions import EmptyParameterValue
from openapi_core.schema.parameters.enums import ParameterStyle
from openapi_core.schema.parameters.models import Parameter
@ -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.exceptions import InvalidValueType, InvalidValue
from openapi_core.schema.schemas.exceptions import InvalidSchemaValue
from openapi_core.schema.schemas.models import Schema
@ -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,7 +1,7 @@
import mock
import pytest
from openapi_core.exceptions import InvalidOperation
from openapi_core.schema.operations.exceptions import InvalidOperation
from openapi_core.schema.paths.models import Path
from openapi_core.schema.specs.models import Spec