Rewrok exception handling

Main motivation behind this change is to be able to catch exceptions
as per raise_for_errors() helpers, but to inspect state of exceptions
instead of just getting a rendered string. This allows rendering
exceptions into JSON, for example.
This commit is contained in:
Domen Kožar 2018-09-06 15:50:16 +01:00
parent 89a53f6edc
commit 6bdd1a6756
No known key found for this signature in database
GPG key ID: C2FFBCAFD2C24246
16 changed files with 167 additions and 93 deletions

View file

@ -1,9 +1,16 @@
from openapi_core.schema.exceptions import OpenAPIMappingError
import attr
class OpenAPIContentError(OpenAPIMappingError):
pass
@attr.s
class MimeTypeNotFound(OpenAPIContentError):
pass
mimetype = attr.ib()
availableMimetypes = attr.ib()
def __str__(self):
return "Mimetype not found: {0}. Valid mimetypes: {1}".format(self.mimetype, self.availableMimetypes)

View file

@ -18,4 +18,4 @@ class Content(dict):
if fnmatch.fnmatch(mimetype, key):
return value
raise MimeTypeNotFound("{0} mimetype not found")
raise MimeTypeNotFound(mimetype, self.keys())

View file

@ -1,13 +1,21 @@
from openapi_core.schema.exceptions import OpenAPIMappingError
import attr
class OpenAPIMediaTypeError(OpenAPIMappingError):
pass
@attr.s
class InvalidMediaTypeValue(OpenAPIMediaTypeError):
pass
original_exception = attr.ib()
def __str__(self):
return "Mimetype invalid: {0}".format(self.original_exception)
@attr.s
class InvalidContentType(OpenAPIMediaTypeError):
pass
mimetype = attr.ib()
def __str__(self):
return "Content for following mimetype not found: {0}".format(self.mimetype)

View file

@ -39,14 +39,14 @@ class MediaType(object):
try:
deserialized = self.deserialize(value)
except ValueError as exc:
raise InvalidMediaTypeValue(str(exc))
raise InvalidMediaTypeValue(exc)
try:
unmarshalled = self.schema.unmarshal(deserialized, custom_formatters=custom_formatters)
except InvalidSchemaValue as exc:
raise InvalidMediaTypeValue(str(exc))
raise InvalidMediaTypeValue(exc)
try:
return self.schema.validate(unmarshalled, custom_formatters=custom_formatters)
except InvalidSchemaValue as exc:
raise InvalidMediaTypeValue(str(exc))
raise InvalidMediaTypeValue(exc)

View file

@ -1,9 +1,17 @@
from openapi_core.schema.exceptions import OpenAPIMappingError
import attr
class OpenAPIOperationError(OpenAPIMappingError):
pass
@attr.s
class InvalidOperation(OpenAPIOperationError):
pass
path_pattern = attr.ib()
http_method = attr.ib()
def __str__(self):
return "Unknown operation path {0} with method {1}".format(
self.path_pattern, self.http_method)

View file

@ -32,7 +32,6 @@ class Operation(object):
return self.responses[http_status_range]
if 'default' not in self.responses:
raise InvalidResponse(
"Unknown response http status {0}".format(http_status))
raise InvalidResponse(http_status, self.responses)
return self.responses['default']

View file

@ -1,21 +1,40 @@
from openapi_core.schema.exceptions import OpenAPIMappingError
import attr
class OpenAPIParameterError(OpenAPIMappingError):
pass
@attr.s
class MissingParameter(OpenAPIParameterError):
pass
name = attr.ib()
def __str__(self):
return "Missing parameter (without default value): {0}".format(self.name)
@attr.s
class MissingRequiredParameter(OpenAPIParameterError):
pass
name = attr.ib()
def __str__(self):
return "Missing required parameter: {0}".format(self.name)
@attr.s
class EmptyParameterValue(OpenAPIParameterError):
pass
name = attr.ib()
def __str__(self):
return "Value of parameter cannot be empty: {0}".format(self.name)
@attr.s
class InvalidParameterValue(OpenAPIParameterError):
pass
name = attr.ib()
original_exception = attr.ib()
def __str__(self):
return "Invalid parameter value for `{0}`: {1}".format(sef.name, self.original_exception)

View file

