Skip to content

Move schema validator to separate subpackage #186

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 5 additions & 11 deletions openapi_core/schema/media_types/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@

from openapi_core.schema.media_types.exceptions import InvalidMediaTypeValue
from openapi_core.schema.media_types.util import json_loads
from openapi_core.schema.schemas.exceptions import (
ValidateError,
)
from openapi_core.casting.schemas.exceptions import CastError
from openapi_core.unmarshalling.schemas.exceptions import UnmarshalError
from openapi_core.unmarshalling.schemas.exceptions import (
UnmarshalError, ValidateError,
)


MEDIA_TYPE_DESERIALIZERS = {
Expand Down Expand Up @@ -53,13 +52,8 @@ def unmarshal(self, value, custom_formatters=None, resolver=None):
if not self.schema:
return value

try:
self.schema.validate(value, resolver=resolver)
except ValidateError as exc:
raise InvalidMediaTypeValue(exc)

try:
return self.schema.unmarshal(
value, custom_formatters=custom_formatters)
except UnmarshalError as exc:
value, resolver=resolver, custom_formatters=custom_formatters)
except (ValidateError, UnmarshalError) as exc:
raise InvalidMediaTypeValue(exc)
16 changes: 5 additions & 11 deletions openapi_core/schema/parameters/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,10 @@
EmptyParameterValue,
)
from openapi_core.schema.schemas.enums import SchemaType
from openapi_core.schema.schemas.exceptions import (
ValidateError,
)
from openapi_core.casting.schemas.exceptions import CastError
from openapi_core.unmarshalling.schemas.exceptions import UnmarshalError
from openapi_core.unmarshalling.schemas.exceptions import (
UnmarshalError, ValidateError,
)

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -120,16 +119,11 @@ def unmarshal(self, value, custom_formatters=None, resolver=None):
if not self.schema:
return value

try:
self.schema.validate(value, resolver=resolver)
except ValidateError as exc:
raise InvalidParameterValue(self.name, exc)

try:
return self.schema.unmarshal(
value,
resolver=resolver,
custom_formatters=custom_formatters,
strict=True,
)
except UnmarshalError as exc:
except (ValidateError, UnmarshalError) as exc:
raise InvalidParameterValue(self.name, exc)
26 changes: 0 additions & 26 deletions openapi_core/schema/schemas/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,5 @@
import attr

from openapi_core.schema.exceptions import OpenAPIMappingError


class OpenAPISchemaError(OpenAPIMappingError):
pass


class ValidateError(OpenAPISchemaError):
"""Schema validate operation error"""
pass


@attr.s(hash=True)
class InvalidSchemaValue(ValidateError):
value = attr.ib()
type = attr.ib()
_schema_errors = attr.ib(default=None)
_schema_errors_iter = attr.ib(factory=list)

@property
def schema_errors(self):
if self._schema_errors is None:
self._schema_errors = list(self._schema_errors_iter)
return self._schema_errors

def __str__(self):
return (
"Value {value} not valid for schema of type {type}: {errors}"
).format(value=self.value, type=self.type, errors=self.schema_errors)
26 changes: 3 additions & 23 deletions openapi_core/schema/schemas/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,8 @@
import logging
import re

from jsonschema.exceptions import ValidationError

from openapi_core.schema.schemas._format import oas30_format_checker
from openapi_core.schema.schemas.enums import SchemaType
from openapi_core.schema.schemas.exceptions import InvalidSchemaValue
from openapi_core.schema.schemas.types import NoValue
from openapi_core.schema.schemas.validators import OAS30Validator
from openapi_core.unmarshalling.schemas.exceptions import (
UnmarshalValueError,
)
Expand Down Expand Up @@ -110,30 +105,15 @@ def cast(self, value):
except (ValueError, TypeError):
raise CastError(value, self.type)

def get_validator(self, resolver=None):
return OAS30Validator(
self.__dict__,
resolver=resolver, format_checker=oas30_format_checker,
)

def validate(self, value, resolver=None):
validator = self.get_validator(resolver=resolver)
try:
return validator.validate(value)
except ValidationError:
errors_iter = validator.iter_errors(value)
raise InvalidSchemaValue(
value, self.type, schema_errors_iter=errors_iter)

def unmarshal(self, value, custom_formatters=None, strict=True):
def unmarshal(self, value, resolver=None, custom_formatters=None):
"""Unmarshal parameter from the value."""
from openapi_core.unmarshalling.schemas.factories import (
SchemaUnmarshallersFactory,
)
unmarshallers_factory = SchemaUnmarshallersFactory(
custom_formatters)
resolver, custom_formatters)
unmarshaller = unmarshallers_factory.create(self)
try:
return unmarshaller(value, strict=strict)
return unmarshaller(value)
except ValueError as exc:
raise UnmarshalValueError(value, self.type, exc)
4 changes: 4 additions & 0 deletions openapi_core/schema_validator/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from openapi_core.schema_validator._format import oas30_format_checker
from openapi_core.schema_validator.validators import OAS30Validator

__all__ = ['OAS30Validator', 'oas30_format_checker']
Original file line number Diff line number Diff line change
Expand Up @@ -28,60 +28,41 @@
DATETIME_RAISES += (ValueError, TypeError)


class StrictFormatChecker(FormatChecker):

def check(self, instance, format):
if format not in self.checkers:
raise FormatError(
"Format checker for %r format not found" % (format, ))
return super(StrictFormatChecker, self).check(
instance, format)


oas30_format_checker = StrictFormatChecker()


@oas30_format_checker.checks('int32')
def is_int32(instance):
return isinstance(instance, integer_types)


@oas30_format_checker.checks('int64')
def is_int64(instance):
return isinstance(instance, integer_types)


@oas30_format_checker.checks('float')
def is_float(instance):
return isinstance(instance, float)


@oas30_format_checker.checks('double')
def is_double(instance):
# float has double precision in Python
# It's double in CPython and Jython
return isinstance(instance, float)


@oas30_format_checker.checks('binary')
def is_binary(instance):
return isinstance(instance, binary_type)


@oas30_format_checker.checks('byte', raises=(binascii.Error, TypeError))
def is_byte(instance):
if isinstance(instance, text_type):
instance = instance.encode()

return b64encode(b64decode(instance)) == instance
try:
return b64encode(b64decode(instance)) == instance
except TypeError:
return False


@oas30_format_checker.checks("date-time", raises=DATETIME_RAISES)
def is_datetime(instance):
if isinstance(instance, binary_type):
if not isinstance(instance, (binary_type, text_type)):
return False
if not isinstance(instance, text_type):
return True

if DATETIME_HAS_STRICT_RFC3339:
return strict_rfc3339.validate_rfc3339(instance)
Expand All @@ -92,24 +73,63 @@ def is_datetime(instance):
return True


@oas30_format_checker.checks("date", raises=ValueError)
def is_date(instance):
if isinstance(instance, binary_type):
if not isinstance(instance, (binary_type, text_type)):
return False
if not isinstance(instance, text_type):
return True

if isinstance(instance, binary_type):
instance = instance.decode()

return datetime.strptime(instance, "%Y-%m-%d")


@oas30_format_checker.checks("uuid", raises=AttributeError)
def is_uuid(instance):
if isinstance(instance, binary_type):
return False
if not isinstance(instance, text_type):
return True
try:
uuid_obj = UUID(instance)
except ValueError:
if not isinstance(instance, (binary_type, text_type)):
return False

return text_type(uuid_obj) == instance
if isinstance(instance, binary_type):
instance = instance.decode()

return text_type(UUID(instance)) == instance


def is_password(instance):
return True


class OASFormatChecker(FormatChecker):

checkers = {
'int32': (is_int32, ()),
'int64': (is_int64, ()),
'float': (is_float, ()),
'double': (is_double, ()),
'byte': (is_byte, (binascii.Error, TypeError)),
'binary': (is_binary, ()),
'date': (is_date, (ValueError, )),
'date-time': (is_datetime, DATETIME_RAISES),
'password': (is_password, ()),
# non standard
'uuid': (is_uuid, (AttributeError, ValueError)),
}

def check(self, instance, format):
if format not in self.checkers:
raise FormatError(
"Format checker for %r format not found" % (format, ))

func, raises = self.checkers[format]
result, cause = None, None
try:
result = func(instance)
except raises as e:
cause = e

if not result:
raise FormatError(
"%r is not a %r" % (instance, format), cause=cause,
)
return result


oas30_format_checker = OASFormatChecker()
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from jsonschema import _legacy_validators, _utils, _validators
from jsonschema.validators import create

from openapi_core.schema.schemas import _types as oas_types
from openapi_core.schema.schemas import _validators as oas_validators
from openapi_core.schema_validator import _types as oas_types
from openapi_core.schema_validator import _validators as oas_validators


BaseOAS30Validator = create(
Expand Down
32 changes: 19 additions & 13 deletions openapi_core/unmarshalling/schemas/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ class UnmarshalError(OpenAPIError):
pass


class ValidateError(UnmarshalError):
"""Schema validate operation error"""
pass


class UnmarshallerError(UnmarshalError):
"""Unmarshaller error"""
pass
Expand All @@ -30,8 +35,20 @@ def __str__(self):


@attr.s(hash=True)
class InvalidCustomFormatSchemaValue(UnmarshallerError):
"""Value failed to format with custom formatter"""
class InvalidSchemaValue(ValidateError):
value = attr.ib()
type = attr.ib()
schema_errors = attr.ib(factory=tuple)

def __str__(self):
return (
"Value {value} not valid for schema of type {type}: {errors}"
).format(value=self.value, type=self.type, errors=self.schema_errors)


@attr.s(hash=True)
class InvalidSchemaFormatValue(UnmarshallerError):
"""Value failed to format with formatter"""
value = attr.ib()
type = attr.ib()
original_exception = attr.ib()
Expand All @@ -53,14 +70,3 @@ class FormatterNotFoundError(UnmarshallerError):
def __str__(self):
return "Formatter not found for {format} format".format(
format=self.type_format)


@attr.s(hash=True)
class UnmarshallerStrictTypeError(UnmarshallerError):
value = attr.ib()
types = attr.ib()

def __str__(self):
types = ', '.join(list(map(str, self.types)))
return "Value {value} is not one of types: {types}".format(
value=self.value, types=types)
Loading