Skip to content

Commit 376dc20

Browse files
committed
unmarshalling formatters
1 parent 6b6abc0 commit 376dc20

File tree

14 files changed

+1045
-986
lines changed

14 files changed

+1045
-986
lines changed

openapi_core/schema/media_types/models.py

+5-11
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@
33

44
from openapi_core.schema.media_types.exceptions import InvalidMediaTypeValue
55
from openapi_core.schema.media_types.util import json_loads
6-
from openapi_core.schema.schemas.exceptions import (
7-
ValidateError,
8-
)
96
from openapi_core.casting.schemas.exceptions import CastError
10-
from openapi_core.unmarshalling.schemas.exceptions import UnmarshalError
7+
from openapi_core.unmarshalling.schemas.exceptions import (
8+
UnmarshalError, ValidateError,
9+
)
1110

1211

1312
MEDIA_TYPE_DESERIALIZERS = {
@@ -53,13 +52,8 @@ def unmarshal(self, value, custom_formatters=None, resolver=None):
5352
if not self.schema:
5453
return value
5554

56-
try:
57-
self.schema.validate(value, resolver=resolver)
58-
except ValidateError as exc:
59-
raise InvalidMediaTypeValue(exc)
60-
6155
try:
6256
return self.schema.unmarshal(
63-
value, custom_formatters=custom_formatters)
64-
except UnmarshalError as exc:
57+
value, resolver=resolver, custom_formatters=custom_formatters)
58+
except (ValidateError, UnmarshalError) as exc:
6559
raise InvalidMediaTypeValue(exc)

openapi_core/schema/parameters/models.py

+5-11
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,10 @@
1010
EmptyParameterValue,
1111
)
1212
from openapi_core.schema.schemas.enums import SchemaType
13-
from openapi_core.schema.schemas.exceptions import (
14-
ValidateError,
15-
)
1613
from openapi_core.casting.schemas.exceptions import CastError
17-
from openapi_core.unmarshalling.schemas.exceptions import UnmarshalError
14+
from openapi_core.unmarshalling.schemas.exceptions import (
15+
UnmarshalError, ValidateError,
16+
)
1817

1918
log = logging.getLogger(__name__)
2019

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

123-
try:
124-
self.schema.validate(value, resolver=resolver)
125-
except ValidateError as exc:
126-
raise InvalidParameterValue(self.name, exc)
127-
128122
try:
129123
return self.schema.unmarshal(
130124
value,
125+
resolver=resolver,
131126
custom_formatters=custom_formatters,
132-
strict=True,
133127
)
134-
except UnmarshalError as exc:
128+
except (ValidateError, UnmarshalError) as exc:
135129
raise InvalidParameterValue(self.name, exc)
-26
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,5 @@
1-
import attr
2-
31
from openapi_core.schema.exceptions import OpenAPIMappingError
42

53

64
class OpenAPISchemaError(OpenAPIMappingError):
75
pass
8-
9-
10-
class ValidateError(OpenAPISchemaError):
11-
"""Schema validate operation error"""
12-
pass
13-
14-
15-
@attr.s(hash=True)
16-
class InvalidSchemaValue(ValidateError):
17-
value = attr.ib()
18-
type = attr.ib()
19-
_schema_errors = attr.ib(default=None)
20-
_schema_errors_iter = attr.ib(factory=list)
21-
22-
@property
23-
def schema_errors(self):
24-
if self._schema_errors is None:
25-
self._schema_errors = list(self._schema_errors_iter)
26-
return self._schema_errors
27-
28-
def __str__(self):
29-
return (
30-
"Value {value} not valid for schema of type {type}: {errors}"
31-
).format(value=self.value, type=self.type, errors=self.schema_errors)

openapi_core/schema/schemas/models.py

+3-23
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,7 @@
33
import logging
44
import re
55

6-
from jsonschema.exceptions import ValidationError
7-
8-
from openapi_core.schema_validator import OAS30Validator
9-
from openapi_core.schema_validator import oas30_format_checker
106
from openapi_core.schema.schemas.enums import SchemaType
11-
from openapi_core.schema.schemas.exceptions import InvalidSchemaValue
127
from openapi_core.schema.schemas.types import NoValue
138
from openapi_core.unmarshalling.schemas.exceptions import (
149
UnmarshalValueError,
@@ -110,30 +105,15 @@ def cast(self, value):
110105
except (ValueError, TypeError):
111106
raise CastError(value, self.type)
112107

113-
def get_validator(self, resolver=None):
114-
return OAS30Validator(
115-
self.__dict__,
116-
resolver=resolver, format_checker=oas30_format_checker,
117-
)
118-
119-
def validate(self, value, resolver=None):
120-
validator = self.get_validator(resolver=resolver)
121-
try:
122-
return validator.validate(value)
123-
except ValidationError:
124-
errors_iter = validator.iter_errors(value)
125-
raise InvalidSchemaValue(
126-
value, self.type, schema_errors_iter=errors_iter)
127-
128-
def unmarshal(self, value, custom_formatters=None, strict=True):
108+
def unmarshal(self, value, resolver=None, custom_formatters=None):
129109
"""Unmarshal parameter from the value."""
130110
from openapi_core.unmarshalling.schemas.factories import (
131111
SchemaUnmarshallersFactory,
132112
)
133113
unmarshallers_factory = SchemaUnmarshallersFactory(
134-
custom_formatters)
114+
resolver, custom_formatters)
135115
unmarshaller = unmarshallers_factory.create(self)
136116
try:
137-
return unmarshaller(value, strict=strict)
117+
return unmarshaller(value)
138118
except ValueError as exc:
139119
raise UnmarshalValueError(value, self.type, exc)

