From 3339e1311ae6eaa57255f23408b0ba5441b2b957 Mon Sep 17 00:00:00 2001 From: Janez Troha Date: Wed, 15 May 2019 18:26:23 +0200 Subject: [PATCH 1/2] Fix number validator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `integer_types` is always a tuple. When checking if an instance is a number it fails because it's doing a comparison against a tuple instead of real type. ➜ python -c "from six import integer_types;import sys;print(integer_types);print(sys.version)" (, ) 2.7.16 (default, Apr 6 2019, 01:42:57) [GCC 8.3.0] ➜ python3 -c "from six import integer_types;import sys;print(integer_types);print(sys.version)" (,) 3.7.3 (default, Apr 3 2019, 05:39:12) [GCC 8.3.0] And spec defines a number as both int and float https://swagger.io/docs/specification/data-models/data-types/#numbers so both validators need to support both types. --- openapi_core/schema/schemas/models.py | 8 ++++---- tests/unit/schema/test_schemas.py | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openapi_core/schema/schemas/models.py b/openapi_core/schema/schemas/models.py index 7353dd0c..213182dc 100644 --- a/openapi_core/schema/schemas/models.py +++ b/openapi_core/schema/schemas/models.py @@ -54,7 +54,7 @@ class Schema(object): SchemaType.ANY: lambda x: True, SchemaType.BOOLEAN: TypeValidator(bool), SchemaType.INTEGER: TypeValidator(integer_types, exclude=bool), - SchemaType.NUMBER: TypeValidator(integer_types, float, exclude=bool), + SchemaType.NUMBER: TypeValidator(integer_types + (float, ), exclude=bool), SchemaType.STRING: TypeValidator( text_type, date, datetime, binary_type, UUID), SchemaType.ARRAY: TypeValidator(list, tuple), @@ -228,16 +228,16 @@ def _unmarshal_string(self, value, custom_formatters=None, strict=True): "Failed to format value {value} to format {type}: {exception}", value, self.format, exc) def _unmarshal_integer(self, value, custom_formatters=None, strict=True): - if strict and not isinstance(value, (integer_types, )): + if strict and not isinstance(value, integer_types): raise InvalidSchemaValue("Value {value} is not of type {type}", value, self.type) return int(value) def _unmarshal_number(self, value, custom_formatters=None, strict=True): - if strict and not isinstance(value, (float, )): + if strict and not isinstance(value, (float, ) + integer_types): raise InvalidSchemaValue("Value {value} is not of type {type}", value, self.type) - return float(value) + return value def _unmarshal_boolean(self, value, custom_formatters=None, strict=True): if strict and not isinstance(value, (bool, )): diff --git a/tests/unit/schema/test_schemas.py b/tests/unit/schema/test_schemas.py index b66dad7d..8666fdee 100644 --- a/tests/unit/schema/test_schemas.py +++ b/tests/unit/schema/test_schemas.py @@ -262,12 +262,12 @@ def test_number_string_invalid(self): with pytest.raises(InvalidSchemaValue): schema.unmarshal(value) - def test_number_int_invalid(self): + def test_number_int(self): schema = Schema('number') value = 1 + result = schema.unmarshal(value) - with pytest.raises(InvalidSchemaValue): - schema.unmarshal(value) + assert result == value class TestSchemaValidate(object): From 63f3ffb09fb2efb69cb2ffa208ebfe10c60d2164 Mon Sep 17 00:00:00 2001 From: Artur Maciag Date: Tue, 21 May 2019 12:54:13 +0100 Subject: [PATCH 2/2] Number format added --- openapi_core/schema/schemas/models.py | 30 +++++++++++++++++++++++++-- openapi_core/schema/schemas/util.py | 9 +++++++- tests/unit/schema/test_schemas.py | 25 +++++++++++++++++++++- 3 files changed, 60 insertions(+), 4 deletions(-) diff --git a/openapi_core/schema/schemas/models.py b/openapi_core/schema/schemas/models.py index 213182dc..c738cc1d 100644 --- a/openapi_core/schema/schemas/models.py +++ b/openapi_core/schema/schemas/models.py @@ -20,6 +20,7 @@ ) from openapi_core.schema.schemas.util import ( forcebool, format_date, format_datetime, format_byte, format_uuid, + format_number, ) from openapi_core.schema.schemas.validators import ( TypeValidator, AttributeValidator, @@ -50,11 +51,19 @@ class Schema(object): SchemaFormat.BYTE: Format(format_byte, TypeValidator(text_type)), } + NUMBER_FORMAT_CALLABLE_GETTER = { + SchemaFormat.NONE: Format(format_number, TypeValidator( + integer_types + (float, ), exclude=bool)), + SchemaFormat.FLOAT: Format(float, TypeValidator(float)), + SchemaFormat.DOUBLE: Format(float, TypeValidator(float)), + } + TYPE_VALIDATOR_CALLABLE_GETTER = { SchemaType.ANY: lambda x: True, SchemaType.BOOLEAN: TypeValidator(bool), SchemaType.INTEGER: TypeValidator(integer_types, exclude=bool), - SchemaType.NUMBER: TypeValidator(integer_types + (float, ), exclude=bool), + SchemaType.NUMBER: TypeValidator( + integer_types + (float, ), exclude=bool), SchemaType.STRING: TypeValidator( text_type, date, datetime, binary_type, UUID), SchemaType.ARRAY: TypeValidator(list, tuple), @@ -237,7 +246,24 @@ def _unmarshal_number(self, value, custom_formatters=None, strict=True): if strict and not isinstance(value, (float, ) + integer_types): raise InvalidSchemaValue("Value {value} is not of type {type}", value, self.type) - return value + try: + schema_format = SchemaFormat(self.format) + except ValueError: + msg = "Unsupported format {type} unmarshalling for value {value}" + if custom_formatters is not None: + formatnumber = custom_formatters.get(self.format) + if formatnumber is None: + raise InvalidSchemaValue(msg, value, self.format) + else: + raise InvalidSchemaValue(msg, value, self.format) + else: + formatnumber = self.NUMBER_FORMAT_CALLABLE_GETTER[schema_format] + + try: + return formatnumber.unmarshal(value) + except ValueError as exc: + raise InvalidCustomFormatSchemaValue( + "Failed to format value {value} to format {type}: {exception}", value, self.format, exc) def _unmarshal_boolean(self, value, custom_formatters=None, strict=True): if strict and not isinstance(value, (bool, )): diff --git a/openapi_core/schema/schemas/util.py b/openapi_core/schema/schemas/util.py index 9cfab42a..528b8fbc 100644 --- a/openapi_core/schema/schemas/util.py +++ b/openapi_core/schema/schemas/util.py @@ -3,7 +3,7 @@ import datetime from distutils.util import strtobool from json import dumps -from six import string_types, text_type +from six import string_types, text_type, integer_types import strict_rfc3339 from uuid import UUID @@ -36,3 +36,10 @@ def format_uuid(value): def format_byte(value, encoding='utf8'): return text_type(b64decode(value), encoding) + + +def format_number(value): + if isinstance(value, integer_types + (float, )): + return value + + return float(value) diff --git a/tests/unit/schema/test_schemas.py b/tests/unit/schema/test_schemas.py index 8666fdee..eaf5e32f 100644 --- a/tests/unit/schema/test_schemas.py +++ b/tests/unit/schema/test_schemas.py @@ -267,7 +267,30 @@ def test_number_int(self): value = 1 result = schema.unmarshal(value) - assert result == 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 class TestSchemaValidate(object):