@ -77,12 +77,10 @@ class Parameter(object):
if self.name not in location:
if self.required:
raise MissingRequiredParameter(
"Missing required `{0}` parameter".format(self.name))
raise MissingRequiredParameter(self.name)
if not self.schema or self.schema.default is None:
raise MissingParameter(
"Missing `{0}` parameter".format(self.name))
raise MissingParameter(self.name)
return self.schema.default
@ -100,8 +98,7 @@ class Parameter(object):
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))
raise EmptyParameterValue(self.name)
if not self.schema:
return value
@ -109,14 +106,14 @@ class Parameter(object):
try:
deserialized = self.deserialize(value)
except (ValueError, AttributeError) as exc:
raise InvalidParameterValue(str(exc))
raise InvalidParameterValue(self.name, exc)
try:
unmarshalled = self.schema.unmarshal(deserialized, custom_formatters=custom_formatters)
except InvalidSchemaValue as exc:
raise InvalidParameterValue(str(exc))
raise InvalidParameterValue(self.name, exc)
try:
return self.schema.validate(unmarshalled, custom_formatters=custom_formatters)
except InvalidSchemaValue as exc:
raise InvalidParameterValue(str(exc))
raise InvalidParameterValue(self.name, exc)

View file

@ -1,9 +1,15 @@
from openapi_core.schema.exceptions import OpenAPIMappingError
import attr
class OpenAPIRequestBodyError(OpenAPIMappingError):
pass
@attr.s
class MissingRequestBody(OpenAPIRequestBodyError):
pass
request = attr.ib()
def __str__(self):
return "Missing required request body"

View file

@ -16,11 +16,9 @@ class RequestBody(object):
try:
return self.content[mimetype]
except MimeTypeNotFound:
raise InvalidContentType(
"Invalid mime type `{0}`".format(mimetype))
raise InvalidContentType(mimetype)
def get_value(self, request):
if not request.body and self.required:
raise MissingRequestBody("Missing required request body")
raise MissingRequestBody(request)
return request.body

View file

@ -1,13 +1,24 @@
from openapi_core.schema.exceptions import OpenAPIMappingError
import attr
class OpenAPIResponseError(OpenAPIMappingError):
pass
@attr.s
class InvalidResponse(OpenAPIResponseError):
pass
http_status = attr.ib()
responses = attr.ib()
def __str__(self):
return "Unknown response http status: {0}".format(str(self.http_status))
@attr.s
class MissingResponseContent(OpenAPIResponseError):
pass
response = attr.ib()
def __str__(self):
return "Missing response content"

View file

@ -23,11 +23,10 @@ class Response(object):
try:
return self.content[mimetype]
except MimeTypeNotFound:
raise InvalidContentType(
"Invalid mime type `{0}`".format(mimetype))
raise InvalidContentType(mimetype)
def get_value(self, response):
if not response.data:
raise MissingResponseContent("Missing response content")
raise MissingResponseContent(response)
return response.data

View file

@ -1,33 +1,72 @@
from openapi_core.schema.exceptions import OpenAPIMappingError
import attr
class OpenAPISchemaError(OpenAPIMappingError):
pass
@attr.s
class NoValidSchema(OpenAPISchemaError):
pass
value = attr.ib()
def __str__(self):
return "No valid schema found for value: {0}".format(self.value)
@attr.s
class UndefinedItemsSchema(OpenAPISchemaError):
pass
type = attr.ib()
def __str__(self):
return "Null value for schema type {0}".format(self.type)
@attr.s
class InvalidSchemaValue(OpenAPISchemaError):
pass
msg = attr.ib()
value = attr.ib()
type = attr.ib()
def __str__(self):
return self.msg.format(value=self.value, type=self.type)
@attr.s
class InvalidCustomFormatSchemaValue(InvalidSchemaValue):
original_exception = attr.ib()
def __str__(self):
return self.msg.format(value=self.value, type=self.type, exception=self.original_exception)
@attr.s
class UndefinedSchemaProperty(OpenAPISchemaError):
pass
extra_props = attr.ib()
def __str__(self):
return "Extra unexpected properties found in schema: {0}".format(self.extra_props)
@attr.s
class MissingSchemaProperty(OpenAPISchemaError):
pass
property_name = attr.ib()
def __str__(self):
return "Missing schema property: {0}".format(self.property_name)
@attr.s
class NoOneOfSchema(OpenAPISchemaError):
pass
type = attr.ib()
def __str__(self):
return "Exactly one valid schema type {0} should be valid, None found.".format(self.type)
@attr.s
class MultipleOneOfSchema(OpenAPISchemaError):
pass
type = attr.ib()
def __str__(self):
return "Exactly one schema type {0} should be valid, more than one found".format(self.type)