openapi_core/schema_validator/_format.py

+57-37
Original file line numberDiff line numberDiff line change
@@ -28,60 +28,41 @@
2828
DATETIME_RAISES += (ValueError, TypeError)
2929

3030

31-
class StrictFormatChecker(FormatChecker):
32-
33-
def check(self, instance, format):
34-
if format not in self.checkers:
35-
raise FormatError(
36-
"Format checker for %r format not found" % (format, ))
37-
return super(StrictFormatChecker, self).check(
38-
instance, format)
39-
40-
41-
oas30_format_checker = StrictFormatChecker()
42-
43-
44-
@oas30_format_checker.checks('int32')
4531
def is_int32(instance):
4632
return isinstance(instance, integer_types)
4733

4834

49-
@oas30_format_checker.checks('int64')
5035
def is_int64(instance):
5136
return isinstance(instance, integer_types)
5237

5338

54-
@oas30_format_checker.checks('float')
5539
def is_float(instance):
5640
return isinstance(instance, float)
5741

5842

59-
@oas30_format_checker.checks('double')
6043
def is_double(instance):
6144
# float has double precision in Python
6245
# It's double in CPython and Jython
6346
return isinstance(instance, float)
6447

6548

66-
@oas30_format_checker.checks('binary')
6749
def is_binary(instance):
6850
return isinstance(instance, binary_type)
6951

7052

71-
@oas30_format_checker.checks('byte', raises=(binascii.Error, TypeError))
7253
def is_byte(instance):
7354
if isinstance(instance, text_type):
7455
instance = instance.encode()
7556

76-
return b64encode(b64decode(instance)) == instance
57+
try:
58+
return b64encode(b64decode(instance)) == instance
59+
except TypeError:
60+
return False
7761

7862

79-
@oas30_format_checker.checks("date-time", raises=DATETIME_RAISES)
8063
def is_datetime(instance):
81-
if isinstance(instance, binary_type):
64+
if not isinstance(instance, (binary_type, text_type)):
8265
return False
83-
if not isinstance(instance, text_type):
84-
return True
8566

8667
if DATETIME_HAS_STRICT_RFC3339:
8768
return strict_rfc3339.validate_rfc3339(instance)
@@ -92,24 +73,63 @@ def is_datetime(instance):
9273
return True
9374

9475

95-
@oas30_format_checker.checks("date", raises=ValueError)
9676
def is_date(instance):
97-
if isinstance(instance, binary_type):
77+
if not isinstance(instance, (binary_type, text_type)):
9878
return False
99-
if not isinstance(instance, text_type):
100-
return True
79+
80+
if isinstance(instance, binary_type):
81+
instance = instance.decode()
82+
10183
return datetime.strptime(instance, "%Y-%m-%d")
10284

10385

104-
@oas30_format_checker.checks("uuid", raises=AttributeError)
10586
def is_uuid(instance):
106-
if isinstance(instance, binary_type):
107-
return False
108-
if not isinstance(instance, text_type):
109-
return True
110-
try:
111-
uuid_obj = UUID(instance)
112-
except ValueError:
87+
if not isinstance(instance, (binary_type, text_type)):
11388
return False
11489

115-
return text_type(uuid_obj) == instance
90+
if isinstance(instance, binary_type):
91+
instance = instance.decode()
92+
93+
return text_type(UUID(instance)) == instance
94+
95+
96+
def is_password(instance):
97+
return True
98+
99+
100+
class OASFormatChecker(FormatChecker):
101+
102+
checkers = {
103+
'int32': (is_int32, ()),
104+
'int64': (is_int64, ()),
105+
'float': (is_float, ()),
106+
'double': (is_double, ()),
107+
'byte': (is_byte, (binascii.Error, TypeError)),
108+
'binary': (is_binary, ()),
109+
'date': (is_date, (ValueError, )),
110+
'date-time': (is_datetime, DATETIME_RAISES),
111+
'password': (is_password, ()),
112+
# non standard
113+
'uuid': (is_uuid, (AttributeError, ValueError)),
114+
}
115+
116+
def check(self, instance, format):
117+
if format not in self.checkers:
118+
raise FormatError(
119+
"Format checker for %r format not found" % (format, ))
120+
121+
func, raises = self.checkers[format]
122+
result, cause = None, None
123+
try:
124+
result = func(instance)
125+
except raises as e:
126+
cause = e
127+
128+
if not result:
129+
raise FormatError(
130+
"%r is not a %r" % (instance, format), cause=cause,
131+
)
132+
return result
133+
134+
135+
oas30_format_checker = OASFormatChecker()

