diff --git a/README.md b/README.md index 09b92a8..50365de 100644 --- a/README.md +++ b/README.md @@ -66,23 +66,28 @@ pip install apimatic-core | [`EndpointLogger`](apimatic_core/logger/endpoint_logger.py) | A class to provide logging for an HTTP request | ## Types -| Name | Description | -|------------------------------------------------------------------------------|------------------------------------------------------------------------------| -| [`SerializationFormats`](apimatic_core/types/array_serialization_format.py) | An Enumeration of Array serialization formats | -| [`DateTimeFormat`](apimatic_core/types/datetime_format.py ) | An Enumeration of Date Time formats | -| [`ErrorCase`](apimatic_core/types/error_case.py ) | A class to represent Exception types | -| [`FileWrapper`](apimatic_core/types/file_wrapper.py) | A wrapper to allow passing in content type for file uploads | -| [`Parameter`](apimatic_core/types/parameter.py ) | A class to represent information about a Parameter passed in an endpoint | -| [`XmlAttributes`](apimatic_core/types/xml_attributes.py ) | A class to represent information about a XML Parameter passed in an endpoint | +| Name | Description | +|-------------------------------------------------------------------------------|------------------------------------------------------------------------------| +| [`SerializationFormats`](apimatic_core/types/array_serialization_format.py) | An Enumeration of Array serialization formats | +| [`DateTimeFormat`](apimatic_core/types/datetime_format.py ) | An Enumeration of Date Time formats | +| [`ErrorCase`](apimatic_core/types/error_case.py ) | A class to represent Exception types | +| [`FileWrapper`](apimatic_core/types/file_wrapper.py) | A wrapper to allow passing in content type for file uploads | +| [`Parameter`](apimatic_core/types/parameter.py ) | A class to represent information about a Parameter passed in an endpoint | +| [`XmlAttributes`](apimatic_core/types/xml_attributes.py ) | A class to represent information about a XML Parameter passed in an endpoint | +| [`OneOf`](apimatic_core/types/union_types/one_of.py ) | A class to represent information about OneOf union types | +| [`AnyOf`](apimatic_core/types/union_types/any_of.py ) | A class to represent information about AnyOf union types | +| [`LeafType`](apimatic_core/types/union_types/leaf_type.py ) | A class to represent the case information in an OneOf or AnyOf union type | ## Utilities -| Name | Description | -|--------------------------------------------------------------------|--------------------------------------------------------------------------------------| -| [`ApiHelper`](apimatic_core/utilities/api_helper.py) | A Helper Class with various functions associated with making an API Call | -| [`AuthHelper`](apimatic_core/utilities/auth_helper.py) | A Helper Class with various functions associated with authentication in API Calls | -| [`ComparisonHelper`](apimatic_core/utilities/comparison_helper.py) | A Helper Class used for the comparison of expected and actual API response | -| [` FileHelper`](apimatic_core/utilities/file_helper.py) | A Helper Class for files | -| [`XmlHelper`](apimatic_core/utilities/xml_helper.py ) | A Helper class that holds utility methods for xml serialization and deserialization. | +| Name | Description | +|--------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------| +| [`ApiHelper`](apimatic_core/utilities/api_helper.py) | A Helper Class with various functions associated with making an API Call | +| [`AuthHelper`](apimatic_core/utilities/auth_helper.py) | A Helper Class with various functions associated with authentication in API Calls | +| [`ComparisonHelper`](apimatic_core/utilities/comparison_helper.py) | A Helper Class used for the comparison of expected and actual API response | +| [`FileHelper`](apimatic_core/utilities/file_helper.py) | A Helper Class for files | +| [`XmlHelper`](apimatic_core/utilities/xml_helper.py ) | A Helper class that holds utility methods for xml serialization and deserialization. | +| [`DateTimeHelper`](apimatic_core/utilities/datetime_helper.py ) | A Helper class that holds utility methods for validation of different datetime formats. | +| [`UnionTypeHelper`](apimatic_core/utilities/union_type_helper.py ) | A Helper class that holds utility methods for deserialization and validation of OneOf/AnyOf union types. | ## Links * [apimatic-core-interfaces](https://pypi.org/project/apimatic-core-interfaces/) diff --git a/apimatic_core/__init__.py b/apimatic_core/__init__.py index 7cd4996..192a438 100644 --- a/apimatic_core/__init__.py +++ b/apimatic_core/__init__.py @@ -9,5 +9,6 @@ 'utilities', 'factories', 'types', - 'logger' + 'logger', + 'exceptions' ] \ No newline at end of file diff --git a/apimatic_core/authentication/multiple/auth_group.py b/apimatic_core/authentication/multiple/auth_group.py index 78b74d0..315151c 100644 --- a/apimatic_core/authentication/multiple/auth_group.py +++ b/apimatic_core/authentication/multiple/auth_group.py @@ -38,7 +38,7 @@ def with_auth_managers(self, auth_managers): return self - def is_valid(self): + def is_valid(self): # pragma: no cover ... def apply(self, http_request): diff --git a/apimatic_core/exceptions/__init__.py b/apimatic_core/exceptions/__init__.py new file mode 100644 index 0000000..3a56e42 --- /dev/null +++ b/apimatic_core/exceptions/__init__.py @@ -0,0 +1,4 @@ +__all__ = [ + 'oneof_validation_exception', + 'anyof_validation_exception' +] \ No newline at end of file diff --git a/apimatic_core/exceptions/anyof_validation_exception.py b/apimatic_core/exceptions/anyof_validation_exception.py new file mode 100644 index 0000000..3e2583d --- /dev/null +++ b/apimatic_core/exceptions/anyof_validation_exception.py @@ -0,0 +1,5 @@ + +class AnyOfValidationException(Exception): + def __init__(self, message): + self.message = message + super().__init__(self.message) diff --git a/apimatic_core/exceptions/oneof_validation_exception.py b/apimatic_core/exceptions/oneof_validation_exception.py new file mode 100644 index 0000000..3d527d9 --- /dev/null +++ b/apimatic_core/exceptions/oneof_validation_exception.py @@ -0,0 +1,5 @@ + +class OneOfValidationException(Exception): + def __init__(self, message): + self.message = message + super().__init__(self.message) diff --git a/apimatic_core/http/configurations/http_client_configuration.py b/apimatic_core/http/configurations/http_client_configuration.py index 1ebcb20..05ed747 100644 --- a/apimatic_core/http/configurations/http_client_configuration.py +++ b/apimatic_core/http/configurations/http_client_configuration.py @@ -2,7 +2,7 @@ from apimatic_core.factories.http_response_factory import HttpResponseFactory -class HttpClientConfiguration(object): +class HttpClientConfiguration(object): # pragma: no cover """A class used for configuring the SDK by a user. """ diff --git a/apimatic_core/http/http_callback.py b/apimatic_core/http/http_callback.py index 02f1768..368e599 100644 --- a/apimatic_core/http/http_callback.py +++ b/apimatic_core/http/http_callback.py @@ -9,8 +9,7 @@ class HttpCallBack(object): """ - def on_before_request(self, - request): + def on_before_request(self, request): # pragma: no cover """The controller will call this method before making the HttpRequest. Args: @@ -19,8 +18,7 @@ def on_before_request(self, """ raise NotImplementedError("This method has not been implemented.") - def on_after_response(self, - http_response): + def on_after_response(self, http_response): # pragma: no cover """The controller will call this method after making the HttpRequest. Args: diff --git a/apimatic_core/http/request/http_request.py b/apimatic_core/http/request/http_request.py index d1e07b5..d22a3f1 100644 --- a/apimatic_core/http/request/http_request.py +++ b/apimatic_core/http/request/http_request.py @@ -56,7 +56,7 @@ def add_header(self, name, value): """ self.headers[name] = value - def add_parameter(self, name, value): + def add_parameter(self, name, value): # pragma: no cover """ Add a parameter to the HttpRequest. Args: diff --git a/apimatic_core/request_builder.py b/apimatic_core/request_builder.py index a320aa5..4b8cfe2 100644 --- a/apimatic_core/request_builder.py +++ b/apimatic_core/request_builder.py @@ -26,7 +26,6 @@ def __init__( self._additional_query_params = {} self._multipart_params = [] self._body_param = None - self._should_wrap_body_param = None self._body_serializer = None self._auth = None self._array_serialization_format = SerializationFormats.INDEXED @@ -90,10 +89,6 @@ def body_param(self, body_param): self._body_param = body_param.get_value() return self - def should_wrap_body_param(self, should_wrap_body_param): - self._should_wrap_body_param = should_wrap_body_param - return self - def body_serializer(self, body_serializer): self._body_serializer = body_serializer return self @@ -184,8 +179,6 @@ def process_body_params(self): self.add_additional_form_params() return ApiHelper.form_encode_parameters(self._form_params, self._array_serialization_format) elif self._body_param is not None and self._body_serializer: - if self._should_wrap_body_param: - return self._body_serializer(self.resolve_body_param(), self._should_wrap_body_param) return self._body_serializer(self.resolve_body_param()) elif self._body_param is not None and not self._body_serializer: return self.resolve_body_param() diff --git a/apimatic_core/types/__init__.py b/apimatic_core/types/__init__.py index 6b862c4..3217ddf 100644 --- a/apimatic_core/types/__init__.py +++ b/apimatic_core/types/__init__.py @@ -4,5 +4,6 @@ 'error_case', 'file_wrapper', 'array_serialization_format', - 'xml_attributes' + 'xml_attributes', + 'union_types' ] \ No newline at end of file diff --git a/apimatic_core/types/parameter.py b/apimatic_core/types/parameter.py index e22aba1..6f2110f 100644 --- a/apimatic_core/types/parameter.py +++ b/apimatic_core/types/parameter.py @@ -21,6 +21,7 @@ def __init__( self._is_required = False self._should_encode = False self._default_content_type = None + self._validator = None def key(self, key): self._key = key @@ -42,7 +43,14 @@ def default_content_type(self, default_content_type): self._default_content_type = default_content_type return self + def validator(self, validator): + self._validator = validator + return self + def validate(self): if self._is_required and self._value is None: raise ValueError("Required parameter {} cannot be None.".format(self._key)) + if self._validator is not None and self._validator(self._value): + return + diff --git a/apimatic_core/types/union_types/__init__.py b/apimatic_core/types/union_types/__init__.py new file mode 100644 index 0000000..3bf1608 --- /dev/null +++ b/apimatic_core/types/union_types/__init__.py @@ -0,0 +1,6 @@ +__all__ = [ + "any_of", + "one_of", + "union_type_context", + "leaf_type" +] diff --git a/apimatic_core/types/union_types/any_of.py b/apimatic_core/types/union_types/any_of.py new file mode 100644 index 0000000..667f9b6 --- /dev/null +++ b/apimatic_core/types/union_types/any_of.py @@ -0,0 +1,63 @@ +from apimatic_core_interfaces.types.union_type import UnionType +from apimatic_core.types.union_types.union_type_context import UnionTypeContext +from apimatic_core.utilities.union_type_helper import UnionTypeHelper + + +class AnyOf(UnionType): + + def __init__(self, union_types, union_type_context: UnionTypeContext = UnionTypeContext()): + super(AnyOf, self).__init__(union_types, union_type_context) + self.collection_cases = None + + def validate(self, value): + context = self._union_type_context + UnionTypeHelper.update_nested_flag_for_union_types(self._union_types) + is_optional_or_nullable = UnionTypeHelper.is_optional_or_nullable_case(context, + [nested_type.get_context() + for nested_type in self._union_types]) + + if value is None and is_optional_or_nullable: + self.is_valid = True + return self + + if value is None: + self.is_valid = False + self.error_messages = UnionTypeHelper.process_errors(value, self._union_types, self.error_messages, + self.get_context().is_nested, False) + return self + + self._validate_value_against_case(value, context) + + if not self.is_valid: + self.error_messages = UnionTypeHelper.process_errors(value, self._union_types, self.error_messages, + self.get_context().is_nested, False) + + return self + + def deserialize(self, value): + if value is None: + return None + + return UnionTypeHelper.deserialize_value(value, self._union_type_context, self.collection_cases, + self._union_types) + + def _validate_value_against_case(self, value, context): + if context.is_array() and context.is_dict() and context.is_array_of_dict(): + self.is_valid, self.collection_cases = UnionTypeHelper.validate_array_of_dict_case(self._union_types, value, + False) + elif context.is_array() and context.is_dict(): + self.is_valid, self.collection_cases = UnionTypeHelper.validate_dict_of_array_case(self._union_types, value, + False) + elif context.is_array(): + self.is_valid, self.collection_cases = UnionTypeHelper.validate_array_case(self._union_types, value, False) + elif context.is_dict(): + self.is_valid, self.collection_cases = UnionTypeHelper.validate_dict_case(self._union_types, value, False) + else: + self.is_valid = UnionTypeHelper.get_matched_count(value, self._union_types, False) >= 1 + + def __deepcopy__(self, memo={}): + copy_object = AnyOf(self._union_types, self._union_type_context) + copy_object.is_valid = self.is_valid + copy_object.collection_cases = self.collection_cases + copy_object.error_messages = self.error_messages + return copy_object diff --git a/apimatic_core/types/union_types/leaf_type.py b/apimatic_core/types/union_types/leaf_type.py new file mode 100644 index 0000000..fa98916 --- /dev/null +++ b/apimatic_core/types/union_types/leaf_type.py @@ -0,0 +1,197 @@ +from datetime import date, datetime +from apimatic_core_interfaces.types.union_type import UnionType +from apimatic_core.types.union_types.union_type_context import UnionTypeContext +from apimatic_core.utilities.api_helper import ApiHelper +from apimatic_core.utilities.datetime_helper import DateTimeHelper +from apimatic_core.utilities.union_type_helper import UnionTypeHelper + + +class LeafType(UnionType): + + def __init__(self, type_to_match: type, union_type_context: UnionTypeContext = UnionTypeContext()): + super(LeafType, self).__init__(None, union_type_context) + self.type_to_match = type_to_match + + def validate(self, value): + context = self._union_type_context + + if value is None: + self.is_valid = context.is_nullable_or_optional() + else: + self.is_valid = self._validate_value_against_case(value, context) + + return self + + def deserialize(self, value): + if value is None: + return None + + context = self._union_type_context + deserialized_value = self._deserialize_value_against_case(value, context) + + return deserialized_value + + def _validate_value_against_case(self, value, context): + if context.is_array() and context.is_dict() and context.is_array_of_dict(): + return self._validate_array_of_dict_case(value) + + if context.is_array() and context.is_dict(): + return self._validate_dict_of_array_case(value) + + if context.is_array(): + return self._validate_array_case(value) + + if context.is_dict(): + return self._validate_dict_case(value) + + return self._validate_simple_case(value) + + def _validate_dict_case(self, dict_value): + if not isinstance(dict_value, dict): + return False + + for key, value in dict_value.items(): + is_valid = self._validate_simple_case(value) + if not is_valid: + return False + + return True + + def _validate_dict_of_array_case(self, dict_value): + if not isinstance(dict_value, dict): + return False + + for key, value in dict_value.items(): + is_valid = self._validate_array_case(value) + if not is_valid: + return False + + return True + + def _validate_array_case(self, array_value): + if not isinstance(array_value, list): + return False + + for item in array_value: + is_valid = self._validate_simple_case(item) + if not is_valid: + return False + + return True + + def _validate_array_of_dict_case(self, array_value): + if not isinstance(array_value, list): + return False + + for item in array_value: + is_valid = self._validate_dict_case(item) + if not is_valid: + return False + + return True + + def _validate_simple_case(self, value): + context = self._union_type_context + + if value is None or context.is_nullable_or_optional(): + return True + + if value is None or isinstance(value, list): + return False + + return self._validate_value(value, context) + + def _validate_value(self, value, context): + if self.type_to_match is datetime: + return UnionTypeHelper.validate_date_time(value, context) + + if self.type_to_match is date: + return DateTimeHelper.validate_date(value) + + return self._validate_value_with_discriminator(value, context) + + def _validate_value_with_discriminator(self, value, context): + discriminator = context.get_discriminator() + discriminator_value = context.get_discriminator_value() + if discriminator and discriminator_value: + return self._validate_with_discriminator(discriminator, discriminator_value, value) + + if hasattr(self.type_to_match, 'validate'): + return self.type_to_match.validate(value) + + return type(value) is self.type_to_match + + def _validate_with_discriminator(self, discriminator, discriminator_value, value): + if not isinstance(value, dict) or value.get(discriminator) != discriminator_value: + return False + + if hasattr(self.type_to_match, 'validate'): + return self.type_to_match.validate(value) + + return type(value) is self.type_to_match + + def _deserialize_value_against_case(self, value, context): + if context.is_array() and context.is_dict() and context.is_array_of_dict(): + return self._deserialize_array_of_dict_case(value) + + if context.is_array() and context.is_dict(): + return self._deserialize_dict_of_array_case(value) + + if context.is_array(): + return self._deserialize_array_case(value) + + if context.is_dict(): + return self._deserialize_dict_case(value) + + return self._deserialize_simple_case(value) + + def _deserialize_dict_case(self, dict_value): + deserialized_value = {} + for key, value in dict_value.items(): + result_value = self._deserialize_simple_case(value) + deserialized_value[key] = result_value + + return deserialized_value + + def _deserialize_dict_of_array_case(self, dict_value): + deserialized_value = {} + for key, value in dict_value.items(): + result_value = self._deserialize_array_case(value) + deserialized_value[key] = result_value + + return deserialized_value + + def _deserialize_array_case(self, array_value): + deserialized_value = [] + for item in array_value: + result_value = self._deserialize_simple_case(item) + deserialized_value.append(result_value) + + return deserialized_value + + def _deserialize_array_of_dict_case(self, array_value): + deserialized_value = [] + for item in array_value: + result_value = self._deserialize_dict_case(item) + deserialized_value.append(result_value) + + return deserialized_value + + def _deserialize_simple_case(self, value): + if hasattr(self.type_to_match, 'from_dictionary'): + return self.type_to_match.from_dictionary(value) + + if self.type_to_match is date: + return ApiHelper.date_deserialize(value) + + if self.type_to_match is datetime: + return ApiHelper.datetime_deserialize( + value, self._union_type_context.get_date_time_format()) + + return value + + def __deepcopy__(self, memo={}): + copy_object = LeafType(self.type_to_match, self._union_type_context) + copy_object._union_types = self._union_types + copy_object.is_valid = self.is_valid + return copy_object diff --git a/apimatic_core/types/union_types/one_of.py b/apimatic_core/types/union_types/one_of.py new file mode 100644 index 0000000..e1212b5 --- /dev/null +++ b/apimatic_core/types/union_types/one_of.py @@ -0,0 +1,63 @@ +from apimatic_core_interfaces.types.union_type import UnionType +from apimatic_core.types.union_types.union_type_context import UnionTypeContext +from apimatic_core.utilities.union_type_helper import UnionTypeHelper + + +class OneOf(UnionType): + + def __init__(self, union_types, union_type_context: UnionTypeContext = UnionTypeContext()): + super(OneOf, self).__init__(union_types, union_type_context) + self.collection_cases = None + + def validate(self, value): + context = self._union_type_context + UnionTypeHelper.update_nested_flag_for_union_types(self._union_types) + is_optional_or_nullable = UnionTypeHelper.is_optional_or_nullable_case(context, + [nested_type.get_context() + for nested_type in self._union_types]) + + if value is None and is_optional_or_nullable: + self.is_valid = True + return self + + if value is None: + self.is_valid = False + self.error_messages = UnionTypeHelper.process_errors(value, self._union_types, self.error_messages, + self.get_context().is_nested, True) + return self + + self._validate_value_against_case(value, context) + + if not self.is_valid: + self.error_messages = UnionTypeHelper.process_errors(value, self._union_types, self.error_messages, + self.get_context().is_nested, True) + + return self + + def deserialize(self, value): + if value is None: + return None + + return UnionTypeHelper.deserialize_value(value, self._union_type_context, + self.collection_cases, self._union_types) + + def _validate_value_against_case(self, value, context): + if context.is_array() and context.is_dict() and context.is_array_of_dict(): + self.is_valid, self.collection_cases = UnionTypeHelper.validate_array_of_dict_case(self._union_types, value, + True) + elif context.is_array() and context.is_dict(): + self.is_valid, self.collection_cases = UnionTypeHelper.validate_dict_of_array_case(self._union_types, value, + True) + elif context.is_array(): + self.is_valid, self.collection_cases = UnionTypeHelper.validate_array_case(self._union_types, value, True) + elif context.is_dict(): + self.is_valid, self.collection_cases = UnionTypeHelper.validate_dict_case(self._union_types, value, True) + else: + self.is_valid = UnionTypeHelper.get_matched_count(value, self._union_types, True) == 1 + + def __deepcopy__(self, memo={}): + copy_object = OneOf(self._union_types, self._union_type_context) + copy_object.is_valid = self.is_valid + copy_object.collection_cases = self.collection_cases + copy_object.error_messages = self.error_messages + return copy_object diff --git a/apimatic_core/types/union_types/union_type_context.py b/apimatic_core/types/union_types/union_type_context.py new file mode 100644 index 0000000..4933f65 --- /dev/null +++ b/apimatic_core/types/union_types/union_type_context.py @@ -0,0 +1,94 @@ + +class UnionTypeContext: + + @classmethod + def create(cls, is_array=False, is_dict=False, is_array_of_dict=False, is_optional=False, is_nullable=False, + discriminator=None, discriminator_value=None, date_time_format=None, date_time_converter=None): + return cls().array(is_array).dict(is_dict)\ + .array_of_dict(is_array_of_dict)\ + .optional(is_optional)\ + .nullable(is_nullable)\ + .discriminator(discriminator)\ + .discriminator_value(discriminator_value)\ + .date_time_format(date_time_format)\ + .date_time_converter(date_time_converter) + + def __init__(self): + self._is_array = False + self._is_dict = False + self._is_array_of_dict = False + self._is_optional = False + self._is_nullable = False + self._discriminator = None + self._discriminator_value = None + self._date_time_format = None + self._date_time_converter = None + self.path = None + self.is_nested = False + + def array(self, is_array): + self._is_array = is_array + return self + + def is_array(self): + return self._is_array + + def dict(self, is_dict): + self._is_dict = is_dict + return self + + def is_dict(self): + return self._is_dict + + def array_of_dict(self, is_array_of_dict): + self._is_array_of_dict = is_array_of_dict + return self + + def is_array_of_dict(self): + return self._is_array_of_dict + + def optional(self, is_optional): + self._is_optional = is_optional + return self + + def is_optional(self): + return self._is_optional + + def nullable(self, is_nullable): + self._is_nullable = is_nullable + return self + + def is_nullable(self): + return self._is_nullable + + def is_nullable_or_optional(self): + return self.is_nullable() or self.is_optional() + + def discriminator(self, discriminator): + self._discriminator = discriminator + return self + + def get_discriminator(self): + return self._discriminator + + def discriminator_value(self, discriminator_value): + self._discriminator_value = discriminator_value + return self + + def get_discriminator_value(self): + return self._discriminator_value + + def date_time_format(self, date_time_format): + self._date_time_format = date_time_format + return self + + def get_date_time_format(self): + return self._date_time_format + + def date_time_converter(self, date_time_converter): + self._date_time_converter = date_time_converter + return self + + def get_date_time_converter(self): + return self._date_time_converter + diff --git a/apimatic_core/utilities/__init__.py b/apimatic_core/utilities/__init__.py index 9bac680..fbc1e4c 100644 --- a/apimatic_core/utilities/__init__.py +++ b/apimatic_core/utilities/__init__.py @@ -3,5 +3,7 @@ 'auth_helper', 'xml_helper', 'comparison_helper', - 'file_helper' + 'file_helper', + 'datetime_helper', + 'union_type_helper' ] \ No newline at end of file diff --git a/apimatic_core/utilities/api_helper.py b/apimatic_core/utilities/api_helper.py index 0db7b64..9ddd0a8 100644 --- a/apimatic_core/utilities/api_helper.py +++ b/apimatic_core/utilities/api_helper.py @@ -5,11 +5,9 @@ import calendar import email.utils as eut from time import mktime - import jsonpickle import dateutil.parser from jsonpointer import JsonPointerException, resolve_pointer - from apimatic_core.types.datetime_format import DateTimeFormat from apimatic_core.types.file_wrapper import FileWrapper from apimatic_core.types.array_serialization_format import SerializationFormats @@ -28,26 +26,6 @@ class ApiHelper(object): SKIP = '#$%^S0K1I2P3))*' - @staticmethod - def get_request_parameter(value, is_wrapped=False): - """get the correct serialization method for a oneof/anyof parameter type. - - Args: - value: the value of the request parameter - is_wrapped: whether parameter are wrapped in object or not - - Returns: - A correct serialized value which can be used - when sending a request. - - """ - - if type(value) is str: - return value - if is_wrapped: - return ApiHelper.json_serialize_wrapped_params(value) - return ApiHelper.json_serialize(value) - @staticmethod def json_serialize_wrapped_params(obj): """JSON Serialization of a given wrapped object. @@ -79,18 +57,34 @@ def json_serialize(obj, should_encode=True): str: The JSON serialized string of the object. """ + if obj is None: return None + if isinstance(obj, str): + return obj + # Resolve any Names if it's one of our objects that needs to have this called on if isinstance(obj, list): value = list() for item in obj: - if hasattr(item, "_names"): + if isinstance(item, dict) or isinstance(item, list): + value.append(ApiHelper.json_serialize(item, False)) + elif hasattr(item, "_names"): value.append(ApiHelper.to_dictionary(item)) else: value.append(item) obj = value + elif isinstance(obj, dict): + value = dict() + for key, item in obj.items(): + if isinstance(item, list) or isinstance(item, dict): + value[key] = ApiHelper.json_serialize(item, False) + elif hasattr(item, "_names"): + value[key] = ApiHelper.to_dictionary(item) + else: + value[key] = item + obj = value else: if hasattr(obj, "_names"): obj = ApiHelper.to_dictionary(obj) @@ -104,6 +98,8 @@ def json_deserialize(json, unboxing_function=None, as_dict=False): Args: json (str): The JSON serialized string to deserialize. + unboxing_function (callable): The deserialization funtion to be used. + as_dict (bool): The flag to determine to deserialize json as dictionary type Returns: dict: A dictionary representing the data contained in the @@ -175,6 +171,9 @@ def datetime_deserialize(response, datetime_format): JSON serialized string. """ + if response is None: + return None + if isinstance(response, str): deserialized_response = ApiHelper.json_deserialize(response) else: @@ -199,6 +198,15 @@ def datetime_deserialize(response, datetime_format): else: return ApiHelper.RFC3339DateTime.from_value(response).datetime + @staticmethod + def deserialize_union_type(union_type, response, should_deserialize=True): + if should_deserialize: + response = ApiHelper.json_deserialize(response, as_dict=True) + + union_type_result = union_type.validate(response) + + return union_type_result.deserialize(response) + @staticmethod def get_content_type(value): """Get content type header for oneof. @@ -314,9 +322,7 @@ def append_url_with_template_parameters(url, parameters): return url @staticmethod - def append_url_with_query_parameters(url, - parameters, - array_serialization="indexed"): + def append_url_with_query_parameters(url, parameters, array_serialization="indexed"): """Adds query parameters to a URL. Args: @@ -377,8 +383,7 @@ def clean_url(url): return protocol + query_url + parameters @staticmethod - def form_encode_parameters(form_parameters, - array_serialization="indexed"): + def form_encode_parameters(form_parameters, array_serialization="indexed"): """Form encodes a dictionary of form parameters Args: @@ -453,6 +458,9 @@ def to_dictionary(obj, should_ignore_null_values=False): optional_fields = obj._optionals if hasattr(obj, "_optionals") else [] nullable_fields = obj._nullables if hasattr(obj, "_nullables") else [] + if hasattr(obj, 'validate'): + obj.validate(obj) + # Loop through all properties in this model names = {k: v for k, v in obj.__dict__.items() if v is not None} if should_ignore_null_values else obj._names for name in names: @@ -470,19 +478,25 @@ def to_dictionary(obj, should_ignore_null_values=False): # Loop through each item dictionary[obj._names[name]] = list() for item in value: - dictionary[obj._names[name]].append( - ApiHelper.to_dictionary(item, should_ignore_null_values) if hasattr(item, "_names") else item) + if isinstance(item, list) or isinstance(item, dict): + dictionary[obj._names[name]].append(ApiHelper.process_nested_collection( + item, should_ignore_null_values)) + else: + dictionary[obj._names[name]].append(ApiHelper.to_dictionary(item, should_ignore_null_values) + if hasattr(item, "_names") else item) elif isinstance(value, dict): # Loop through each item dictionary[obj._names[name]] = dict() - for key in value: - dictionary[obj._names[name]][key] = ApiHelper.to_dictionary(value[key], - should_ignore_null_values) if hasattr( - value[key], - "_names") else \ - value[key] + for k, v in value.items(): + if isinstance(v, list) or isinstance(v, dict): + dictionary[obj._names[name]][k] = ApiHelper.process_nested_collection( + v, should_ignore_null_values) + else: + dictionary[obj._names[name]][k] = ApiHelper.to_dictionary(value[k], should_ignore_null_values) \ + if hasattr(value[k], "_names") else value[k] else: - dictionary[obj._names[name]] = ApiHelper.to_dictionary(value, should_ignore_null_values) if hasattr(value, "_names") else value + dictionary[obj._names[name]] = ApiHelper.to_dictionary(value, should_ignore_null_values) if \ + hasattr(value, "_names") else value # Loop through all additional properties in this model if hasattr(obj, "additional_properties"): @@ -508,6 +522,29 @@ def to_dictionary(obj, should_ignore_null_values=False): # Return the result return dictionary + @staticmethod + def process_nested_collection(value, should_ignore_null_values): + if isinstance(value, list): + return [ApiHelper.process_nested_collection(item, should_ignore_null_values) for item in value] + + if isinstance(value, dict): + return {k: ApiHelper.process_nested_collection(v, should_ignore_null_values) for k, v in value.items()} + + return ApiHelper.to_dictionary(value, should_ignore_null_values) if hasattr(value, "_names") else value + + @staticmethod + def apply_datetime_converter(value, datetime_converter_obj): + if isinstance(value, list): + return [ApiHelper.apply_datetime_converter(item, datetime_converter_obj) for item in value] + + if isinstance(value, dict): + return {k: ApiHelper.apply_datetime_converter(v, datetime_converter_obj) for k, v in value.items()} + + if isinstance(value, datetime.datetime): + return ApiHelper.when_defined(datetime_converter_obj, value) + + return value + @staticmethod def when_defined(func, value): return func(value) if value else None @@ -516,6 +553,15 @@ def when_defined(func, value): def is_file_wrapper_instance(param): return isinstance(param, FileWrapper) + @staticmethod + def is_valid_type(value, type_callable): + if isinstance(value, list): + return all(ApiHelper.is_valid_type(item, type_callable) for item in value) + elif isinstance(value, dict): + return all(ApiHelper.is_valid_type(item, type_callable) for item in value.values()) + + return value is not None and type_callable(value) + @staticmethod def resolve_template_placeholders_using_json_pointer(placeholders, value, template): """Updates all placeholders in the given message template with provided value. @@ -593,7 +639,7 @@ def __repr__(self): def __getstate__(self): return self.value - def __setstate__(self, state): + def __setstate__(self, state): # pragma: no cover pass class HttpDateTime(CustomDate): @@ -602,8 +648,7 @@ class HttpDateTime(CustomDate): @classmethod def from_datetime(cls, date_time): - return eut.formatdate(timeval=mktime(date_time.timetuple()), - localtime=False, usegmt=True) + return eut.formatdate(timeval=mktime(date_time.timetuple()), localtime=False, usegmt=True) @classmethod def from_value(cls, value): diff --git a/apimatic_core/utilities/datetime_helper.py b/apimatic_core/utilities/datetime_helper.py new file mode 100644 index 0000000..662409e --- /dev/null +++ b/apimatic_core/utilities/datetime_helper.py @@ -0,0 +1,56 @@ +from datetime import datetime, date +from apimatic_core.types.datetime_format import DateTimeFormat + + +class DateTimeHelper: + + @staticmethod + def validate_datetime(datetime_value, datetime_format): + if DateTimeFormat.RFC3339_DATE_TIME == datetime_format: + return DateTimeHelper.is_rfc_3339(datetime_value) + elif DateTimeFormat.UNIX_DATE_TIME == datetime_format: + return DateTimeHelper.is_unix_timestamp(datetime_value) + elif DateTimeFormat.HTTP_DATE_TIME == datetime_format: + return DateTimeHelper.is_rfc_1123(datetime_value) + + return False + + @staticmethod + def validate_date(date_value): + try: + if isinstance(date_value, date): + datetime.strptime(date_value.isoformat(), "%Y-%m-%d") + return True + elif isinstance(date_value, str): + datetime.strptime(date_value, "%Y-%m-%d") + return True + else: + return False + except ValueError: + return False + + @staticmethod + def is_rfc_1123(datetime_value): + try: + datetime.strptime(datetime_value, "%a, %d %b %Y %H:%M:%S %Z") + return True + except (ValueError, AttributeError, TypeError): + return False + + @staticmethod + def is_rfc_3339(datetime_value): + try: + if '.' in datetime_value: + datetime_value = datetime_value[:datetime_value.rindex('.')] + datetime.strptime(datetime_value, "%Y-%m-%dT%H:%M:%S") + return True + except (ValueError, AttributeError, TypeError): + return False + + @staticmethod + def is_unix_timestamp(timestamp): + try: + datetime.fromtimestamp(float(timestamp)) + return True + except (ValueError, AttributeError, TypeError): + return False diff --git a/apimatic_core/utilities/union_type_helper.py b/apimatic_core/utilities/union_type_helper.py new file mode 100644 index 0000000..61fcddf --- /dev/null +++ b/apimatic_core/utilities/union_type_helper.py @@ -0,0 +1,253 @@ +import copy +from datetime import datetime +from apimatic_core.exceptions.anyof_validation_exception import AnyOfValidationException +from apimatic_core.exceptions.oneof_validation_exception import OneOfValidationException +from apimatic_core.types.datetime_format import DateTimeFormat +from apimatic_core.utilities.api_helper import ApiHelper +from apimatic_core.utilities.datetime_helper import DateTimeHelper + + +class UnionTypeHelper: + + NONE_MATCHED_ERROR_MESSAGE = 'We could not match any acceptable types against the given JSON.' + MORE_THAN_1_MATCHED_ERROR_MESSAGE = 'There are more than one acceptable type matched against the given JSON.' + + @staticmethod + def get_deserialized_value(union_types, value): + return [union_type for union_type in union_types if union_type.is_valid][0].deserialize(value) + + @staticmethod + def validate_array_of_dict_case(union_types, array_value, is_for_one_of): + if UnionTypeHelper.is_invalid_array_value(array_value): + return tuple((False, [])) + + collection_cases = [] + valid_cases = [] + for item in array_value: + case_validity, inner_dictionary = UnionTypeHelper.validate_dict_case(union_types, item, is_for_one_of) + collection_cases.append(inner_dictionary) + valid_cases.append(case_validity) + is_valid = sum(valid_cases) == array_value.__len__() + return tuple((is_valid, collection_cases)) + + @staticmethod + def validate_dict_of_array_case(union_types, dict_value, is_for_one_of): + if UnionTypeHelper.is_invalid_dict_value(dict_value): + return tuple((False, [])) + + collection_cases = {} + valid_cases = [] + for key, item in dict_value.items(): + case_validity, inner_array = UnionTypeHelper.validate_array_case(union_types, item, is_for_one_of) + collection_cases[key] = inner_array + valid_cases.append(case_validity) + is_valid = sum(valid_cases) == dict_value.__len__() + return tuple((is_valid, collection_cases)) + + @staticmethod + def validate_dict_case(union_types, dict_value, is_for_one_of): + if UnionTypeHelper.is_invalid_dict_value(dict_value): + return tuple((False, [])) + + is_valid, collection_cases = UnionTypeHelper.process_dict_items(union_types, dict_value, is_for_one_of) + + return tuple((is_valid, collection_cases)) + + @staticmethod + def process_dict_items(union_types, dict_value, is_for_one_of): + is_valid = True + collection_cases = {} + + for key, value in dict_value.items(): + union_type_cases = UnionTypeHelper.make_deep_copies(union_types) + matched_count = UnionTypeHelper.get_matched_count(value, union_type_cases, is_for_one_of) + is_valid = UnionTypeHelper.check_item_validity(is_for_one_of, is_valid, matched_count) + collection_cases[key] = union_type_cases + + return is_valid, collection_cases + + @staticmethod + def validate_array_case(union_types, array_value, is_for_one_of): + if UnionTypeHelper.is_invalid_array_value(array_value): + return tuple((False, [])) + + is_valid, collection_cases = UnionTypeHelper.process_array_items(union_types, array_value, is_for_one_of) + + return tuple((is_valid, collection_cases)) + + @staticmethod + def process_array_items(union_types, array_value, is_for_one_of): + is_valid = True + collection_cases = [] + + for item in array_value: + union_type_cases = UnionTypeHelper.make_deep_copies(union_types) + matched_count = UnionTypeHelper.get_matched_count(item, union_type_cases, is_for_one_of) + is_valid = UnionTypeHelper.check_item_validity(is_for_one_of, is_valid, matched_count) + collection_cases.append(union_type_cases) + + return is_valid, collection_cases + + @staticmethod + def check_item_validity(is_for_one_of, is_valid, matched_count): + if is_valid and is_for_one_of: + is_valid = matched_count == 1 + elif is_valid: + is_valid = matched_count >= 1 + return is_valid + + @staticmethod + def make_deep_copies(union_types): + nested_cases = [] + for union_type in union_types: + nested_cases.append(copy.deepcopy(union_type)) + + return nested_cases + + @staticmethod + def get_matched_count(value, union_types, is_for_one_of): + matched_count = UnionTypeHelper.get_valid_cases_count(value, union_types) + + if is_for_one_of and matched_count == 1: + return matched_count + elif not is_for_one_of and matched_count > 0: + return matched_count + + matched_count = UnionTypeHelper.handle_discriminator_cases(value, union_types) + return matched_count + + @staticmethod + def get_valid_cases_count(value, union_types): + return sum(union_type.validate(value).is_valid for union_type in union_types) + + @staticmethod + def handle_discriminator_cases(value, union_types): + has_discriminator_cases = all(union_type.get_context().get_discriminator() is not None and + union_type.get_context().get_discriminator_value() is not None + for union_type in union_types) + + if has_discriminator_cases: + for union_type in union_types: + union_type.get_context().discriminator(None) + union_type.get_context().discriminator_value(None) + + return UnionTypeHelper.get_valid_cases_count(value, union_types) + + return 0 + + @staticmethod + def validate_date_time(value, context): + if isinstance(value, ApiHelper.RFC3339DateTime): + return context.get_date_time_format() == DateTimeFormat.RFC3339_DATE_TIME + + if isinstance(value, ApiHelper.HttpDateTime): + return context.get_date_time_format() == DateTimeFormat.HTTP_DATE_TIME + + if isinstance(value, ApiHelper.UnixDateTime): + return context.get_date_time_format() == DateTimeFormat.UNIX_DATE_TIME + + if isinstance(value, datetime) and context.get_date_time_converter() is not None: + serialized_dt = str(ApiHelper.when_defined(context.get_date_time_converter(), value)) + return DateTimeHelper.validate_datetime(serialized_dt, context.get_date_time_format()) + + return DateTimeHelper.validate_datetime(value, context.get_date_time_format()) + + @staticmethod + def is_optional_or_nullable_case(current_context, inner_contexts): + return current_context.is_nullable_or_optional() or \ + any(context.is_nullable_or_optional() for context in inner_contexts) + + @staticmethod + def update_nested_flag_for_union_types(nested_union_types): + for union_type in nested_union_types: + union_type.get_context().is_nested = True + + @staticmethod + def is_invalid_array_value(value): + return value is None or not isinstance(value, list) + + @staticmethod + def is_invalid_dict_value(value): + return value is None or not isinstance(value, dict) + + @staticmethod + def deserialize_value(value, context, collection_cases, union_types): + if context.is_array() and context.is_dict() and context.is_array_of_dict(): + return UnionTypeHelper.deserialize_array_of_dict_case(value, collection_cases) + + if context.is_array() and context.is_dict(): + return UnionTypeHelper.deserialize_dict_of_array_case(value, collection_cases) + + if context.is_array(): + return UnionTypeHelper.deserialize_array_case(value, collection_cases) + + if context.is_dict(): + return UnionTypeHelper.deserialize_dict_case(value, collection_cases) + + return UnionTypeHelper.get_deserialized_value(union_types, value) + + @staticmethod + def deserialize_array_of_dict_case(array_value, collection_cases): + deserialized_value = [] + for index, item in enumerate(array_value): + deserialized_value.append(UnionTypeHelper.deserialize_dict_case(item, collection_cases[index])) + + return deserialized_value + + @staticmethod + def deserialize_dict_of_array_case(dict_value, collection_cases): + deserialized_value = {} + for key, value in dict_value.items(): + deserialized_value[key] = UnionTypeHelper.deserialize_array_case(value, collection_cases[key]) + + return deserialized_value + + @staticmethod + def deserialize_dict_case(dict_value, collection_cases): + deserialized_value = {} + for key, value in dict_value.items(): + valid_case = [case for case in collection_cases[key] if case.is_valid][0] + deserialized_value[key] = valid_case.deserialize(value) + + return deserialized_value + + @staticmethod + def deserialize_array_case(array_value, collection_cases): + deserialized_value = [] + for index, item in enumerate(array_value): + valid_case = [case for case in collection_cases[index] if case.is_valid][0] + deserialized_value.append(valid_case.deserialize(item)) + + return deserialized_value + + @staticmethod + def process_errors(value, union_types, error_messages, is_nested, is_for_one_of): + error_messages.add(', '.join(UnionTypeHelper.get_combined_error_messages(union_types))) + + if not is_nested: + UnionTypeHelper.raise_validation_exception(value, union_types, ', '.join(error_messages), is_for_one_of) + + return error_messages + + @staticmethod + def get_combined_error_messages(union_types): + combined_error_messages = [] + from apimatic_core.types.union_types.leaf_type import LeafType + for union_type in union_types: + if isinstance(union_type, LeafType): + combined_error_messages.append(union_type.type_to_match.__name__) + elif union_type.error_messages: + combined_error_messages.append(', '.join(union_type.error_messages)) + return combined_error_messages + + @staticmethod + def raise_validation_exception(value, union_types, error_message, is_for_one_of): + if is_for_one_of: + matched_count = sum(union_type.is_valid for union_type in union_types) + message = UnionTypeHelper.MORE_THAN_1_MATCHED_ERROR_MESSAGE if matched_count > 0 \ + else UnionTypeHelper.NONE_MATCHED_ERROR_MESSAGE + raise OneOfValidationException('{} \nActual Value: {}\nExpected Type: One Of {}.'.format( + message, value, error_message)) + else: + raise AnyOfValidationException('{} \nActual Value: {}\nExpected Type: Any Of {}.'.format( + UnionTypeHelper.NONE_MATCHED_ERROR_MESSAGE, value, error_message)) diff --git a/setup.py b/setup.py index 5b71132..05ab829 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( name='apimatic-core', - version='0.2.3', + version='0.2.4', description='A library that contains core logic and utilities for ' 'consuming REST APIs using Python SDKs generated by APIMatic.', long_description=long_description, diff --git a/tests/apimatic_core/__init__.py b/tests/apimatic_core/__init__.py index 4d35bea..672f182 100644 --- a/tests/apimatic_core/__init__.py +++ b/tests/apimatic_core/__init__.py @@ -5,5 +5,6 @@ 'utility_tests', 'mocks', 'api_call_tests', - 'api_logger_tests' -] \ No newline at end of file + 'api_logger_tests', + 'union_type_tests' +] diff --git a/tests/apimatic_core/base.py b/tests/apimatic_core/base.py index 2a00470..1bc12cf 100644 --- a/tests/apimatic_core/base.py +++ b/tests/apimatic_core/base.py @@ -24,8 +24,11 @@ from tests.apimatic_core.mocks.http.http_response_catcher import HttpResponseCatcher from tests.apimatic_core.mocks.http.http_client import MockHttpClient from tests.apimatic_core.mocks.models.cat_model import CatModel +from tests.apimatic_core.mocks.models.complex_type import ComplexType from tests.apimatic_core.mocks.models.dog_model import DogModel +from tests.apimatic_core.mocks.models.inner_complex_type import InnerComplexType from tests.apimatic_core.mocks.models.one_of_xml import OneOfXML +from tests.apimatic_core.mocks.models.union_type_scalar_model import UnionTypeScalarModel from tests.apimatic_core.mocks.models.wolf_model import WolfModel from tests.apimatic_core.mocks.models.xml_model import XMLModel from tests.apimatic_core.mocks.models.days import Days @@ -251,3 +254,27 @@ def global_configuration_with_uninitialized_auth_params(self): def global_configuration_with_partially_initialized_auth_params(self): return self.global_configuration.auth_managers( {'basic_auth': BasicAuth(None, None), 'custom_header_auth': self.custom_header_auth()}) + + @staticmethod + def get_complex_type(): + inner_complex_type = InnerComplexType(boolean_type=True, + long_type=100003, + string_type='abc', + precision_type=55.44, + string_list_type=['item1', 'item2'], + additional_properties={'key0': 'abc', 'key1': 400}) + + return ComplexType(inner_complex_type=inner_complex_type, + inner_complex_list_type=[inner_complex_type, inner_complex_type], + inner_complex_list_of_map_type=[{'key0': inner_complex_type, 'key1': inner_complex_type}], + inner_complex_map_type={'key0': inner_complex_type, 'key1': inner_complex_type}, + inner_complex_map_of_list_type={'key0': [inner_complex_type, inner_complex_type], + 'key2': [inner_complex_type, inner_complex_type]}, + additional_properties={'prop1': [1, 2, 3], 'prop2': {'key0': 'abc', 'key1': 'def'}}) + + @staticmethod + def get_union_type_scalar_model(): + return UnionTypeScalarModel(any_of_required=1.5, + one_of_req_nullable='abc', + one_of_optional=200, + any_of_opt_nullable=True) diff --git a/tests/apimatic_core/mocks/__init__.py b/tests/apimatic_core/mocks/__init__.py index 3be9907..7f6485e 100644 --- a/tests/apimatic_core/mocks/__init__.py +++ b/tests/apimatic_core/mocks/__init__.py @@ -5,5 +5,6 @@ 'authentications', 'exceptions', 'http', - 'logger' + 'logger', + 'union_type_lookup' ] \ No newline at end of file diff --git a/tests/apimatic_core/mocks/models/__init__.py b/tests/apimatic_core/mocks/models/__init__.py index da1e60f..173d677 100644 --- a/tests/apimatic_core/mocks/models/__init__.py +++ b/tests/apimatic_core/mocks/models/__init__.py @@ -1,6 +1,7 @@ __all__ = [ 'person', 'days', + 'months', 'xml_model', 'validate', 'api_response', @@ -8,4 +9,13 @@ 'cat_model', 'dog_model', 'wolf_model', + 'complex_type', + 'inner_complex_type', + 'atom', + 'deer', + 'lion', + 'orbit', + 'rabbit', + 'grand_parent_class_model', + 'union_type_scalar_model' ] \ No newline at end of file diff --git a/tests/apimatic_core/mocks/models/atom.py b/tests/apimatic_core/mocks/models/atom.py new file mode 100644 index 0000000..254643a --- /dev/null +++ b/tests/apimatic_core/mocks/models/atom.py @@ -0,0 +1,90 @@ +from apimatic_core.utilities.api_helper import ApiHelper + + +class Atom(object): + + """Implementation of the 'Atom' model. + + TODO: type model description here. + + Attributes: + atom_number_of_electrons (int): TODO: type description here. + atom_number_of_protons (int): TODO: type description here. + + """ + + # Create a mapping from Model property names to API property names + _names = { + "atom_number_of_electrons": 'AtomNumberOfElectrons', + "atom_number_of_protons": 'AtomNumberOfProtons' + } + + _optionals = [ + 'atom_number_of_protons', + ] + + def __init__(self, + atom_number_of_electrons=None, + atom_number_of_protons=ApiHelper.SKIP): + """Constructor for the Atom class""" + + # Initialize members of the class + self.atom_number_of_electrons = atom_number_of_electrons + if atom_number_of_protons is not ApiHelper.SKIP: + self.atom_number_of_protons = atom_number_of_protons + + @classmethod + def from_dictionary(cls, + dictionary): + """Creates an instance of this model from a dictionary + + Args: + dictionary (dictionary): A dictionary representation of the object + as obtained from the deserialization of the server's response. The + keys MUST match property names in the API description. + + Returns: + object: An instance of this structure class. + + """ + if dictionary is None: + return None + + # Extract variables from the dictionary + atom_number_of_electrons = dictionary.get("AtomNumberOfElectrons") if \ + dictionary.get("AtomNumberOfElectrons") else None + atom_number_of_protons = dictionary.get("AtomNumberOfProtons") if \ + dictionary.get("AtomNumberOfProtons") else ApiHelper.SKIP + # Return an object of this model + return cls(atom_number_of_electrons, + atom_number_of_protons) + + @classmethod + def validate(cls, dictionary): + """Validates dictionary against class required properties + + Args: + dictionary (dictionary): A dictionary representation of the object + as obtained from the deserialization of the server's response. The + keys MUST match property names in the API description. + + Returns: + boolean : if dictionary is valid contains required properties. + + """ + + if isinstance(dictionary, cls): + return ApiHelper.is_valid_type(value=dictionary.atom_number_of_electrons, + type_callable=lambda value: isinstance(value, int)) + + if not isinstance(dictionary, dict): + return False + + return ApiHelper.is_valid_type(value=dictionary.get('AtomNumberOfElectrons'), + type_callable=lambda value: isinstance(value, int)) + + def __eq__(self, other): + if isinstance(self, other.__class__): + return self.atom_number_of_electrons == other.atom_number_of_electrons and \ + self.atom_number_of_protons == other.atom_number_of_protons + return False diff --git a/tests/apimatic_core/mocks/models/complex_type.py b/tests/apimatic_core/mocks/models/complex_type.py new file mode 100644 index 0000000..aaf19c4 --- /dev/null +++ b/tests/apimatic_core/mocks/models/complex_type.py @@ -0,0 +1,103 @@ +from apimatic_core.utilities.api_helper import ApiHelper +from tests.apimatic_core.mocks.models.inner_complex_type import InnerComplexType + + +class ComplexType(object): + + """Implementation of the 'ComplexType' model. + + TODO: type model description here. + + Attributes: + inner_complex_type (InnerComplexType): TODO: type description here. + inner_complex_list_type (list of InnerComplexType): TODO: type + description here. + inner_complex_map_type (dict): TODO: type description here. + inner_complex_list_of_map_type (list of InnerComplexType): TODO: type + description here. + inner_complex_map_of_list_type (list of InnerComplexType): TODO: type + description here. + + """ + + # Create a mapping from Model property names to API property names + _names = { + "inner_complex_list_type": 'innerComplexListType', + "inner_complex_type": 'innerComplexType', + "inner_complex_list_of_map_type": 'innerComplexListOfMapType', + "inner_complex_map_of_list_type": 'innerComplexMapOfListType', + "inner_complex_map_type": 'innerComplexMapType' + } + + _optionals = [ + 'inner_complex_map_type', + 'inner_complex_list_of_map_type', + 'inner_complex_map_of_list_type', + ] + + def __init__(self, + inner_complex_list_type=None, + inner_complex_type=None, + inner_complex_list_of_map_type=ApiHelper.SKIP, + inner_complex_map_of_list_type=ApiHelper.SKIP, + inner_complex_map_type=ApiHelper.SKIP, + additional_properties={}): + """Constructor for the ComplexType class""" + + # Initialize members of the class + self.inner_complex_type = inner_complex_type + self.inner_complex_list_type = inner_complex_list_type + if inner_complex_map_type is not ApiHelper.SKIP: + self.inner_complex_map_type = inner_complex_map_type + if inner_complex_list_of_map_type is not ApiHelper.SKIP: + self.inner_complex_list_of_map_type = inner_complex_list_of_map_type + if inner_complex_map_of_list_type is not ApiHelper.SKIP: + self.inner_complex_map_of_list_type = inner_complex_map_of_list_type + + # Add additional model properties to the instance + self.additional_properties = additional_properties + + @classmethod + def from_dictionary(cls, + dictionary): + """Creates an instance of this model from a dictionary + + Args: + dictionary (dictionary): A dictionary representation of the object + as obtained from the deserialization of the server's response. The + keys MUST match property names in the API description. + + Returns: + object: An instance of this structure class. + + """ + if dictionary is None: + return None + + # Extract variables from the dictionary + inner_complex_list_type = None + if dictionary.get('innerComplexListType') is not None: + inner_complex_list_type = [InnerComplexType.from_dictionary(x) for x in dictionary.get('innerComplexListType')] + inner_complex_type = InnerComplexType.from_dictionary(dictionary.get('innerComplexType')) if dictionary.get('innerComplexType') else None + inner_complex_list_of_map_type = None + if dictionary.get('innerComplexListOfMapType') is not None: + inner_complex_list_of_map_type = [InnerComplexType.from_dictionary(x) for x in dictionary.get('innerComplexListOfMapType')] + else: + inner_complex_list_of_map_type = ApiHelper.SKIP + inner_complex_map_of_list_type = None + if dictionary.get('innerComplexMapOfListType') is not None: + inner_complex_map_of_list_type = [InnerComplexType.from_dictionary(x) for x in dictionary.get('innerComplexMapOfListType')] + else: + inner_complex_map_of_list_type = ApiHelper.SKIP + inner_complex_map_type = InnerComplexType.from_dictionary(dictionary.get('innerComplexMapType')) if 'innerComplexMapType' in dictionary.keys() else ApiHelper.SKIP + # Clean out expected properties from dictionary + for key in cls._names.values(): + if key in dictionary: + del dictionary[key] + # Return an object of this model + return cls(inner_complex_list_type, + inner_complex_type, + inner_complex_list_of_map_type, + inner_complex_map_of_list_type, + inner_complex_map_type, + dictionary) diff --git a/tests/apimatic_core/mocks/models/days.py b/tests/apimatic_core/mocks/models/days.py index f40b115..6b08a55 100644 --- a/tests/apimatic_core/mocks/models/days.py +++ b/tests/apimatic_core/mocks/models/days.py @@ -1,4 +1,3 @@ - class Days(object): """Implementation of the 'Days' enum. @@ -16,6 +15,8 @@ class Days(object): """ + _all_values = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday','Saturday'] + SUNDAY = 'Sunday' MONDAY = 'Monday' @@ -29,3 +30,19 @@ class Days(object): FRI_DAY = 'Friday' SATURDAY = 'Saturday' + + @classmethod + def validate(cls, value): + """Validates value against enum. + + Args: + value: the value to be validated against. + + Returns: + boolean : if value is valid for this model. + + """ + if value is None: + return None + + return value in cls._all_values \ No newline at end of file diff --git a/tests/apimatic_core/mocks/models/deer.py b/tests/apimatic_core/mocks/models/deer.py new file mode 100644 index 0000000..7c9c0d2 --- /dev/null +++ b/tests/apimatic_core/mocks/models/deer.py @@ -0,0 +1,85 @@ +from apimatic_core.utilities.api_helper import ApiHelper + + +class Deer(object): + + """Implementation of the 'Deer' model. + + TODO: type model description here. + + Attributes: + name (str): TODO: type description here. + weight (int): TODO: type description here. + mtype (str): TODO: type description here. + + """ + + # Create a mapping from Model property names to API property names + _names = { + "name": 'name', + "weight": 'weight', + "mtype": 'type' + } + + def __init__(self, + name=None, + weight=None, + mtype=None): + """Constructor for the Deer class""" + + # Initialize members of the class + self.name = name + self.weight = weight + self.mtype = mtype + + @classmethod + def from_dictionary(cls, + dictionary): + """Creates an instance of this model from a dictionary + + Args: + dictionary (dictionary): A dictionary representation of the object + as obtained from the deserialization of the server's response. The + keys MUST match property names in the API description. + + Returns: + object: An instance of this structure class. + + """ + if dictionary is None: + return None + + # Extract variables from the dictionary + name = dictionary.get("name") if dictionary.get("name") else None + weight = dictionary.get("weight") if dictionary.get("weight") else None + mtype = dictionary.get("type") if dictionary.get("type") else None + # Return an object of this model + return cls(name, + weight, + mtype) + + @classmethod + def validate(cls, dictionary): + """Validates dictionary against class required properties + + Args: + dictionary (dictionary): A dictionary representation of the object + as obtained from the deserialization of the server's response. The + keys MUST match property names in the API description. + + Returns: + boolean : if dictionary is valid contains required properties. + + """ + + if isinstance(dictionary, cls): + return ApiHelper.is_valid_type(value=dictionary.name, type_callable=lambda value: isinstance(value, str)) \ + and ApiHelper.is_valid_type(value=dictionary.weight, type_callable=lambda value: isinstance(value, int)) \ + and ApiHelper.is_valid_type(value=dictionary.mtype, type_callable=lambda value: isinstance(value, str)) + + if not isinstance(dictionary, dict): + return False + + return ApiHelper.is_valid_type(value=dictionary.get('name'), type_callable=lambda value: isinstance(value, str)) \ + and ApiHelper.is_valid_type(value=dictionary.get('weight'), type_callable=lambda value: isinstance(value, int)) \ + and ApiHelper.is_valid_type(value=dictionary.get('type'), type_callable=lambda value: isinstance(value, str)) diff --git a/tests/apimatic_core/mocks/models/inner_complex_type.py b/tests/apimatic_core/mocks/models/inner_complex_type.py new file mode 100644 index 0000000..7913774 --- /dev/null +++ b/tests/apimatic_core/mocks/models/inner_complex_type.py @@ -0,0 +1,83 @@ +import dateutil +from apimatic_core.utilities.api_helper import ApiHelper + + +class InnerComplexType(object): + + """Implementation of the 'InnerComplexType' model. + + TODO: type model description here. + + Attributes: + string_type (str): TODO: type description here. + boolean_type (bool): TODO: type description here. + long_type (long|int): TODO: type description here. + precision_type (float): TODO: type description here. + string_list_type (list of str): TODO: type description here. + + """ + + # Create a mapping from Model property names to API property names + _names = { + "boolean_type": 'booleanType', + "date_time_type": 'dateTimeType', + "date_type": 'dateType', + "long_type": 'longType', + "precision_type": 'precisionType', + "string_list_type": 'stringListType', + "string_type": 'stringType' + } + + def __init__(self, + boolean_type=None, + long_type=None, + precision_type=None, + string_list_type=None, + string_type=None, + additional_properties={}): + """Constructor for the InnerComplexType class""" + + # Initialize members of the class + self.string_type = string_type + self.boolean_type = boolean_type + self.long_type = long_type + self.precision_type = precision_type + self.string_list_type = string_list_type + + # Add additional model properties to the instance + self.additional_properties = additional_properties + + @classmethod + def from_dictionary(cls, + dictionary): + """Creates an instance of this model from a dictionary + + Args: + dictionary (dictionary): A dictionary representation of the object + as obtained from the deserialization of the server's response. The + keys MUST match property names in the API description. + + Returns: + object: An instance of this structure class. + + """ + if dictionary is None: + return None + + # Extract variables from the dictionary + boolean_type = dictionary.get("booleanType") if "booleanType" in dictionary.keys() else None + long_type = dictionary.get("longType") if dictionary.get("longType") else None + precision_type = dictionary.get("precisionType") if dictionary.get("precisionType") else None + string_list_type = dictionary.get("stringListType") if dictionary.get("stringListType") else None + string_type = dictionary.get("stringType") if dictionary.get("stringType") else None + # Clean out expected properties from dictionary + for key in cls._names.values(): + if key in dictionary: + del dictionary[key] + # Return an object of this model + return cls(boolean_type, + long_type, + precision_type, + string_list_type, + string_type, + dictionary) diff --git a/tests/apimatic_core/mocks/models/lion.py b/tests/apimatic_core/mocks/models/lion.py new file mode 100644 index 0000000..d3b454d --- /dev/null +++ b/tests/apimatic_core/mocks/models/lion.py @@ -0,0 +1,96 @@ +from apimatic_core.utilities.api_helper import ApiHelper + + +class Lion(object): + + """Implementation of the 'Lion' model. + + TODO: type model description here. + + Attributes: + id (int): TODO: type description here. + weight (int): TODO: type description here. + mtype (str): TODO: type description here. + kind (str): TODO: type description here. + + """ + + # Create a mapping from Model property names to API property names + _names = { + "id": 'id', + "weight": 'weight', + "mtype": 'type', + "kind": 'kind' + } + + _optionals = [ + 'kind', + ] + + def __init__(self, + id=None, + weight=None, + mtype=None, + kind=ApiHelper.SKIP): + """Constructor for the Lion class""" + + # Initialize members of the class + self.id = id + self.weight = weight + self.mtype = mtype + if kind is not ApiHelper.SKIP: + self.kind = kind + + @classmethod + def from_dictionary(cls, + dictionary): + """Creates an instance of this model from a dictionary + + Args: + dictionary (dictionary): A dictionary representation of the object + as obtained from the deserialization of the server's response. The + keys MUST match property names in the API description. + + Returns: + object: An instance of this structure class. + + """ + if dictionary is None: + return None + + # Extract variables from the dictionary + id = dictionary.get("id") if dictionary.get("id") else None + weight = dictionary.get("weight") if dictionary.get("weight") else None + mtype = dictionary.get("type") if dictionary.get("type") else None + kind = dictionary.get("kind") if dictionary.get("kind") else ApiHelper.SKIP + # Return an object of this model + return cls(id, + weight, + mtype, + kind) + + @classmethod + def validate(cls, dictionary): + """Validates dictionary against class required properties + + Args: + dictionary (dictionary): A dictionary representation of the object + as obtained from the deserialization of the server's response. The + keys MUST match property names in the API description. + + Returns: + boolean : if dictionary is valid contains required properties. + + """ + + if isinstance(dictionary, cls): + return ApiHelper.is_valid_type(value=dictionary.id, type_callable=lambda value: isinstance(value, int)) \ + and ApiHelper.is_valid_type(value=dictionary.weight, type_callable=lambda value: isinstance(value, int)) \ + and ApiHelper.is_valid_type(value=dictionary.mtype, type_callable=lambda value: isinstance(value, str)) + + if not isinstance(dictionary, dict): + return False + + return ApiHelper.is_valid_type(value=dictionary.get('id'), type_callable=lambda value: isinstance(value, int)) \ + and ApiHelper.is_valid_type(value=dictionary.get('weight'), type_callable=lambda value: isinstance(value, int)) \ + and ApiHelper.is_valid_type(value=dictionary.get('type'), type_callable=lambda value: isinstance(value, str)) diff --git a/tests/apimatic_core/mocks/models/months.py b/tests/apimatic_core/mocks/models/months.py new file mode 100644 index 0000000..baba247 --- /dev/null +++ b/tests/apimatic_core/mocks/models/months.py @@ -0,0 +1,62 @@ + +class Months(object): + + """Implementation of the 'Months' enum. + + An integer enum representing Month names + + Attributes: + JANUARY: TODO: type description here. + FEBRUARY: TODO: type description here. + MARCH: TODO: type description here. + APRIL: TODO: type description here. + MAY: TODO: type description here. + JUNE: TODO: type description here. + JULY: TODO: type description here. + SEPTEMBER: TODO: type description here. + OCTOBER: TODO: type description here. + NOVEMBER: TODO: type description here. + DECEMBER: TODO: type description here. + + """ + _all_values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] + + JANUARY = 1 + + FEBRUARY = 2 + + MARCH = 3 + + APRIL = 4 + + MAY = 5 + + JUNE = 6 + + JULY = 7 + + AUGUST = 8 + + SEPTEMBER = 9 + + OCTOBER = 10 + + NOVEMBER = 11 + + DECEMBER = 12 + + @classmethod + def validate(cls, value): + """Validates value against enum. + + Args: + value: the value to be validated against. + + Returns: + boolean : if value is valid for this model. + + """ + if value is None: + return None + + return value in cls._all_values \ No newline at end of file diff --git a/tests/apimatic_core/mocks/models/orbit.py b/tests/apimatic_core/mocks/models/orbit.py new file mode 100644 index 0000000..a124c09 --- /dev/null +++ b/tests/apimatic_core/mocks/models/orbit.py @@ -0,0 +1,77 @@ +from apimatic_core.utilities.api_helper import ApiHelper + + +class Orbit(object): + + """Implementation of the 'Orbit' model. + + TODO: type model description here. + + Attributes: + orbit_number_of_electrons (int): TODO: type description here. + + """ + + # Create a mapping from Model property names to API property names + _names = { + "orbit_number_of_electrons": 'OrbitNumberOfElectrons' + } + + def __init__(self, + orbit_number_of_electrons=None): + """Constructor for the Orbit class""" + + # Initialize members of the class + self.orbit_number_of_electrons = orbit_number_of_electrons + + @classmethod + def from_dictionary(cls, + dictionary): + """Creates an instance of this model from a dictionary + + Args: + dictionary (dictionary): A dictionary representation of the object + as obtained from the deserialization of the server's response. The + keys MUST match property names in the API description. + + Returns: + object: An instance of this structure class. + + """ + if dictionary is None: + return None + + # Extract variables from the dictionary + orbit_number_of_electrons = dictionary.get("OrbitNumberOfElectrons") \ + if dictionary.get("OrbitNumberOfElectrons") else None + # Return an object of this model + return cls(orbit_number_of_electrons) + + @classmethod + def validate(cls, dictionary): + """Validates dictionary against class required properties + + Args: + dictionary (dictionary): A dictionary representation of the object + as obtained from the deserialization of the server's response. The + keys MUST match property names in the API description. + + Returns: + boolean : if dictionary is valid contains required properties. + + """ + + if isinstance(dictionary, cls): + return ApiHelper.is_valid_type(value=dictionary.orbit_number_of_electrons, + type_callable=lambda value: isinstance(value, int)) + + if not isinstance(dictionary, dict): + return False + + return ApiHelper.is_valid_type(value=dictionary.get('OrbitNumberOfElectrons'), + type_callable=lambda value: isinstance(value, int)) + + def __eq__(self, other): + if isinstance(self, other.__class__): + return self.orbit_number_of_electrons == other.orbit_number_of_electrons + return False diff --git a/tests/apimatic_core/mocks/models/rabbit.py b/tests/apimatic_core/mocks/models/rabbit.py new file mode 100644 index 0000000..ff1cc09 --- /dev/null +++ b/tests/apimatic_core/mocks/models/rabbit.py @@ -0,0 +1,94 @@ +from apimatic_core.utilities.api_helper import ApiHelper + + +class Rabbit(object): + """Implementation of the 'Lion' model. + + TODO: type model description here. + + Attributes: + id (string): TODO: type description here. + weight (string): TODO: type description here. + mtype (string): TODO: type description here. + kind (string): TODO: type description here. + + """ + + # Create a mapping from Model property names to API property names + _names = { + "id": 'id', + "weight": 'weight', + "mtype": 'type', + "kind": 'kind' + } + + _optionals = [ + 'kind', + ] + + def __init__(self, + id=None, + weight=None, + mtype=None, + kind=ApiHelper.SKIP): + """Constructor for the Lion class""" + + # Initialize members of the class + self.id = id + self.weight = weight + self.mtype = mtype + if kind is not ApiHelper.SKIP: + self.kind = kind + + @classmethod + def from_dictionary(cls, + dictionary): + """Creates an instance of this model from a dictionary + + Args: + dictionary (dictionary): A dictionary representation of the object + as obtained from the deserialization of the server's response. The + keys MUST match property names in the API description. + + Returns: + object: An instance of this structure class. + + """ + if dictionary is None: + return None + + # Extract variables from the dictionary + + id = dictionary.get("id") if dictionary.get("id") else None + weight = dictionary.get("weight") if dictionary.get("weight") else None + mtype = dictionary.get("type") if dictionary.get("type") else None + kind = dictionary.get("kind") if dictionary.get("kind") else ApiHelper.SKIP + # Return an object of this model + return cls(id, + weight, + mtype, + kind) + + @classmethod + def validate(cls, dictionary): + """Validates dictionary against class properties. + + Args: + dictionary: the dictionary to be validated against. + + Returns: + boolean : if value is valid for this model. + + """ + if isinstance(dictionary, cls): + return True + + if not isinstance(dictionary, dict): + return False + + return dictionary.get("id") is not None and \ + ApiHelper.is_valid_type(dictionary.get("id"), lambda value: isinstance(value, str)) and \ + dictionary.get("weight") is not None and \ + ApiHelper.is_valid_type(dictionary.get("weight"), lambda value: isinstance(value, str)) and \ + dictionary.get("type") is not None and \ + ApiHelper.is_valid_type(dictionary.get("type"), lambda value: isinstance(value, str)) diff --git a/tests/apimatic_core/mocks/models/union_type_scalar_model.py b/tests/apimatic_core/mocks/models/union_type_scalar_model.py new file mode 100644 index 0000000..1c8ff81 --- /dev/null +++ b/tests/apimatic_core/mocks/models/union_type_scalar_model.py @@ -0,0 +1,121 @@ +from apimatic_core.utilities.api_helper import ApiHelper +from tests.apimatic_core.mocks.union_type_lookup import UnionTypeLookUp + + +class UnionTypeScalarModel(object): + + """Implementation of the 'ScalarModel' model. + + This class contains scalar types in oneOf/anyOf cases. + + Attributes: + any_of_required (float | bool): TODO: type description here. + one_of_req_nullable (int | str | None): TODO: type description here. + one_of_optional (int | float | str | None): TODO: type description + here. + any_of_opt_nullable (int | bool | None): TODO: type description here. + + """ + + # Create a mapping from Model property names to API property names + _names = { + "any_of_required": 'anyOfRequired', + "one_of_req_nullable": 'oneOfReqNullable', + "one_of_optional": 'oneOfOptional', + "any_of_opt_nullable": 'anyOfOptNullable' + } + + _optionals = [ + 'one_of_optional', + 'any_of_opt_nullable', + ] + + _nullables = [ + 'one_of_req_nullable', + 'any_of_opt_nullable', + ] + + def __init__(self, + any_of_required=None, + one_of_req_nullable=None, + one_of_optional=ApiHelper.SKIP, + any_of_opt_nullable=ApiHelper.SKIP): + """Constructor for the ScalarModel class""" + + # Initialize members of the class + self.any_of_required = any_of_required + self.one_of_req_nullable = one_of_req_nullable + if one_of_optional is not ApiHelper.SKIP: + self.one_of_optional = one_of_optional + if any_of_opt_nullable is not ApiHelper.SKIP: + self.any_of_opt_nullable = any_of_opt_nullable + + @classmethod + def from_dictionary(cls, + dictionary): + """Creates an instance of this model from a dictionary + + Args: + dictionary (dictionary): A dictionary representation of the object + as obtained from the deserialization of the server's response. The + keys MUST match property names in the API description. + + Returns: + object: An instance of this structure class. + + """ + if dictionary is None: + return None + + # Extract variables from the dictionary + any_of_required = ApiHelper.deserialize_union_type( + UnionTypeLookUp.get('ScalarModelAnyOfRequired'), + dictionary.get('anyOfRequired'), False) if dictionary.get('anyOfRequired') is not None else None + one_of_req_nullable = ApiHelper.deserialize_union_type( + UnionTypeLookUp.get('ScalarModelOneOfReqNullable'), + dictionary.get('oneOfReqNullable'), False) if dictionary.get('oneOfReqNullable') is not None else None + one_of_optional = ApiHelper.deserialize_union_type( + UnionTypeLookUp.get('ScalarModelOneOfOptional'), + dictionary.get('oneOfOptional'), False) if dictionary.get('oneOfOptional') is not None else ApiHelper.SKIP + if 'anyOfOptNullable' in dictionary.keys(): + any_of_opt_nullable = ApiHelper.deserialize_union_type( + UnionTypeLookUp.get('ScalarModelAnyOfOptNullable'), + dictionary.get('anyOfOptNullable'), False) if dictionary.get('anyOfOptNullable') is not None else None + else: + any_of_opt_nullable = ApiHelper.SKIP + # Return an object of this model + return cls(any_of_required, + one_of_req_nullable, + one_of_optional, + any_of_opt_nullable) + + @classmethod + def validate(cls, dictionary): + """Validates dictionary against class required properties + + Args: + dictionary (dictionary): A dictionary representation of the object + as obtained from the deserialization of the server's response. The + keys MUST match property names in the API description. + + Returns: + boolean : if dictionary is valid contains required properties. + + """ + if isinstance(dictionary, cls): + return ApiHelper.is_valid_type( + value=dictionary.any_of_required, + type_callable=lambda value: UnionTypeLookUp.get('ScalarModelAnyOfRequired').validate) and \ + ApiHelper.is_valid_type( + value=dictionary.one_of_req_nullable, + type_callable=lambda value: UnionTypeLookUp.get('ScalarModelOneOfReqNullable').validate) + + if not isinstance(dictionary, dict): + return False + + return ApiHelper.is_valid_type( + value=dictionary.get('anyOfRequired'), + type_callable=lambda value: UnionTypeLookUp.get('ScalarModelAnyOfRequired').validate) and \ + ApiHelper.is_valid_type( + value=dictionary.get('oneOfReqNullable'), + type_callable=lambda value: UnionTypeLookUp.get('ScalarModelOneOfReqNullable').validate) diff --git a/tests/apimatic_core/mocks/union_type_lookup.py b/tests/apimatic_core/mocks/union_type_lookup.py new file mode 100644 index 0000000..bdcd03f --- /dev/null +++ b/tests/apimatic_core/mocks/union_type_lookup.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- + +""" +typecombinatorsimple + +This file was automatically generated by APIMATIC v3.0 ( + https://www.apimatic.io ). +""" + +from apimatic_core.types.union_types.any_of import AnyOf +from apimatic_core.types.union_types.leaf_type import LeafType +from apimatic_core.types.union_types.one_of import OneOf +from apimatic_core.types.union_types.union_type_context import UnionTypeContext as Context + + +class UnionTypeLookUp: + + """The `UnionTypeLookUp` class serves as a utility class for + storing and managing type combinator templates.It acts as a container for the templates + used in handling various data types within the application. + + """ + _union_types = { + 'ScalarModelAnyOfRequired': AnyOf([LeafType(float), LeafType(bool)]), + 'ScalarModelOneOfReqNullable': OneOf([LeafType(int), LeafType(str)], Context.create(is_nullable=True)), + 'ScalarModelOneOfOptional': OneOf([LeafType(int), LeafType(float), LeafType(str)], Context.create(is_optional=True)), + 'ScalarModelAnyOfOptNullable': AnyOf([LeafType(int), LeafType(bool)], Context.create(is_optional=True, is_nullable=True)), + 'ScalarTypes': OneOf([LeafType(float), LeafType(bool)]), + } + + @staticmethod + def get(name): + return UnionTypeLookUp._union_types[name] + diff --git a/tests/apimatic_core/request_builder_tests/test_request_builder.py b/tests/apimatic_core/request_builder_tests/test_request_builder.py index 9c3900f..9ec6d4f 100644 --- a/tests/apimatic_core/request_builder_tests/test_request_builder.py +++ b/tests/apimatic_core/request_builder_tests/test_request_builder.py @@ -15,6 +15,7 @@ from tests.apimatic_core.base import Base from tests.apimatic_core.mocks.callables.base_uri_callable import Server from requests.utils import quote +from tests.apimatic_core.mocks.union_type_lookup import UnionTypeLookUp class TestRequestBuilder(Base): @@ -417,27 +418,18 @@ def test_json_body_params_with_serializer(self, input_body_param_value, expected .build(self.global_configuration) assert http_request.parameters == expected_body_param_value - @pytest.mark.parametrize('input_body_param_value, input_should_wrap_body_param,' - 'input_body_key, expected_body_param_value', [ - (100, False, None, '100'), - (100, True, 'body', '{"body": 100}'), - ([1, 2, 3, 4], False, None, '[1, 2, 3, 4]'), - ([1, 2, 3, 4], True, 'body', '{"body": [1, 2, 3, 4]}'), - ({'key1': 'value1', 'key2': [1, 2, 3, 4]}, False, None, - '{"key1": "value1", "key2": [1, 2, 3, 4]}'), - ({'key1': 'value1', 'key2': [1, 2, 3, 4]}, True, 'body', - '{"body": {"key1": "value1", "key2": [1, 2, 3, 4]}}') - ]) - def test_type_combinator_body_param_with_serializer(self, input_body_param_value, input_should_wrap_body_param, - input_body_key, expected_body_param_value): + @pytest.mark.parametrize('input_value, expected_value', [ + (100, '100'), + (True, 'true') + ]) + def test_type_combinator_validation_in_request(self, input_value, expected_value): http_request = self.new_request_builder \ .body_param(Parameter() - .key(input_body_key) - .value(input_body_param_value)) \ - .body_serializer(ApiHelper.get_request_parameter) \ - .should_wrap_body_param(input_should_wrap_body_param) \ + .validator(lambda value: UnionTypeLookUp.get('ScalarTypes')) + .value(input_value)) \ + .body_serializer(ApiHelper.json_serialize) \ .build(self.global_configuration) - assert http_request.parameters == expected_body_param_value + assert http_request.parameters == expected_value @pytest.mark.parametrize('input_body_param_value, expected_body_param_value', [ (Base.xml_model(), '' diff --git a/tests/apimatic_core/union_type_tests/__init__.py b/tests/apimatic_core/union_type_tests/__init__.py new file mode 100644 index 0000000..519decc --- /dev/null +++ b/tests/apimatic_core/union_type_tests/__init__.py @@ -0,0 +1,4 @@ +__all__ = [ + 'test_any_of', + 'test_one_of', +] \ No newline at end of file diff --git a/tests/apimatic_core/union_type_tests/test_any_of.py b/tests/apimatic_core/union_type_tests/test_any_of.py new file mode 100644 index 0000000..690b26a --- /dev/null +++ b/tests/apimatic_core/union_type_tests/test_any_of.py @@ -0,0 +1,816 @@ +from datetime import datetime, date +import pytest +from apimatic_core.exceptions.anyof_validation_exception import AnyOfValidationException +from apimatic_core.types.datetime_format import DateTimeFormat +from apimatic_core.types.union_types.leaf_type import LeafType +from apimatic_core.types.union_types.any_of import AnyOf +from apimatic_core.types.union_types.union_type_context import UnionTypeContext +from apimatic_core.utilities.api_helper import ApiHelper +from apimatic_core.utilities.union_type_helper import UnionTypeHelper +from tests.apimatic_core.base import Base +from tests.apimatic_core.mocks.models.atom import Atom +from tests.apimatic_core.mocks.models.days import Days +from tests.apimatic_core.mocks.models.deer import Deer +from tests.apimatic_core.mocks.models.lion import Lion +from tests.apimatic_core.mocks.models.months import Months +from tests.apimatic_core.mocks.models.orbit import Orbit +from tests.apimatic_core.mocks.models.rabbit import Rabbit + + +class TestAnyOf: + + @pytest.mark.parametrize( + 'input_value, input_types, input_context, expected_validity, expected_deserialized_value', [ + # Simple Cases + (100, [LeafType(int), LeafType(str)], UnionTypeContext(), True, 100), + (100, [LeafType(int), LeafType(int), LeafType(str)], UnionTypeContext(), True, 100), + ('abc', [LeafType(int), LeafType(str)], UnionTypeContext(), True, 'abc'), + (True, [LeafType(bool), LeafType(str)], UnionTypeContext(), True, True), + (100.44, [LeafType(int), LeafType(str)], UnionTypeContext(), False, None), + (None, [LeafType(int), LeafType(str)], UnionTypeContext().nullable(True), True, None), + (None, [LeafType(int), LeafType(str)], UnionTypeContext().optional(True), True, None), + (None, [LeafType(int), LeafType(str)], UnionTypeContext(), False, None), + + # Outer Array Cases + (['abc', 'def'], [LeafType(int), LeafType(str)], UnionTypeContext().array(True), True, ['abc', 'def']), + ([100, 200], [LeafType(int), LeafType(str)], UnionTypeContext().array(True), True, [100, 200]), + ([100, 'abc'], [LeafType(int), LeafType(str)], UnionTypeContext().array(True), True, [100, 'abc']), + (100, [LeafType(int), LeafType(str)], UnionTypeContext().array(True), False, None), + ('100', [LeafType(int), LeafType(str)], UnionTypeContext().array(True), False, None), + ([['abc', 'def']], [LeafType(int), LeafType(str)], UnionTypeContext().array(True), False, None), + ([100, 200], [LeafType(int, UnionTypeContext().array(True)), LeafType(int)], UnionTypeContext(), True, [100, 200]), + + # Inner Array Cases + (['abc', 'def'], [LeafType(int, UnionTypeContext().array(True)), + LeafType(str, UnionTypeContext().array(True))], + UnionTypeContext(), True, ['abc', 'def']), + ([100, 200], [LeafType(int, UnionTypeContext().array(True)), LeafType(str, UnionTypeContext().array(True))], + UnionTypeContext(), True, [100, 200]), + ([100, 'abc'], + [LeafType(int, UnionTypeContext().array(True)), LeafType(str, UnionTypeContext().array(True))], + UnionTypeContext(), False, None), + + # Partial Array Case + ('abc', [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], + UnionTypeContext(), True, 'abc'), + ([100, 200], [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], + UnionTypeContext(), True, [100, 200]), + ([100, 'abc'], [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], + UnionTypeContext(), False, None), + (100, [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], + UnionTypeContext(), False, None), + + # Array of Partial Arrays Cases + (['abc', 'def'], [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], + UnionTypeContext().array(True), True, ['abc', 'def']), + ([[100, 200]], [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], + UnionTypeContext().array(True), True, [[100, 200]]), + ([[100, 200], 'abc'], [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], + UnionTypeContext().array(True), True, [[100, 200], 'abc']), + ([[100, 'abc']], [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], + UnionTypeContext().array(True), False, None), + ([100], [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], + UnionTypeContext().array(True), False, None), + + # Array of Arrays Cases + ([['abc', 'def'], ['def', 'ghi']], [LeafType(int, UnionTypeContext().array(True)), + LeafType(str, UnionTypeContext().array(True))], + UnionTypeContext().array(True), True, [['abc', 'def'], ['def', 'ghi']]), + ([[100, 200], [300, 400]], [LeafType(int, UnionTypeContext().array(True)), + LeafType(str, UnionTypeContext().array(True))], + UnionTypeContext().array(True), True, [[100, 200], [300, 400]]), + ([[100, 200], ['abc', 'def']], [LeafType(int, UnionTypeContext().array(True)), + LeafType(str, UnionTypeContext().array(True))], + UnionTypeContext().array(True), True, [[100, 200], ['abc', 'def']]), + ([[100, 'abc'], [200, 'def']], [LeafType(int, UnionTypeContext().array(True)), + LeafType(str, UnionTypeContext().array(True))], + UnionTypeContext().array(True), False, None), + ([[100, 'abc'], ['def', 'ghi']], [LeafType(int, UnionTypeContext().array(True)), + LeafType(str, UnionTypeContext().array(True))], + UnionTypeContext().array(True), False, None), + ([[100.45, 200.45], [100, 200]], [LeafType(int, UnionTypeContext().array(True)), + LeafType(str, UnionTypeContext().array(True))], + UnionTypeContext().array(True), False, None), + ([['abc', 'def'], [100.45, 200.45]], [LeafType(int, UnionTypeContext().array(True)), + LeafType(str, UnionTypeContext().array(True))], + UnionTypeContext().array(True), False, None), + + # Outer Dictionary Cases + ({'key0': 'abc', 'key1': 'def'}, [LeafType(int), LeafType(str)], UnionTypeContext().dict(True), True, + {'key0': 'abc', 'key1': 'def'}), + ({'key0': 100, 'key1': 200}, [LeafType(int), LeafType(str)], UnionTypeContext().dict(True), True, + {'key0': 100, 'key1': 200}), + ({'key0': 100, 'key2': 'abc'}, [LeafType(int), LeafType(str)], UnionTypeContext().dict(True), True, + {'key0': 100, 'key2': 'abc'}), + (100, [LeafType(int), LeafType(str)], UnionTypeContext().dict(True), False, None), + ('100', [LeafType(int), LeafType(str)], UnionTypeContext().dict(True), False, None), + ({'key0': {'key0': 'abc', 'key1': 'def'}}, [LeafType(int), LeafType(str)], + UnionTypeContext().dict(True), False, None), + + # Inner Dictionary Cases + ({'key0': 'abc', 'key1': 'def'}, [LeafType(int, UnionTypeContext().dict(True)), + LeafType(str, UnionTypeContext().dict(True))], + UnionTypeContext(), True, {'key0': 'abc', 'key1': 'def'}), + ({'key0': 100, 'key1': 200}, [LeafType(int, UnionTypeContext().dict(True)), + LeafType(str, UnionTypeContext().dict(True))], + UnionTypeContext(), True, {'key0': 100, 'key1': 200}), + ({'key0': 100, 'key1': 'abc'}, [LeafType(int, UnionTypeContext().dict(True)), + LeafType(str, UnionTypeContext().array(True))], + UnionTypeContext(), False, None), + + # Partial Dictionary Cases + ('abc', [LeafType(int, UnionTypeContext().dict(True)), LeafType(str)], + UnionTypeContext(), True, 'abc'), + ({'key0': 100, 'key1': 200}, [LeafType(int, UnionTypeContext().dict(True)), LeafType(str)], + UnionTypeContext(), True, {'key0': 100, 'key1': 200}), + ({'key0': 100, 'key1': 'abc'}, [LeafType(int, UnionTypeContext().dict(True)), LeafType(str)], + UnionTypeContext(), False, None), + (100, [LeafType(int, UnionTypeContext().dict(True)), LeafType(str)], + UnionTypeContext(), False, None), + + # Dictionary of Partial Dictionary Cases + ({'key0': 'abc', 'key1': 'def'}, [LeafType(int, UnionTypeContext().dict(True)), LeafType(str)], + UnionTypeContext().dict(True), True, {'key0': 'abc', 'key1': 'def'}), + ({'key0': {'key0': 100, 'key1': 200}}, [LeafType(int, UnionTypeContext().dict(True)), LeafType(str)], + UnionTypeContext().dict(True), True, {'key0': {'key0': 100, 'key1': 200}}), + ({'key0': {'key0': 100, 'key1': 200}, 'key1': 'abc'}, + [LeafType(int, UnionTypeContext().dict(True)), LeafType(str)], + UnionTypeContext().dict(True), True, {'key0': {'key0': 100, 'key1': 200}, 'key1': 'abc'}), + ({'key0': {'key0': 100, 'key1': 'abc'}}, [LeafType(int, UnionTypeContext().dict(True)), LeafType(str)], + UnionTypeContext().dict(True), False, None), + ({'key0': 100}, [LeafType(int, UnionTypeContext().dict(True)), LeafType(str)], + UnionTypeContext().dict(True), False, None), + + # Dictionary of Dictionary Cases + ({'key0': {'key0': 'abc', 'key1': 'def'}, 'key1': {'key0': 'ghi', 'key1': 'jkl'}}, + [LeafType(int, UnionTypeContext().array(True)), LeafType(str, UnionTypeContext().dict(True))], + UnionTypeContext().dict(True), True, + {'key0': {'key0': 'abc', 'key1': 'def'}, 'key1': {'key0': 'ghi', 'key1': 'jkl'}}), + ({'key0': {'key0': 100, 'key1': 200}, 'key1': {'key0': 300, 'key1': 400}}, + [LeafType(int, UnionTypeContext().dict(True)), LeafType(str, UnionTypeContext().dict(True))], + UnionTypeContext().dict(True), True, + {'key0': {'key0': 100, 'key1': 200}, 'key1': {'key0': 300, 'key1': 400}}), + ({'key0': {'key0': 100, 'key1': 200}, 'key1': {'key0': 'abc', 'key1': 'def'}}, + [LeafType(int, UnionTypeContext().dict(True)), LeafType(str, UnionTypeContext().dict(True))], + UnionTypeContext().dict(True), True, + {'key0': {'key0': 100, 'key1': 200}, 'key1': {'key0': 'abc', 'key1': 'def'}}), + ({'key0': {'key0': 100, 'key1': 'abc'}, 'key1': {'key0': 200, 'key1': 'def'}}, + [LeafType(int, UnionTypeContext().dict(True)), LeafType(str, UnionTypeContext().dict(True))], + UnionTypeContext().dict(True), False, None), + ({'key0': {'key0': 100, 'key1': 'abc'}, 'key1': {'key0': 'abc', 'key1': 'def'}}, + [LeafType(int, UnionTypeContext().dict(True)), LeafType(str, UnionTypeContext().dict(True))], + UnionTypeContext().dict(True), False, None), + ({'key0': {'key0': 100.45, 'key1': 200.45}, 'key1': {'key0': 100, 'key1': 200}}, + [LeafType(int, UnionTypeContext().dict(True)), LeafType(str, UnionTypeContext().dict(True))], + UnionTypeContext().dict(True), False, None), + ({'key0': {'key0': 'abc', 'key1': 'def'}, 'key1': {'key0': 100.45, 'key1': 200.45}}, + [LeafType(int, UnionTypeContext().dict(True)), LeafType(str, UnionTypeContext().dict(True))], + UnionTypeContext().dict(True), False, None), + + # Inner array of dictionary cases + ([{'key0': 'abc', 'key1': 'def'}, {'key0': 'ghi', 'key1': 'jkl'}], + [LeafType(int, UnionTypeContext().dict(True).array(True).array_of_dict(True)), + LeafType(str, UnionTypeContext().dict(True).array(True).array_of_dict(True))], + UnionTypeContext(), True, [{'key0': 'abc', 'key1': 'def'}, {'key0': 'ghi', 'key1': 'jkl'}]), + ([{'key0': 100, 'key1': 200}, {'key0': 300, 'key1': 400}], + [LeafType(int, UnionTypeContext().dict(True).array(True).array_of_dict(True)), + LeafType(str, UnionTypeContext().dict(True).array(True).array_of_dict(True))], + UnionTypeContext(), True, [{'key0': 100, 'key1': 200}, {'key0': 300, 'key1': 400}]), + ([{'key0': 'abc', 'key1': 200}, {'key0': 'def', 'key1': 400}], + [LeafType(int, UnionTypeContext().dict(True).array(True).array_of_dict(True)), + LeafType(str, UnionTypeContext().dict(True).array(True).array_of_dict(True))], + UnionTypeContext(), False, None), + ([{'key0': 'abc', 'key1': 'def'}, {'key0': 100, 'key1': 200}], + [LeafType(int, UnionTypeContext().dict(True).array(True).array_of_dict(True)), + LeafType(str, UnionTypeContext().dict(True).array(True).array_of_dict(True))], + UnionTypeContext(), False, None), + ([{'key0': 'abc', 'key1': 'def'}, {'key0': 100, 'key1': 200}], + [LeafType(int, UnionTypeContext().dict(True).array(True).array_of_dict(True)), + LeafType(str, UnionTypeContext().dict(True).array(True).array_of_dict(True))], + UnionTypeContext(), False, None), + ({'key0': 100, 'key1': 200}, + [LeafType(int, UnionTypeContext().dict(True).array(True).array_of_dict(True)), + LeafType(str, UnionTypeContext().dict(True).array(True).array_of_dict(True))], + UnionTypeContext(), False, None), + + # Outer array of dictionary cases + ([{'key0': 'abc', 'key1': 'def'}, {'key0': 'ghi', 'key1': 'jkl'}], + [LeafType(int, UnionTypeContext()), LeafType(str, UnionTypeContext())], + UnionTypeContext().dict(True).array(True).array_of_dict(True), True, + [{'key0': 'abc', 'key1': 'def'}, {'key0': 'ghi', 'key1': 'jkl'}]), + ([{'key0': 100, 'key1': 200}, {'key0': 300, 'key1': 400}], + [LeafType(int, UnionTypeContext()), LeafType(str, UnionTypeContext())], + UnionTypeContext().dict(True).array(True).array_of_dict(True), True, + [{'key0': 100, 'key1': 200}, {'key0': 300, 'key1': 400}]), + ([{'key0': 'abc', 'key1': 200}, {'key0': 'def', 'key1': 400}], + [LeafType(int, UnionTypeContext()), LeafType(str, UnionTypeContext())], + UnionTypeContext().dict(True).array(True).array_of_dict(True), True, + [{'key0': 'abc', 'key1': 200}, {'key0': 'def', 'key1': 400}]), + ([{'key0': 'abc', 'key1': 'def'}, {'key0': 100, 'key1': 200}], + [LeafType(int, UnionTypeContext()), LeafType(str, UnionTypeContext())], + UnionTypeContext().dict(True).array(True).array_of_dict(True), True, + [{'key0': 'abc', 'key1': 'def'}, {'key0': 100, 'key1': 200}]), + ({'key0': 'abc', 'key1': 'def'}, + [LeafType(int, UnionTypeContext()), LeafType(str, UnionTypeContext())], + UnionTypeContext().dict(True).array(True).array_of_dict(True), False, + None), + + # dictionary of array cases + ({'key0': ['abc', 'def'], 'key1': ['ghi', 'jkl']}, + [LeafType(int, UnionTypeContext().dict(True).array(True)), + LeafType(str, UnionTypeContext().dict(True).array(True))], + UnionTypeContext(), True, {'key0': ['abc', 'def'], 'key1': ['ghi', 'jkl']}), + ({'key0': [100, 200], 'key1': [300, 400]}, + [LeafType(int, UnionTypeContext().dict(True).array(True)), + LeafType(str, UnionTypeContext().dict(True).array(True))], + UnionTypeContext(), True, {'key0': [100, 200], 'key1': [300, 400]}), + ({'key0': ['abc', 200], 'key1': ['def', 400]}, + [LeafType(int, UnionTypeContext().dict(True).array(True)), + LeafType(str, UnionTypeContext().dict(True).array(True))], + UnionTypeContext(), False, None), + ({'key0': [100, 200], 'key1': ['abc', 'def']}, + [LeafType(int, UnionTypeContext().dict(True).array(True)), + LeafType(str, UnionTypeContext().dict(True).array(True))], + UnionTypeContext(), False, None), + + # Outer dictionary of array cases + ({'key0': ['abc', 'def'], 'key1': ['ghi', 'jkl']}, + [LeafType(int, UnionTypeContext()), LeafType(str, UnionTypeContext())], + UnionTypeContext().dict(True).array(True), True, {'key0': ['abc', 'def'], 'key1': ['ghi', 'jkl']}), + ({'key0': [100, 200], 'key1': [300, 400]}, + [LeafType(int, UnionTypeContext()), LeafType(str, UnionTypeContext())], + UnionTypeContext().dict(True).array(True), True, {'key0': [100, 200], 'key1': [300, 400]}), + ({'key0': ['abc', 200], 'key1': ['def', 400]}, + [LeafType(int, UnionTypeContext()), LeafType(str, UnionTypeContext())], + UnionTypeContext().dict(True).array(True), True, {'key0': ['abc', 200], 'key1': ['def', 400]}), + ({'key0': [100, 200], 'key1': ['abc', 'def']}, + [LeafType(int, UnionTypeContext()), LeafType(str, UnionTypeContext())], + UnionTypeContext().dict(True).array(True), True, {'key0': [100, 200], 'key1': ['abc', 'def']}), + ([{'key0': [100, 200]}, {'key1': ['abc', 'def']}], + [LeafType(int, UnionTypeContext()), LeafType(str, UnionTypeContext())], + UnionTypeContext().dict(True).array(True), False, None), + + # Nested oneOf cases + ([[100, 200], ['abc', True]], + [AnyOf([LeafType(str, UnionTypeContext()), LeafType(bool, UnionTypeContext())], + UnionTypeContext().array(True)), LeafType(int, UnionTypeContext().array(True))], + UnionTypeContext().array(True), True, [[100, 200], ['abc', True]]), + ([[100, 200], ['abc', True], None], + [AnyOf([LeafType(str, UnionTypeContext()), LeafType(bool, UnionTypeContext())], + UnionTypeContext().array(True).nullable(True)), LeafType(int, UnionTypeContext().array(True))], + UnionTypeContext().array(True), True, [[100, 200], ['abc', True], None]), + ({'key0': {'key0': 100, 'key1': 200}, 'key2': {'key0': 'abc', 'key1': True}, 'key3': None}, + [AnyOf([LeafType(str, UnionTypeContext()), LeafType(bool, UnionTypeContext())], + UnionTypeContext().dict(True).nullable(True)), LeafType(int, UnionTypeContext().dict(True))], + UnionTypeContext().dict(True), True, + {'key0': {'key0': 100, 'key1': 200}, 'key2': {'key0': 'abc', 'key1': True}, 'key3': None}), + ({'key0': [100, 200], 'key2': ['abc', True], 'key3': None}, + [AnyOf([LeafType(str, UnionTypeContext()), LeafType(bool, UnionTypeContext())], + UnionTypeContext().array(True).nullable(True)), LeafType(int, UnionTypeContext().array(True))], + UnionTypeContext().dict(True), True, + {'key0': [100, 200], 'key2': ['abc', True], 'key3': None}), + ({'key0': [100, 200], 'key2': ['abc', True], 'key3': None}, + [AnyOf([LeafType(str, UnionTypeContext()), LeafType(bool, UnionTypeContext())], + UnionTypeContext().array(True).nullable(True)), LeafType(int, UnionTypeContext().array(True))], + UnionTypeContext().dict(True), True, + {'key0': [100, 200], 'key2': ['abc', True], 'key3': None}), + ([{'key0': 100, 'key1': 200}, {'key0': 'abc', 'key1': True}, None], + [AnyOf([LeafType(str, UnionTypeContext()), LeafType(bool, UnionTypeContext())], + UnionTypeContext().dict(True).nullable(True)), LeafType(int, UnionTypeContext().dict(True))], + UnionTypeContext().array(True).array_of_dict(True), True, + [{'key0': 100, 'key1': 200}, {'key0': 'abc', 'key1': True}, None]), + ([[100, 200], None], + [AnyOf([LeafType(str, UnionTypeContext()), LeafType(bool, UnionTypeContext())], + UnionTypeContext().array(True)), LeafType(int, UnionTypeContext().array(True))], + UnionTypeContext().array(True), False, None), + ]) + def test_any_of_primitive_type(self, input_value, input_types, input_context, expected_validity, + expected_deserialized_value): + try: + union_type_result = AnyOf(input_types, input_context).validate(input_value) + actual_is_valid = union_type_result.is_valid + actual_deserialized_value = union_type_result.deserialize(input_value) + except AnyOfValidationException: + actual_is_valid = False + actual_deserialized_value = None + + assert actual_is_valid == expected_validity + assert actual_deserialized_value == expected_deserialized_value + + @pytest.mark.parametrize( + 'input_value, input_date, input_types, input_context, expected_validity, expected_value', [ + (Base.get_rfc3339_datetime(datetime(1994, 11, 6, 8, 49, 37), False), + Base.get_rfc3339_datetime(datetime(1994, 11, 6, 8, 49, 37)), + [LeafType(datetime, UnionTypeContext().date_time_format(DateTimeFormat.RFC3339_DATE_TIME)), + LeafType(date)], UnionTypeContext(), True, datetime(1994, 11, 6, 8, 49, 37)), + (Base.get_http_datetime(datetime(1994, 11, 6, 8, 49, 37), False), + Base.get_http_datetime(datetime(1994, 11, 6, 8, 49, 37)), + [LeafType(datetime, UnionTypeContext().date_time_format(DateTimeFormat.HTTP_DATE_TIME)), LeafType(date)], + UnionTypeContext(), True, datetime(1994, 11, 6, 8, 49, 37)), + (ApiHelper.UnixDateTime(datetime(1994, 11, 6, 8, 49, 37)), 1480809600, + [LeafType(datetime, UnionTypeContext().date_time_format(DateTimeFormat.UNIX_DATE_TIME)), LeafType(date)], + UnionTypeContext(), True, datetime.utcfromtimestamp(1480809600)), + (datetime(1994, 11, 6, 8, 49, 37), Base.get_rfc3339_datetime(datetime(1994, 11, 6, 8, 49, 37)), + [LeafType(datetime, UnionTypeContext().date_time_converter(ApiHelper.RFC3339DateTime).date_time_format(DateTimeFormat.RFC3339_DATE_TIME)), LeafType(date)], + UnionTypeContext(), True, datetime(1994, 11, 6, 8, 49, 37)), + ('1994-11-06', '1994-11-06', [LeafType(date), LeafType(datetime, UnionTypeContext().date_time_format(DateTimeFormat.RFC3339_DATE_TIME))], UnionTypeContext(), + True, date(1994, 11, 6)) + ]) + def test_any_of_date_and_datetime(self, input_value, input_date, input_types, input_context, expected_validity, expected_value): + union_type_result = AnyOf(input_types, input_context).validate(input_value) + assert union_type_result.is_valid == expected_validity + actual_deserialized_value = union_type_result.deserialize(input_date) + assert actual_deserialized_value == expected_value + + @pytest.mark.parametrize( + 'input_value, input_types, input_context, expected_validity, expected_value', [ + (None, [LeafType(int, UnionTypeContext().optional(True)), LeafType(str)], UnionTypeContext(), True, None), + (None, [LeafType(int, UnionTypeContext().optional(True)), LeafType(str, UnionTypeContext().optional(True))], + UnionTypeContext(), True, None), + (None, [LeafType(int), LeafType(str)], UnionTypeContext().nullable(True), True, None), + (None, [LeafType(int, UnionTypeContext().nullable(True)), LeafType(str)], UnionTypeContext(), True, None), + (None, [LeafType(int, UnionTypeContext().nullable(True)), LeafType(str, UnionTypeContext().optional(True))], + UnionTypeContext(), True, None), + (None, [LeafType(int), LeafType(str)], UnionTypeContext().nullable(True), True, None), + ([1, None, 2], [LeafType(int, UnionTypeContext().nullable(True)), LeafType(str)], + UnionTypeContext().array(True), True, [1, None, 2]), + ({'key0': 1, None: None, 'key3': 2}, [LeafType(int, UnionTypeContext().nullable(True)), LeafType(str)], + UnionTypeContext().dict(True), True, {'key0': 1, None: None, 'key3': 2}) + ]) + def test_any_of_optional_nullable(self, input_value, input_types, input_context, expected_validity, expected_value): + union_type_result = AnyOf(input_types, input_context).validate(input_value) + assert union_type_result.is_valid == expected_validity + actual_deserialized_value = union_type_result.deserialize(input_value) + assert actual_deserialized_value == expected_value + + @pytest.mark.parametrize( + 'input_value, input_types, input_context, expected_is_valid_output, expected_deserialized_value_output', [ + # Simple Cases + ({"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, [LeafType(Atom), LeafType(Orbit)], + UnionTypeContext(), True, Atom(2, 5)), + ({"OrbitNumberOfElectrons": 4}, [LeafType(Atom), LeafType(Orbit)], + UnionTypeContext(), True, Orbit(4)), + + # Outer Array Cases + ([{"OrbitNumberOfElectrons": 4}, {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}], + [LeafType(Atom), LeafType(Orbit)], UnionTypeContext().array(True), True, [Orbit(4), Atom(2, 5)]), + ([{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, {"AtomNumberOfElectrons": 4, "AtomNumberOfProtons": 10}], + [LeafType(Atom), LeafType(Orbit)], UnionTypeContext().array(True), True, [Atom(2, 5), Atom(4, 10)]), + ([{"OrbitNumberOfElectrons": 4}, {"OrbitNumberOfElectrons": 5}], + [LeafType(Atom), LeafType(Orbit)], + UnionTypeContext().array(True), True, [Orbit(4), Orbit(5)]), + ({"OrbitNumberOfElectrons": 4}, + [LeafType(Atom), LeafType(Orbit)], + UnionTypeContext().array(True), False, None), + ({"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, + [LeafType(Atom), LeafType(Orbit)], + UnionTypeContext().array(True), False, None), + + # Inner Array Cases + ([{"OrbitNumberOfElectrons": 4}, {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}], + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext().array(True))], + UnionTypeContext(), False, None), + ([{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, {"AtomNumberOfElectrons": 4, "AtomNumberOfProtons": 10}], + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext().array(True))], + UnionTypeContext(), True, [Atom(2, 5), Atom(4, 10)]), + ([{"OrbitNumberOfElectrons": 4}, {"OrbitNumberOfElectrons": 5}], + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext().array(True))], + UnionTypeContext(), True, [Orbit(4), Orbit(5)]), + ([{"OrbitNumberOfElectrons": 4}, {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}], + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext().array(True))], + UnionTypeContext(), False, None), + ({"OrbitNumberOfElectrons": 4}, + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext().array(True))], + UnionTypeContext(), False, None), + ({"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext().array(True))], + UnionTypeContext(), False, None), + + # Partial Array Case + ({"OrbitNumberOfElectrons": 4}, + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext())], UnionTypeContext(), True, Orbit(4)), + ([{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, {"AtomNumberOfElectrons": 4, "AtomNumberOfProtons": 10}], + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext())], UnionTypeContext(), True, [Atom(2, 5), Atom(4, 10)]), + ('[{"OrbitNumberOfElectrons": 4}, {"AtomNumberOfElectrons": 4, "AtomNumberOfProtons": 10}]', + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext())], + UnionTypeContext(), False, None), + ('{"OrbitNumberOfElectrons": 4}', [LeafType(Orbit, UnionTypeContext().array(True)), LeafType(Atom)], + UnionTypeContext(), False, None), + + # Array of Partial Arrays Cases + ([[{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, + {"AtomNumberOfElectrons": 4, "AtomNumberOfProtons": 10}]], + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit)], + UnionTypeContext().array(True), True, [[Atom(2, 5), Atom(4, 10)]]), + ([[{"OrbitNumberOfElectrons": 4}, {"OrbitNumberOfElectrons": 4}]], + [LeafType(Orbit, UnionTypeContext().array(True)), LeafType(Atom)], + UnionTypeContext().array(True), True, [[Orbit(4), Orbit(4)]]), + ([[{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, + {"AtomNumberOfElectrons": 4, "AtomNumberOfProtons": 10}], {"OrbitNumberOfElectrons": 4}], + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext())], + UnionTypeContext().array(True), True, [[Atom(2, 5), Atom(4, 10)], Orbit(4)]), + ([[{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, {"OrbitNumberOfElectrons": 4}]], + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit)], + UnionTypeContext().array(True), False, None), + ([{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}], + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit)], + UnionTypeContext().array(True), False, None), + + # Array of Arrays Cases + ([[{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, + {"AtomNumberOfElectrons": 3, "AtomNumberOfProtons": 6}], + [{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}, + {"AtomNumberOfElectrons": 3, "AtomNumberOfProtons": 7}]], + [LeafType(Atom, UnionTypeContext().array(True)), + LeafType(Orbit, UnionTypeContext().array(True))], + UnionTypeContext().array(True), True, [[Atom(2, 5), Atom(3, 6)], [Atom(2, 10), Atom(3, 7)]]), + ([[{"OrbitNumberOfElectrons": 4}, {"OrbitNumberOfElectrons": 6}], + [{"OrbitNumberOfElectrons": 8}, {"OrbitNumberOfElectrons": 10}]], + [LeafType(Orbit, UnionTypeContext().array(True)), LeafType(Atom, UnionTypeContext().array(True))], + UnionTypeContext().array(True), True, [[Orbit(4), Orbit(6)], [Orbit(8), Orbit(10)]]), + ([[{"OrbitNumberOfElectrons": 8}, {"OrbitNumberOfElectrons": 10}], + [{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, + {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}]], + [LeafType(Orbit, UnionTypeContext().array(True)), LeafType(Atom, UnionTypeContext().array(True))], + UnionTypeContext().array(True), True, [[Orbit(8), Orbit(10)], [Atom(2, 5), Atom(2, 10)]]), + ([[{"OrbitNumberOfElectrons": 8}, {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}], + [{"OrbitNumberOfElectrons": 10}, {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}]], + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext().array(True))], + UnionTypeContext().array(True), False, None), + ([[{"OrbitNumberOfElectrons": 8}, {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}], + [{"OrbitNumberOfElectrons": 8}, {"OrbitNumberOfElectrons": 10}]], + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext().array(True))], + UnionTypeContext().array(True), False, None), + ([[{"OrbitNumberOfElectrons": 8.5}, {"OrbitNumberOfElectrons": 10.5}], + [{"OrbitNumberOfElectrons": 8}, {"OrbitNumberOfElectrons": 10}]], + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext().array(True))], + UnionTypeContext().array(True), False, None), + ([[{"OrbitNumberOfElectrons": 8}, {"OrbitNumberOfElectrons": 10}, + {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, + {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}]], + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext().array(True))], + UnionTypeContext().array(True), False, None), + + # Outer Dictionary Cases + ({'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, + 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}}, + [LeafType(Atom), LeafType(Orbit)], UnionTypeContext().dict(True), True, + {'key0': Atom(2, 5), 'key1': Atom(2, 10)}), + ({'key0': {"OrbitNumberOfElectrons": 8}, 'key1': {"OrbitNumberOfElectrons": 10}}, + [LeafType(Atom), LeafType(Orbit)], UnionTypeContext().dict(True), True, + {'key0': Orbit(8), 'key1': Orbit(10)}), + ({'key0': {"OrbitNumberOfElectrons": 8}, 'key2': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}}, + [LeafType(Orbit), LeafType(Atom)], UnionTypeContext().dict(True), True, + {'key0': Orbit(8), 'key2': Atom(2, 5)}), + ({"OrbitNumberOfElectrons": 8}, [LeafType(Orbit), LeafType(Atom)], UnionTypeContext().dict(True), False, None), + ({"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, [LeafType(Orbit), LeafType(Atom)], + UnionTypeContext().dict(True), False, None), + ({'key0': {'key0': {"OrbitNumberOfElectrons": 8}, 'key1': {"OrbitNumberOfElectrons": 10}}}, + [LeafType(Atom), LeafType(Orbit)], UnionTypeContext().dict(True), False, None), + + # Inner Dictionary Cases + ({'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, + 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}}, + [LeafType(Atom, UnionTypeContext().dict(True)), LeafType(Orbit, UnionTypeContext().dict(True))], + UnionTypeContext(), True, {'key0': Atom(2, 5), 'key1': Atom(2, 10)}), + ({'key0': {"OrbitNumberOfElectrons": 8}, 'key1': {"OrbitNumberOfElectrons": 10}}, + [LeafType(Atom, UnionTypeContext().dict(True)), LeafType(Orbit, UnionTypeContext().dict(True))], + UnionTypeContext(), True, {'key0': Orbit(8), 'key1': Orbit(10)}), + ({'key0': {"OrbitNumberOfElectrons": 8}, 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}}, + [LeafType(Atom, UnionTypeContext().dict(True)), + LeafType(Orbit, UnionTypeContext().dict(True))], + UnionTypeContext(), False, None), + + # Partial Dictionary Cases + ({"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, [LeafType(Orbit, UnionTypeContext().dict(True)), + LeafType(Atom)], UnionTypeContext(), True, + Atom(2, 5)), + ({'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, + 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}}, + [LeafType(Atom, UnionTypeContext().dict(True)), LeafType(Orbit)], + UnionTypeContext(), True, {'key0': Atom(2, 5), 'key1': Atom(2, 10)}), + ({'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, 'key1': {"OrbitNumberOfElectrons": 8}}, + [LeafType(Atom, UnionTypeContext().dict(True)), LeafType(Orbit)], UnionTypeContext(), False, None), + ({"OrbitNumberOfElectrons": 8}, [LeafType(Orbit, UnionTypeContext().dict(True)), LeafType(Atom)], + UnionTypeContext(), False, None), + + # Dictionary of Partial Dictionary Cases + ({'key0': {"OrbitNumberOfElectrons": 8}, 'key1': {"OrbitNumberOfElectrons": 10}}, + [LeafType(Atom, UnionTypeContext().dict(True)), LeafType(Orbit)], + UnionTypeContext().dict(True), True, {'key0': Orbit(8), 'key1': Orbit(10)}), + ({'key0': {'key0': {"OrbitNumberOfElectrons": 8}, 'key1': {"OrbitNumberOfElectrons": 10}}}, + [LeafType(Orbit, UnionTypeContext().dict(True)), LeafType(Atom)], + UnionTypeContext().dict(True), True, {'key0': {'key0': Orbit(8), 'key1': Orbit(10)}}), + ({'key0': {'key0': {"OrbitNumberOfElectrons": 8}, 'key1': {"OrbitNumberOfElectrons": 10}}, + 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}}, + [LeafType(Orbit, UnionTypeContext().dict(True)), LeafType(Atom)], + UnionTypeContext().dict(True), True, {'key0': {'key0': Orbit(8), 'key1': Orbit(10)}, 'key1': Atom(2, 5)}), + ({'key0': {'key0': {"OrbitNumberOfElectrons": 10}, + 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}}}, + [LeafType(Orbit, UnionTypeContext().dict(True)), LeafType(Atom)], + UnionTypeContext().dict(True), False, None), + ({'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}}, + [LeafType(Atom, UnionTypeContext().dict(True)), LeafType(Orbit)], + UnionTypeContext().dict(True), False, None), + + # Dictionary of Dictionary Cases + ({'key0': {'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, + 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}}, + 'key1': {'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, + 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}}}, + [LeafType(Atom, UnionTypeContext().dict(True)), LeafType(Orbit, UnionTypeContext().dict(True))], + UnionTypeContext().dict(True), True, + {'key0': {'key0': Atom(2, 5), 'key1': Atom(2, 10)}, 'key1': {'key0': Atom(2, 5), 'key1': Atom(2, 10)}}), + ({'key0': {'key0': {"OrbitNumberOfElectrons": 4}, 'key1': {"OrbitNumberOfElectrons": 8}}, + 'key1': {'key0': {"OrbitNumberOfElectrons": 10}, 'key1': {"OrbitNumberOfElectrons": 12}}}, + [LeafType(Orbit, UnionTypeContext().dict(True)), LeafType(Atom, UnionTypeContext().dict(True))], + UnionTypeContext().dict(True), True, + {'key0': {'key0': Orbit(4), 'key1': Orbit(8)}, 'key1': {'key0': Orbit(10), 'key1': Orbit(12)}}), + ({'key0': {'key0': {"OrbitNumberOfElectrons": 10}, 'key1': {"OrbitNumberOfElectrons": 8}}, + 'key1': {'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, + 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}}}, + [LeafType(Atom, UnionTypeContext().dict(True)), LeafType(Orbit, UnionTypeContext().dict(True))], + UnionTypeContext().dict(True), True, + {'key0': {'key0': Orbit(10), 'key1': Orbit(8)}, 'key1': {'key0': Atom(2, 5), 'key1': Atom(2, 10)}}), + ({'key0': {'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}, + 'key1': {"OrbitNumberOfElectrons": 10}}, + 'key1': {'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, + 'key1': {"OrbitNumberOfElectrons": 12}}}, + [LeafType(Atom, UnionTypeContext().dict(True)), LeafType(Orbit, UnionTypeContext().dict(True))], + UnionTypeContext().dict(True), False, None), + ({'key0': {'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}, + 'key1': {"OrbitNumberOfElectrons": 10}}, + 'key1': {'key0': {"OrbitNumberOfElectrons": 12}, 'key1': {"OrbitNumberOfElectrons": 14}}}, + [LeafType(Atom, UnionTypeContext().dict(True)), LeafType(Orbit, UnionTypeContext().dict(True))], + UnionTypeContext().dict(True), False, None), + + # Inner array of dictionary cases + ([{'key0': {"OrbitNumberOfElectrons": 10}, 'key1': {"OrbitNumberOfElectrons": 12}}, + {'key0': {"OrbitNumberOfElectrons": 14}, 'key1': {"OrbitNumberOfElectrons": 8}}], + [LeafType(Orbit, UnionTypeContext().dict(True).array(True).array_of_dict(True)), + LeafType(Atom, UnionTypeContext().dict(True).array(True).array_of_dict(True))], + UnionTypeContext(), True, + [{'key0': Orbit(10), 'key1': Orbit(12)}, {'key0': Orbit(14), 'key1': Orbit(8)}]), + ([{'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, + 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}}, + {'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 15}, + 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 20}}], + [LeafType(Atom, UnionTypeContext().dict(True).array(True).array_of_dict(True)), + LeafType(Orbit, UnionTypeContext().dict(True).array(True).array_of_dict(True))], + UnionTypeContext(), True, + [{'key0': Atom(2, 5), 'key1': Atom(2, 10)}, {'key0': Atom(2, 15), 'key1': Atom(2, 20)}]), + ([{'key0': {"OrbitNumberOfElectrons": 12}, 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}}, + {'key0': {"OrbitNumberOfElectrons": 10}, + 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 15}}], + [LeafType(Orbit, UnionTypeContext().dict(True).array(True).array_of_dict(True)), + LeafType(Atom, UnionTypeContext().dict(True).array(True).array_of_dict(True))], + UnionTypeContext(), False, None), + ([{'key0': {"OrbitNumberOfElectrons": 12}, 'key1': {"OrbitNumberOfElectrons": 10}}, + {'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}, + 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 20}}], + [LeafType(Orbit, UnionTypeContext().dict(True).array(True).array_of_dict(True)), + LeafType(Atom, UnionTypeContext().dict(True).array(True).array_of_dict(True))], + UnionTypeContext(), False, None), + + # Outer array of dictionary cases + ([{'key0': {"OrbitNumberOfElectrons": 10}, 'key1': {"OrbitNumberOfElectrons": 12}}, + {'key0': {"OrbitNumberOfElectrons": 14}, 'key1': {"OrbitNumberOfElectrons": 16}}], + [LeafType(Orbit, UnionTypeContext()), LeafType(Atom, UnionTypeContext())], + UnionTypeContext().dict(True).array(True).array_of_dict(True), True, + [{'key0': Orbit(10), 'key1': Orbit(12)}, {'key0': Orbit(14), 'key1': Orbit(16)}]), + ([{'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}, + 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 15}}, + {'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 20}, + 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}}], + [LeafType(Orbit, UnionTypeContext()), LeafType(Atom, UnionTypeContext())], + UnionTypeContext().dict(True).array(True).array_of_dict(True), True, + [{'key0': Atom(2, 10), 'key1': Atom(2, 15)}, {'key0': Atom(2, 20), 'key1': Atom(2, 5)}]), + ([{'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}, 'key1': {"OrbitNumberOfElectrons": 10}}, + {'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, + 'key1': {"OrbitNumberOfElectrons": 12}}], + [LeafType(Atom, UnionTypeContext()), LeafType(Orbit, UnionTypeContext())], + UnionTypeContext().dict(True).array(True).array_of_dict(True), True, + [{'key0': Atom(2, 10), 'key1': Orbit(10)}, {'key0': Atom(2, 5), 'key1': Orbit(12)}]), + ([{'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}, + 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 12}}, + {'key0': {"OrbitNumberOfElectrons": 10}, 'key1': {"OrbitNumberOfElectrons": 12}}], + [LeafType(Atom, UnionTypeContext()), LeafType(Orbit, UnionTypeContext())], + UnionTypeContext().dict(True).array(True).array_of_dict(True), True, + [{'key0': Atom(2, 10), 'key1': Atom(2, 12)}, {'key0': Orbit(10), 'key1': Orbit(12)}]), + + # dictionary of array cases + ({'key0': [{"OrbitNumberOfElectrons": 10}, {"OrbitNumberOfElectrons": 12}], + 'key1': [{"OrbitNumberOfElectrons": 14}, {"OrbitNumberOfElectrons": 16}]}, + [LeafType(Orbit, UnionTypeContext().dict(True).array(True)), + LeafType(Atom, UnionTypeContext().dict(True).array(True))], + UnionTypeContext(), True, {'key0': [Orbit(10), Orbit(12)], 'key1': [Orbit(14), Orbit(16)]}), + ({'key0': [{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}, + {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 12}], + 'key1': [{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 14}, + {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 16}]}, + [LeafType(Atom, UnionTypeContext().dict(True).array(True)), + LeafType(Orbit, UnionTypeContext().dict(True).array(True))], + UnionTypeContext(), True, {'key0': [Atom(2, 10), Atom(2, 12)], 'key1': [Atom(2, 14), Atom(2, 16)]}), + ({'key0': [{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}, {"OrbitNumberOfElectrons": 10}], + 'key1': [{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 12}, {"OrbitNumberOfElectrons": 12}]}, + [LeafType(Atom, UnionTypeContext().dict(True).array(True)), + LeafType(Orbit, UnionTypeContext().dict(True).array(True))], + UnionTypeContext(), False, None), + ({'key0': [{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}, + {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 12}], + 'key1': [{"OrbitNumberOfElectrons": 10}, {"OrbitNumberOfElectrons": 12}]}, + [LeafType(Atom, UnionTypeContext().dict(True).array(True)), + LeafType(Orbit, UnionTypeContext().dict(True).array(True))], + UnionTypeContext(), False, None), + + # Outer dictionary of array cases + ({'key0': [{"OrbitNumberOfElectrons": 10}, {"OrbitNumberOfElectrons": 12}], + 'key1': [{"OrbitNumberOfElectrons": 14}, {"OrbitNumberOfElectrons": 16}]}, + [LeafType(Orbit, UnionTypeContext()), LeafType(Atom, UnionTypeContext())], + UnionTypeContext().dict(True).array(True), True, + {'key0': [Orbit(10), Orbit(12)], 'key1': [Orbit(14), Orbit(16)]}), + ({'key0': [{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}, + {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 12}], + 'key1': [{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}, + {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 12}]}, + [LeafType(Orbit, UnionTypeContext()), LeafType(Atom, UnionTypeContext())], + UnionTypeContext().dict(True).array(True), True, + {'key0': [Atom(2, 10), Atom(2, 12)], 'key1': [Atom(2, 10), Atom(2, 12)]}), + ({'key0': [{"OrbitNumberOfElectrons": 10}, {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}], + 'key1': [{"OrbitNumberOfElectrons": 12}, {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 12}]}, + [LeafType(Atom, UnionTypeContext()), LeafType(Orbit, UnionTypeContext())], + UnionTypeContext().dict(True).array(True), True, + {'key0': [Orbit(10), Atom(2, 10)], 'key1': [Orbit(12), Atom(2, 12)]}), + ]) + def test_any_of_custom_type(self, input_value, input_types, input_context, expected_is_valid_output, + expected_deserialized_value_output): + try: + union_type_result = AnyOf(input_types, input_context).validate(input_value) + actual_is_valid = union_type_result.is_valid + actual_deserialized_value = union_type_result.deserialize(input_value) + except AnyOfValidationException: + actual_is_valid = False + actual_deserialized_value = None + + assert actual_is_valid == expected_is_valid_output + assert actual_deserialized_value == expected_deserialized_value_output + + @pytest.mark.parametrize('input_value, input_types, input_context, expected_output', [ + # Simple Cases + ('{"id": 123, "weight": 5, "type": "lion", "kind": "hunter"}', + [LeafType(Lion, UnionTypeContext().discriminator('type').discriminator_value('lion')), + LeafType(Deer, UnionTypeContext().discriminator('type').discriminator_value('deer'))], + UnionTypeContext(), True), + ('{"name": "sam", "weight": 5, "type": "deer", "kind": "hunter"}', + [LeafType(Lion, UnionTypeContext().discriminator('type').discriminator_value('lion')), + LeafType(Deer, UnionTypeContext().discriminator('type').discriminator_value('deer'))], + UnionTypeContext(), True), + ('{"id": 123, "weight": 5, "type": "lion123", "kind": "hunter"}', + [LeafType(Lion, UnionTypeContext().discriminator('type').discriminator_value('lion')), + LeafType(Deer, UnionTypeContext().discriminator('type').discriminator_value('deer'))], + UnionTypeContext(), True), + ('{"name": "sam", "weight": 5, "type": "deer123", "kind": "hunted"}', + [LeafType(Lion, UnionTypeContext().discriminator('type').discriminator_value('lion')), + LeafType(Deer, UnionTypeContext().discriminator('type').discriminator_value('deer'))], + UnionTypeContext(), True), + ('{"id": 123, "weight": 5, "type": "lion", "kind": "hunter123"}', + [LeafType(Lion, UnionTypeContext().discriminator('type').discriminator_value('lion')), + LeafType(Deer, UnionTypeContext().discriminator('type').discriminator_value('deer'))], + UnionTypeContext(), True), + ('{"name": "sam", "weight": 5, "type": "deer", "kind": "hunted123"}', + [LeafType(Lion, UnionTypeContext().discriminator('type').discriminator_value('lion')), + LeafType(Deer, UnionTypeContext().discriminator('type').discriminator_value('deer'))], + UnionTypeContext(), True), + ('{"id": 123, "weight": 5, "type": "lion", "kind": "hunter"}', + [LeafType(Deer, UnionTypeContext().discriminator('type').discriminator_value('lion')), + LeafType(Rabbit, UnionTypeContext().discriminator('type').discriminator_value('lion'))], + UnionTypeContext(), False), + ('{"name": "sam", "weight": 5, "type": "deer", "kind": "hunted"}', + [LeafType(Lion, UnionTypeContext().discriminator('type').discriminator_value('deer')), + LeafType(Rabbit, UnionTypeContext().discriminator('type').discriminator_value('deer'))], + UnionTypeContext(), False), + + ('{"id": 123, "weight": 5, "type": "lion", "kind": "hunter"}', + [LeafType(dict, UnionTypeContext().discriminator('type').discriminator_value('lion')), + LeafType(dict, UnionTypeContext().discriminator('type').discriminator_value('deer'))], + UnionTypeContext(), True), + ('{"name": "sam", "weight": 5, "type": "deer", "kind": "hunter"}', + [LeafType(dict, UnionTypeContext().discriminator('type').discriminator_value('lion')), + LeafType(dict, UnionTypeContext().discriminator('type').discriminator_value('deer'))], + UnionTypeContext(), True), + ('{"name": "sam", "weight": 5, "type": "deer", "kind": "hunter"}', + [LeafType(dict, UnionTypeContext().discriminator('type').discriminator_value('deer')), + LeafType(dict, UnionTypeContext().discriminator('type').discriminator_value('deer'))], + UnionTypeContext(), True), + ('{"name": "sam", "weight": 5, "type": "deer", "kind": "hunter"}', + [LeafType(dict, UnionTypeContext().discriminator('type').discriminator_value('lion')), + LeafType(dict, UnionTypeContext().discriminator('type').discriminator_value('lion'))], + UnionTypeContext(), True), + ('{"name": "sam", "weight": 5, "type": "deer", "kind": "hunter"}', + [LeafType(dict), LeafType(dict)], UnionTypeContext(), True), + ]) + def test_any_of_with_discriminator_custom_type(self, input_value, input_types, input_context, expected_output): + try: + deserialized_dict_input = ApiHelper.json_deserialize(input_value, as_dict=True) + union_type_result = AnyOf(input_types, input_context).validate(deserialized_dict_input) + actual_is_valid = union_type_result.is_valid + except AnyOfValidationException: + actual_is_valid = False + + assert actual_is_valid == expected_output + + @pytest.mark.parametrize('input_value, input_types, input_context, expected_is_valid_output, ' + 'expected_deserialized_value_output', [ + # Simple Cases + ('Monday', [LeafType(Days, UnionTypeContext()), LeafType(Months, UnionTypeContext())], + UnionTypeContext(), True, 'Monday'), + (1, [LeafType(Days, UnionTypeContext()), LeafType(Months, UnionTypeContext())], + UnionTypeContext(), True, 1), + (0, [LeafType(Days, UnionTypeContext()), LeafType(Months, UnionTypeContext())], + UnionTypeContext(), False, None), + ('Monday_', [LeafType(Days, UnionTypeContext()), LeafType(Months, UnionTypeContext())], + UnionTypeContext(), False, None), + + # Outer Array + (['Monday', 'Tuesday'], [LeafType(Days), LeafType(Months)], UnionTypeContext().array(True), True, + ['Monday', 'Tuesday']), + ([1, 2], [LeafType(Days), LeafType(Months)], UnionTypeContext().array(True), True, + [1, 2]), + ([1, 'Monday'], [LeafType(Days), LeafType(Months)], UnionTypeContext().array(True), True, [1, 'Monday']), + (2, [LeafType(Days), LeafType(Months)], UnionTypeContext().array(True), False, None), + ('Monday', [LeafType(Days), LeafType(Months)], UnionTypeContext().array(True), False, None), + ([['January', 'February']], [LeafType(int), LeafType(str)], UnionTypeContext().array(True), False, None), + + # Inner Array Cases + (['Monday', 'Tuesday'], [LeafType(Days, UnionTypeContext().array(True)), + LeafType(Months, UnionTypeContext().array(True))], + UnionTypeContext(), True, ['Monday', 'Tuesday']), + ([1, 2], [LeafType(Days, UnionTypeContext().array(True)), LeafType(Months, UnionTypeContext().array(True))], + UnionTypeContext(), True, [1, 2]), + ([1, 'Monday'], + [LeafType(Months, UnionTypeContext().array(True)), LeafType(Days, UnionTypeContext().array(True))], + UnionTypeContext(), False, None), + + # Partial Array Case + ('Monday', [LeafType(Months, UnionTypeContext().array(True)), LeafType(Days)], + UnionTypeContext(), True, 'Monday'), + ([1, 2], [LeafType(Months, UnionTypeContext().array(True)), LeafType(Days)], + UnionTypeContext(), True, [1, 2]), + ([1, 'Monday'], [LeafType(Months, UnionTypeContext().array(True)), LeafType(Days)], + UnionTypeContext(), False, None), + (1, [LeafType(Months, UnionTypeContext().array(True)), LeafType(Days)], + UnionTypeContext(), False, None), + + # Array of Partial Arrays Cases + (['Monday', 'Tuesday'], [LeafType(Months, UnionTypeContext().array(True)), LeafType(Days)], + UnionTypeContext().array(True), True, ['Monday', 'Tuesday']), + ([[1, 2]], [LeafType(Months, UnionTypeContext().array(True)), LeafType(Days)], + UnionTypeContext().array(True), True, [[1, 2]]), + ([[1, 2], 'Monday'], [LeafType(Months, UnionTypeContext().array(True)), LeafType(Days)], + UnionTypeContext().array(True), True, [[1, 2], 'Monday']), + ([[1, 'Monday']], [LeafType(Months, UnionTypeContext().array(True)), LeafType(Days)], + UnionTypeContext().array(True), False, None), + ([1], [LeafType(Months, UnionTypeContext().array(True)), LeafType(Days)], + UnionTypeContext().array(True), False, None), + + # Array of Arrays Cases + ([['Monday', 'Tuesday'], ['Wednesday', 'Thursday']], [LeafType(Days, UnionTypeContext().array(True)), + LeafType(Months, UnionTypeContext().array(True))], + UnionTypeContext().array(True), True, [['Monday', 'Tuesday'], ['Wednesday', 'Thursday']]), + ([[1, 2], [3, 4]], [LeafType(Months, UnionTypeContext().array(True)), + LeafType(Days, UnionTypeContext().array(True))], + UnionTypeContext().array(True), True, [[1, 2], [3, 4]]), + ([[1, 2], ['Monday', 'Tuesday']], [LeafType(Months, UnionTypeContext().array(True)), + LeafType(Days, UnionTypeContext().array(True))], + UnionTypeContext().array(True), True, [[1, 2], ['Monday', 'Tuesday']]), + ]) + def test_any_of_enum_type(self, input_value, input_types, input_context, expected_is_valid_output, expected_deserialized_value_output): + try: + union_type_result = AnyOf(input_types, input_context).validate(input_value) + actual_is_valid = union_type_result.is_valid + actual_deserialized_value = union_type_result.deserialize(input_value) + except AnyOfValidationException: + actual_is_valid = False + actual_deserialized_value = None + + assert actual_is_valid == expected_is_valid_output + assert actual_deserialized_value == expected_deserialized_value_output + + @pytest.mark.parametrize('input_value, input_types, input_context, expected_validation_message', [ + # Simple Cases + (100.5, [LeafType(int), LeafType(bool), LeafType(str)], UnionTypeContext(), + '{} \nActual Value: 100.5\nExpected Type: Any Of int, bool, str.'.format( + UnionTypeHelper.NONE_MATCHED_ERROR_MESSAGE)), + (100.5, [LeafType(int), AnyOf([LeafType(bool), LeafType(str)])], UnionTypeContext(), + '{} \nActual Value: 100.5\nExpected Type: Any Of int, bool, str.'.format( + UnionTypeHelper.NONE_MATCHED_ERROR_MESSAGE)), + ([[100, 200], None], [AnyOf([LeafType(str, UnionTypeContext()), LeafType(bool, UnionTypeContext())], + UnionTypeContext().array(True)), LeafType(int, UnionTypeContext().array(True))], + UnionTypeContext().array(True), + '{} \nActual Value: [[100, 200], None]\nExpected Type: Any Of str, bool, int.'.format( + UnionTypeHelper.NONE_MATCHED_ERROR_MESSAGE)), + ]) + def test_one_of_validation_errors(self, input_value, input_types, input_context, expected_validation_message): + with pytest.raises(AnyOfValidationException) as validation_error: + AnyOf(input_types, input_context).validate(input_value) + assert validation_error.value.message == expected_validation_message diff --git a/tests/apimatic_core/union_type_tests/test_one_of.py b/tests/apimatic_core/union_type_tests/test_one_of.py new file mode 100644 index 0000000..23af19a --- /dev/null +++ b/tests/apimatic_core/union_type_tests/test_one_of.py @@ -0,0 +1,824 @@ +from datetime import datetime, date +import pytest +from apimatic_core.exceptions.oneof_validation_exception import OneOfValidationException +from apimatic_core.types.datetime_format import DateTimeFormat +from apimatic_core.types.union_types.leaf_type import LeafType +from apimatic_core.types.union_types.one_of import OneOf +from apimatic_core.types.union_types.union_type_context import UnionTypeContext +from apimatic_core.utilities.api_helper import ApiHelper +from apimatic_core.utilities.union_type_helper import UnionTypeHelper +from tests.apimatic_core.base import Base +from tests.apimatic_core.mocks.models.atom import Atom +from tests.apimatic_core.mocks.models.days import Days +from tests.apimatic_core.mocks.models.deer import Deer +from tests.apimatic_core.mocks.models.lion import Lion +from tests.apimatic_core.mocks.models.months import Months +from tests.apimatic_core.mocks.models.orbit import Orbit +from tests.apimatic_core.mocks.models.rabbit import Rabbit + + +class TestOneOf: + + @pytest.mark.parametrize( + 'input_value, input_types, input_context, expected_is_valid_output, expected_deserialized_value_output', [ + # Simple Cases + (100, [LeafType(int), LeafType(str)], UnionTypeContext(), True, 100), + (100, [LeafType(int), LeafType(int), LeafType(str)], UnionTypeContext(), False, None), + ('abc', [LeafType(int), LeafType(str)], UnionTypeContext(), True, 'abc'), + (True, [LeafType(bool), LeafType(str)], UnionTypeContext(), True, True), + (100.44, [LeafType(int), LeafType(str)], UnionTypeContext(), False, None), + (None, [LeafType(int), LeafType(str)], UnionTypeContext().nullable(True), True, None), + (None, [LeafType(int), LeafType(str)], UnionTypeContext().optional(True), True, None), + (None, [LeafType(int), LeafType(str)], UnionTypeContext(), False, None), + + # Outer Array Cases + (['abc', 'def'], [LeafType(int), LeafType(str)], UnionTypeContext().array(True), True, ['abc', 'def']), + ([100, 200], [LeafType(int), LeafType(str)], UnionTypeContext().array(True), True, [100, 200]), + ([100, 'abc'], [LeafType(int), LeafType(str)], UnionTypeContext().array(True), True, [100, 'abc']), + (100, [LeafType(int), LeafType(str)], UnionTypeContext().array(True), False, None), + ('100', [LeafType(int), LeafType(str)], UnionTypeContext().array(True), False, None), + ([['abc', 'def']], [LeafType(int), LeafType(str)], UnionTypeContext().array(True), False, None), + + # Inner Array Cases + (['abc', 'def'], [LeafType(int, UnionTypeContext().array(True)), + LeafType(str, UnionTypeContext().array(True))], + UnionTypeContext(), True, ['abc', 'def']), + ([100, 200], [LeafType(int, UnionTypeContext().array(True)), LeafType(str, UnionTypeContext().array(True))], + UnionTypeContext(), True, [100, 200]), + ([100, 'abc'], + [LeafType(int, UnionTypeContext().array(True)), LeafType(str, UnionTypeContext().array(True))], + UnionTypeContext(), False, None), + + # Partial Array Case + ('abc', [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], + UnionTypeContext(), True, 'abc'), + ([100, 200], [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], + UnionTypeContext(), True, [100, 200]), + ([100, 'abc'], [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], + UnionTypeContext(), False, None), + (100, [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], + UnionTypeContext(), False, None), + + # Array of Partial Arrays Cases + (['abc', 'def'], [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], + UnionTypeContext().array(True), True, ['abc', 'def']), + ([[100, 200]], [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], + UnionTypeContext().array(True), True, [[100, 200]]), + ([[100, 200], 'abc'], [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], + UnionTypeContext().array(True), True, [[100, 200], 'abc']), + ([[100, 'abc']], [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], + UnionTypeContext().array(True), False, None), + ([100], [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], + UnionTypeContext().array(True), False, None), + + # Array of Arrays Cases + ([['abc', 'def'], ['def', 'ghi']], [LeafType(int, UnionTypeContext().array(True)), + LeafType(str, UnionTypeContext().array(True))], + UnionTypeContext().array(True), True, [['abc', 'def'], ['def', 'ghi']]), + ([[100, 200], [300, 400]], [LeafType(int, UnionTypeContext().array(True)), + LeafType(str, UnionTypeContext().array(True))], + UnionTypeContext().array(True), True, [[100, 200], [300, 400]]), + ([[100, 200], ['abc', 'def']], [LeafType(int, UnionTypeContext().array(True)), + LeafType(str, UnionTypeContext().array(True))], + UnionTypeContext().array(True), True, [[100, 200], ['abc', 'def']]), + ([[100, 'abc'], [200, 'def']], [LeafType(int, UnionTypeContext().array(True)), + LeafType(str, UnionTypeContext().array(True))], + UnionTypeContext().array(True), False, None), + ([[100, 'abc'], ['def', 'ghi']], [LeafType(int, UnionTypeContext().array(True)), + LeafType(str, UnionTypeContext().array(True))], + UnionTypeContext().array(True), False, None), + ([[100.45, 200.45], [100, 200]], [LeafType(int, UnionTypeContext().array(True)), + LeafType(str, UnionTypeContext().array(True))], + UnionTypeContext().array(True), False, None), + ([['abc', 'def'], [100.45, 200.45]], [LeafType(int, UnionTypeContext().array(True)), + LeafType(str, UnionTypeContext().array(True))], + UnionTypeContext().array(True), False, None), + + # Outer Dictionary Cases + ({'key0': 'abc', 'key1': 'def'}, [LeafType(int), LeafType(str)], UnionTypeContext().dict(True), True, + {'key0': 'abc', 'key1': 'def'}), + ({'key0': 100, 'key1': 200}, [LeafType(int), LeafType(str)], UnionTypeContext().dict(True), True, + {'key0': 100, 'key1': 200}), + ({'key0': 100, 'key2': 'abc'}, [LeafType(int), LeafType(str)], UnionTypeContext().dict(True), True, + {'key0': 100, 'key2': 'abc'}), + (100, [LeafType(int), LeafType(str)], UnionTypeContext().dict(True), False, None), + ('100', [LeafType(int), LeafType(str)], UnionTypeContext().dict(True), False, None), + ({'key0': {'key0': 'abc', 'key1': 'def'}}, [LeafType(int), LeafType(str)], + UnionTypeContext().dict(True), False, None), + + # Inner Dictionary Cases + ({'key0': 'abc', 'key1': 'def'}, [LeafType(int, UnionTypeContext().dict(True)), + LeafType(str, UnionTypeContext().dict(True))], + UnionTypeContext(), True, {'key0': 'abc', 'key1': 'def'}), + ({'key0': 100, 'key1': 200}, [LeafType(int, UnionTypeContext().dict(True)), + LeafType(str, UnionTypeContext().dict(True))], + UnionTypeContext(), True, {'key0': 100, 'key1': 200}), + ({'key0': 100, 'key1': 'abc'}, [LeafType(int, UnionTypeContext().dict(True)), + LeafType(str, UnionTypeContext().array(True))], + UnionTypeContext(), False, None), + + # Partial Dictionary Cases + ('abc', [LeafType(int, UnionTypeContext().dict(True)), LeafType(str)], + UnionTypeContext(), True, 'abc'), + ({'key0': 100, 'key1': 200}, [LeafType(int, UnionTypeContext().dict(True)), LeafType(str)], + UnionTypeContext(), True, {'key0': 100, 'key1': 200}), + ({'key0': 100, 'key1': 'abc'}, [LeafType(int, UnionTypeContext().dict(True)), LeafType(str)], + UnionTypeContext(), False, None), + (100, [LeafType(int, UnionTypeContext().dict(True)), LeafType(str)], + UnionTypeContext(), False, None), + + # Dictionary of Partial Dictionary Cases + ({'key0': 'abc', 'key1': 'def'}, [LeafType(int, UnionTypeContext().dict(True)), LeafType(str)], + UnionTypeContext().dict(True), True, {'key0': 'abc', 'key1': 'def'}), + ({'key0': {'key0': 100, 'key1': 200}}, [LeafType(int, UnionTypeContext().dict(True)), LeafType(str)], + UnionTypeContext().dict(True), True, {'key0': {'key0': 100, 'key1': 200}}), + ({'key0': {'key0': 100, 'key1': 200}, 'key1': 'abc'}, + [LeafType(int, UnionTypeContext().dict(True)), LeafType(str)], + UnionTypeContext().dict(True), True, {'key0': {'key0': 100, 'key1': 200}, 'key1': 'abc'}), + ({'key0': {'key0': 100, 'key1': 'abc'}}, [LeafType(int, UnionTypeContext().dict(True)), LeafType(str)], + UnionTypeContext().dict(True), False, None), + ({'key0': 100}, [LeafType(int, UnionTypeContext().dict(True)), LeafType(str)], + UnionTypeContext().dict(True), False, None), + + # Dictionary of Dictionary Cases + ({'key0': {'key0': 'abc', 'key1': 'def'}, 'key1': {'key0': 'ghi', 'key1': 'jkl'}}, + [LeafType(int, UnionTypeContext().array(True)), LeafType(str, UnionTypeContext().dict(True))], + UnionTypeContext().dict(True), True, + {'key0': {'key0': 'abc', 'key1': 'def'}, 'key1': {'key0': 'ghi', 'key1': 'jkl'}}), + ({'key0': {'key0': 100, 'key1': 200}, 'key1': {'key0': 300, 'key1': 400}}, + [LeafType(int, UnionTypeContext().dict(True)), LeafType(str, UnionTypeContext().dict(True))], + UnionTypeContext().dict(True), True, + {'key0': {'key0': 100, 'key1': 200}, 'key1': {'key0': 300, 'key1': 400}}), + ({'key0': {'key0': 100, 'key1': 200}, 'key1': {'key0': 'abc', 'key1': 'def'}}, + [LeafType(int, UnionTypeContext().dict(True)), LeafType(str, UnionTypeContext().dict(True))], + UnionTypeContext().dict(True), True, + {'key0': {'key0': 100, 'key1': 200}, 'key1': {'key0': 'abc', 'key1': 'def'}}), + ({'key0': {'key0': 100, 'key1': 'abc'}, 'key1': {'key0': 200, 'key1': 'def'}}, + [LeafType(int, UnionTypeContext().dict(True)), LeafType(str, UnionTypeContext().dict(True))], + UnionTypeContext().dict(True), False, None), + ({'key0': {'key0': 100, 'key1': 'abc'}, 'key1': {'key0': 'abc', 'key1': 'def'}}, + [LeafType(int, UnionTypeContext().dict(True)), LeafType(str, UnionTypeContext().dict(True))], + UnionTypeContext().dict(True), False, None), + ({'key0': {'key0': 100.45, 'key1': 200.45}, 'key1': {'key0': 100, 'key1': 200}}, + [LeafType(int, UnionTypeContext().dict(True)), LeafType(str, UnionTypeContext().dict(True))], + UnionTypeContext().dict(True), False, None), + ({'key0': {'key0': 'abc', 'key1': 'def'}, 'key1': {'key0': 100.45, 'key1': 200.45}}, + [LeafType(int, UnionTypeContext().dict(True)), LeafType(str, UnionTypeContext().dict(True))], + UnionTypeContext().dict(True), False, None), + + # Inner array of dictionary cases + ([{'key0': 'abc', 'key1': 'def'}, {'key0': 'ghi', 'key1': 'jkl'}], + [LeafType(int, UnionTypeContext().dict(True).array(True).array_of_dict(True)), + LeafType(str, UnionTypeContext().dict(True).array(True).array_of_dict(True))], + UnionTypeContext(), True, [{'key0': 'abc', 'key1': 'def'}, {'key0': 'ghi', 'key1': 'jkl'}]), + ([{'key0': 100, 'key1': 200}, {'key0': 300, 'key1': 400}], + [LeafType(int, UnionTypeContext().dict(True).array(True).array_of_dict(True)), + LeafType(str, UnionTypeContext().dict(True).array(True).array_of_dict(True))], + UnionTypeContext(), True, [{'key0': 100, 'key1': 200}, {'key0': 300, 'key1': 400}]), + ([{'key0': 'abc', 'key1': 200}, {'key0': 'def', 'key1': 400}], + [LeafType(int, UnionTypeContext().dict(True).array(True).array_of_dict(True)), + LeafType(str, UnionTypeContext().dict(True).array(True).array_of_dict(True))], + UnionTypeContext(), False, None), + ([{'key0': 'abc', 'key1': 'def'}, {'key0': 100, 'key1': 200}], + [LeafType(int, UnionTypeContext().dict(True).array(True).array_of_dict(True)), + LeafType(str, UnionTypeContext().dict(True).array(True).array_of_dict(True))], + UnionTypeContext(), False, None), + ([{'key0': 'abc', 'key1': 'def'}, {'key0': 100, 'key1': 200}], + [LeafType(int, UnionTypeContext().dict(True).array(True).array_of_dict(True)), + LeafType(str, UnionTypeContext().dict(True).array(True).array_of_dict(True))], + UnionTypeContext(), False, None), + ({'key0': 100, 'key1': 200}, + [LeafType(int, UnionTypeContext().dict(True).array(True).array_of_dict(True)), + LeafType(str, UnionTypeContext().dict(True).array(True).array_of_dict(True))], + UnionTypeContext(), False, None), + + # Outer array of dictionary cases + ([{'key0': 'abc', 'key1': 'def'}, {'key0': 'ghi', 'key1': 'jkl'}], + [LeafType(int, UnionTypeContext()), LeafType(str, UnionTypeContext())], + UnionTypeContext().dict(True).array(True).array_of_dict(True), True, + [{'key0': 'abc', 'key1': 'def'}, {'key0': 'ghi', 'key1': 'jkl'}]), + ([{'key0': 100, 'key1': 200}, {'key0': 300, 'key1': 400}], + [LeafType(int, UnionTypeContext()), LeafType(str, UnionTypeContext())], + UnionTypeContext().dict(True).array(True).array_of_dict(True), True, + [{'key0': 100, 'key1': 200}, {'key0': 300, 'key1': 400}]), + ([{'key0': 'abc', 'key1': 200}, {'key0': 'def', 'key1': 400}], + [LeafType(int, UnionTypeContext()), LeafType(str, UnionTypeContext())], + UnionTypeContext().dict(True).array(True).array_of_dict(True), True, + [{'key0': 'abc', 'key1': 200}, {'key0': 'def', 'key1': 400}]), + ([{'key0': 'abc', 'key1': 'def'}, {'key0': 100, 'key1': 200}], + [LeafType(int, UnionTypeContext()), LeafType(str, UnionTypeContext())], + UnionTypeContext().dict(True).array(True).array_of_dict(True), True, + [{'key0': 'abc', 'key1': 'def'}, {'key0': 100, 'key1': 200}]), + ({'key0': 'abc', 'key1': 'def'}, + [LeafType(int, UnionTypeContext()), LeafType(str, UnionTypeContext())], + UnionTypeContext().dict(True).array(True).array_of_dict(True), False, + None), + + # dictionary of array cases + ({'key0': ['abc', 'def'], 'key1': ['ghi', 'jkl']}, + [LeafType(int, UnionTypeContext().dict(True).array(True)), + LeafType(str, UnionTypeContext().dict(True).array(True))], + UnionTypeContext(), True, {'key0': ['abc', 'def'], 'key1': ['ghi', 'jkl']}), + ({'key0': [100, 200], 'key1': [300, 400]}, + [LeafType(int, UnionTypeContext().dict(True).array(True)), + LeafType(str, UnionTypeContext().dict(True).array(True))], + UnionTypeContext(), True, {'key0': [100, 200], 'key1': [300, 400]}), + ({'key0': ['abc', 200], 'key1': ['def', 400]}, + [LeafType(int, UnionTypeContext().dict(True).array(True)), + LeafType(str, UnionTypeContext().dict(True).array(True))], + UnionTypeContext(), False, None), + ({'key0': [100, 200], 'key1': ['abc', 'def']}, + [LeafType(int, UnionTypeContext().dict(True).array(True)), + LeafType(str, UnionTypeContext().dict(True).array(True))], + UnionTypeContext(), False, None), + ([{'key0': [100, 200]}, {'key1': ['abc', 'def']}], + [LeafType(int, UnionTypeContext().dict(True).array(True)), + LeafType(str, UnionTypeContext().dict(True).array(True))], + UnionTypeContext(), False, None), + + # Outer dictionary of array cases + ({'key0': ['abc', 'def'], 'key1': ['ghi', 'jkl']}, + [LeafType(int, UnionTypeContext()), LeafType(str, UnionTypeContext())], + UnionTypeContext().dict(True).array(True), True, {'key0': ['abc', 'def'], 'key1': ['ghi', 'jkl']}), + ({'key0': [100, 200], 'key1': [300, 400]}, + [LeafType(int, UnionTypeContext()), LeafType(str, UnionTypeContext())], + UnionTypeContext().dict(True).array(True), True, {'key0': [100, 200], 'key1': [300, 400]}), + ({'key0': ['abc', 200], 'key1': ['def', 400]}, + [LeafType(int, UnionTypeContext()), LeafType(str, UnionTypeContext())], + UnionTypeContext().dict(True).array(True), True, {'key0': ['abc', 200], 'key1': ['def', 400]}), + ({'key0': [100, 200], 'key1': ['abc', 'def']}, + [LeafType(int, UnionTypeContext()), LeafType(str, UnionTypeContext())], + UnionTypeContext().dict(True).array(True), True, {'key0': [100, 200], 'key1': ['abc', 'def']}), + ([{'key0': [100, 200]}, {'key1': ['abc', 'def']}], + [LeafType(int, UnionTypeContext()), LeafType(str, UnionTypeContext())], + UnionTypeContext().dict(True).array(True), False, None), + + # Nested oneOf cases + ([[100, 200], ['abc', True]], + [OneOf([LeafType(str, UnionTypeContext()), LeafType(bool, UnionTypeContext())], + UnionTypeContext().array(True)), LeafType(int, UnionTypeContext().array(True))], + UnionTypeContext().array(True), True, [[100, 200], ['abc', True]]), + ([[100, 200], ['abc', True], None], + [OneOf([LeafType(str, UnionTypeContext()), LeafType(bool, UnionTypeContext())], + UnionTypeContext().array(True).nullable(True)), LeafType(int, UnionTypeContext().array(True))], + UnionTypeContext().array(True), True, [[100, 200], ['abc', True], None]), + ({'key0': {'key0': 100, 'key1': 200}, 'key2': {'key0': 'abc', 'key1': True}, 'key3': None}, + [OneOf([LeafType(str, UnionTypeContext()), LeafType(bool, UnionTypeContext())], + UnionTypeContext().dict(True).nullable(True)), LeafType(int, UnionTypeContext().dict(True))], + UnionTypeContext().dict(True), True, + {'key0': {'key0': 100, 'key1': 200}, 'key2': {'key0': 'abc', 'key1': True}, 'key3': None}), + ({'key0': [100, 200], 'key2': ['abc', True], 'key3': None}, + [OneOf([LeafType(str, UnionTypeContext()), LeafType(bool, UnionTypeContext())], + UnionTypeContext().array(True).nullable(True)), LeafType(int, UnionTypeContext().array(True))], + UnionTypeContext().dict(True), True, + {'key0': [100, 200], 'key2': ['abc', True], 'key3': None}), + ({'key0': [100, 200], 'key2': ['abc', True], 'key3': None}, + [OneOf([LeafType(str, UnionTypeContext()), LeafType(bool, UnionTypeContext())], + UnionTypeContext().array(True).nullable(True)), LeafType(int, UnionTypeContext().array(True))], + UnionTypeContext().dict(True), True, + {'key0': [100, 200], 'key2': ['abc', True], 'key3': None}), + ([{'key0': 100, 'key1': 200}, {'key0': 'abc', 'key1': True}, None], + [OneOf([LeafType(str, UnionTypeContext()), LeafType(bool, UnionTypeContext())], + UnionTypeContext().dict(True).nullable(True)), LeafType(int, UnionTypeContext().dict(True))], + UnionTypeContext().array(True).array_of_dict(True), True, + [{'key0': 100, 'key1': 200}, {'key0': 'abc', 'key1': True}, None]), + ([[100, 200], None], + [OneOf([LeafType(str, UnionTypeContext()), LeafType(bool, UnionTypeContext())], + UnionTypeContext().array(True)), LeafType(int, UnionTypeContext().array(True))], + UnionTypeContext().array(True), False, None), + ]) + + def test_one_of_primitive_type(self, input_value, input_types, input_context, expected_is_valid_output, + expected_deserialized_value_output): + try: + union_type_result = OneOf(input_types, input_context).validate(input_value) + actual_is_valid = union_type_result.is_valid + actual_deserialized_value = union_type_result.deserialize(input_value) + except OneOfValidationException: + actual_is_valid = False + actual_deserialized_value = None + + assert actual_is_valid == expected_is_valid_output + assert actual_deserialized_value == expected_deserialized_value_output + + @pytest.mark.parametrize( + 'input_value, input_date, input_types, input_context, expected_validity, expected_value', [ + (Base.get_rfc3339_datetime(datetime(1994, 11, 6, 8, 49, 37), False), + Base.get_rfc3339_datetime(datetime(1994, 11, 6, 8, 49, 37)), + [LeafType(datetime, UnionTypeContext().date_time_format(DateTimeFormat.RFC3339_DATE_TIME)), + LeafType(date)], UnionTypeContext(), True, datetime(1994, 11, 6, 8, 49, 37)), + (Base.get_http_datetime(datetime(1994, 11, 6, 8, 49, 37), False), + Base.get_http_datetime(datetime(1994, 11, 6, 8, 49, 37)), + [LeafType(datetime, UnionTypeContext().date_time_format(DateTimeFormat.HTTP_DATE_TIME)), LeafType(date)], + UnionTypeContext(), True, datetime(1994, 11, 6, 8, 49, 37)), + (ApiHelper.UnixDateTime(datetime(1994, 11, 6, 8, 49, 37)), 1480809600, + [LeafType(datetime, UnionTypeContext().date_time_format(DateTimeFormat.UNIX_DATE_TIME)), LeafType(date)], + UnionTypeContext(), True, datetime.utcfromtimestamp(1480809600)), + (datetime(1994, 11, 6, 8, 49, 37), Base.get_rfc3339_datetime(datetime(1994, 11, 6, 8, 49, 37)), + [LeafType(datetime, UnionTypeContext().date_time_converter(ApiHelper.RFC3339DateTime).date_time_format(DateTimeFormat.RFC3339_DATE_TIME)), LeafType(date)], + UnionTypeContext(), True, datetime(1994, 11, 6, 8, 49, 37)), + ('1994-11-06', '1994-11-06', [LeafType(date), LeafType(datetime, UnionTypeContext().date_time_format(DateTimeFormat.RFC3339_DATE_TIME))], UnionTypeContext(), + True, date(1994, 11, 6)) + ]) + def test_one_of_date_and_datetime(self, input_value, input_date, input_types, input_context, expected_validity, expected_value): + union_type = OneOf(input_types, input_context) + union_type_result = union_type.validate(input_value) + assert union_type_result.is_valid == expected_validity + actual_deserialized_value = union_type_result.deserialize(input_date) + assert actual_deserialized_value == expected_value + + @pytest.mark.parametrize( + 'input_value, input_types, input_context, expected_validity, expected_value', [ + (None, [LeafType(int, UnionTypeContext().optional(True)), LeafType(str)], UnionTypeContext(), True, None), + (None, [LeafType(int, UnionTypeContext().optional(True)), LeafType(str, UnionTypeContext().optional(True))], + UnionTypeContext(), True, None), + (None, [LeafType(int), LeafType(str)], UnionTypeContext().nullable(True), True, None), + (None, [LeafType(int, UnionTypeContext().nullable(True)), LeafType(str)], UnionTypeContext(), True, None), + (None, [LeafType(int, UnionTypeContext().nullable(True)), LeafType(str, UnionTypeContext().optional(True))], + UnionTypeContext(), True, None), + (None, [LeafType(int), LeafType(str)], UnionTypeContext().nullable(True), True, None), + ([1, None, 2], [LeafType(int, UnionTypeContext().nullable(True)), LeafType(str)], + UnionTypeContext().array(True), True, [1, None, 2]), + ({'key0': 1, None: None, 'key3': 2}, [LeafType(int, UnionTypeContext().nullable(True)), LeafType(str)], + UnionTypeContext().dict(True), True, {'key0': 1, None: None, 'key3': 2}) + ]) + def test_one_of_optional_nullable(self, input_value, input_types, input_context, expected_validity, expected_value): + union_type_result = OneOf(input_types, input_context).validate(input_value) + assert union_type_result.is_valid == expected_validity + actual_deserialized_value = union_type_result.deserialize(input_value) + assert actual_deserialized_value == expected_value + + @pytest.mark.parametrize( + 'input_value, input_types, input_context, expected_is_valid_output, expected_deserialized_value_output', [ + # Simple Cases + ({"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, [LeafType(Atom), LeafType(Orbit)], + UnionTypeContext(), True, Atom(2, 5)), + ({"OrbitNumberOfElectrons": 4}, [LeafType(Atom), LeafType(Orbit)], + UnionTypeContext(), True, Orbit(4)), + + # Outer Array Cases + ([{"OrbitNumberOfElectrons": 4}, {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}], + [LeafType(Atom), LeafType(Orbit)], UnionTypeContext().array(True), True, [Orbit(4), Atom(2, 5)]), + ([{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, {"AtomNumberOfElectrons": 4, "AtomNumberOfProtons": 10}], + [LeafType(Atom), LeafType(Orbit)], UnionTypeContext().array(True), True, [Atom(2, 5), Atom(4, 10)]), + ([{"OrbitNumberOfElectrons": 4}, {"OrbitNumberOfElectrons": 5}], + [LeafType(Atom), LeafType(Orbit)], + UnionTypeContext().array(True), True, [Orbit(4), Orbit(5)]), + ({"OrbitNumberOfElectrons": 4}, + [LeafType(Atom), LeafType(Orbit)], + UnionTypeContext().array(True), False, None), + ({"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, + [LeafType(Atom), LeafType(Orbit)], + UnionTypeContext().array(True), False, None), + + # Inner Array Cases + ([{"OrbitNumberOfElectrons": 4}, {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}], + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext().array(True))], + UnionTypeContext(), False, None), + ([{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, {"AtomNumberOfElectrons": 4, "AtomNumberOfProtons": 10}], + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext().array(True))], + UnionTypeContext(), True, [Atom(2, 5), Atom(4, 10)]), + ([{"OrbitNumberOfElectrons": 4}, {"OrbitNumberOfElectrons": 5}], + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext().array(True))], + UnionTypeContext(), True, [Orbit(4), Orbit(5)]), + ([{"OrbitNumberOfElectrons": 4}, {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}], + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext().array(True))], + UnionTypeContext(), False, None), + ({"OrbitNumberOfElectrons": 4}, + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext().array(True))], + UnionTypeContext(), False, None), + ({"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext().array(True))], + UnionTypeContext(), False, None), + + # Partial Array Case + ({"OrbitNumberOfElectrons": 4}, + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext())], UnionTypeContext(), True, Orbit(4)), + ([{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, {"AtomNumberOfElectrons": 4, "AtomNumberOfProtons": 10}], + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext())], UnionTypeContext(), True, [Atom(2, 5), Atom(4, 10)]), + ('[{"OrbitNumberOfElectrons": 4}, {"AtomNumberOfElectrons": 4, "AtomNumberOfProtons": 10}]', + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext())], + UnionTypeContext(), False, None), + ('{"OrbitNumberOfElectrons": 4}', [LeafType(Orbit, UnionTypeContext().array(True)), LeafType(Atom)], + UnionTypeContext(), False, None), + + # Array of Partial Arrays Cases + ([[{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, + {"AtomNumberOfElectrons": 4, "AtomNumberOfProtons": 10}]], + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit)], + UnionTypeContext().array(True), True, [[Atom(2, 5), Atom(4, 10)]]), + ([[{"OrbitNumberOfElectrons": 4}, {"OrbitNumberOfElectrons": 4}]], + [LeafType(Orbit, UnionTypeContext().array(True)), LeafType(Atom)], + UnionTypeContext().array(True), True, [[Orbit(4), Orbit(4)]]), + ([[{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, + {"AtomNumberOfElectrons": 4, "AtomNumberOfProtons": 10}], {"OrbitNumberOfElectrons": 4}], + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext())], + UnionTypeContext().array(True), True, [[Atom(2, 5), Atom(4, 10)], Orbit(4)]), + ([[{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, {"OrbitNumberOfElectrons": 4}]], + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit)], + UnionTypeContext().array(True), False, None), + ([{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}], + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit)], + UnionTypeContext().array(True), False, None), + + # Array of Arrays Cases + ([[{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, + {"AtomNumberOfElectrons": 3, "AtomNumberOfProtons": 6}], + [{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}, + {"AtomNumberOfElectrons": 3, "AtomNumberOfProtons": 7}]], + [LeafType(Atom, UnionTypeContext().array(True)), + LeafType(Orbit, UnionTypeContext().array(True))], + UnionTypeContext().array(True), True, [[Atom(2, 5), Atom(3, 6)], [Atom(2, 10), Atom(3, 7)]]), + ([[{"OrbitNumberOfElectrons": 4}, {"OrbitNumberOfElectrons": 6}], + [{"OrbitNumberOfElectrons": 8}, {"OrbitNumberOfElectrons": 10}]], + [LeafType(Orbit, UnionTypeContext().array(True)), LeafType(Atom, UnionTypeContext().array(True))], + UnionTypeContext().array(True), True, [[Orbit(4), Orbit(6)], [Orbit(8), Orbit(10)]]), + ([[{"OrbitNumberOfElectrons": 8}, {"OrbitNumberOfElectrons": 10}], + [{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, + {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}]], + [LeafType(Orbit, UnionTypeContext().array(True)), LeafType(Atom, UnionTypeContext().array(True))], + UnionTypeContext().array(True), True, [[Orbit(8), Orbit(10)], [Atom(2, 5), Atom(2, 10)]]), + ([[{"OrbitNumberOfElectrons": 8}, {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}], + [{"OrbitNumberOfElectrons": 10}, {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}]], + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext().array(True))], + UnionTypeContext().array(True), False, None), + ([[{"OrbitNumberOfElectrons": 8}, {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}], + [{"OrbitNumberOfElectrons": 8}, {"OrbitNumberOfElectrons": 10}]], + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext().array(True))], + UnionTypeContext().array(True), False, None), + ([[{"OrbitNumberOfElectrons": 8.5}, {"OrbitNumberOfElectrons": 10.5}], + [{"OrbitNumberOfElectrons": 8}, {"OrbitNumberOfElectrons": 10}]], + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext().array(True))], + UnionTypeContext().array(True), False, None), + ([[{"OrbitNumberOfElectrons": 8}, {"OrbitNumberOfElectrons": 10}, + {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, + {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}]], + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext().array(True))], + UnionTypeContext().array(True), False, None), + + # Outer Dictionary Cases + ({'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, + 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}}, + [LeafType(Atom), LeafType(Orbit)], UnionTypeContext().dict(True), True, + {'key0': Atom(2, 5), 'key1': Atom(2, 10)}), + ({'key0': {"OrbitNumberOfElectrons": 8}, 'key1': {"OrbitNumberOfElectrons": 10}}, + [LeafType(Atom), LeafType(Orbit)], UnionTypeContext().dict(True), True, + {'key0': Orbit(8), 'key1': Orbit(10)}), + ({'key0': {"OrbitNumberOfElectrons": 8}, 'key2': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}}, + [LeafType(Orbit), LeafType(Atom)], UnionTypeContext().dict(True), True, + {'key0': Orbit(8), 'key2': Atom(2, 5)}), + ({"OrbitNumberOfElectrons": 8}, [LeafType(Orbit), LeafType(Atom)], UnionTypeContext().dict(True), False, None), + ({"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, [LeafType(Orbit), LeafType(Atom)], + UnionTypeContext().dict(True), False, None), + ({'key0': {'key0': {"OrbitNumberOfElectrons": 8}, 'key1': {"OrbitNumberOfElectrons": 10}}}, + [LeafType(Atom), LeafType(Orbit)], UnionTypeContext().dict(True), False, None), + + # Inner Dictionary Cases + ({'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, + 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}}, + [LeafType(Atom, UnionTypeContext().dict(True)), LeafType(Orbit, UnionTypeContext().dict(True))], + UnionTypeContext(), True, {'key0': Atom(2, 5), 'key1': Atom(2, 10)}), + ({'key0': {"OrbitNumberOfElectrons": 8}, 'key1': {"OrbitNumberOfElectrons": 10}}, + [LeafType(Atom, UnionTypeContext().dict(True)), LeafType(Orbit, UnionTypeContext().dict(True))], + UnionTypeContext(), True, {'key0': Orbit(8), 'key1': Orbit(10)}), + ({'key0': {"OrbitNumberOfElectrons": 8}, 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}}, + [LeafType(Atom, UnionTypeContext().dict(True)), + LeafType(Orbit, UnionTypeContext().dict(True))], + UnionTypeContext(), False, None), + + # Partial Dictionary Cases + ({"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, [LeafType(Orbit, UnionTypeContext().dict(True)), + LeafType(Atom)], UnionTypeContext(), True, + Atom(2, 5)), + ({'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, + 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}}, + [LeafType(Atom, UnionTypeContext().dict(True)), LeafType(Orbit)], + UnionTypeContext(), True, {'key0': Atom(2, 5), 'key1': Atom(2, 10)}), + ({'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, 'key1': {"OrbitNumberOfElectrons": 8}}, + [LeafType(Atom, UnionTypeContext().dict(True)), LeafType(Orbit)], UnionTypeContext(), False, None), + ({"OrbitNumberOfElectrons": 8}, [LeafType(Orbit, UnionTypeContext().dict(True)), LeafType(Atom)], + UnionTypeContext(), False, None), + + # Dictionary of Partial Dictionary Cases + ({'key0': {"OrbitNumberOfElectrons": 8}, 'key1': {"OrbitNumberOfElectrons": 10}}, + [LeafType(Atom, UnionTypeContext().dict(True)), LeafType(Orbit)], + UnionTypeContext().dict(True), True, {'key0': Orbit(8), 'key1': Orbit(10)}), + ({'key0': {'key0': {"OrbitNumberOfElectrons": 8}, 'key1': {"OrbitNumberOfElectrons": 10}}}, + [LeafType(Orbit, UnionTypeContext().dict(True)), LeafType(Atom)], + UnionTypeContext().dict(True), True, {'key0': {'key0': Orbit(8), 'key1': Orbit(10)}}), + ({'key0': {'key0': {"OrbitNumberOfElectrons": 8}, 'key1': {"OrbitNumberOfElectrons": 10}}, + 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}}, + [LeafType(Orbit, UnionTypeContext().dict(True)), LeafType(Atom)], + UnionTypeContext().dict(True), True, {'key0': {'key0': Orbit(8), 'key1': Orbit(10)}, 'key1': Atom(2, 5)}), + ({'key0': {'key0': {"OrbitNumberOfElectrons": 10}, + 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}}}, + [LeafType(Orbit, UnionTypeContext().dict(True)), LeafType(Atom)], + UnionTypeContext().dict(True), False, None), + ({'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}}, + [LeafType(Atom, UnionTypeContext().dict(True)), LeafType(Orbit)], + UnionTypeContext().dict(True), False, None), + + # Dictionary of Dictionary Cases + ({'key0': {'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, + 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}}, \ + 'key1': {'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, \ + 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}}}, + [LeafType(Atom, UnionTypeContext().dict(True)), LeafType(Orbit, UnionTypeContext().dict(True))], + UnionTypeContext().dict(True), True, + {'key0': {'key0': Atom(2, 5), 'key1': Atom(2, 10)}, 'key1': {'key0': Atom(2, 5), 'key1': Atom(2, 10)}}), + ({'key0': {'key0': {"OrbitNumberOfElectrons": 4}, 'key1': {"OrbitNumberOfElectrons": 8}}, + 'key1': {'key0': {"OrbitNumberOfElectrons": 10}, 'key1': {"OrbitNumberOfElectrons": 12}}}, + [LeafType(Orbit, UnionTypeContext().dict(True)), LeafType(Atom, UnionTypeContext().dict(True))], + UnionTypeContext().dict(True), True, + {'key0': {'key0': Orbit(4), 'key1': Orbit(8)}, 'key1': {'key0': Orbit(10), 'key1': Orbit(12)}}), + ({'key0': {'key0': {"OrbitNumberOfElectrons": 10}, 'key1': {"OrbitNumberOfElectrons": 8}}, + 'key1': {'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, + 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}}}, + [LeafType(Atom, UnionTypeContext().dict(True)), LeafType(Orbit, UnionTypeContext().dict(True))], + UnionTypeContext().dict(True), True, + {'key0': {'key0': Orbit(10), 'key1': Orbit(8)}, 'key1': {'key0': Atom(2, 5), 'key1': Atom(2, 10)}}), + ({'key0': {'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}, + 'key1': {"OrbitNumberOfElectrons": 10}}, + 'key1': {'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, + 'key1': {"OrbitNumberOfElectrons": 12}}}, + [LeafType(Atom, UnionTypeContext().dict(True)), LeafType(Orbit, UnionTypeContext().dict(True))], + UnionTypeContext().dict(True), False, None), + ({'key0': {'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}, + 'key1': {"OrbitNumberOfElectrons": 10}}, + 'key1': {'key0': {"OrbitNumberOfElectrons": 12}, 'key1': {"OrbitNumberOfElectrons": 14}}}, + [LeafType(Atom, UnionTypeContext().dict(True)), LeafType(Orbit, UnionTypeContext().dict(True))], + UnionTypeContext().dict(True), False, None), + + # Inner array of dictionary cases + ([{'key0': {"OrbitNumberOfElectrons": 10}, 'key1': {"OrbitNumberOfElectrons": 12}}, + {'key0': {"OrbitNumberOfElectrons": 14}, 'key1': {"OrbitNumberOfElectrons": 8}}], + [LeafType(Orbit, UnionTypeContext().dict(True).array(True).array_of_dict(True)), + LeafType(Atom, UnionTypeContext().dict(True).array(True).array_of_dict(True))], + UnionTypeContext(), True, + [{'key0': Orbit(10), 'key1': Orbit(12)}, {'key0': Orbit(14), 'key1': Orbit(8)}]), + ([{'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, + 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}}, + {'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 15}, + 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 20}}], + [LeafType(Atom, UnionTypeContext().dict(True).array(True).array_of_dict(True)), + LeafType(Orbit, UnionTypeContext().dict(True).array(True).array_of_dict(True))], + UnionTypeContext(), True, + [{'key0': Atom(2, 5), 'key1': Atom(2, 10)}, {'key0': Atom(2, 15), 'key1': Atom(2, 20)}]), + ([{'key0': {"OrbitNumberOfElectrons": 12}, 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}}, + {'key0': {"OrbitNumberOfElectrons": 10}, + 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 15}}], + [LeafType(Orbit, UnionTypeContext().dict(True).array(True).array_of_dict(True)), + LeafType(Atom, UnionTypeContext().dict(True).array(True).array_of_dict(True))], + UnionTypeContext(), False, None), + ([{'key0': {"OrbitNumberOfElectrons": 12}, 'key1': {"OrbitNumberOfElectrons": 10}}, + {'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}, + 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 20}}], + [LeafType(Orbit, UnionTypeContext().dict(True).array(True).array_of_dict(True)), + LeafType(Atom, UnionTypeContext().dict(True).array(True).array_of_dict(True))], + UnionTypeContext(), False, None), + + # Outer array of dictionary cases + ([{'key0': {"OrbitNumberOfElectrons": 10}, 'key1': {"OrbitNumberOfElectrons": 12}}, + {'key0': {"OrbitNumberOfElectrons": 14}, 'key1': {"OrbitNumberOfElectrons": 16}}], + [LeafType(Orbit, UnionTypeContext()), LeafType(Atom, UnionTypeContext())], + UnionTypeContext().dict(True).array(True).array_of_dict(True), True, + [{'key0': Orbit(10), 'key1': Orbit(12)}, {'key0': Orbit(14), 'key1': Orbit(16)}]), + ([{'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}, + 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 15}}, + {'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 20}, + 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}}], + [LeafType(Orbit, UnionTypeContext()), LeafType(Atom, UnionTypeContext())], + UnionTypeContext().dict(True).array(True).array_of_dict(True), True, + [{'key0': Atom(2, 10), 'key1': Atom(2, 15)}, {'key0': Atom(2, 20), 'key1': Atom(2, 5)}]), + ([{'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}, 'key1': {"OrbitNumberOfElectrons": 10}}, + {'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, + 'key1': {"OrbitNumberOfElectrons": 12}}], + [LeafType(Atom, UnionTypeContext()), LeafType(Orbit, UnionTypeContext())], + UnionTypeContext().dict(True).array(True).array_of_dict(True), True, + [{'key0': Atom(2, 10), 'key1': Orbit(10)}, {'key0': Atom(2, 5), 'key1': Orbit(12)}]), + ([{'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}, + 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 12}}, + {'key0': {"OrbitNumberOfElectrons": 10}, 'key1': {"OrbitNumberOfElectrons": 12}}], + [LeafType(Atom, UnionTypeContext()), LeafType(Orbit, UnionTypeContext())], + UnionTypeContext().dict(True).array(True).array_of_dict(True), True, + [{'key0': Atom(2, 10), 'key1': Atom(2, 12)}, {'key0': Orbit(10), 'key1': Orbit(12)}]), + + # dictionary of array cases + ({'key0': [{"OrbitNumberOfElectrons": 10}, {"OrbitNumberOfElectrons": 12}], + 'key1': [{"OrbitNumberOfElectrons": 14}, {"OrbitNumberOfElectrons": 16}]}, + [LeafType(Orbit, UnionTypeContext().dict(True).array(True)), + LeafType(Atom, UnionTypeContext().dict(True).array(True))], + UnionTypeContext(), True, {'key0': [Orbit(10), Orbit(12)], 'key1': [Orbit(14), Orbit(16)]}), + ({'key0': [{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}, + {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 12}], + 'key1': [{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 14}, + {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 16}]}, + [LeafType(Atom, UnionTypeContext().dict(True).array(True)), + LeafType(Orbit, UnionTypeContext().dict(True).array(True))], + UnionTypeContext(), True, {'key0': [Atom(2, 10), Atom(2, 12)], 'key1': [Atom(2, 14), Atom(2, 16)]}), + ({'key0': [{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}, {"OrbitNumberOfElectrons": 10}], + 'key1': [{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 12}, {"OrbitNumberOfElectrons": 12}]}, + [LeafType(Atom, UnionTypeContext().dict(True).array(True)), + LeafType(Orbit, UnionTypeContext().dict(True).array(True))], + UnionTypeContext(), False, None), + ({'key0': [{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}, + {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 12}], + 'key1': [{"OrbitNumberOfElectrons": 10}, {"OrbitNumberOfElectrons": 12}]}, + [LeafType(Atom, UnionTypeContext().dict(True).array(True)), + LeafType(Orbit, UnionTypeContext().dict(True).array(True))], + UnionTypeContext(), False, None), + + # Outer dictionary of array cases + ({'key0': [{"OrbitNumberOfElectrons": 10}, {"OrbitNumberOfElectrons": 12}], + 'key1': [{"OrbitNumberOfElectrons": 14}, {"OrbitNumberOfElectrons": 16}]}, + [LeafType(Orbit, UnionTypeContext()), LeafType(Atom, UnionTypeContext())], + UnionTypeContext().dict(True).array(True), True, + {'key0': [Orbit(10), Orbit(12)], 'key1': [Orbit(14), Orbit(16)]}), + ({'key0': [{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}, + {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 12}], + 'key1': [{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}, + {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 12}]}, + [LeafType(Orbit, UnionTypeContext()), LeafType(Atom, UnionTypeContext())], + UnionTypeContext().dict(True).array(True), True, + {'key0': [Atom(2, 10), Atom(2, 12)], 'key1': [Atom(2, 10), Atom(2, 12)]}), + ({'key0': [{"OrbitNumberOfElectrons": 10}, {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}], + 'key1': [{"OrbitNumberOfElectrons": 12}, {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 12}]}, + [LeafType(Atom, UnionTypeContext()), LeafType(Orbit, UnionTypeContext())], + UnionTypeContext().dict(True).array(True), True, + {'key0': [Orbit(10), Atom(2, 10)], 'key1': [Orbit(12), Atom(2, 12)]}), + ]) + def test_one_of_custom_type(self, input_value, input_types, input_context, expected_is_valid_output, + expected_deserialized_value_output): + try: + union_type_result = OneOf(input_types, input_context).validate(input_value) + actual_is_valid = union_type_result.is_valid + actual_deserialized_value = union_type_result.deserialize(input_value) + except OneOfValidationException: + actual_is_valid = False + actual_deserialized_value = None + + assert actual_is_valid == expected_is_valid_output + assert actual_deserialized_value == expected_deserialized_value_output + + @pytest.mark.parametrize('input_value, input_types, input_context, expected_output', [ + # Simple Cases + ('{"id": 123, "weight": 5, "type": "lion", "kind": "hunter"}', + [LeafType(Lion, UnionTypeContext().discriminator('type').discriminator_value('lion')), + LeafType(Deer, UnionTypeContext().discriminator('type').discriminator_value('deer'))], + UnionTypeContext(), True), + ('{"name": "sam", "weight": 5, "type": "deer", "kind": "hunter"}', + [LeafType(Lion, UnionTypeContext().discriminator('type').discriminator_value('lion')), + LeafType(Deer, UnionTypeContext().discriminator('type').discriminator_value('deer'))], + UnionTypeContext(), True), + ('{"id": 123, "weight": 5, "type": "lion123", "kind": "hunter"}', + [LeafType(Lion, UnionTypeContext().discriminator('type').discriminator_value('lion')), + LeafType(Deer, UnionTypeContext().discriminator('type').discriminator_value('deer'))], + UnionTypeContext(), True), + ('{"name": "sam", "weight": 5, "type": "deer123", "kind": "hunted"}', + [LeafType(Lion, UnionTypeContext().discriminator('type').discriminator_value('lion')), + LeafType(Deer, UnionTypeContext().discriminator('type').discriminator_value('deer'))], + UnionTypeContext(), True), + ('{"id": 123, "weight": 5, "type": "lion", "kind": "hunter123"}', + [LeafType(Lion, UnionTypeContext().discriminator('type').discriminator_value('lion')), + LeafType(Deer, UnionTypeContext().discriminator('type').discriminator_value('deer'))], + UnionTypeContext(), True), + ('{"name": "sam", "weight": 5, "type": "deer", "kind": "hunted123"}', + [LeafType(Lion, UnionTypeContext().discriminator('type').discriminator_value('lion')), + LeafType(Deer, UnionTypeContext().discriminator('type').discriminator_value('deer'))], + UnionTypeContext(), True), + ('{"id": 123, "weight": 5, "type": "lion", "kind": "hunter"}', + [LeafType(Deer, UnionTypeContext().discriminator('type').discriminator_value('lion')), + LeafType(Rabbit, UnionTypeContext().discriminator('type').discriminator_value('lion'))], + UnionTypeContext(), False), + ('{"name": "sam", "weight": 5, "type": "deer", "kind": "hunted"}', + [LeafType(Lion, UnionTypeContext().discriminator('type').discriminator_value('deer')), + LeafType(Rabbit, UnionTypeContext().discriminator('type').discriminator_value('deer'))], + UnionTypeContext(), False), + + ('{"id": 123, "weight": 5, "type": "lion", "kind": "hunter"}', + [LeafType(dict, UnionTypeContext().discriminator('type').discriminator_value('lion')), + LeafType(dict, UnionTypeContext().discriminator('type').discriminator_value('deer'))], + UnionTypeContext(), True), + ('{"name": "sam", "weight": 5, "type": "deer", "kind": "hunter"}', + [LeafType(dict, UnionTypeContext().discriminator('type').discriminator_value('lion')), + LeafType(dict, UnionTypeContext().discriminator('type').discriminator_value('deer'))], + UnionTypeContext(), True), + ('{"name": "sam", "weight": 5, "type": "deer", "kind": "hunter"}', + [LeafType(dict, UnionTypeContext().discriminator('type').discriminator_value('deer')), + LeafType(dict, UnionTypeContext().discriminator('type').discriminator_value('deer'))], + UnionTypeContext(), False), + ('{"name": "sam", "weight": 5, "type": "deer", "kind": "hunter"}', + [LeafType(dict, UnionTypeContext().discriminator('type').discriminator_value('lion')), + LeafType(dict, UnionTypeContext().discriminator('type').discriminator_value('lion'))], + UnionTypeContext(), False), + ('{"name": "sam", "weight": 5, "type": "deer", "kind": "hunter"}', + [LeafType(dict), LeafType(dict)], UnionTypeContext(), False), + ]) + def test_one_of_with_discriminator_custom_type(self, input_value, input_types, input_context, expected_output): + try: + deserialized_dict_input = ApiHelper.json_deserialize(input_value, as_dict=True) + union_type_result = OneOf(input_types, input_context).validate(deserialized_dict_input) + actual_is_valid = union_type_result.is_valid + except OneOfValidationException: + actual_is_valid = False + + assert actual_is_valid == expected_output + + @pytest.mark.parametrize('input_value, input_types, input_context, expected_is_valid_output, ' + 'expected_deserialized_value_output', [ + # Simple Cases + ('Monday',[LeafType(Days, UnionTypeContext()), LeafType(Months, UnionTypeContext())], + UnionTypeContext(), True, 'Monday'), + (1, [LeafType(Days, UnionTypeContext()), LeafType(Months, UnionTypeContext())], + UnionTypeContext(), True, 1), + (0, [LeafType(Days, UnionTypeContext()), LeafType(Months, UnionTypeContext())], + UnionTypeContext(), False, None), + ('Monday_', [LeafType(Days, UnionTypeContext()), LeafType(Months, UnionTypeContext())], + UnionTypeContext(), False, None), + + # Outer Array + (['Monday', 'Tuesday'], [LeafType(Days), LeafType(Months)], UnionTypeContext().array(True), True, + ['Monday', 'Tuesday']), + ([1, 2], [LeafType(Days), LeafType(Months)], UnionTypeContext().array(True), True, + [1, 2]), + ([1, 'Monday'], [LeafType(Days), LeafType(Months)], UnionTypeContext().array(True), True, [1, 'Monday']), + (2, [LeafType(Days), LeafType(Months)], UnionTypeContext().array(True), False, None), + ('Monday', [LeafType(Days), LeafType(Months)], UnionTypeContext().array(True), False, None), + ([['January', 'February']], [LeafType(int), LeafType(str)], UnionTypeContext().array(True), False, None), + + # Inner Array Cases + (['Monday', 'Tuesday'], [LeafType(Days, UnionTypeContext().array(True)), + LeafType(Months, UnionTypeContext().array(True))], + UnionTypeContext(), True, ['Monday', 'Tuesday']), + ([1, 2], [LeafType(Days, UnionTypeContext().array(True)), LeafType(Months, UnionTypeContext().array(True))], + UnionTypeContext(), True, [1, 2]), + ([1, 'Monday'], + [LeafType(Months, UnionTypeContext().array(True)), LeafType(Days, UnionTypeContext().array(True))], + UnionTypeContext(), False, None), + + # Partial Array Case + ('Monday', [LeafType(Months, UnionTypeContext().array(True)), LeafType(Days)], + UnionTypeContext(), True, 'Monday'), + ([1, 2], [LeafType(Months, UnionTypeContext().array(True)), LeafType(Days)], + UnionTypeContext(), True, [1, 2]), + ([1, 'Monday'], [LeafType(Months, UnionTypeContext().array(True)), LeafType(Days)], + UnionTypeContext(), False, None), + (1, [LeafType(Months, UnionTypeContext().array(True)), LeafType(Days)], + UnionTypeContext(), False, None), + + # Array of Partial Arrays Cases + (['Monday', 'Tuesday'], [LeafType(Months, UnionTypeContext().array(True)), LeafType(Days)], + UnionTypeContext().array(True), True, ['Monday', 'Tuesday']), + ([[1, 2]], [LeafType(Months, UnionTypeContext().array(True)), LeafType(Days)], + UnionTypeContext().array(True), True, [[1, 2]]), + ([[1, 2], 'Monday'], [LeafType(Months, UnionTypeContext().array(True)), LeafType(Days)], + UnionTypeContext().array(True), True, [[1, 2], 'Monday']), + ([[1, 'Monday']], [LeafType(Months, UnionTypeContext().array(True)), LeafType(Days)], + UnionTypeContext().array(True), False, None), + ([1], [LeafType(Months, UnionTypeContext().array(True)), LeafType(Days)], + UnionTypeContext().array(True), False, None), + + # Array of Arrays Cases + ([['Monday', 'Tuesday'], ['Wednesday', 'Thursday']], [LeafType(Days, UnionTypeContext().array(True)), + LeafType(Months, UnionTypeContext().array(True))], + UnionTypeContext().array(True), True, [['Monday', 'Tuesday'], ['Wednesday', 'Thursday']]), + ([[1, 2], [3, 4]], [LeafType(Months, UnionTypeContext().array(True)), + LeafType(Days, UnionTypeContext().array(True))], + UnionTypeContext().array(True), True, [[1, 2], [3, 4]]), + ([[1, 2], ['Monday', 'Tuesday']], [LeafType(Months, UnionTypeContext().array(True)), + LeafType(Days, UnionTypeContext().array(True))], + UnionTypeContext().array(True), True, [[1, 2], ['Monday', 'Tuesday']]), + ]) + def test_one_of_enum_type(self, input_value, input_types, input_context, expected_is_valid_output, + expected_deserialized_value_output): + try: + union_type_result = OneOf(input_types, input_context).validate(input_value) + actual_is_valid = union_type_result.is_valid + actual_deserialized_value = union_type_result.deserialize(input_value) + except OneOfValidationException: + actual_is_valid = False + actual_deserialized_value = None + + assert actual_is_valid == expected_is_valid_output + assert actual_deserialized_value == expected_deserialized_value_output + + @pytest.mark.parametrize('input_value, input_types, input_context, expected_validation_message', [ + # Simple Cases + (100, [LeafType(int), LeafType(int), LeafType(str)], UnionTypeContext(), + '{} \nActual Value: 100\nExpected Type: One Of int, int, str.'.format( + UnionTypeHelper.MORE_THAN_1_MATCHED_ERROR_MESSAGE)), + (100.5, [LeafType(int), LeafType(bool), LeafType(str)], UnionTypeContext(), + '{} \nActual Value: 100.5\nExpected Type: One Of int, bool, str.'.format( + UnionTypeHelper.NONE_MATCHED_ERROR_MESSAGE)), + (100.5, [LeafType(int), OneOf([LeafType(bool), LeafType(str)])], UnionTypeContext(), + '{} \nActual Value: 100.5\nExpected Type: One Of int, bool, str.'.format( + UnionTypeHelper.NONE_MATCHED_ERROR_MESSAGE)), + ([[100, 200], None], [OneOf([LeafType(str, UnionTypeContext()), LeafType(bool, UnionTypeContext())], + UnionTypeContext().array(True)), LeafType(int, UnionTypeContext().array(True))], + UnionTypeContext().array(True), '{} \nActual Value: [[100, 200], None]\nExpected Type: One Of str, bool, int.'.format( + UnionTypeHelper.NONE_MATCHED_ERROR_MESSAGE)), + ]) + def test_one_of_validation_errors(self, input_value, input_types, input_context, expected_validation_message): + with pytest.raises(OneOfValidationException) as validation_error: + OneOf(input_types, input_context).validate(input_value) + assert validation_error.value.message == expected_validation_message diff --git a/tests/apimatic_core/utility_tests/__init__.py b/tests/apimatic_core/utility_tests/__init__.py index 0140e86..de39803 100644 --- a/tests/apimatic_core/utility_tests/__init__.py +++ b/tests/apimatic_core/utility_tests/__init__.py @@ -2,5 +2,7 @@ 'test_auth_helper', 'test_xml_helper', 'test_file_helper', - 'test_comparison_helper' + 'test_comparison_helper', + 'test_api_helper', + 'test_datetime_helper' ] \ No newline at end of file diff --git a/tests/apimatic_core/utility_tests/test_api_helper.py b/tests/apimatic_core/utility_tests/test_api_helper.py index 09379e8..41ddedd 100644 --- a/tests/apimatic_core/utility_tests/test_api_helper.py +++ b/tests/apimatic_core/utility_tests/test_api_helper.py @@ -1,6 +1,9 @@ from datetime import datetime, date import jsonpickle import pytest +from apimatic_core.types.union_types.leaf_type import LeafType +from apimatic_core.types.union_types.one_of import OneOf +from apimatic_core.types.union_types.union_type_context import UnionTypeContext from dateutil.tz import tzutc from apimatic_core.types.array_serialization_format import SerializationFormats @@ -8,6 +11,7 @@ from apimatic_core.types.file_wrapper import FileWrapper from apimatic_core.utilities.api_helper import ApiHelper from tests.apimatic_core.base import Base +from tests.apimatic_core.mocks.models.days import Days from tests.apimatic_core.mocks.models.grand_parent_class_model import ChildClassModel from tests.apimatic_core.mocks.models.person import Employee @@ -16,24 +20,6 @@ class TestApiHelper(Base): - @pytest.mark.parametrize('expected_value, input_value, is_wrapped', [ - ('value', 'value', False), - ('true', True, False), - ('{{"bodyScalar": true, "bodyNonScalar": {{"address": "street abc", "age": 27, ' - '"birthday": "1994-02-13", "birthtime": "{0}", "department": ' - '"IT", "dependents": [{{"address": "street abc", "age": 12, "birthday": ' - '"1994-02-13", "birthtime": "{0}", "name": "John", "uid": ' - '7654321, "personType": "Per", "key1": "value1", "key2": "value2"}}], ' - '"hiredAt": "{1}", "joiningDay": "Monday", "name": ' - '"Bob", "salary": 30000, "uid": 1234567, "workingDays": ["Monday", ' - '"Tuesday"], "personType": "Empl"}}}}'.format(Base.get_rfc3339_datetime(datetime(1994, 2, 13, 5, 30, 15)), - Base.get_http_datetime(datetime(1994, 2, 13, 5, 30, 15))), - Base.wrapped_parameters(), True), - ]) - def test_get_request_parameter(self, expected_value, input_value, is_wrapped): - request_param = ApiHelper.get_request_parameter(input_value, is_wrapped) - assert request_param == expected_value - @pytest.mark.parametrize('input_value, expected_value', [ (None, None), (Base.wrapped_parameters(), '{{"bodyScalar": true, "bodyNonScalar": {{"address": "street abc", "age": 27, ' @@ -71,7 +57,78 @@ def test_json_serialize_wrapped_params(self, input_value, expected_value): '"Bob", "salary": 30000, "uid": 1234567, "workingDays": ["Monday", ' '"Tuesday"], "personType": "Empl"}}]'.format(Base.get_rfc3339_datetime(datetime(1994, 2, 13, 5, 30, 15)), Base.get_http_datetime(datetime(1994, 2, 13, 5, 30, 15)))), + ([[Base.employee_model(), Base.employee_model()], [Base.employee_model(), Base.employee_model()]], + '[[{{"address": "street abc", "age": 27, ' + '"birthday": "1994-02-13", "birthtime": "{0}", "department": ' + '"IT", "dependents": [{{"address": "street abc", "age": 12, "birthday": ' + '"1994-02-13", "birthtime": "{0}", "name": "John", "uid": ' + '7654321, "personType": "Per", "key1": "value1", "key2": "value2"}}], ' + '"hiredAt": "{1}", "joiningDay": "Monday", "name": ' + '"Bob", "salary": 30000, "uid": 1234567, "workingDays": ["Monday", ' + '"Tuesday"], "personType": "Empl"}}, ' + '{{"address": "street abc", "age": 27, ' + '"birthday": "1994-02-13", "birthtime": "{0}", "department": ' + '"IT", "dependents": [{{"address": "street abc", "age": 12, "birthday": ' + '"1994-02-13", "birthtime": "{0}", "name": "John", "uid": ' + '7654321, "personType": "Per", "key1": "value1", "key2": "value2"}}], ' + '"hiredAt": "{1}", "joiningDay": "Monday", "name": ' + '"Bob", "salary": 30000, "uid": 1234567, "workingDays": ["Monday", ' + '"Tuesday"], "personType": "Empl"}}], [{{"address": "street abc", "age": 27, ' + '"birthday": "1994-02-13", "birthtime": "{0}", "department": ' + '"IT", "dependents": [{{"address": "street abc", "age": 12, "birthday": ' + '"1994-02-13", "birthtime": "{0}", "name": "John", "uid": ' + '7654321, "personType": "Per", "key1": "value1", "key2": "value2"}}], ' + '"hiredAt": "{1}", "joiningDay": "Monday", "name": ' + '"Bob", "salary": 30000, "uid": 1234567, "workingDays": ["Monday", ' + '"Tuesday"], "personType": "Empl"}}, ' + '{{"address": "street abc", "age": 27, ' + '"birthday": "1994-02-13", "birthtime": "{0}", "department": ' + '"IT", "dependents": [{{"address": "street abc", "age": 12, "birthday": ' + '"1994-02-13", "birthtime": "{0}", "name": "John", "uid": ' + '7654321, "personType": "Per", "key1": "value1", "key2": "value2"}}], ' + '"hiredAt": "{1}", "joiningDay": "Monday", "name": ' + '"Bob", "salary": 30000, "uid": 1234567, "workingDays": ["Monday", ' + '"Tuesday"], "personType": "Empl"}}]]'.format(Base.get_rfc3339_datetime(datetime(1994, 2, 13, 5, 30, 15)), + Base.get_http_datetime(datetime(1994, 2, 13, 5, 30, 15)))), + ({'key0': [Base.employee_model(), Base.employee_model()], + 'key1': [Base.employee_model(), Base.employee_model()]}, + '{{"key0": [{{"address": "street abc", "age": 27, ' + '"birthday": "1994-02-13", "birthtime": "{0}", "department": ' + '"IT", "dependents": [{{"address": "street abc", "age": 12, "birthday": ' + '"1994-02-13", "birthtime": "{0}", "name": "John", "uid": ' + '7654321, "personType": "Per", "key1": "value1", "key2": "value2"}}], ' + '"hiredAt": "{1}", "joiningDay": "Monday", "name": ' + '"Bob", "salary": 30000, "uid": 1234567, "workingDays": ["Monday", ' + '"Tuesday"], "personType": "Empl"}}, ' + '{{"address": "street abc", "age": 27, ' + '"birthday": "1994-02-13", "birthtime": "{0}", "department": ' + '"IT", "dependents": [{{"address": "street abc", "age": 12, "birthday": ' + '"1994-02-13", "birthtime": "{0}", "name": "John", "uid": ' + '7654321, "personType": "Per", "key1": "value1", "key2": "value2"}}], ' + '"hiredAt": "{1}", "joiningDay": "Monday", "name": ' + '"Bob", "salary": 30000, "uid": 1234567, "workingDays": ["Monday", ' + '"Tuesday"], "personType": "Empl"}}], "key1": [{{"address": "street abc", "age": 27, ' + '"birthday": "1994-02-13", "birthtime": "{0}", "department": ' + '"IT", "dependents": [{{"address": "street abc", "age": 12, "birthday": ' + '"1994-02-13", "birthtime": "{0}", "name": "John", "uid": ' + '7654321, "personType": "Per", "key1": "value1", "key2": "value2"}}], ' + '"hiredAt": "{1}", "joiningDay": "Monday", "name": ' + '"Bob", "salary": 30000, "uid": 1234567, "workingDays": ["Monday", ' + '"Tuesday"], "personType": "Empl"}}, ' + '{{"address": "street abc", "age": 27, ' + '"birthday": "1994-02-13", "birthtime": "{0}", "department": ' + '"IT", "dependents": [{{"address": "street abc", "age": 12, "birthday": ' + '"1994-02-13", "birthtime": "{0}", "name": "John", "uid": ' + '7654321, "personType": "Per", "key1": "value1", "key2": "value2"}}], ' + '"hiredAt": "{1}", "joiningDay": "Monday", "name": ' + '"Bob", "salary": 30000, "uid": 1234567, "workingDays": ["Monday", ' + '"Tuesday"], "personType": "Empl"}}]}}'.format(Base.get_rfc3339_datetime(datetime(1994, 2, 13, 5, 30, 15)), + Base.get_http_datetime(datetime(1994, 2, 13, 5, 30, 15)))), ([1, 2, 3], '[1, 2, 3]'), + ({'key0': 1, 'key1': 'abc'}, '{"key0": 1, "key1": "abc"}'), + ([[1, 2, 3], ['abc', 'def']], '[[1, 2, 3], ["abc", "def"]]'), + ([{'key0': [1, 2, 3]}, {'key1': ['abc', 'def']}], '[{"key0": [1, 2, 3]}, {"key1": ["abc", "def"]}]'), + ({'key0': [1, 2, 3], 'key1': ['abc', 'def']}, '{"key0": [1, 2, 3], "key1": ["abc", "def"]}'), (Base.employee_model(), '{{"address": "street abc", "age": 27, ' '"birthday": "1994-02-13", "birthtime": "{0}", "department": ' @@ -82,7 +139,8 @@ def test_json_serialize_wrapped_params(self, input_value, expected_value): '"Bob", "salary": 30000, "uid": 1234567, "workingDays": ["Monday", ' '"Tuesday"], "personType": "Empl"}}'.format(Base.get_rfc3339_datetime(datetime(1994, 2, 13, 5, 30, 15)), Base.get_http_datetime(datetime(1994, 2, 13, 5, 30, 15)))), - (1, '1') + (1, '1'), + ('1', '1') ]) def test_json_serialize(self, input_value, expected_value): serialized_value = ApiHelper.json_serialize(input_value) @@ -98,23 +156,17 @@ def test_json_serialize(self, input_value, expected_value): ApiHelper.json_serialize([Base.employee_model(), Base.employee_model()])), (ApiHelper.json_serialize({'key1': Base.employee_model(), 'key2': Base.employee_model()}), Employee.from_dictionary, True, - '{{"key1": {{"department": "IT", "dependents": [{{"address": "street abc", ' - '"age": 12, "birthday": "1994-02-13", "birthtime": "{0}", ' - '"name": "John", "uid": 7654321, "person_type": "Per", ' - '"additional_properties": {{"person_type": "Per", "additional_properties": ' - '{{"key1": "value1", "key2": "value2"}}}}}}], "hired_at": null, "joining_day": ' - '"Monday", "salary": 30000, "working_days": null, "additional_properties": ' - '{{}}, "address": "street abc", "age": 27, "birthday": "1994-02-13", ' - '"birthtime": "{0}", "name": "Bob", "uid": 1234567, ' - '"person_type": "Empl"}}, "key2": {{"department": "IT", "dependents": ' - '[{{"address": "street abc", "age": 12, "birthday": "1994-02-13", "birthtime": ' - '"{0}", "name": "John", "uid": 7654321, "person_type": "Per", ' - '"additional_properties": {{"person_type": "Per", "additional_properties": ' - '{{"key1": "value1", "key2": "value2"}}}}}}], "hired_at": null, "joining_day": ' - '"Monday", "salary": 30000, "working_days": null, "additional_properties": ' - '{{}}, "address": "street abc", "age": 27, "birthday": "1994-02-13", ' - '"birthtime": "{0}", "name": "Bob", "uid": 1234567, ' - '"person_type": "Empl"}}}}'.format(Base.get_rfc3339_datetime(datetime(1994, 2, 13, 5, 30, 15)))), + '{{"key1": {{"address": "street abc", "age": 27, "birthday": "1994-02-13", "birthtime": "{0}", ' + '"department": "IT", "dependents": [{{"address": "street abc", "age": 12, "birthday": "1994-02-13", ' + '"birthtime": "{0}", "name": "John", "uid": 7654321, "personType": "Per", "key1": "value1", ' + '"key2": "value2"}}], "hiredAt": "{1}", "joiningDay": "Monday", "name": "Bob", "salary": 30000, ' + '"uid": 1234567, "workingDays": ["Monday", "Tuesday"], "personType": "Empl"}}, "key2": ' + '{{"address": "street abc", "age": 27, "birthday": "1994-02-13", "birthtime": "{0}", "department": "IT",' + ' "dependents": [{{"address": "street abc", "age": 12, "birthday": "1994-02-13", "birthtime": "{0}", ' + '"name": "John", "uid": 7654321, "personType": "Per", "key1": "value1", "key2": "value2"}}], "hiredAt": "{1}",' + ' "joiningDay": "Monday", "name": "Bob", "salary": 30000, "uid": 1234567, "workingDays": ["Monday",' + ' "Tuesday"], "personType": "Empl"}}}}'.format(Base.get_rfc3339_datetime(datetime(1994, 2, 13, 5, 30, 15)), + Base.get_http_datetime(datetime(1994, 2, 13, 5, 30, 15)))) ]) def test_json_deserialize(self, input_json_value, unboxing_function, as_dict, expected_value): @@ -303,6 +355,28 @@ def test_clean_url(self, input_url, expected_url): '"Bob", "salary": 30000, "uid": 1234567, "workingDays": ["Monday", ' '"Tuesday"], "personType": "Empl"}}'.format(Base.get_rfc3339_datetime(datetime(1994, 2, 13, 5, 30, 15)), Base.get_http_datetime(datetime(1994, 2, 13, 5, 30, 15)))), + (Base.get_complex_type(), + '{"innerComplexListType": [{"booleanType": true, "longType": 100003, "precisionType": 55.44, ' + '"stringListType": ["item1", "item2"], "stringType": "abc", "key0": "abc", "key1": 400}, ' + '{"booleanType": true, "longType": 100003, "precisionType": 55.44, "stringListType": ["item1", "item2"],' + ' "stringType": "abc", "key0": "abc", "key1": 400}], "innerComplexType": {"booleanType": true, ' + '"longType": 100003, "precisionType": 55.44, "stringListType": ["item1", "item2"], "stringType": "abc",' + ' "key0": "abc", "key1": 400}, "innerComplexListOfMapType": [{"key0": {"booleanType": true, ' + '"longType": 100003, "precisionType": 55.44, "stringListType": ["item1", "item2"], ' + '"stringType": "abc", "key0": "abc", "key1": 400}, "key1": {"booleanType": true, "longType": 100003, ' + '"precisionType": 55.44, "stringListType": ["item1", "item2"], "stringType": "abc", "key0": "abc", ' + '"key1": 400}}], "innerComplexMapOfListType": {"key0": [{"booleanType": true, "longType": 100003, ' + '"precisionType": 55.44, "stringListType": ["item1", "item2"], "stringType": "abc", "key0": "abc", ' + '"key1": 400}, {"booleanType": true, "longType": 100003, "precisionType": 55.44, "stringListType": ' + '["item1", "item2"], "stringType": "abc", "key0": "abc", "key1": 400}], "key2": [{"booleanType": true, ' + '"longType": 100003, "precisionType": 55.44, "stringListType": ["item1", "item2"], "stringType": "abc", ' + '"key0": "abc", "key1": 400}, {"booleanType": true, "longType": 100003, "precisionType": 55.44, ' + '"stringListType": ["item1", "item2"], "stringType": "abc", "key0": "abc", "key1": 400}]}, ' + '"innerComplexMapType": {"key0": {"booleanType": true, "longType": 100003, "precisionType": 55.44, ' + '"stringListType": ["item1", "item2"], "stringType": "abc", "key0": "abc", "key1": 400}, "key1": ' + '{"booleanType": true, "longType": 100003, "precisionType": 55.44, "stringListType": ["item1", "item2"], ' + '"stringType": "abc", "key0": "abc", "key1": 400}}, "prop1": [1, 2, 3], "prop2": {"key0": "abc", ' + '"key1": "def"}}'), (ApiHelper.json_deserialize('{"Grand_Parent_Required_Nullable":{"key1": "value1", "key2": "value2"},' '"Grand_Parent_Required":"not nullable and required","class":23,' '"Parent_Optional_Nullable_With ' @@ -332,6 +406,8 @@ def test_clean_url(self, input_url, expected_url): '"workingDays": ["Monday", "Tuesday"], "personType": "Empl"}}'.format( Base.get_rfc3339_datetime(datetime(1994, 2, 13, 5, 30, 15)), Base.get_http_datetime(datetime(1994, 2, 13, 5, 30, 15)))), + (Base.get_union_type_scalar_model(), + '{"anyOfRequired": 1.5, "oneOfReqNullable": "abc", "oneOfOptional": 200, "anyOfOptNullable": true}'), ]) def test_to_dictionary(self, obj, expected_value): @@ -464,6 +540,105 @@ def test_when_defined(self, input_function, input_body, expected_value): else: assert ApiHelper.when_defined(input_function, input_body) == expected_value + @pytest.mark.parametrize('input_value, input_converter, expected_obj', [ + (datetime(1994, 2, 13, 5, 30, 15), ApiHelper.RFC3339DateTime, + ApiHelper.RFC3339DateTime), + (datetime(1994, 2, 13, 5, 30, 15), ApiHelper.HttpDateTime, ApiHelper.HttpDateTime), + (datetime(1994, 2, 13, 5, 30, 15), ApiHelper.UnixDateTime, ApiHelper.UnixDateTime), + (500, ApiHelper.UnixDateTime, int), + ('500', ApiHelper.UnixDateTime, str), + (None, ApiHelper.RFC3339DateTime, None) + ]) + def test_apply_date_time_converter(self, input_value, input_converter, expected_obj): + if input_value is None: + assert ApiHelper.apply_datetime_converter(input_value, input_converter) == expected_obj + else: + assert isinstance(ApiHelper.apply_datetime_converter(input_value, input_converter), expected_obj) + + @pytest.mark.parametrize('input_value, input_converter, expected_obj', [ + ([datetime(1994, 2, 13, 5, 30, 15), datetime(1994, 2, 13, 5, 30, 15)], ApiHelper.RFC3339DateTime, + [ApiHelper.RFC3339DateTime, ApiHelper.RFC3339DateTime]), + ([datetime(1994, 2, 13, 5, 30, 15), datetime(1994, 2, 13, 5, 30, 15)], ApiHelper.HttpDateTime, + [ApiHelper.HttpDateTime, ApiHelper.HttpDateTime]), + ([datetime(1994, 2, 13, 5, 30, 15), datetime(1994, 2, 13, 5, 30, 15)], ApiHelper.UnixDateTime, + [ApiHelper.UnixDateTime, ApiHelper.UnixDateTime]), + ([500, 1000], ApiHelper.UnixDateTime, [int, int]), + (['500', '1000'], ApiHelper.UnixDateTime, [str, str]), + (['500', datetime(1994, 2, 13, 5, 30, 15)], ApiHelper.UnixDateTime, [str, ApiHelper.UnixDateTime]), + (None, ApiHelper.RFC3339DateTime, None) + ]) + def test_apply_date_time_converter_to_list(self, input_value, input_converter, expected_obj): + if input_value is None: + assert ApiHelper.apply_datetime_converter(input_value, input_converter) == expected_obj + else: + actual_converted_value = ApiHelper.apply_datetime_converter(input_value, input_converter) + for index, actual_value in enumerate(actual_converted_value): + assert isinstance(actual_value, expected_obj[index]) + + @pytest.mark.parametrize('input_value, input_converter, expected_obj', [ + ([[datetime(1994, 2, 13, 5, 30, 15), datetime(1994, 2, 13, 5, 30, 15)]], ApiHelper.RFC3339DateTime, + [[ApiHelper.RFC3339DateTime, ApiHelper.RFC3339DateTime]]), + ([[datetime(1994, 2, 13, 5, 30, 15), datetime(1994, 2, 13, 5, 30, 15)]], ApiHelper.HttpDateTime, + [[ApiHelper.HttpDateTime, ApiHelper.HttpDateTime]]), + ([[datetime(1994, 2, 13, 5, 30, 15), datetime(1994, 2, 13, 5, 30, 15)]], ApiHelper.UnixDateTime, + [[ApiHelper.UnixDateTime, ApiHelper.UnixDateTime]]), + ([[500, 1000]], ApiHelper.UnixDateTime, [[int, int]]), + ([['500', '1000']], ApiHelper.UnixDateTime, [[str, str]]), + ([['500', datetime(1994, 2, 13, 5, 30, 15)]], ApiHelper.UnixDateTime, [[str, ApiHelper.UnixDateTime]]), + (None, ApiHelper.RFC3339DateTime, None) + ]) + def test_apply_date_time_converter_to_list_of_list(self, input_value, input_converter, expected_obj): + if input_value is None: + assert ApiHelper.apply_datetime_converter(input_value, input_converter) == expected_obj + else: + actual_converted_value = ApiHelper.apply_datetime_converter(input_value, input_converter) + for outer_index, actual_outer_value in enumerate(actual_converted_value): + for index, actual_value in enumerate(actual_outer_value): + assert isinstance(actual_value, expected_obj[outer_index][index]) + + @pytest.mark.parametrize('input_value, input_converter, expected_obj', [ + ({'key0': datetime(1994, 2, 13, 5, 30, 15), 'key1': datetime(1994, 2, 13, 5, 30, 15)}, ApiHelper.RFC3339DateTime, + [ApiHelper.RFC3339DateTime, ApiHelper.RFC3339DateTime]), + ({'key0': datetime(1994, 2, 13, 5, 30, 15), 'key1': datetime(1994, 2, 13, 5, 30, 15)}, ApiHelper.HttpDateTime, + [ApiHelper.HttpDateTime, ApiHelper.HttpDateTime]), + ({'key0': datetime(1994, 2, 13, 5, 30, 15), 'key1': datetime(1994, 2, 13, 5, 30, 15)}, ApiHelper.UnixDateTime, + [ApiHelper.UnixDateTime, ApiHelper.UnixDateTime]), + ({'key0': '5000', 'key1': datetime(1994, 2, 13, 5, 30, 15)}, ApiHelper.UnixDateTime, + [str, ApiHelper.UnixDateTime]), + ({'key0': 5000, 'key1': 10000}, ApiHelper.UnixDateTime, [int, int]), + ({'key0': '5000', 'key1': '10000'}, ApiHelper.UnixDateTime, [str, str]), + (None, ApiHelper.RFC3339DateTime, None) + ]) + def test_apply_date_time_converter_to_dict(self, input_value, input_converter, expected_obj): + if input_value is None: + assert ApiHelper.apply_datetime_converter(input_value, input_converter) == expected_obj + else: + actual_converted_value = ApiHelper.apply_datetime_converter(input_value, input_converter) + for index, actual_value in enumerate(actual_converted_value.values()): + assert isinstance(actual_value, expected_obj[index]) + + @pytest.mark.parametrize('input_value, input_converter, expected_obj', [ + ({'key': {'key0': datetime(1994, 2, 13, 5, 30, 15), 'key1': datetime(1994, 2, 13, 5, 30, 15)}}, + ApiHelper.RFC3339DateTime, {'key': [ApiHelper.RFC3339DateTime, ApiHelper.RFC3339DateTime]}), + ({'key': {'key0': datetime(1994, 2, 13, 5, 30, 15), 'key1': datetime(1994, 2, 13, 5, 30, 15)}}, ApiHelper.HttpDateTime, + {'key': [ApiHelper.HttpDateTime, ApiHelper.HttpDateTime]}), + ({'key': {'key0': datetime(1994, 2, 13, 5, 30, 15), 'key1': datetime(1994, 2, 13, 5, 30, 15)}}, ApiHelper.UnixDateTime, + {'key': [ApiHelper.UnixDateTime, ApiHelper.UnixDateTime]}), + ({'key': {'key0': '5000', 'key1': datetime(1994, 2, 13, 5, 30, 15)}}, ApiHelper.UnixDateTime, + {'key': [str, ApiHelper.UnixDateTime]}), + ({'key': {'key0': 5000, 'key1': 10000}}, ApiHelper.UnixDateTime, {'key': [int, int]}), + ({'key': {'key0': '5000', 'key1': '10000'}}, ApiHelper.UnixDateTime, {'key': [str, str]}), + (None, ApiHelper.RFC3339DateTime, None) + ]) + def test_apply_date_time_converter_to_dict_of_dict(self, input_value, input_converter, expected_obj): + if input_value is None: + assert ApiHelper.apply_datetime_converter(input_value, input_converter) == expected_obj + else: + actual_converted_value = ApiHelper.apply_datetime_converter(input_value, input_converter) + for outer_key, actual_outer_value in actual_converted_value.items(): + for index, actual_value in enumerate(actual_outer_value.values()): + assert isinstance(actual_value, expected_obj[outer_key][index]) + @pytest.mark.parametrize('input_array,formatting, is_query, expected_array', [ ([1, True, 'string', 2.36, Base.get_rfc3339_datetime(datetime(1994, 2, 13, 5, 30, 15)), str(date(1994, 2, 13))], SerializationFormats.INDEXED, False, [('test_array[0]', 1), @@ -673,3 +848,46 @@ def test_resolve_template_placeholders_using_json_pointer(self, input_placeholde actual_message = ApiHelper.resolve_template_placeholders_using_json_pointer(input_placeholders, input_value, input_template) assert actual_message == expected_message + + @pytest.mark.parametrize('input_value, input_callable, expected_value', [ + (100, lambda value: isinstance(value, int), True), + ('100', lambda value: isinstance(value, str), True), + ("Sunday", lambda value: Days.validate(value), True), + (100.5, lambda value: isinstance(value, str), False), + ("Invalid", lambda value: Days.validate(value), False), + (None, lambda value: isinstance(value, str), False), + (None, None, False), + + ([100, 200], lambda value: isinstance(value, int), True), + (['100', '200'], lambda value: isinstance(value, str), True), + (["Sunday", "Monday"], lambda value: Days.validate(value), True), + ([100.5, 200], lambda value: isinstance(value, str), False), + (["Invalid1", "Invalid2"], lambda value: Days.validate(value), False), + ([None, None], lambda value: isinstance(value, str), False), + + ([[100, 200], [300, 400]], lambda value: isinstance(value, int), True), + ([['100', '200'], ['abc', 'def']], lambda value: isinstance(value, str), True), + ([["Sunday", "Monday"], ["Tuesday", "Friday"]], lambda value: Days.validate(value), True), + ([[100.5, 200], [400, 500]], lambda value: isinstance(value, str), False), + ([["Invalid1", "Invalid2"], ["Sunday", "Invalid4"]], lambda value: Days.validate(value), False), + ([[None, None], [None, None]], lambda value: isinstance(value, str), False), + + ({'key0': 100, 'key2': 200}, lambda value: isinstance(value, int), True), + ({'key0': 'abc', 'key2': 'def'}, lambda value: isinstance(value, str), True), + ({'key0': 'Sunday', 'key2': 'Tuesday'}, lambda value: Days.validate(value), True), + ({'key0': 100.5, 'key2': 200}, lambda value: isinstance(value, str), False), + ({'key0': "Invalid1", 'key2': "Invalid2"}, lambda value: Days.validate(value), False), + ({'key0': None, 'key2': None}, lambda value: isinstance(value, str), False), + ]) + def test_is_valid_type(self, input_value, input_callable, expected_value): + actual_value = ApiHelper.is_valid_type(input_value, input_callable) + assert actual_value == expected_value + + @pytest.mark.parametrize('input_value, input_union_type, input_should_deserialize, expected_value', [ + (100, OneOf([LeafType(int), LeafType(str)]), False, 100), + ('[100, "200"]', OneOf([LeafType(int), LeafType(str)], UnionTypeContext.create(is_array=True)), True, + [100, '200']), + ]) + def test_union_type_deserialize(self, input_value, input_union_type, input_should_deserialize, expected_value): + actual_value = ApiHelper.deserialize_union_type(input_union_type, input_value, input_should_deserialize) + assert actual_value == expected_value diff --git a/tests/apimatic_core/utility_tests/test_datetime_helper.py b/tests/apimatic_core/utility_tests/test_datetime_helper.py new file mode 100644 index 0000000..9514d54 --- /dev/null +++ b/tests/apimatic_core/utility_tests/test_datetime_helper.py @@ -0,0 +1,44 @@ +from datetime import datetime, date +import pytest +from apimatic_core.types.datetime_format import DateTimeFormat +from apimatic_core.utilities.api_helper import ApiHelper +from apimatic_core.utilities.datetime_helper import DateTimeHelper + + +class TestDateTimeHelper: + + @pytest.mark.parametrize('input_dt, input_datetime_format, expected_output', [ + ('1994-11-06T08:49:37', DateTimeFormat.RFC3339_DATE_TIME, True), + ('1994-02-13T14:01:54.656647Z', DateTimeFormat.RFC3339_DATE_TIME, True), + ('Sun, 06 Nov 1994 03:49:37 GMT', DateTimeFormat.HTTP_DATE_TIME, True), + (1480809600, DateTimeFormat.UNIX_DATE_TIME, True), + ('1994-11-06T08:49:37', DateTimeFormat.HTTP_DATE_TIME, False), + (1480809600, DateTimeFormat.HTTP_DATE_TIME, False), + ('Sun, 06 Nov 1994 03:49:37 GMT', DateTimeFormat.RFC3339_DATE_TIME, False), + (1480809600, DateTimeFormat.RFC3339_DATE_TIME, False), + ('1994-11-06T08:49:37', DateTimeFormat.UNIX_DATE_TIME, False), + ('Sun, 06 Nov 1994 03:49:37 GMT', DateTimeFormat.UNIX_DATE_TIME, False), + (None, None, False) + ]) + def test_is_valid_datetime(self, input_dt, input_datetime_format, expected_output): + actual_output = DateTimeHelper.validate_datetime(input_dt, input_datetime_format) + assert actual_output == expected_output + + @pytest.mark.parametrize('input_date, expected_output', [ + ('1994-11-06', True), + (date(1994, 11, 6), True), + (date(94, 11, 6), True), + ('1994/11/06', False), + ('19941106', False), + ('941106', False), + ('1941106', False), + ('1994=11=06', False), + (123, False) + ]) + def test_is_valid_date(self, input_date, expected_output): + actual_output = DateTimeHelper.validate_date(input_date) + assert actual_output == expected_output + + + +