View file

@ -14,7 +14,7 @@ from openapi_core.schema.schemas.enums import SchemaFormat, SchemaType
from openapi_core.schema.schemas.exceptions import (
InvalidSchemaValue, UndefinedSchemaProperty, MissingSchemaProperty,
OpenAPISchemaError, NoOneOfSchema, MultipleOneOfSchema, NoValidSchema,
UndefinedItemsSchema,
UndefinedItemsSchema, InvalidCustomFormatSchemaValue,
)
from openapi_core.schema.schemas.util import (
forcebool, format_date, format_datetime,
@ -160,7 +160,7 @@ class Schema(object):
"""Cast value to schema type"""
if value is None:
if not self.nullable:
raise InvalidSchemaValue("Null value for non-nullable schema")
raise InvalidSchemaValue("Null value for non-nullable schema", value, self.type)
return self.default
cast_mapping = self.get_cast_mapping(custom_formatters=custom_formatters)
@ -173,8 +173,7 @@ class Schema(object):
return cast_callable(value)
except ValueError:
raise InvalidSchemaValue(
"Failed to cast value of {0} to {1}".format(value, self.type)
)
"Failed to cast value {value} to type {type}", value, self.type)
def unmarshal(self, value, custom_formatters=None):
"""Unmarshal parameter from the value."""
@ -188,9 +187,7 @@ class Schema(object):
if self.enum and casted not in self.enum:
raise InvalidSchemaValue(
"Value of {0} not in enum choices: {1}".format(
value, self.enum)
)
"Value {value} not in enum choices: {type}", value, self.enum)
return casted
@ -198,23 +195,21 @@ class Schema(object):
try:
schema_format = SchemaFormat(self.format)
except ValueError:
msg = "Unsupported {0} format unmarshalling".format(self.format)
msg = "Unsupported format {type} unmarshalling for value {value}"
if custom_formatters is not None:
formatstring = custom_formatters.get(self.format)
if formatstring is None:
raise OpenAPISchemaError(msg)
raise InvalidSchemaValue(msg, value, self.format)
else:
raise OpenAPISchemaError(msg)
raise InvalidSchemaValue(msg, value, self.format)
else:
formatstring = self.STRING_FORMAT_CALLABLE_GETTER[schema_format]
try:
return formatstring.unmarshal(value)
except ValueError:
raise InvalidSchemaValue(
"Failed to format value of {0} to {1}".format(
value, self.format)
)
except ValueError as exc:
raise InvalidCustomFormatSchemaValue(
"Failed to format value {value} to format {type}: {exception}", value, self.format, exc)
def _unmarshal_any(self, value, custom_formatters=None):
types_resolve_order = [
@ -230,12 +225,11 @@ class Schema(object):
except (OpenAPISchemaError, TypeError, ValueError):
continue
raise NoValidSchema(
"No valid schema found for value {0}".format(value))
raise NoValidSchema(value)
def _unmarshal_collection(self, value, custom_formatters=None):
if self.items is None:
raise UndefinedItemsSchema("Undefined items' schema")
raise UndefinedItemsSchema(self.type)
f = functools.partial(self.items.unmarshal,
custom_formatters=custom_formatters)
@ -244,8 +238,7 @@ class Schema(object):
def _unmarshal_object(self, value, model_factory=None,
custom_formatters=None):
if not isinstance(value, (dict, )):
raise InvalidSchemaValue(
"Value of {0} not a dict".format(value))
raise InvalidSchemaValue("Value {value} is not of type {type}", value, self.type)
model_factory = model_factory or ModelFactory()
@ -259,14 +252,11 @@ class Schema(object):
pass
else:
if properties is not None:
raise MultipleOneOfSchema(
"Exactly one schema should be valid,"
"multiple found")
raise MultipleOneOfSchema(self.type)
properties = found_props
if properties is None:
raise NoOneOfSchema(
"Exactly one valid schema should be valid, None found.")
raise NoOneOfSchema(self.type)
else:
properties = self._unmarshal_properties(
@ -290,8 +280,7 @@ class Schema(object):
value_props_names = value.keys()
extra_props = set(value_props_names) - set(all_props_names)
if extra_props and self.additional_properties is None:
raise UndefinedSchemaProperty(
"Undefined properties in schema: {0}".format(extra_props))
raise UndefinedSchemaProperty(extra_props)
properties = {}
for prop_name in extra_props:
@ -304,8 +293,7 @@ class Schema(object):
prop_value = value[prop_name]
except KeyError:
if prop_name in all_req_props_names:
raise MissingSchemaProperty(
"Missing schema property {0}".format(prop_name))
raise MissingSchemaProperty(prop_name)
if not prop.nullable and not prop.default:
continue
prop_value = prop.default
@ -334,7 +322,7 @@ class Schema(object):
def validate(self, value, custom_formatters=None):
if value is None:
if not self.nullable:
raise InvalidSchemaValue("Null value for non-nullable schema")
raise InvalidSchemaValue("Null value for non-nullable schema of type {type}", value, self.type)
return
# type validation
@ -342,9 +330,7 @@ class Schema(object):
self.type]
if not type_validator_callable(value):
raise InvalidSchemaValue(
"Value of {0} not valid type of {1}".format(
value, self.type.value)
)
"Value {value} not valid type {type}", value, self.type.value)
# structure validation
validator_mapping = self.get_validator_mapping()
@ -355,7 +341,7 @@ class Schema(object):
def _validate_collection(self, value, custom_formatters=None):
if self.items is None:
raise OpenAPISchemaError("Schema for collection not defined")
raise UndefinedItemsSchema(self.type)
if self.min_items is not None:
if self.min_items < 0:
@ -436,9 +422,7 @@ class Schema(object):
if not formatstring.validate(value):
raise InvalidSchemaValue(
"Value of {0} not valid format of {1}".format(
value, self.format)
)
"Value {value} not valid format {type}", value, self.format)
if self.min_length is not None:
if self.min_length < 0:
@ -484,14 +468,11 @@ class Schema(object):
pass
else:
if valid_one_of_schema is not None:
raise MultipleOneOfSchema(
"Exactly one schema should be valid,"
"multiple found")
raise MultipleOneOfSchema(self.type)
valid_one_of_schema = True
if valid_one_of_schema is None:
raise NoOneOfSchema(
"Exactly one valid schema should be valid, None found.")
raise NoOneOfSchema(self.type)
else:
self._validate_properties(properties,
@ -542,8 +523,7 @@ class Schema(object):
value_props_names = value.keys()
extra_props = set(value_props_names) - set(all_props_names)
if extra_props and self.additional_properties is None:
raise UndefinedSchemaProperty(
"Undefined properties in schema: {0}".format(extra_props))
raise UndefinedSchemaProperty(extra_props)
for prop_name in extra_props:
prop_value = value[prop_name]
@ -555,8 +535,7 @@ class Schema(object):
prop_value = value[prop_name]
except KeyError:
if prop_name in all_req_props_names:
raise MissingSchemaProperty(
"Missing schema property {0}".format(prop_name))
raise MissingSchemaProperty(prop_name)
if not prop.nullable and not prop.default:
continue
prop_value = prop.default

View file

@ -1,9 +1,16 @@
from openapi_core.schema.exceptions import OpenAPIMappingError
import attr
class OpenAPIServerError(OpenAPIMappingError):
pass
@attr.s
class InvalidServer(OpenAPIServerError):
pass
full_url_pattern = attr.ib()
def __str__(self):
return "Invalid request server {0}".format(
self.full_url_pattern)

View file

@ -31,8 +31,7 @@ class Spec(object):
if spec_server.default_url in full_url_pattern:
return spec_server
raise InvalidServer(
"Invalid request server {0}".format(full_url_pattern))
raise InvalidServer(full_url_pattern)
def get_server_url(self, index=0):
return self.servers[index].default_url
@ -41,9 +40,7 @@ class Spec(object):
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))
raise InvalidOperation(path_pattern, http_method)
def get_schema(self, name):
return self.components.schemas[name]