openapi_core/unmarshalling/schemas/exceptions.py

+19-13
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ class UnmarshalError(OpenAPIError):
88
pass
99

1010

11+
class ValidateError(UnmarshalError):
12+
"""Schema validate operation error"""
13+
pass
14+
15+
1116
class UnmarshallerError(UnmarshalError):
1217
"""Unmarshaller error"""
1318
pass
@@ -30,8 +35,20 @@ def __str__(self):
3035

3136

3237
@attr.s(hash=True)
33-
class InvalidCustomFormatSchemaValue(UnmarshallerError):
34-
"""Value failed to format with custom formatter"""
38+
class InvalidSchemaValue(ValidateError):
39+
value = attr.ib()
40+
type = attr.ib()
41+
schema_errors = attr.ib(factory=tuple)
42+
43+
def __str__(self):
44+
return (
45+
"Value {value} not valid for schema of type {type}: {errors}"
46+
).format(value=self.value, type=self.type, errors=self.schema_errors)
47+
48+
49+
@attr.s(hash=True)
50+
class InvalidSchemaFormatValue(UnmarshallerError):
51+
"""Value failed to format with formatter"""
3552
value = attr.ib()
3653
type = attr.ib()
3754
original_exception = attr.ib()
@@ -53,14 +70,3 @@ class FormatterNotFoundError(UnmarshallerError):
5370
def __str__(self):
5471
return "Formatter not found for {format} format".format(
5572
format=self.type_format)
56-
57-
58-
@attr.s(hash=True)
59-
class UnmarshallerStrictTypeError(UnmarshallerError):
60-
value = attr.ib()
61-
types = attr.ib()
62-
63-
def __str__(self):
64-
types = ', '.join(list(map(str, self.types)))
65-
return "Value {value} is not one of types: {types}".format(
66-
value=self.value, types=types)

openapi_core/unmarshalling/schemas/factories.py

+21-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
from copy import deepcopy
12
import warnings
23

34
from openapi_core.schema.schemas.enums import SchemaType, SchemaFormat
5+
from openapi_core.schema_validator import OAS30Validator
6+
from openapi_core.schema_validator import oas30_format_checker
47
from openapi_core.unmarshalling.schemas.exceptions import (
58
FormatterNotFoundError,
69
)
@@ -25,7 +28,8 @@ class SchemaUnmarshallersFactory(object):
2528
SchemaType.ANY: AnyUnmarshaller,
2629
}
2730

28-
def __init__(self, custom_formatters=None):
31+
def __init__(self, resolver=None, custom_formatters=None):
32+
self.resolver = resolver
2933
if custom_formatters is None:
3034
custom_formatters = {}
3135
self.custom_formatters = custom_formatters
@@ -42,21 +46,31 @@ def create(self, schema, type_override=None):
4246

4347
elif schema_type in self.COMPLEX_UNMARSHALLERS:
4448
klass = self.COMPLEX_UNMARSHALLERS[schema_type]
45-
kwargs = dict(schema=schema, unmarshallers_factory=self)
49+
kwargs = dict(
50+
schema=schema, unmarshallers_factory=self)
4651

4752
formatter = self.get_formatter(klass.FORMATTERS, schema.format)
4853

4954
if formatter is None:
5055
raise FormatterNotFoundError(schema.format)
5156

52-
return klass(formatter, **kwargs)
57+
validator = self.get_validator(schema)
5358

54-
def get_formatter(self, formatters, type_format=SchemaFormat.NONE):
59+
return klass(formatter, validator, **kwargs)
60+
61+
def get_formatter(self, default_formatters, type_format=SchemaFormat.NONE):
5562
try:
5663
schema_format = SchemaFormat(type_format)
5764
except ValueError:
5865
return self.custom_formatters.get(type_format)
5966
else:
60-
if schema_format == SchemaFormat.NONE:
61-
return lambda x: x
62-
return formatters.get(schema_format)
67+
return default_formatters.get(schema_format)
68+
69+
def get_validator(self, schema):
70+
format_checker = deepcopy(oas30_format_checker)
71+
for name, formatter in self.custom_formatters.items():
72+
format_checker.checks(name)(formatter.validate)
73+
return OAS30Validator(
74+
schema.__dict__,
75+
resolver=self.resolver, format_checker=format_checker,
76+
)

0 commit comments

Comments
 (0)