From 61fb830ac252d3782d2dd9cd4a82e54528d66ccf Mon Sep 17 00:00:00 2001 From: p1c2u Date: Mon, 3 Feb 2020 01:29:00 +0000 Subject: [PATCH] Get rid of unmarshal in schema --- openapi_core/schema/schemas/models.py | 16 - .../unmarshalling/schemas/exceptions.py | 16 - openapi_core/validation/request/validators.py | 11 +- .../validation/response/validators.py | 11 +- tests/unit/schema/test_schemas.py | 317 ---------------- tests/unit/unmarshalling/test_unmarshal.py | 338 +++++++++++++++++- 6 files changed, 341 insertions(+), 368 deletions(-) diff --git a/openapi_core/schema/schemas/models.py b/openapi_core/schema/schemas/models.py index af780f3e..1be1a56c 100644 --- a/openapi_core/schema/schemas/models.py +++ b/openapi_core/schema/schemas/models.py @@ -5,9 +5,6 @@ from openapi_core.schema.schemas.enums import SchemaType from openapi_core.schema.schemas.types import NoValue -from openapi_core.unmarshalling.schemas.exceptions import ( - UnmarshalValueError, -) log = logging.getLogger(__name__) @@ -104,16 +101,3 @@ def cast(self, value): return caster(value) except (ValueError, TypeError): raise CastError(value, self.type) - - 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( - resolver, custom_formatters) - unmarshaller = unmarshallers_factory.create(self) - try: - return unmarshaller(value) - except ValueError as exc: - raise UnmarshalValueError(value, self.type, exc) diff --git a/openapi_core/unmarshalling/schemas/exceptions.py b/openapi_core/unmarshalling/schemas/exceptions.py index 76db72b6..67eede67 100644 --- a/openapi_core/unmarshalling/schemas/exceptions.py +++ b/openapi_core/unmarshalling/schemas/exceptions.py @@ -18,22 +18,6 @@ class UnmarshallerError(UnmarshalError): pass -@attr.s(hash=True) -class UnmarshalValueError(UnmarshalError): - """Failed to unmarshal value to type""" - value = attr.ib() - type = attr.ib() - original_exception = attr.ib(default=None) - - def __str__(self): - return ( - "Failed to unmarshal value {value} to type {type}: {exception}" - ).format( - value=self.value, type=self.type, - exception=self.original_exception, - ) - - @attr.s(hash=True) class InvalidSchemaValue(ValidateError): value = attr.ib() diff --git a/openapi_core/validation/request/validators.py b/openapi_core/validation/request/validators.py index 2fc3199d..37106e4f 100644 --- a/openapi_core/validation/request/validators.py +++ b/openapi_core/validation/request/validators.py @@ -157,8 +157,11 @@ def _unmarshal(self, param_or_media_type, value): if not param_or_media_type.schema: return value - return param_or_media_type.schema.unmarshal( - value, - resolver=self.spec._resolver, - custom_formatters=self.custom_formatters, + from openapi_core.unmarshalling.schemas.factories import ( + SchemaUnmarshallersFactory, ) + unmarshallers_factory = SchemaUnmarshallersFactory( + self.spec._resolver, self.custom_formatters) + unmarshaller = unmarshallers_factory.create( + param_or_media_type.schema) + return unmarshaller(value) diff --git a/openapi_core/validation/response/validators.py b/openapi_core/validation/response/validators.py index 6ca479ae..350184b0 100644 --- a/openapi_core/validation/response/validators.py +++ b/openapi_core/validation/response/validators.py @@ -104,8 +104,11 @@ def _unmarshal(self, param_or_media_type, value): if not param_or_media_type.schema: return value - return param_or_media_type.schema.unmarshal( - value, - resolver=self.spec._resolver, - custom_formatters=self.custom_formatters, + from openapi_core.unmarshalling.schemas.factories import ( + SchemaUnmarshallersFactory, ) + unmarshallers_factory = SchemaUnmarshallersFactory( + self.spec._resolver, self.custom_formatters) + unmarshaller = unmarshallers_factory.create( + param_or_media_type.schema) + return unmarshaller(value) diff --git a/tests/unit/schema/test_schemas.py b/tests/unit/schema/test_schemas.py index bd31049f..e9f8b06f 100644 --- a/tests/unit/schema/test_schemas.py +++ b/tests/unit/schema/test_schemas.py @@ -1,19 +1,7 @@ -import datetime -import uuid - import mock import pytest -from openapi_core.schema.schemas.enums import SchemaType from openapi_core.schema.schemas.models import Schema -from openapi_core.schema.schemas.types import NoValue -from openapi_core.unmarshalling.schemas.exceptions import ( - InvalidSchemaFormatValue, - FormatterNotFoundError, - UnmarshalError, - InvalidSchemaValue, -) -from openapi_core.unmarshalling.schemas.formatters import Formatter class TestSchemaIteritems(object): @@ -30,308 +18,3 @@ def schema(self): def test_valid(self, schema): for name in schema.properties: assert schema[name] == schema.properties[name] - - -class TestSchemaUnmarshal(object): - - def test_deprecated(self): - schema = Schema('string', deprecated=True) - value = 'test' - - with pytest.warns(DeprecationWarning): - result = schema.unmarshal(value) - - assert result == value - - @pytest.mark.parametrize('schema_type', [ - 'boolean', 'array', 'integer', 'number', - ]) - def test_non_string_empty_value(self, schema_type): - schema = Schema(schema_type) - value = '' - - with pytest.raises(InvalidSchemaValue): - schema.unmarshal(value) - - def test_string_valid(self): - schema = Schema('string') - value = 'test' - - result = schema.unmarshal(value) - - assert result == value - - def test_string_format_uuid_valid(self): - schema = Schema(SchemaType.STRING, schema_format='uuid') - value = str(uuid.uuid4()) - - result = schema.unmarshal(value) - - assert result == uuid.UUID(value) - - def test_string_format_uuid_uuid_quirks_invalid(self): - schema = Schema(SchemaType.STRING, schema_format='uuid') - value = uuid.uuid4() - - with pytest.raises(InvalidSchemaValue): - schema.unmarshal(value) - - def test_string_format_password(self): - schema = Schema(SchemaType.STRING, schema_format='password') - value = 'password' - - result = schema.unmarshal(value) - - assert result == 'password' - - def test_string_float_invalid(self): - schema = Schema('string') - value = 1.23 - - with pytest.raises(InvalidSchemaValue): - schema.unmarshal(value) - - def test_string_default(self): - default_value = 'default' - schema = Schema('string', default=default_value) - value = NoValue - - result = schema.unmarshal(value) - - assert result == default_value - - @pytest.mark.parametrize('default_value', ['default', None]) - def test_string_default_nullable(self, default_value): - schema = Schema('string', default=default_value, nullable=True) - value = NoValue - - result = schema.unmarshal(value) - - assert result == default_value - - def test_string_format_date(self): - schema = Schema('string', schema_format='date') - value = '2018-01-02' - - result = schema.unmarshal(value) - - assert result == datetime.date(2018, 1, 2) - - def test_string_format_datetime(self): - schema = Schema('string', schema_format='date-time') - value = '2018-01-02T00:00:00Z' - - result = schema.unmarshal(value) - - assert result == datetime.datetime(2018, 1, 2, 0, 0) - - def test_string_format_custom(self): - formatted = 'x-custom' - - class CustomFormatter(Formatter): - def unmarshal(self, value): - return formatted - custom_format = 'custom' - schema = Schema('string', schema_format=custom_format) - value = 'x' - formatter = CustomFormatter() - custom_formatters = { - custom_format: formatter, - } - - result = schema.unmarshal(value, custom_formatters=custom_formatters) - - assert result == formatted - - def test_string_format_custom_value_error(self): - - class CustomFormatter(Formatter): - def unmarshal(self, value): - raise ValueError - custom_format = 'custom' - schema = Schema('string', schema_format=custom_format) - value = 'x' - formatter = CustomFormatter() - custom_formatters = { - custom_format: formatter, - } - - with pytest.raises(InvalidSchemaFormatValue): - schema.unmarshal( - value, custom_formatters=custom_formatters) - - def test_string_format_unknown(self): - unknown_format = 'unknown' - schema = Schema('string', schema_format=unknown_format) - value = 'x' - - with pytest.raises(FormatterNotFoundError): - schema.unmarshal(value) - - def test_string_format_invalid_value(self): - custom_format = 'custom' - schema = Schema('string', schema_format=custom_format) - value = 'x' - - with pytest.raises( - FormatterNotFoundError, - message=( - 'Formatter not found for custom format' - ), - ): - schema.unmarshal(value) - - def test_integer_valid(self): - schema = Schema('integer') - value = 123 - - result = schema.unmarshal(value) - - assert result == int(value) - - def test_integer_string_invalid(self): - schema = Schema('integer') - value = '123' - - with pytest.raises(InvalidSchemaValue): - schema.unmarshal(value) - - def test_integer_enum_invalid(self): - schema = Schema('integer', enum=[1, 2, 3]) - value = '123' - - with pytest.raises(UnmarshalError): - schema.unmarshal(value) - - def test_integer_enum(self): - schema = Schema('integer', enum=[1, 2, 3]) - value = 2 - - result = schema.unmarshal(value) - - assert result == int(value) - - def test_integer_enum_string_invalid(self): - schema = Schema('integer', enum=[1, 2, 3]) - value = '2' - - with pytest.raises(UnmarshalError): - schema.unmarshal(value) - - def test_integer_default(self): - default_value = 123 - schema = Schema('integer', default=default_value) - value = NoValue - - result = schema.unmarshal(value) - - assert result == default_value - - def test_integer_default_nullable(self): - default_value = 123 - schema = Schema('integer', default=default_value, nullable=True) - value = None - - result = schema.unmarshal(value) - - assert result is None - - def test_integer_invalid(self): - schema = Schema('integer') - value = 'abc' - - with pytest.raises(InvalidSchemaValue): - schema.unmarshal(value) - - def test_array_valid(self): - schema = Schema('array', items=Schema('integer')) - value = [1, 2, 3] - - result = schema.unmarshal(value) - - assert result == value - - def test_array_of_string_string_invalid(self): - schema = Schema('array', items=Schema('string')) - value = '123' - - with pytest.raises(InvalidSchemaValue): - schema.unmarshal(value) - - def test_array_of_integer_string_invalid(self): - schema = Schema('array', items=Schema('integer')) - value = '123' - - with pytest.raises(InvalidSchemaValue): - schema.unmarshal(value) - - def test_boolean_valid(self): - schema = Schema('boolean') - value = True - - result = schema.unmarshal(value) - - assert result == value - - def test_boolean_string_invalid(self): - schema = Schema('boolean') - value = 'True' - - with pytest.raises(InvalidSchemaValue): - schema.unmarshal(value) - - def test_number_valid(self): - schema = Schema('number') - value = 1.23 - - result = schema.unmarshal(value) - - assert result == value - - def test_number_string_invalid(self): - schema = Schema('number') - value = '1.23' - - with pytest.raises(InvalidSchemaValue): - schema.unmarshal(value) - - def test_number_int(self): - schema = Schema('number') - value = 1 - result = schema.unmarshal(value) - - assert result == 1 - assert type(result) == int - - def test_number_float(self): - schema = Schema('number') - value = 1.2 - result = schema.unmarshal(value) - - assert result == 1.2 - assert type(result) == float - - def test_number_format_float(self): - schema = Schema('number', schema_format='float') - value = 1.2 - result = schema.unmarshal(value) - - assert result == 1.2 - - def test_number_format_double(self): - schema = Schema('number', schema_format='double') - value = 1.2 - result = schema.unmarshal(value) - - assert result == 1.2 - - def test_schema_any_one_of(self): - schema = Schema(one_of=[ - Schema('string'), - Schema('array', items=Schema('string')), - ]) - assert schema.unmarshal(['hello']) == ['hello'] - - def test_schema_any(self): - schema = Schema() - assert schema.unmarshal('string') == 'string' diff --git a/tests/unit/unmarshalling/test_unmarshal.py b/tests/unit/unmarshalling/test_unmarshal.py index 718e024e..7c88c957 100644 --- a/tests/unit/unmarshalling/test_unmarshal.py +++ b/tests/unit/unmarshalling/test_unmarshal.py @@ -1,10 +1,16 @@ +import datetime +import uuid + import pytest from openapi_core.schema.media_types.models import MediaType from openapi_core.schema.parameters.models import Parameter +from openapi_core.schema.schemas.enums import SchemaType from openapi_core.schema.schemas.models import Schema +from openapi_core.schema.schemas.types import NoValue from openapi_core.unmarshalling.schemas.exceptions import ( - InvalidSchemaFormatValue, + InvalidSchemaFormatValue, InvalidSchemaValue, UnmarshalError, + FormatterNotFoundError, ) from openapi_core.unmarshalling.schemas.factories import ( SchemaUnmarshallersFactory, @@ -14,10 +20,9 @@ @pytest.fixture def unmarshaller_factory(): - def create_unmarshaller(param_or_media_type, custom_formatters=None): + def create_unmarshaller(schema, custom_formatters=None): return SchemaUnmarshallersFactory( - custom_formatters=custom_formatters).create( - param_or_media_type.schema) + custom_formatters=custom_formatters).create(schema) return create_unmarshaller @@ -28,7 +33,7 @@ def test_no_schema(self, unmarshaller_factory): value = 'test' with pytest.raises(TypeError): - unmarshaller_factory(param).unmarshal(value) + unmarshaller_factory(param.schema).unmarshal(value) def test_schema_type_invalid(self, unmarshaller_factory): schema = Schema('integer', _source={'type': 'integer'}) @@ -36,7 +41,7 @@ def test_schema_type_invalid(self, unmarshaller_factory): value = 'test' with pytest.raises(InvalidSchemaFormatValue): - unmarshaller_factory(param).unmarshal(value) + unmarshaller_factory(param.schema).unmarshal(value) def test_schema_custom_format_invalid(self, unmarshaller_factory): @@ -58,7 +63,9 @@ def unmarshal(self, value): with pytest.raises(InvalidSchemaFormatValue): unmarshaller_factory( - param, custom_formatters=custom_formatters).unmarshal(value) + param.schema, + custom_formatters=custom_formatters, + ).unmarshal(value) class TestMediaTypeUnmarshal(object): @@ -68,7 +75,7 @@ def test_no_schema(self, unmarshaller_factory): value = 'test' with pytest.raises(TypeError): - unmarshaller_factory(media_type).unmarshal(value) + unmarshaller_factory(media_type.schema).unmarshal(value) def test_schema_type_invalid(self, unmarshaller_factory): schema = Schema('integer', _source={'type': 'integer'}) @@ -76,7 +83,7 @@ def test_schema_type_invalid(self, unmarshaller_factory): value = 'test' with pytest.raises(InvalidSchemaFormatValue): - unmarshaller_factory(media_type).unmarshal(value) + unmarshaller_factory(media_type.schema).unmarshal(value) def test_schema_custom_format_invalid(self, unmarshaller_factory): @@ -98,5 +105,314 @@ def unmarshal(self, value): with pytest.raises(InvalidSchemaFormatValue): unmarshaller_factory( - media_type, custom_formatters=custom_formatters).unmarshal( - value) + media_type.schema, + custom_formatters=custom_formatters, + ).unmarshal(value) + + +class TestSchemaUnmarshallerCall(object): + + def test_deprecated(self, unmarshaller_factory): + schema = Schema('string', deprecated=True) + value = 'test' + + with pytest.warns(DeprecationWarning): + result = unmarshaller_factory(schema)(value) + + assert result == value + + @pytest.mark.parametrize('schema_type', [ + 'boolean', 'array', 'integer', 'number', + ]) + def test_non_string_empty_value(self, schema_type, unmarshaller_factory): + schema = Schema(schema_type) + value = '' + + with pytest.raises(InvalidSchemaValue): + unmarshaller_factory(schema)(value) + + def test_string_valid(self, unmarshaller_factory): + schema = Schema('string') + value = 'test' + + result = unmarshaller_factory(schema)(value) + + assert result == value + + def test_string_format_uuid_valid(self, unmarshaller_factory): + schema = Schema(SchemaType.STRING, schema_format='uuid') + value = str(uuid.uuid4()) + + result = unmarshaller_factory(schema)(value) + + assert result == uuid.UUID(value) + + def test_string_format_uuid_uuid_quirks_invalid( + self, unmarshaller_factory): + schema = Schema(SchemaType.STRING, schema_format='uuid') + value = uuid.uuid4() + + with pytest.raises(InvalidSchemaValue): + unmarshaller_factory(schema)(value) + + def test_string_format_password(self, unmarshaller_factory): + schema = Schema(SchemaType.STRING, schema_format='password') + value = 'password' + + result = unmarshaller_factory(schema)(value) + + assert result == 'password' + + def test_string_float_invalid(self, unmarshaller_factory): + schema = Schema('string') + value = 1.23 + + with pytest.raises(InvalidSchemaValue): + unmarshaller_factory(schema)(value) + + def test_string_default(self, unmarshaller_factory): + default_value = 'default' + schema = Schema('string', default=default_value) + value = NoValue + + result = unmarshaller_factory(schema)(value) + + assert result == default_value + + @pytest.mark.parametrize('default_value', ['default', None]) + def test_string_default_nullable( + self, default_value, unmarshaller_factory): + schema = Schema('string', default=default_value, nullable=True) + value = NoValue + + result = unmarshaller_factory(schema)(value) + + assert result == default_value + + def test_string_format_date(self, unmarshaller_factory): + schema = Schema('string', schema_format='date') + value = '2018-01-02' + + result = unmarshaller_factory(schema)(value) + + assert result == datetime.date(2018, 1, 2) + + def test_string_format_datetime(self, unmarshaller_factory): + schema = Schema('string', schema_format='date-time') + value = '2018-01-02T00:00:00Z' + + result = unmarshaller_factory(schema)(value) + + assert result == datetime.datetime(2018, 1, 2, 0, 0) + + def test_string_format_custom(self, unmarshaller_factory): + formatted = 'x-custom' + + class CustomFormatter(Formatter): + def unmarshal(self, value): + return formatted + custom_format = 'custom' + schema = Schema('string', schema_format=custom_format) + value = 'x' + formatter = CustomFormatter() + custom_formatters = { + custom_format: formatter, + } + + result = unmarshaller_factory( + schema, custom_formatters=custom_formatters)(value) + + assert result == formatted + + def test_string_format_custom_value_error(self, unmarshaller_factory): + + class CustomFormatter(Formatter): + def unmarshal(self, value): + raise ValueError + custom_format = 'custom' + schema = Schema('string', schema_format=custom_format) + value = 'x' + formatter = CustomFormatter() + custom_formatters = { + custom_format: formatter, + } + + with pytest.raises(InvalidSchemaFormatValue): + unmarshaller_factory(schema, custom_formatters=custom_formatters)( + value) + + def test_string_format_unknown(self, unmarshaller_factory): + unknown_format = 'unknown' + schema = Schema('string', schema_format=unknown_format) + value = 'x' + + with pytest.raises(FormatterNotFoundError): + unmarshaller_factory(schema)(value) + + def test_string_format_invalid_value(self, unmarshaller_factory): + custom_format = 'custom' + schema = Schema('string', schema_format=custom_format) + value = 'x' + + with pytest.raises( + FormatterNotFoundError, + message=( + 'Formatter not found for custom format' + ), + ): + unmarshaller_factory(schema)(value) + + def test_integer_valid(self, unmarshaller_factory): + schema = Schema('integer') + value = 123 + + result = unmarshaller_factory(schema)(value) + + assert result == int(value) + + def test_integer_string_invalid(self, unmarshaller_factory): + schema = Schema('integer') + value = '123' + + with pytest.raises(InvalidSchemaValue): + unmarshaller_factory(schema)(value) + + def test_integer_enum_invalid(self, unmarshaller_factory): + schema = Schema('integer', enum=[1, 2, 3]) + value = '123' + + with pytest.raises(UnmarshalError): + unmarshaller_factory(schema)(value) + + def test_integer_enum(self, unmarshaller_factory): + schema = Schema('integer', enum=[1, 2, 3]) + value = 2 + + result = unmarshaller_factory(schema)(value) + + assert result == int(value) + + def test_integer_enum_string_invalid(self, unmarshaller_factory): + schema = Schema('integer', enum=[1, 2, 3]) + value = '2' + + with pytest.raises(UnmarshalError): + unmarshaller_factory(schema)(value) + + def test_integer_default(self, unmarshaller_factory): + default_value = 123 + schema = Schema('integer', default=default_value) + value = NoValue + + result = unmarshaller_factory(schema)(value) + + assert result == default_value + + def test_integer_default_nullable(self, unmarshaller_factory): + default_value = 123 + schema = Schema('integer', default=default_value, nullable=True) + value = None + + result = unmarshaller_factory(schema)(value) + + assert result is None + + def test_integer_invalid(self, unmarshaller_factory): + schema = Schema('integer') + value = 'abc' + + with pytest.raises(InvalidSchemaValue): + unmarshaller_factory(schema)(value) + + def test_array_valid(self, unmarshaller_factory): + schema = Schema('array', items=Schema('integer')) + value = [1, 2, 3] + + result = unmarshaller_factory(schema)(value) + + assert result == value + + def test_array_of_string_string_invalid(self, unmarshaller_factory): + schema = Schema('array', items=Schema('string')) + value = '123' + + with pytest.raises(InvalidSchemaValue): + unmarshaller_factory(schema)(value) + + def test_array_of_integer_string_invalid(self, unmarshaller_factory): + schema = Schema('array', items=Schema('integer')) + value = '123' + + with pytest.raises(InvalidSchemaValue): + unmarshaller_factory(schema)(value) + + def test_boolean_valid(self, unmarshaller_factory): + schema = Schema('boolean') + value = True + + result = unmarshaller_factory(schema)(value) + + assert result == value + + def test_boolean_string_invalid(self, unmarshaller_factory): + schema = Schema('boolean') + value = 'True' + + with pytest.raises(InvalidSchemaValue): + unmarshaller_factory(schema)(value) + + def test_number_valid(self, unmarshaller_factory): + schema = Schema('number') + value = 1.23 + + result = unmarshaller_factory(schema)(value) + + assert result == value + + def test_number_string_invalid(self, unmarshaller_factory): + schema = Schema('number') + value = '1.23' + + with pytest.raises(InvalidSchemaValue): + unmarshaller_factory(schema)(value) + + def test_number_int(self, unmarshaller_factory): + schema = Schema('number') + value = 1 + result = unmarshaller_factory(schema)(value) + + assert result == 1 + assert type(result) == int + + def test_number_float(self, unmarshaller_factory): + schema = Schema('number') + value = 1.2 + result = unmarshaller_factory(schema)(value) + + assert result == 1.2 + assert type(result) == float + + def test_number_format_float(self, unmarshaller_factory): + schema = Schema('number', schema_format='float') + value = 1.2 + result = unmarshaller_factory(schema)(value) + + assert result == 1.2 + + def test_number_format_double(self, unmarshaller_factory): + schema = Schema('number', schema_format='double') + value = 1.2 + result = unmarshaller_factory(schema)(value) + + assert result == 1.2 + + def test_schema_any_one_of(self, unmarshaller_factory): + schema = Schema(one_of=[ + Schema('string'), + Schema('array', items=Schema('string')), + ]) + assert unmarshaller_factory(schema)(['hello']) == ['hello'] + + def test_schema_any(self, unmarshaller_factory): + schema = Schema() + assert unmarshaller_factory(schema)('string') == 'string'