Skip to content

Commit fc60083

Browse files
committed
Get rid of schema validation in unmarshal
1 parent a2fc528 commit fc60083

File tree

7 files changed

+80
-136
lines changed

7 files changed

+80
-136
lines changed

openapi_core/schema/schemas/_format.py

+33-23
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,26 @@
77
from jsonschema.exceptions import FormatError
88
from six import binary_type, text_type, integer_types
99

10+
DATETIME_HAS_STRICT_RFC3339 = False
11+
DATETIME_HAS_ISODATE = False
12+
DATETIME_RAISES = ()
13+
14+
try:
15+
import isodate
16+
except ImportError:
17+
pass
18+
else:
19+
DATETIME_HAS_ISODATE = True
20+
DATETIME_RAISES += (ValueError, isodate.ISO8601Error)
21+
22+
try:
23+
import strict_rfc3339
24+
except ImportError:
25+
pass
26+
else:
27+
DATETIME_HAS_STRICT_RFC3339 = True
28+
DATETIME_RAISES += (ValueError, TypeError)
29+
1030

1131
class StrictFormatChecker(FormatChecker):
1232

@@ -56,30 +76,20 @@ def is_byte(instance):
5676
return b64encode(b64decode(instance)) == instance
5777

5878

59-
try:
60-
import strict_rfc3339
61-
except ImportError:
62-
try:
63-
import isodate
64-
except ImportError:
65-
pass
66-
else:
67-
@oas30_format_checker.checks(
68-
"date-time", raises=(ValueError, isodate.ISO8601Error))
69-
def is_datetime(instance):
70-
if isinstance(instance, binary_type):
71-
return False
72-
if not isinstance(instance, text_type):
73-
return True
74-
return isodate.parse_datetime(instance)
75-
else:
76-
@oas30_format_checker.checks("date-time")
77-
def is_datetime(instance):
78-
if isinstance(instance, binary_type):
79-
return False
80-
if not isinstance(instance, text_type):
81-
return True
79+
@oas30_format_checker.checks("date-time", raises=DATETIME_RAISES)
80+
def is_datetime(instance):
81+
if isinstance(instance, binary_type):
82+
return False
83+
if not isinstance(instance, text_type):
84+
return True
85+
86+
if DATETIME_HAS_STRICT_RFC3339:
8287
return strict_rfc3339.validate_rfc3339(instance)
88+
89+
if DATETIME_HAS_ISODATE:
90+
return isodate.parse_datetime(instance)
91+
92+
return True
8393

8494

8595
@oas30_format_checker.checks("date", raises=ValueError)

openapi_core/schema/schemas/exceptions.py

-16
Original file line numberDiff line numberDiff line change
@@ -66,22 +66,6 @@ def __str__(self):
6666
return "Missing schema property: {0}".format(self.property_name)
6767

6868

69-
@attr.s(hash=True)
70-
class NoOneOfSchema(OpenAPISchemaError):
71-
type = attr.ib()
72-
73-
def __str__(self):
74-
return "Exactly one valid schema type {0} should be valid, None found.".format(self.type)
75-
76-
77-
@attr.s(hash=True)
78-
class MultipleOneOfSchema(OpenAPISchemaError):
79-
type = attr.ib()
80-
81-
def __str__(self):
82-
return "Exactly one schema type {0} should be valid, more than one found".format(self.type)
83-
84-
8569
class UnmarshallerError(OpenAPIMappingError):
8670
pass
8771

openapi_core/schema/schemas/models.py

+15-46
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,15 @@
1616
from openapi_core.schema.schemas.enums import SchemaFormat, SchemaType
1717
from openapi_core.schema.schemas.exceptions import (
1818
InvalidSchemaValue, UndefinedSchemaProperty, MissingSchemaProperty,
19-
OpenAPISchemaError, NoOneOfSchema, MultipleOneOfSchema, NoValidSchema,
19+
OpenAPISchemaError, NoValidSchema,
2020
UndefinedItemsSchema, InvalidCustomFormatSchemaValue, InvalidSchemaProperty,
2121
UnmarshallerStrictTypeError,
2222
)
2323
from openapi_core.schema.schemas.util import (
2424
forcebool, format_date, format_datetime, format_byte, format_uuid,
2525
format_number,
2626
)
27-
from openapi_core.schema.schemas.validators import (
28-
TypeValidator, AttributeValidator, OAS30Validator,
29-
)
27+
from openapi_core.schema.schemas.validators import OAS30Validator
3028

3129
log = logging.getLogger(__name__)
3230

@@ -49,36 +47,6 @@ class Schema(object):
4947
DEFAULT_UNMARSHAL_CALLABLE_GETTER = {
5048
}
5149

52-
STRING_FORMAT_CALLABLE_GETTER = {
53-
SchemaFormat.NONE: Format(text_type, TypeValidator(text_type)),
54-
SchemaFormat.PASSWORD: Format(text_type, TypeValidator(text_type)),
55-
SchemaFormat.DATE: Format(
56-
format_date, TypeValidator(date, exclude=datetime)),
57-
SchemaFormat.DATETIME: Format(format_datetime, TypeValidator(datetime)),
58-
SchemaFormat.BINARY: Format(binary_type, TypeValidator(binary_type)),
59-
SchemaFormat.UUID: Format(format_uuid, TypeValidator(UUID)),
60-
SchemaFormat.BYTE: Format(format_byte, TypeValidator(text_type)),
61-
}
62-
63-
NUMBER_FORMAT_CALLABLE_GETTER = {
64-
SchemaFormat.NONE: Format(format_number, TypeValidator(
65-
integer_types + (float, ), exclude=bool)),
66-
SchemaFormat.FLOAT: Format(float, TypeValidator(float)),
67-
SchemaFormat.DOUBLE: Format(float, TypeValidator(float)),
68-
}
69-
70-
TYPE_VALIDATOR_CALLABLE_GETTER = {
71-
SchemaType.ANY: lambda x: True,
72-
SchemaType.BOOLEAN: TypeValidator(bool),
73-
SchemaType.INTEGER: TypeValidator(integer_types, exclude=bool),
74-
SchemaType.NUMBER: TypeValidator(
75-
integer_types + (float, ), exclude=bool),
76-
SchemaType.STRING: TypeValidator(
77-
text_type, date, datetime, binary_type, UUID),
78-
SchemaType.ARRAY: TypeValidator(list, tuple),
79-
SchemaType.OBJECT: AttributeValidator('__dict__'),
80-
}
81-
8250
def __init__(
8351
self, schema_type=None, model=None, properties=None, items=None,
8452
schema_format=None, required=None, default=None, nullable=False,
@@ -304,11 +272,12 @@ def _unmarshal_any(self, value, custom_formatters=None, strict=True):
304272
continue
305273
else:
306274
if result is not None:
307-
raise MultipleOneOfSchema(self.type)
275+
log.warning("multiple valid oneOf schemas found")
276+
continue
308277
result = unmarshalled
309278

310279
if result is None:
311-
raise NoOneOfSchema(self.type)
280+
log.warning("valid oneOf schema not found")
312281

313282
return result
314283
else:
@@ -321,7 +290,8 @@ def _unmarshal_any(self, value, custom_formatters=None, strict=True):
321290
except (OpenAPISchemaError, TypeError):
322291
continue
323292

324-
raise NoValidSchema(value)
293+
log.warning("failed to unmarshal any type")
294+
return value
325295

326296
def _unmarshal_collection(self, value, custom_formatters=None, strict=True):
327297
if not isinstance(value, (list, tuple)):
@@ -344,17 +314,18 @@ def _unmarshal_object(self, value, model_factory=None,
344314
properties = None
345315
for one_of_schema in self.one_of:
346316
try:
347-
found_props = self._unmarshal_properties(
317+
unmarshalled = self._unmarshal_properties(
348318
value, one_of_schema, custom_formatters=custom_formatters)
349319
except OpenAPISchemaError:
350320
pass
351321
else:
352322
if properties is not None:
353-
raise MultipleOneOfSchema(self.type)
354-
properties = found_props
323+
log.warning("multiple valid oneOf schemas found")
324+
continue
325+
properties = unmarshalled
355326

356327
if properties is None:
357-
raise NoOneOfSchema(self.type)
328+
log.warning("valid oneOf schema not found")
358329

359330
else:
360331
properties = self._unmarshal_properties(
@@ -398,10 +369,8 @@ def _unmarshal_properties(self, value, one_of_schema=None,
398369
if not prop.nullable and not prop.default:
399370
continue
400371
prop_value = prop.default
401-
try:
402-
properties[prop_name] = prop.unmarshal(
403-
prop_value, custom_formatters=custom_formatters)
404-
except OpenAPISchemaError as exc:
405-
raise InvalidSchemaProperty(prop_name, exc)
372+
373+
properties[prop_name] = prop.unmarshal(
374+
prop_value, custom_formatters=custom_formatters)
406375

407376
return properties

openapi_core/schema/schemas/unmarshallers.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from openapi_core.schema.schemas.enums import SchemaFormat, SchemaType
44
from openapi_core.schema.schemas.exceptions import (
55
InvalidSchemaValue, InvalidCustomFormatSchemaValue,
6-
OpenAPISchemaError, MultipleOneOfSchema, NoOneOfSchema,
6+
OpenAPISchemaError,
77
InvalidSchemaProperty,
88
UnmarshallerStrictTypeError,
99
)

openapi_core/schema/schemas/validators.py

-28
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,6 @@
55
from openapi_core.schema.schemas import _validators as oas_validators
66

77

8-
class TypeValidator(object):
9-
10-
def __init__(self, *types, **options):
11-
self.types = types
12-
self.exclude = options.get('exclude')
13-
14-
def __call__(self, value):
15-
if self.exclude is not None and isinstance(value, self.exclude):
16-
return False
17-
18-
if not isinstance(value, self.types):
19-
return False
20-
21-
return True
22-
23-
24-
class AttributeValidator(object):
25-
26-
def __init__(self, attribute):
27-
self.attribute = attribute
28-
29-
def __call__(self, value):
30-
if not hasattr(value, self.attribute):
31-
return False
32-
33-
return True
34-
35-
368
BaseOAS30Validator = create(
379
meta_schema=_utils.load_schema("draft4"),
3810
validators={

requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ openapi-spec-validator
22
six
33
lazy-object-proxy
44
strict_rfc3339
5+
isodate
56
attrs

tests/unit/schema/test_schemas.py

+30-22
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from openapi_core.extensions.models.models import Model
88
from openapi_core.schema.schemas.enums import SchemaFormat, SchemaType
99
from openapi_core.schema.schemas.exceptions import (
10-
InvalidSchemaValue, MultipleOneOfSchema, NoOneOfSchema, OpenAPISchemaError,
10+
InvalidSchemaValue, OpenAPISchemaError,
1111
)
1212
from openapi_core.schema.schemas.models import Schema
1313

@@ -167,10 +167,7 @@ def test_string_format_invalid_value(self):
167167
schema = Schema('string', schema_format=custom_format)
168168
value = 'x'
169169

170-
with mock.patch.dict(
171-
Schema.STRING_FORMAT_CALLABLE_GETTER,
172-
{custom_format: mock.Mock(side_effect=ValueError())},
173-
), pytest.raises(
170+
with pytest.raises(
174171
InvalidSchemaValue, message='Failed to format value'
175172
):
176173
schema.unmarshal(value)
@@ -325,22 +322,6 @@ def test_schema_any_one_of(self):
325322
])
326323
assert schema.unmarshal(['hello']) == ['hello']
327324

328-
def test_schema_any_one_of_mutiple(self):
329-
schema = Schema(one_of=[
330-
Schema('array', items=Schema('string')),
331-
Schema('array', items=Schema('number')),
332-
])
333-
with pytest.raises(MultipleOneOfSchema):
334-
schema.unmarshal([])
335-
336-
def test_schema_any_one_of_no_valid(self):
337-
schema = Schema(one_of=[
338-
Schema('array', items=Schema('string')),
339-
Schema('array', items=Schema('number')),
340-
])
341-
with pytest.raises(NoOneOfSchema):
342-
schema.unmarshal({})
343-
344325
def test_schema_any(self):
345326
schema = Schema()
346327
assert schema.unmarshal('string') == 'string'
@@ -635,7 +616,34 @@ def test_string_format_datetime_invalid(self, value):
635616
u('1989-01-02T00:00:00Z'),
636617
u('2018-01-02T23:59:59Z'),
637618
])
638-
def test_string_format_datetime(self, value):
619+
@mock.patch(
620+
'openapi_core.schema.schemas._format.'
621+
'DATETIME_HAS_STRICT_RFC3339', True
622+
)
623+
@mock.patch(
624+
'openapi_core.schema.schemas._format.'
625+
'DATETIME_HAS_ISODATE', False
626+
)
627+
def test_string_format_datetime_strict_rfc3339(self, value):
628+
schema = Schema('string', schema_format='date-time')
629+
630+
result = schema.validate(value)
631+
632+
assert result is None
633+
634+
@pytest.mark.parametrize('value', [
635+
u('1989-01-02T00:00:00Z'),
636+
u('2018-01-02T23:59:59Z'),
637+
])
638+
@mock.patch(
639+
'openapi_core.schema.schemas._format.'
640+
'DATETIME_HAS_STRICT_RFC3339', False
641+
)
642+
@mock.patch(
643+
'openapi_core.schema.schemas._format.'
644+
'DATETIME_HAS_ISODATE', True
645+
)
646+
def test_string_format_datetime_isodate(self, value):
639647
schema = Schema('string', schema_format='date-time')
640648

641649
result = schema.validate(value)

0 commit comments

Comments
 (0)