From 97775cfedafb34f450f86db8e15f50a353bfbe34 Mon Sep 17 00:00:00 2001 From: p1c2u Date: Mon, 24 Apr 2023 12:14:43 +0100 Subject: [PATCH] multi types schema format unmarshal fix --- .../unmarshalling/schemas/exceptions.py | 18 ------ .../unmarshalling/schemas/unmarshallers.py | 58 +++++++++---------- openapi_core/validation/schemas/validators.py | 18 ++++++ .../unmarshalling/test_unmarshallers.py | 21 +++++++ .../test_schema_unmarshallers.py | 6 +- 5 files changed, 69 insertions(+), 52 deletions(-) diff --git a/openapi_core/unmarshalling/schemas/exceptions.py b/openapi_core/unmarshalling/schemas/exceptions.py index defd2142..433de337 100644 --- a/openapi_core/unmarshalling/schemas/exceptions.py +++ b/openapi_core/unmarshalling/schemas/exceptions.py @@ -19,21 +19,3 @@ class FormatterNotFoundError(UnmarshallerError): def __str__(self) -> str: return f"Formatter not found for {self.type_format} format" - - -@dataclass -class FormatUnmarshalError(UnmarshallerError): - """Unable to unmarshal value for format""" - - value: str - type: str - original_exception: Exception - - def __str__(self) -> str: - return ( - "Unable to unmarshal value {value} for format {type}: {exception}" - ).format( - value=self.value, - type=self.type, - exception=self.original_exception, - ) diff --git a/openapi_core/unmarshalling/schemas/unmarshallers.py b/openapi_core/unmarshalling/schemas/unmarshallers.py index 575d24a8..53ddcc34 100644 --- a/openapi_core/unmarshalling/schemas/unmarshallers.py +++ b/openapi_core/unmarshalling/schemas/unmarshallers.py @@ -15,8 +15,6 @@ from openapi_core.unmarshalling.schemas.datatypes import ( FormatUnmarshallersDict, ) -from openapi_core.unmarshalling.schemas.exceptions import FormatUnmarshalError -from openapi_core.unmarshalling.schemas.exceptions import UnmarshallerError from openapi_core.validation.schemas.validators import SchemaValidator log = logging.getLogger(__name__) @@ -138,34 +136,15 @@ def _unmarshal_properties( class MultiTypeUnmarshaller(PrimitiveUnmarshaller): def __call__(self, value: Any) -> Any: - unmarshaller = self._get_best_unmarshaller(value) + primitive_type = self.schema_validator.get_primitive_type(value) + unmarshaller = self.schema_unmarshaller.get_type_unmarshaller( + primitive_type + ) return unmarshaller(value) - @property - def type(self) -> List[str]: - types = self.schema.getkey("type", ["any"]) - assert isinstance(types, list) - return types - - def _get_best_unmarshaller(self, value: Any) -> "PrimitiveUnmarshaller": - for schema_type in self.type: - result = self.schema_validator.type_validator( - value, type_override=schema_type - ) - if not result: - continue - result = self.schema_validator.format_validator(value) - if not result: - continue - return self.schema_unmarshaller.get_type_unmarshaller(schema_type) - - raise UnmarshallerError("Unmarshaller not found for type(s)") - class AnyUnmarshaller(MultiTypeUnmarshaller): - @property - def type(self) -> List[str]: - return self.schema_unmarshaller.types_unmarshaller.get_types() + pass class TypesUnmarshaller: @@ -185,7 +164,7 @@ def __init__( def get_types(self) -> List[str]: return list(self.unmarshallers.keys()) - def get_unmarshaller( + def get_unmarshaller_cls( self, schema_type: Optional[Union[Iterable[str], str]], ) -> Type["PrimitiveUnmarshaller"]: @@ -220,8 +199,8 @@ def unmarshal(self, schema_format: str, value: Any) -> Any: return value try: return format_unmarshaller(value) - except (ValueError, TypeError) as exc: - raise FormatUnmarshalError(value, schema_format, exc) + except (AttributeError, ValueError, TypeError): + return value def get_unmarshaller( self, schema_format: str @@ -279,19 +258,32 @@ def unmarshal(self, value: Any) -> Any: (isinstance(value, bytes) and schema_format in ["binary", "byte"]) ): return typed - return self.formats_unmarshaller.unmarshal(schema_format, typed) + + format_unmarshaller = self.get_format_unmarshaller(schema_format) + if format_unmarshaller is None: + return typed + try: + return format_unmarshaller(typed) + except (AttributeError, ValueError, TypeError): + return typed def get_type_unmarshaller( self, schema_type: Optional[Union[Iterable[str], str]], ) -> PrimitiveUnmarshaller: - klass = self.types_unmarshaller.get_unmarshaller(schema_type) + klass = self.types_unmarshaller.get_unmarshaller_cls(schema_type) return klass( self.schema, self.schema_validator, self, ) + def get_format_unmarshaller( + self, + schema_format: str, + ) -> Optional[FormatUnmarshaller]: + return self.formats_unmarshaller.get_unmarshaller(schema_format) + def evolve(self, schema: SchemaPath) -> "SchemaUnmarshaller": cls = self.__class__ @@ -304,6 +296,10 @@ def evolve(self, schema: SchemaPath) -> "SchemaUnmarshaller": def find_format(self, value: Any) -> Optional[str]: for schema in self.schema_validator.iter_valid_schemas(value): + schema_validator = self.schema_validator.evolve(schema) + primitive_type = schema_validator.get_primitive_type(value) + if primitive_type != "string": + continue if "format" in schema: return str(schema.getkey("format")) return None diff --git a/openapi_core/validation/schemas/validators.py b/openapi_core/validation/schemas/validators.py index 6a4954e9..c822a82f 100644 --- a/openapi_core/validation/schemas/validators.py +++ b/openapi_core/validation/schemas/validators.py @@ -78,6 +78,24 @@ def format_validator_callable(self) -> FormatValidator: return lambda x: True + def get_primitive_type(self, value: Any) -> Optional[str]: + schema_types = self.schema.getkey("type") + if isinstance(schema_types, str): + return schema_types + if schema_types is None: + schema_types = sorted(self.validator.TYPE_CHECKER._type_checkers) + assert isinstance(schema_types, list) + for schema_type in schema_types: + result = self.type_validator(value, type_override=schema_type) + if not result: + continue + result = self.format_validator(value) + if not result: + continue + assert isinstance(schema_type, (str, type(None))) + return schema_type + return None + def iter_valid_schemas(self, value: Any) -> Iterator[SchemaPath]: yield self.schema diff --git a/tests/integration/unmarshalling/test_unmarshallers.py b/tests/integration/unmarshalling/test_unmarshallers.py index 04c980a6..764fc3af 100644 --- a/tests/integration/unmarshalling/test_unmarshallers.py +++ b/tests/integration/unmarshalling/test_unmarshallers.py @@ -2057,6 +2057,27 @@ def test_nultiple_types_invalid(self, unmarshallers_factory, types, value): assert len(exc_info.value.schema_errors) == 1 assert "is not of type" in exc_info.value.schema_errors[0].message + @pytest.mark.parametrize( + "types,format,value,expected", + [ + (["string", "null"], "date", None, None), + (["string", "null"], "date", "2018-12-13", date(2018, 12, 13)), + ], + ) + def test_multiple_types_format_valid_or_ignored( + self, unmarshallers_factory, types, format, value, expected + ): + schema = { + "type": types, + "format": format, + } + spec = SchemaPath.from_dict(schema) + unmarshaller = unmarshallers_factory.create(spec) + + result = unmarshaller.unmarshal(value) + + assert result == expected + def test_any_null(self, unmarshallers_factory): schema = {} spec = SchemaPath.from_dict(schema) diff --git a/tests/unit/unmarshalling/test_schema_unmarshallers.py b/tests/unit/unmarshalling/test_schema_unmarshallers.py index 3373a34f..5a8fe12e 100644 --- a/tests/unit/unmarshalling/test_schema_unmarshallers.py +++ b/tests/unit/unmarshalling/test_schema_unmarshallers.py @@ -8,7 +8,6 @@ from openapi_core.unmarshalling.schemas.exceptions import ( FormatterNotFoundError, ) -from openapi_core.unmarshalling.schemas.exceptions import FormatUnmarshalError from openapi_core.unmarshalling.schemas.factories import ( SchemaUnmarshallersFactory, ) @@ -102,8 +101,9 @@ def custom_format_unmarshaller(value): extra_format_unmarshallers=extra_format_unmarshallers, ) - with pytest.raises(FormatUnmarshalError): - unmarshaller.unmarshal(value) + result = unmarshaller.unmarshal(value) + + assert result == value def test_schema_extra_format_unmarshaller_format_custom( self, schema_unmarshaller_factory pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy