From b25b9068ccc46d7e88d9bcddbe4ec250104ba0dd Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Tue, 16 May 2023 16:14:21 +0500 Subject: [PATCH 01/49] adds design implementation for OneOf and AnyOf along with some unit tests --- apimatic_core/types/__init__.py | 3 +- apimatic_core/types/union_types/__init__.py | 7 + apimatic_core/types/union_types/any_of.py | 21 ++ apimatic_core/types/union_types/leaf_type.py | 173 ++++++++++++++ apimatic_core/types/union_types/one_of.py | 160 +++++++++++++ .../types/union_types/union_type_context.py | 74 ++++++ apimatic_core/utilities/api_helper.py | 24 ++ requirements.txt | 2 +- setup.py | 2 +- tests/apimatic_core/__init__.py | 3 +- tests/apimatic_core/mocks/models/atom.py | 74 ++++++ tests/apimatic_core/mocks/models/orbit.py | 62 +++++ .../type_combinator_tests/__init__.py | 0 .../type_combinator_tests/test_any_of.py | 13 ++ .../type_combinator_tests/test_one_of.py | 218 ++++++++++++++++++ 15 files changed, 832 insertions(+), 4 deletions(-) create mode 100644 apimatic_core/types/union_types/__init__.py create mode 100644 apimatic_core/types/union_types/any_of.py create mode 100644 apimatic_core/types/union_types/leaf_type.py create mode 100644 apimatic_core/types/union_types/one_of.py create mode 100644 apimatic_core/types/union_types/union_type_context.py create mode 100644 tests/apimatic_core/mocks/models/atom.py create mode 100644 tests/apimatic_core/mocks/models/orbit.py create mode 100644 tests/apimatic_core/type_combinator_tests/__init__.py create mode 100644 tests/apimatic_core/type_combinator_tests/test_any_of.py create mode 100644 tests/apimatic_core/type_combinator_tests/test_one_of.py 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/union_types/__init__.py b/apimatic_core/types/union_types/__init__.py new file mode 100644 index 0000000..4df7d04 --- /dev/null +++ b/apimatic_core/types/union_types/__init__.py @@ -0,0 +1,7 @@ +__all__ = [ + "union_type", + "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..8ff72ac --- /dev/null +++ b/apimatic_core/types/union_types/any_of.py @@ -0,0 +1,21 @@ +from apimatic_core_interfaces.types.union_type import UnionType +from apimatic_core.types.union_types.union_type_context import UnionTypeContext + + +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): + return False + + def deserialize(self): + return None + + 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 + 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..79e9aa5 --- /dev/null +++ b/apimatic_core/types/union_types/leaf_type.py @@ -0,0 +1,173 @@ +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 + + +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 and context.is_nullable_or_nullable(): + self.is_valid = True + return self.is_valid + + if value is None: + self.is_valid = False + return self.is_valid + + if context.is_array() and context.is_dict() and context.is_array_of_dict(): + self.is_valid = self.validate_array_of_dict_case(value) + elif context.is_array() and context.is_dict(): + self.is_valid = self.validate_dict_of_array_case(value) + elif context.is_array(): + self.is_valid = self.validate_array_case(value) + elif context.is_dict(): + self.is_valid = self.validate_dict_case(value) + else: + self.is_valid = self.validate_item(value) + + return self + + def deserialize(self, value): + if value is None or not self.is_valid: + return None + + context = self._union_type_context + if context.is_array() and context.is_dict() and context.is_array_of_dict(): + deserialized_value = self.deserialize_array_of_dict_case(value) + elif context.is_array() and context.is_dict(): + deserialized_value = self.deserialize_dict_of_array_case(value) + elif context.is_array(): + deserialized_value = self.deserialize_array_case(value) + elif context.is_dict(): + deserialized_value = self.deserialize_dict_case(value) + else: + deserialized_value = self.deserialize_item(value) + + return deserialized_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_item(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_item(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_item(self, value): + is_native_type = self.type_to_match in UnionType.NATIVE_TYPES + + if is_native_type or self.type_to_match in [date]: + return isinstance(value, self.type_to_match) + elif self.type_to_match in [datetime]: + return isinstance(value, self.type_to_match) \ + and ApiHelper.validate_date_time_case(value, self._union_type_context.get_date_time_format()) + + return self.type_to_match.validate(value) + + def deserialize_dict_case(self, dict_value): + if not isinstance(dict_value, dict): + return None + + deserialized_value = {} + for key, value in dict_value.items(): + result_value = self.deserialize_item(value) + deserialized_value[key] = result_value + + return deserialized_value + + def deserialize_dict_of_array_case(self, dict_value): + if not isinstance(dict_value, dict): + return None + + 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): + if not isinstance(array_value, list): + return None + + deserialized_value = [] + for item in array_value: + result_value = self.deserialize_item(item) + deserialized_value.append(result_value) + + return deserialized_value + + def deserialize_array_of_dict_case(self, array_value): + if not isinstance(array_value, list): + return None + + deserialized_value = [] + for item in array_value: + result_value = self.deserialize_dict_case(item) + deserialized_value.append(result_value) + + return deserialized_value + + def deserialize_item(self, value): + is_native_type = self.type_to_match in UnionType.NATIVE_TYPES + + if is_native_type: + return value + elif self.type_to_match is date: + return ApiHelper.date_deserialize(value) + elif self.type_to_match is datetime: + return ApiHelper.datetime_deserialize( + value, self._union_type_context.get_date_time_format()) + elif hasattr(self.type_to_match, 'from_dictionary'): + return self.type_to_match.from_dictionary(value) + + return None + + 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..7dce1ac --- /dev/null +++ b/apimatic_core/types/union_types/one_of.py @@ -0,0 +1,160 @@ +import copy +from apimatic_core_interfaces.types.union_type import UnionType +from apimatic_core.types.union_types.union_type_context import UnionTypeContext + + +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 + + if value is None and context.is_nullable_or_nullable(): + self.is_valid = True + return self + + if value is None: + self.is_valid = False + return self + + if context.is_array() and context.is_dict() and context.is_array_of_dict(): + if isinstance(value, list): + self.is_valid = self.validate_array_of_dict_case(value) + else: + self.is_valid = False + elif context.is_array() and context.is_dict(): + if isinstance(value, dict): + self.is_valid = self.validate_dict_of_array_case(value) + else: + self.is_valid = False + elif context.is_array(): + if isinstance(value, list): + self.is_valid = self.validate_array_case(value) + else: + self.is_valid = False + elif context.is_dict(): + if isinstance(value, dict): + self.is_valid = self.validate_dict_case(value) + else: + self.is_valid = False + else: + matched_count = sum(union_type.validate(value).is_valid for union_type in self._union_types) + self.is_valid = matched_count == 1 + + return self + + def deserialize(self, value): + + if value is None or not self.is_valid: + return None + + context = self._union_type_context + + if context.is_array() and context.is_dict() and context.is_array_of_dict(): + deserialized_value = self.deserialize_array_of_dict_case(value) + elif context.is_array() and context.is_dict(): + deserialized_value = self.deserialize_dict_of_array_case(value) + elif context.is_array(): + deserialized_value = self.deserialize_array_case(value) + elif context.is_dict(): + deserialized_value = self.deserialize_dict_case(value) + else: + deserialized_value = self.get_deserialized_value(value) + + return deserialized_value + + def get_deserialized_value(self, value): + return [union_type.deserialize(value) for union_type in self._union_types if union_type.is_valid][0] + + def validate_array_of_dict_case(self, array_value): + if array_value is None or not array_value: + return False + + matched_count = sum(self.validate_dict_case(item) for item in array_value) + return matched_count == array_value.__len__() + + def validate_dict_of_array_case(self, dict_value): + if dict_value is None or not dict_value: + return False + + matched_count = sum(self.validate_array_case(item) for item in dict_value.values()) + return matched_count == dict_value.__len__() + + def validate_dict_case(self, dict_value): + if dict_value is None or not dict_value: + return False + + is_valid = True + self.collection_cases = {} + for key, value in dict_value.items(): + nested_cases = [] + for union_type in self._union_types: + nested_cases.append(copy.deepcopy(union_type).validate(value)) + matched_count = sum(processed_inner_type.is_valid for processed_inner_type in nested_cases) + if is_valid: + is_valid = matched_count == 1 + self.collection_cases[key] = nested_cases + return is_valid + + def validate_array_case(self, array_value): + if array_value is None or not array_value: + return False + + is_valid = True + self.collection_cases = [] + for item in array_value: + nested_cases = [] + for union_type in self._union_types: + nested_cases.append(copy.deepcopy(union_type).validate(item)) + matched_count = sum(processed_inner_type.is_valid for processed_inner_type in nested_cases) + if is_valid: + is_valid = matched_count == 1 + self.collection_cases.append(nested_cases) + return is_valid + + def deserialize_array_of_dict_case(self, array_value): + if array_value is None: + return False + + return [self.deserialize_dict_case(item) for item in array_value] + + def deserialize_dict_of_array_case(self, dict_value): + if dict_value is None: + return False + + deserialized_value = {} + for key, value in dict_value.items(): + deserialized_value[key] = self.deserialize_array_case(value) + + return deserialized_value + + def deserialize_dict_case(self, dict_value): + if dict_value is None: + return False + + deserialized_value = {} + for key, value in dict_value.items(): + valid_case = [case for case in self.collection_cases[key] if case.is_valid][0] + deserialized_value[key] = valid_case.deserialize(value) + + return deserialized_value + + def deserialize_array_case(self, array_value): + if array_value is None: + return False + + deserialized_value = [] + for index, item in enumerate(array_value): + valid_case = [case for case in self.collection_cases[index] if case.is_valid][0] + deserialized_value.append(valid_case.deserialize(item)) + + return deserialized_value + + 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 + 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..e380097 --- /dev/null +++ b/apimatic_core/types/union_types/union_type_context.py @@ -0,0 +1,74 @@ +from apimatic_core.types.datetime_format import DateTimeFormat + + +class UnionTypeContext: + + def __init__(self): + self._is_array: bool = False + self._is_dict: bool = False + self._is_array_of_dict: bool = False + self._is_optional: bool = False + self._is_nullable: bool = False + self._discriminator: str | None = None + self._discriminator_values: [] = [] + self._date_time_format: DateTimeFormat | None = None + + def array(self, is_array: bool): + self._is_array = is_array + return self + + def is_array(self): + return self._is_array + + def dict(self, is_dict: bool): + self._is_dict = is_dict + return self + + def is_dict(self): + return self._is_dict + + def array_of_dict(self, is_array_of_dict: bool): + 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: bool): + self._is_optional = is_optional + return self + + def is_optional(self): + return self._is_optional + + def nullable(self, is_nullable: bool): + self._is_nullable = is_nullable + return self + + def is_nullable(self): + return self._is_nullable + + def is_nullable_or_nullable(self): + return self._is_nullable or self._is_optional + + def discriminator(self, discriminator: str): + self._discriminator = discriminator + return self + + def get_discriminator(self): + return self._discriminator + + def discriminator_values(self, discriminator_values: []): + self._discriminator_values = discriminator_values + return self + + def get_discriminator_values(self): + return self._discriminator_values + + def date_time_format(self, date_time_format: DateTimeFormat): + self._date_time_format = date_time_format + return self + + def get_date_time_format(self): + return self._date_time_format + diff --git a/apimatic_core/utilities/api_helper.py b/apimatic_core/utilities/api_helper.py index 0db7b64..a7bef4f 100644 --- a/apimatic_core/utilities/api_helper.py +++ b/apimatic_core/utilities/api_helper.py @@ -635,3 +635,27 @@ def from_datetime(cls, date_time): def from_value(cls, value): dtime = dateutil.parser.parse(value) return cls(dtime, value) + + @classmethod + def validate_date_time_case(cls, value, date_time_format): + + if DateTimeFormat.HTTP_DATE_TIME == date_time_format: + try: + ApiHelper.HttpDateTime.from_value(value).datetime + return True + except Exception as e: + return False + + elif DateTimeFormat.UNIX_DATE_TIME == date_time_format: + try: + ApiHelper.UnixDateTime.from_value(value).datetime + return True + except: + return False + + elif DateTimeFormat.RFC3339_DATE_TIME == date_time_format: + try: + ApiHelper.RFC3339DateTime.from_value(value).datetime + return True + except: + return False diff --git a/requirements.txt b/requirements.txt index b1eae38..cb679fb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ jsonpickle~=3.0.1, >= 3.0.1 python-dateutil~=2.8.1 enum34~=1.1, >=1.1.10 -apimatic-core-interfaces~=0.1.0 +git+https://github.com/apimatic/core-interfaces-python@add-type-combinator-support requests~=2.28.1 setuptools~=67.6.0 jsonpointer~=2.3 diff --git a/setup.py b/setup.py index cdee76c..e59c9ed 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ url='https://github.com/apimatic/core-lib-python', packages=find_packages(), install_requires=[ - 'apimatic-core-interfaces~=0.1.0', + 'git+https://github.com/apimatic/core-interfaces-python@add-type-combinator-support', 'jsonpickle~=3.0.1, >= 3.0.1', 'python-dateutil~=2.8.1', 'requests~=2.28.1', diff --git a/tests/apimatic_core/__init__.py b/tests/apimatic_core/__init__.py index 4d35bea..86629f0 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' + 'api_logger_tests', + 'type_combinator_tests' ] \ 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..c989a2f --- /dev/null +++ b/tests/apimatic_core/mocks/models/atom.py @@ -0,0 +1,74 @@ +from apimatic_core.utilities.api_helper import ApiHelper + + +class Atom(object): + """Implementation of the 'Atom' model. + + TODO: type model description here. + + Attributes: + number_of_electrons (int): TODO: type description here. + number_of_protons (int): TODO: type description here. + + """ + + # Create a mapping from Model property names to API property names + _names = { + "number_of_electrons": 'AtomNumberOfElectrons', # int, bool + "number_of_protons": 'AtomNumberOfProtons' + } + + _optionals = [ + 'number_of_protons', + ] + + def __init__(self, + number_of_electrons=None, + number_of_protons=ApiHelper.SKIP): + """Constructor for the Atom class""" + + # Initialize members of the class + self.number_of_electrons = number_of_electrons + if number_of_protons is not ApiHelper.SKIP: + self.number_of_protons = 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 + + number_of_electrons = dictionary.get("AtomNumberOfElectrons") if dictionary.get("AtomNumberOfElectrons") else None + number_of_protons = dictionary.get("AtomNumberOfProtons") if dictionary.get("AtomNumberOfProtons") else ApiHelper.SKIP + # Return an object of this model + return cls(number_of_electrons, + number_of_protons) + + @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 dictionary is None: + return None + + return dictionary.get("AtomNumberOfElectrons") is not None diff --git a/tests/apimatic_core/mocks/models/orbit.py b/tests/apimatic_core/mocks/models/orbit.py new file mode 100644 index 0000000..3a529d7 --- /dev/null +++ b/tests/apimatic_core/mocks/models/orbit.py @@ -0,0 +1,62 @@ + +class Orbit(object): + + """Implementation of the 'Orbit' model. + + TODO: type model description here. + + Attributes: + number_of_electrons (int): TODO: type description here. + + """ + + # Create a mapping from Model property names to API property names + _names = { + "number_of_electrons": 'OrbitNumberOfElectrons' + } + + def __init__(self, + number_of_electrons=None): + """Constructor for the Orbit class""" + + # Initialize members of the class + self.number_of_electrons = 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 + + number_of_electrons = dictionary.get("OrbitNumberOfElectrons") if dictionary.get("OrbitNumberOfElectrons") else None + # Return an object of this model + return cls(number_of_electrons) + + @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 dictionary is None: + return None + + return dictionary.get("OrbitNumberOfElectrons") is not None diff --git a/tests/apimatic_core/type_combinator_tests/__init__.py b/tests/apimatic_core/type_combinator_tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/apimatic_core/type_combinator_tests/test_any_of.py b/tests/apimatic_core/type_combinator_tests/test_any_of.py new file mode 100644 index 0000000..90613bf --- /dev/null +++ b/tests/apimatic_core/type_combinator_tests/test_any_of.py @@ -0,0 +1,13 @@ +import pytest +from apimatic_core.types.union_types.any_of import AnyOf + + +class TestAnyOf: + + @pytest.mark.parametrize('input_value, types, expected_output', [ + (100, {int, str}, True) + ]) + def test_any_of_primitive_type(self, input_value, types, expected_output): + union_type = AnyOf() + actual_result = union_type.validate() + assert actual_result == expected_output diff --git a/tests/apimatic_core/type_combinator_tests/test_one_of.py b/tests/apimatic_core/type_combinator_tests/test_one_of.py new file mode 100644 index 0000000..a8dbdb9 --- /dev/null +++ b/tests/apimatic_core/type_combinator_tests/test_one_of.py @@ -0,0 +1,218 @@ +import dateutil +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 apimatic_core.utilities.api_helper import ApiHelper +from tests.apimatic_core.mocks.models.atom import Atom +from tests.apimatic_core.mocks.models.orbit import Orbit + + +class TestOneOf: + + @pytest.mark.parametrize('input_value, input_types, input_context, expected_output', [ + # Simple Cases + (100, [LeafType(int), LeafType(str)], UnionTypeContext(), True), + (100, [LeafType(int), LeafType(int), LeafType(str)], UnionTypeContext(), False), + ('abc', [LeafType(int), LeafType(str)], UnionTypeContext(), True), + (100.44, [LeafType(int), LeafType(str)], UnionTypeContext(), False), + + # Outer Array Cases + (['abc', 'def'], [LeafType(int), LeafType(str)], UnionTypeContext().array(True), True), + ([100, 200], [LeafType(int), LeafType(str)], UnionTypeContext().array(True), True), + ([100, 'abc'], [LeafType(int), LeafType(str)], UnionTypeContext().array(True), True), + (100, [LeafType(int), LeafType(str)], UnionTypeContext().array(True), False), + ('100', [LeafType(int), LeafType(str)], UnionTypeContext().array(True), False), + ([['abc', 'def']], [LeafType(int), LeafType(str)], UnionTypeContext().array(True), False), + + # Inner Array Cases + (['abc', 'def'], [LeafType(int, UnionTypeContext().array(True)), + LeafType(str, UnionTypeContext().array(True))], + UnionTypeContext(), True), + ([100, 200], [LeafType(int, UnionTypeContext().array(True)), LeafType(str, UnionTypeContext().array(True))], + UnionTypeContext(), True), + ([100, 'abc'], [LeafType(int, UnionTypeContext().array(True)), LeafType(str, UnionTypeContext().array(True))], + UnionTypeContext(), False), + + # Partial Array Case + ('abc', [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], + UnionTypeContext(), True), + ([100, 200], [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], + UnionTypeContext(), True), + ([100, 'abc'], [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], + UnionTypeContext(), False), + (100, [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], + UnionTypeContext(), False), + + # Array of Partial Arrays Cases + (['abc', 'def'], [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], + UnionTypeContext().array(True), True), + ([[100, 200]], [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], + UnionTypeContext().array(True), True), + ([[100, 200], 'abc'], [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], + UnionTypeContext().array(True), True), + ([[100, 'abc']], [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], + UnionTypeContext().array(True), False), + ([100], [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], + UnionTypeContext().array(True), False), + + # Array of Arrays Cases + ([['abc', 'def'], ['def', 'ghi']], [LeafType(int, UnionTypeContext().array(True)), + LeafType(str, UnionTypeContext().array(True))], + UnionTypeContext().array(True), True), + ([[100, 200], [300, 400]], [LeafType(int, UnionTypeContext().array(True)), + LeafType(str, UnionTypeContext().array(True))], + UnionTypeContext().array(True), True), + ([[100, 200], ['abc', 'def']], [LeafType(int, UnionTypeContext().array(True)), + LeafType(str, UnionTypeContext().array(True))], + UnionTypeContext().array(True), True), + ([[100, 'abc'], [200, 'def']], [LeafType(int, UnionTypeContext().array(True)), + LeafType(str, UnionTypeContext().array(True))], + UnionTypeContext().array(True), False), + ([[100, 'abc'], ['def', 'ghi']], [LeafType(int, UnionTypeContext().array(True)), + LeafType(str, UnionTypeContext().array(True))], + UnionTypeContext().array(True), False), + ([[100.45, 200.45], [100, 200]], [LeafType(int, UnionTypeContext().array(True)), + LeafType(str, UnionTypeContext().array(True))], + UnionTypeContext().array(True), False), + ([['abc', 'def'], [100.45, 200.45]], [LeafType(int, UnionTypeContext().array(True)), + LeafType(str, UnionTypeContext().array(True))], + UnionTypeContext().array(True), False), + + # Outer Dictionary Cases + ({'key0': 'abc', 'key1': 'def'}, [LeafType(int), LeafType(str)], UnionTypeContext().dict(True), True), + ({'key0': 100, 'key1': 200}, [LeafType(int), LeafType(str)], UnionTypeContext().dict(True), True), + ({'key0': 100, 'key2': 'abc'}, [LeafType(int), LeafType(str)], UnionTypeContext().dict(True), True), + (100, [LeafType(int), LeafType(str)], UnionTypeContext().dict(True), False), + ('100', [LeafType(int), LeafType(str)], UnionTypeContext().dict(True), False), + ({'key0': {'key0': 'abc', 'key1': 'def'}}, [LeafType(int), LeafType(str)], + UnionTypeContext().dict(True), False), + + # Inner Dictionary Cases + ({'key0': 'abc', 'key1': 'def'}, [LeafType(int, UnionTypeContext().dict(True)), + LeafType(str, UnionTypeContext().dict(True))], + UnionTypeContext(), True), + ({'key0': 100, 'key1': 200}, [LeafType(int, UnionTypeContext().dict(True)), + LeafType(str, UnionTypeContext().dict(True))], + UnionTypeContext(), True), + ({'key0': 100, 'key1': 'abc'}, [LeafType(int, UnionTypeContext().dict(True)), + LeafType(str, UnionTypeContext().array(True))], + UnionTypeContext(), False), + + # Partial Dictionary Cases + ('abc', [LeafType(int, UnionTypeContext().dict(True)), LeafType(str)], + UnionTypeContext(), True), + ({'key0': 100, 'key1': 200}, [LeafType(int, UnionTypeContext().dict(True)), LeafType(str)], + UnionTypeContext(), True), + ({'key0': 100, 'key1': 'abc'}, [LeafType(int, UnionTypeContext().dict(True)), LeafType(str)], + UnionTypeContext(), False), + (100, [LeafType(int, UnionTypeContext().dict(True)), LeafType(str)], + UnionTypeContext(), False), + + # Dictionary of Partial Dictionary Cases + ({'key0': 'abc', 'key1': 'def'}, [LeafType(int, UnionTypeContext().dict(True)), LeafType(str)], + UnionTypeContext().dict(True), True), + ({'key0': {'key0': 100, 'key1': 200}}, [LeafType(int, UnionTypeContext().dict(True)), LeafType(str)], + UnionTypeContext().dict(True), True), + ({'key0': {'key0': 100, 'key1': 200}, 'key1': 'abc'}, [LeafType(int, UnionTypeContext().dict(True)), LeafType(str)], + UnionTypeContext().dict(True), True), + ({'key0': {'key0': 100, 'key1': 'abc'}}, [LeafType(int, UnionTypeContext().dict(True)), LeafType(str)], + UnionTypeContext().dict(True), False), + ({'key0': 100}, [LeafType(int, UnionTypeContext().dict(True)), LeafType(str)], + UnionTypeContext().dict(True), False), + + # 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': 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': 'abc', 'key1': 'def'}}, + [LeafType(int, UnionTypeContext().dict(True)), LeafType(str, UnionTypeContext().dict(True))], + UnionTypeContext().dict(True), True), + ({'key0': {'key0': 100, 'key1': 'abc'}, 'key1': {'key0': 200, 'key1': 'def'}}, + [LeafType(int, UnionTypeContext().dict(True)), LeafType(str, UnionTypeContext().dict(True))], + UnionTypeContext().dict(True), False), + ({'key0': {'key0': 100, 'key1': 'abc'}, 'key1': {'key0': 'abc', 'key1': 'def'}}, + [LeafType(int, UnionTypeContext().dict(True)), LeafType(str, UnionTypeContext().dict(True))], + UnionTypeContext().dict(True), False), + ({'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), + ({'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), + + # 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': 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': '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), + ([{'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), + + # 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': [100, 200], 'key1': [300, 400]}, + [LeafType(int, UnionTypeContext().dict(True).array(True)), + LeafType(str, UnionTypeContext().dict(True).array(True))], + UnionTypeContext(), True), + ({'key0': ['abc', 200], 'key1': ['def', 400]}, + [LeafType(int, UnionTypeContext().dict(True).array(True)), + LeafType(str, UnionTypeContext().dict(True).array(True))], + UnionTypeContext(), False), + ({'key0': [100, 200], 'key1': ['abc', 'def']}, + [LeafType(int, UnionTypeContext().dict(True).array(True)), + LeafType(str, UnionTypeContext().dict(True).array(True))], + UnionTypeContext(), False), + ]) + def test_one_of_primitive_type(self, input_value, input_types, input_context, expected_output): + union_type = OneOf(input_types, input_context) + union_type_result = union_type.validate(input_value) + actual_result = union_type_result.is_valid + deserialized_value = union_type_result.deserialize(input_value) + assert actual_result == expected_output + + @pytest.mark.parametrize('input_value, input_types, input_context, expected_output', [ + # Simple Cases + ('{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}', [LeafType(Atom), LeafType(Orbit)], + UnionTypeContext(), True), + ('{"OrbitNumberOfElectrons": 4}', [LeafType(Atom), LeafType(Orbit)], + UnionTypeContext(), True), + ('[{"OrbitNumberOfElectrons": 4}, {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}]', + [LeafType(Atom), LeafType(Orbit)], UnionTypeContext().array(True), True), + ('[{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, ' + '{"AtomNumberOfElectrons": 4, "AtomNumberOfProtons": 10}]', + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext().array(True))], + UnionTypeContext(), True), + ('[{"OrbitNumberOfElectrons": 4}, {"OrbitNumberOfElectrons": 5}]', + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext().array(True))], + UnionTypeContext(), True), + ('[{"OrbitNumberOfElectrons": 4}, {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}]', + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext().array(True))], + UnionTypeContext(), False), + ('{"key0": {"OrbitNumberOfElectrons": 4}, "key1": {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}}', + [LeafType(Atom), LeafType(Orbit)], UnionTypeContext().dict(True), True), + ]) + def test_one_of_custom_type(self, input_value, input_types, input_context, expected_output): + union_type = OneOf(input_types, input_context) + deserialized_dict_input = ApiHelper.json_deserialize(input_value, as_dict=True) + union_type_result = union_type.validate(deserialized_dict_input) + actual_result = union_type_result.is_valid + deserialized_value = union_type_result.deserialize(deserialized_dict_input) + assert actual_result == expected_output From 4d9838ea763e4d1ea7e721dfb5b54c63db62863c Mon Sep 17 00:00:00 2001 From: MaryamAdnan3 Date: Fri, 19 May 2023 10:37:57 +0500 Subject: [PATCH 02/49] Add tests in one_of.py --- apimatic_core/types/union_types/any_of.py | 147 +++++++++++- apimatic_core/types/union_types/one_of.py | 2 +- .../types/union_types/union_type_context.py | 2 +- tests/apimatic_core/mocks/models/atom.py | 5 + tests/apimatic_core/mocks/models/orbit.py | 5 + .../type_combinator_tests/test_one_of.py | 221 +++++++++++------- 6 files changed, 292 insertions(+), 90 deletions(-) diff --git a/apimatic_core/types/union_types/any_of.py b/apimatic_core/types/union_types/any_of.py index 8ff72ac..16ac78c 100644 --- a/apimatic_core/types/union_types/any_of.py +++ b/apimatic_core/types/union_types/any_of.py @@ -1,3 +1,4 @@ +import copy from apimatic_core_interfaces.types.union_type import UnionType from apimatic_core.types.union_types.union_type_context import UnionTypeContext @@ -8,11 +9,149 @@ def __init__(self, union_types, union_type_context: UnionTypeContext = UnionType super(AnyOf, self).__init__(union_types, union_type_context) self.collection_cases = None - def validate(self): - return False + def validate(self, value): + context = self._union_type_context - def deserialize(self): - return None + if value is None and context.is_nullable_or_optional(): + self.is_valid = True + return self + + if value is None: + self.is_valid = False + return self + + if context.is_array() and context.is_dict() and context.is_array_of_dict(): + if isinstance(value, list): + self.is_valid = self.validate_array_of_dict_case(value) + else: + self.is_valid = False + elif context.is_array() and context.is_dict(): + if isinstance(value, dict): + self.is_valid = self.validate_dict_of_array_case(value) + else: + self.is_valid = False + elif context.is_array(): + if isinstance(value, list): + self.is_valid = self.validate_array_case(value) + else: + self.is_valid = False + elif context.is_dict(): + if isinstance(value, dict): + self.is_valid = self.validate_dict_case(value) + else: + self.is_valid = False + else: + matched_count = sum(union_type.validate(value).is_valid for union_type in self._union_types) + self.is_valid = matched_count > 1 + + return self + + def deserialize(self, value): + + if value is None or not self.is_valid: + return None + + context = self._union_type_context + + if context.is_array() and context.is_dict() and context.is_array_of_dict(): + deserialized_value = self.deserialize_array_of_dict_case(value) + elif context.is_array() and context.is_dict(): + deserialized_value = self.deserialize_dict_of_array_case(value) + elif context.is_array(): + deserialized_value = self.deserialize_array_case(value) + elif context.is_dict(): + deserialized_value = self.deserialize_dict_case(value) + else: + deserialized_value = self.get_deserialized_value(value) + + return deserialized_value + + def get_deserialized_value(self, value): + return [union_type.deserialize(value) for union_type in self._union_types if union_type.is_valid][0] + + def validate_array_of_dict_case(self, array_value): + if array_value is None or not array_value: + return False + + matched_count = sum(self.validate_dict_case(item) for item in array_value) + return matched_count == array_value.__len__() + + def validate_dict_of_array_case(self, dict_value): + if dict_value is None or not dict_value: + return False + + matched_count = sum(self.validate_array_case(item) for item in dict_value.values()) + return matched_count == dict_value.__len__() + + def validate_dict_case(self, dict_value): + if dict_value is None or not dict_value: + return False + + is_valid = True + self.collection_cases = {} + for key, value in dict_value.items(): + nested_cases = [] + for union_type in self._union_types: + nested_cases.append(copy.deepcopy(union_type).validate(value)) + matched_count = sum(processed_inner_type.is_valid for processed_inner_type in nested_cases) + if is_valid: + is_valid = matched_count > 1 + self.collection_cases[key] = nested_cases + return is_valid + + def validate_array_case(self, array_value): + if array_value is None or not array_value: + return False + + is_valid = True + self.collection_cases = [] + for item in array_value: + nested_cases = [] + for union_type in self._union_types: + nested_cases.append(copy.deepcopy(union_type).validate(item)) + matched_count = sum(processed_inner_type.is_valid for processed_inner_type in nested_cases) + if is_valid: + is_valid = matched_count > 1 + self.collection_cases.append(nested_cases) + return is_valid + + def deserialize_array_of_dict_case(self, array_value): + if array_value is None: + return False + + return [self.deserialize_dict_case(item) for item in array_value] + + def deserialize_dict_of_array_case(self, dict_value): + if dict_value is None: + return False + + deserialized_value = {} + for key, value in dict_value.items(): + deserialized_value[key] = self.deserialize_array_case(value) + + return deserialized_value + + def deserialize_dict_case(self, dict_value): + if dict_value is None: + return False + + deserialized_value = {} + for key, value in dict_value.items(): + valid_case = [case for case in self.collection_cases[key] if case.is_valid][0] + deserialized_value[key] = valid_case.deserialize(value) + + return deserialized_value + + def deserialize_array_case(self, array_value): + if array_value is None: + return False + + deserialized_value = [] + for index, item in enumerate(array_value): + valid_case = [case for case in self.collection_cases[index] if case.is_valid][0] + deserialized_value.append(valid_case.deserialize(item)) + + return deserialized_value def __deepcopy__(self, memo={}): copy_object = AnyOf(self._union_types, self._union_type_context) diff --git a/apimatic_core/types/union_types/one_of.py b/apimatic_core/types/union_types/one_of.py index 7dce1ac..2de8f80 100644 --- a/apimatic_core/types/union_types/one_of.py +++ b/apimatic_core/types/union_types/one_of.py @@ -12,7 +12,7 @@ def __init__(self, union_types, union_type_context: UnionTypeContext = UnionType def validate(self, value): context = self._union_type_context - if value is None and context.is_nullable_or_nullable(): + if value is None and context.is_nullable_or_optional(): self.is_valid = True return self diff --git a/apimatic_core/types/union_types/union_type_context.py b/apimatic_core/types/union_types/union_type_context.py index e380097..f2c4bc7 100644 --- a/apimatic_core/types/union_types/union_type_context.py +++ b/apimatic_core/types/union_types/union_type_context.py @@ -48,7 +48,7 @@ def nullable(self, is_nullable: bool): def is_nullable(self): return self._is_nullable - def is_nullable_or_nullable(self): + def is_nullable_or_optional(self): return self._is_nullable or self._is_optional def discriminator(self, discriminator: str): diff --git a/tests/apimatic_core/mocks/models/atom.py b/tests/apimatic_core/mocks/models/atom.py index c989a2f..d825beb 100644 --- a/tests/apimatic_core/mocks/models/atom.py +++ b/tests/apimatic_core/mocks/models/atom.py @@ -72,3 +72,8 @@ def validate(cls, dictionary): return None return dictionary.get("AtomNumberOfElectrons") is not None + + def __eq__(self, other): + if isinstance(self, other.__class__): + return self.number_of_electrons == other.number_of_electrons and self.number_of_protons == other.number_of_protons + return False diff --git a/tests/apimatic_core/mocks/models/orbit.py b/tests/apimatic_core/mocks/models/orbit.py index 3a529d7..875daa9 100644 --- a/tests/apimatic_core/mocks/models/orbit.py +++ b/tests/apimatic_core/mocks/models/orbit.py @@ -60,3 +60,8 @@ def validate(cls, dictionary): return None return dictionary.get("OrbitNumberOfElectrons") is not None + + def __eq__(self, other): + if isinstance(self, other.__class__): + return self.number_of_electrons == other.number_of_electrons + return False \ No newline at end of file diff --git a/tests/apimatic_core/type_combinator_tests/test_one_of.py b/tests/apimatic_core/type_combinator_tests/test_one_of.py index a8dbdb9..72cfef2 100644 --- a/tests/apimatic_core/type_combinator_tests/test_one_of.py +++ b/tests/apimatic_core/type_combinator_tests/test_one_of.py @@ -1,6 +1,8 @@ -import dateutil +from datetime import date +import datetime import pytest - +from apimatic_core.types.datetime_format import DateTimeFormat +from datetime import datetime 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 @@ -11,208 +13,259 @@ class TestOneOf: - @pytest.mark.parametrize('input_value, input_types, input_context, expected_output', [ + @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, [LeafType(int), LeafType(int), LeafType(str)], UnionTypeContext(), False), - ('abc', [LeafType(int), LeafType(str)], UnionTypeContext(), True), - (100.44, [LeafType(int), LeafType(str)], UnionTypeContext(), False), + (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), + (datetime.date(1994, 2, 13, 14, 1, 54), [LeafType(datetime, UnionTypeContext().date_time_format(DateTimeFormat.RFC3339_DATE_TIME)), LeafType(bool)], UnionTypeContext(), True, "02/13/1994 14:01:54"), + (datetime.date(1994, 11, 6, 8, 49, 37), [LeafType(datetime, UnionTypeContext().date_time_format(DateTimeFormat.HTTP_DATE_TIME)), LeafType(bool)], UnionTypeContext(), True, "Sun, 06 Nov 1994 08:49:37 GMT"), + (datetime.date(1994, 11, 6, 8, 49, 37), [LeafType(datetime, UnionTypeContext().date_time_format(DateTimeFormat.UNIX_DATE_TIME)), LeafType(bool)], UnionTypeContext(), True, 1484719381), + (date(1994, 11, 6), [LeafType(date, UnionTypeContext()), LeafType(bool)], UnionTypeContext(), True, 1484719381), + (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), - ([100, 200], [LeafType(int), LeafType(str)], UnionTypeContext().array(True), True), - ([100, 'abc'], [LeafType(int), LeafType(str)], UnionTypeContext().array(True), True), - (100, [LeafType(int), LeafType(str)], UnionTypeContext().array(True), False), - ('100', [LeafType(int), LeafType(str)], UnionTypeContext().array(True), False), - ([['abc', 'def']], [LeafType(int), LeafType(str)], UnionTypeContext().array(True), False), + (['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), + UnionTypeContext(), True, ['abc', 'def']), ([100, 200], [LeafType(int, UnionTypeContext().array(True)), LeafType(str, UnionTypeContext().array(True))], - UnionTypeContext(), True), + UnionTypeContext(), True, [100, 200]), ([100, 'abc'], [LeafType(int, UnionTypeContext().array(True)), LeafType(str, UnionTypeContext().array(True))], - UnionTypeContext(), False), + UnionTypeContext(), True, [100, 'abc']), # Partial Array Case ('abc', [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], - UnionTypeContext(), True), + UnionTypeContext(), True, 'abc'), ([100, 200], [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], - UnionTypeContext(), True), + UnionTypeContext(), True, [100, 200]), ([100, 'abc'], [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], - UnionTypeContext(), False), + UnionTypeContext(), False, None), (100, [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], - UnionTypeContext(), False), + UnionTypeContext(), False, None), # Array of Partial Arrays Cases (['abc', 'def'], [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], - UnionTypeContext().array(True), True), + UnionTypeContext().array(True), True, ['abc', 'def']), ([[100, 200]], [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], - UnionTypeContext().array(True), True), + UnionTypeContext().array(True), True, [[100, 200]]), ([[100, 200], 'abc'], [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], - UnionTypeContext().array(True), True), + UnionTypeContext().array(True), True, [[100, 200], 'abc']), ([[100, 'abc']], [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], - UnionTypeContext().array(True), False), + UnionTypeContext().array(True), False, None), ([100], [LeafType(int, UnionTypeContext().array(True)), LeafType(str)], - UnionTypeContext().array(True), False), + 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), + 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), + 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), + 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), + UnionTypeContext().array(True), False, None), ([[100, 'abc'], ['def', 'ghi']], [LeafType(int, UnionTypeContext().array(True)), LeafType(str, UnionTypeContext().array(True))], - UnionTypeContext().array(True), False), + 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), + 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), + UnionTypeContext().array(True), False, None), # Outer Dictionary Cases - ({'key0': 'abc', 'key1': 'def'}, [LeafType(int), LeafType(str)], UnionTypeContext().dict(True), True), - ({'key0': 100, 'key1': 200}, [LeafType(int), LeafType(str)], UnionTypeContext().dict(True), True), - ({'key0': 100, 'key2': 'abc'}, [LeafType(int), LeafType(str)], UnionTypeContext().dict(True), True), - (100, [LeafType(int), LeafType(str)], UnionTypeContext().dict(True), False), - ('100', [LeafType(int), LeafType(str)], UnionTypeContext().dict(True), False), + ({'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), + UnionTypeContext().dict(True), False, None), # Inner Dictionary Cases ({'key0': 'abc', 'key1': 'def'}, [LeafType(int, UnionTypeContext().dict(True)), LeafType(str, UnionTypeContext().dict(True))], - UnionTypeContext(), True), + UnionTypeContext(), True, {'key0': 'abc', 'key1': 'def'}), ({'key0': 100, 'key1': 200}, [LeafType(int, UnionTypeContext().dict(True)), LeafType(str, UnionTypeContext().dict(True))], - UnionTypeContext(), True), + UnionTypeContext(), True, {'key0': 100, 'key1': 200}), ({'key0': 100, 'key1': 'abc'}, [LeafType(int, UnionTypeContext().dict(True)), LeafType(str, UnionTypeContext().array(True))], - UnionTypeContext(), False), + UnionTypeContext(), False, None), # Partial Dictionary Cases ('abc', [LeafType(int, UnionTypeContext().dict(True)), LeafType(str)], - UnionTypeContext(), True), + UnionTypeContext(), True, 'abc'), ({'key0': 100, 'key1': 200}, [LeafType(int, UnionTypeContext().dict(True)), LeafType(str)], - UnionTypeContext(), True), + UnionTypeContext(), True, {'key0': 100, 'key1': 200}), ({'key0': 100, 'key1': 'abc'}, [LeafType(int, UnionTypeContext().dict(True)), LeafType(str)], - UnionTypeContext(), False), + UnionTypeContext(), False, None), (100, [LeafType(int, UnionTypeContext().dict(True)), LeafType(str)], - UnionTypeContext(), False), + UnionTypeContext(), False, None), # Dictionary of Partial Dictionary Cases ({'key0': 'abc', 'key1': 'def'}, [LeafType(int, UnionTypeContext().dict(True)), LeafType(str)], - UnionTypeContext().dict(True), True), + UnionTypeContext().dict(True), True, {'key0': 'abc', 'key1': 'def'}), ({'key0': {'key0': 100, 'key1': 200}}, [LeafType(int, UnionTypeContext().dict(True)), LeafType(str)], - UnionTypeContext().dict(True), True), + 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), + 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), + UnionTypeContext().dict(True), False, None), ({'key0': 100}, [LeafType(int, UnionTypeContext().dict(True)), LeafType(str)], - UnionTypeContext().dict(True), False), + 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), + 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), + 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), + 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), + 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), + 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), + 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), + UnionTypeContext().dict(True), False, None), - # array of dictionary cases + # 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), + 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), + 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), + 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), + 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}]), # 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), + 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), + 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), + 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), + 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']}), ]) - def test_one_of_primitive_type(self, input_value, input_types, input_context, expected_output): + + def test_one_of_primitive_type(self, input_value, input_types, input_context, expected_is_valid_output, expected_deserialized_value_output): union_type = OneOf(input_types, input_context) union_type_result = union_type.validate(input_value) - actual_result = union_type_result.is_valid - deserialized_value = union_type_result.deserialize(input_value) - assert actual_result == expected_output + actual_is_valid = union_type_result.is_valid + actual_deserialized_value = union_type_result.deserialize(input_value) + 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', [ + @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), + UnionTypeContext(), True, Atom(2, 5)), ('{"OrbitNumberOfElectrons": 4}', [LeafType(Atom), LeafType(Orbit)], - UnionTypeContext(), True), + UnionTypeContext(), True, Orbit(4)), ('[{"OrbitNumberOfElectrons": 4}, {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}]', - [LeafType(Atom), LeafType(Orbit)], UnionTypeContext().array(True), True), - ('[{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, ' - '{"AtomNumberOfElectrons": 4, "AtomNumberOfProtons": 10}]', + [LeafType(Atom), LeafType(Orbit)], UnionTypeContext().array(True), True, [Orbit(4), Atom(2, 5)]), + ('[{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, {"AtomNumberOfElectrons": 4, "AtomNumberOfProtons": 10}]', [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext().array(True))], - UnionTypeContext(), 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), + UnionTypeContext(), True, [Orbit(4), Orbit(5)]), ('[{"OrbitNumberOfElectrons": 4}, {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}]', [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext().array(True))], - UnionTypeContext(), False), + UnionTypeContext(), False, None), ('{"key0": {"OrbitNumberOfElectrons": 4}, "key1": {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}}', - [LeafType(Atom), LeafType(Orbit)], UnionTypeContext().dict(True), True), + [LeafType(Atom), LeafType(Orbit)], UnionTypeContext().dict(True), True, + {"key0": Orbit(4), "key1": Atom(2, 5)}), ]) - def test_one_of_custom_type(self, input_value, input_types, input_context, expected_output): + def test_one_of_custom_type(self, input_value, input_types, input_context, expected_is_valid_output, expected_deserialized_value_output): union_type = OneOf(input_types, input_context) deserialized_dict_input = ApiHelper.json_deserialize(input_value, as_dict=True) union_type_result = union_type.validate(deserialized_dict_input) - actual_result = union_type_result.is_valid - deserialized_value = union_type_result.deserialize(deserialized_dict_input) - assert actual_result == expected_output + actual_is_valid = union_type_result.is_valid + actual_deserialized_value = union_type_result.deserialize(deserialized_dict_input) + assert actual_is_valid == expected_is_valid_output + assert actual_deserialized_value == expected_deserialized_value_output \ No newline at end of file From 9cd6056a23c2bfa01d49eb6bac0e314f60ee3a2f Mon Sep 17 00:00:00 2001 From: MaryamAdnan3 Date: Fri, 19 May 2023 12:09:33 +0500 Subject: [PATCH 03/49] Adding complex type array cases --- .../type_combinator_tests/test_one_of.py | 232 +++++++++++++++++- 1 file changed, 229 insertions(+), 3 deletions(-) diff --git a/tests/apimatic_core/type_combinator_tests/test_one_of.py b/tests/apimatic_core/type_combinator_tests/test_one_of.py index 72cfef2..86138fb 100644 --- a/tests/apimatic_core/type_combinator_tests/test_one_of.py +++ b/tests/apimatic_core/type_combinator_tests/test_one_of.py @@ -246,8 +246,30 @@ def test_one_of_primitive_type(self, input_value, input_types, input_context, ex UnionTypeContext(), True, Atom(2, 5)), ('{"OrbitNumberOfElectrons": 4}', [LeafType(Atom), LeafType(Orbit)], UnionTypeContext(), True, Orbit(4)), + ('{"key0": {"OrbitNumberOfElectrons": 4}, "key1": {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}}', + [LeafType(Atom), LeafType(Orbit)], UnionTypeContext().dict(True), True, + {"key0": Orbit(4), "key1": Atom(2, 5)}), + + # 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)], + UnionTypeContext(), True, [Atom(2, 5), Atom(4, 10)]), + ('[{"OrbitNumberOfElectrons": 4}, {"OrbitNumberOfElectrons": 5}]', + [LeafType(Atom), LeafType(Orbit), UnionTypeContext().array(True)], + UnionTypeContext(), 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))], + 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)]), @@ -257,9 +279,213 @@ def test_one_of_primitive_type(self, input_value, input_types, input_context, ex ('[{"OrbitNumberOfElectrons": 4}, {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}]', [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext().array(True))], UnionTypeContext(), False, None), - ('{"key0": {"OrbitNumberOfElectrons": 4}, "key1": {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}}', - [LeafType(Atom), LeafType(Orbit)], UnionTypeContext().dict(True), True, - {"key0": Orbit(4), "key1": Atom(2, 5)}), + ('{"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)], + True, Orbit(4)), + ('[{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, {"AtomNumberOfElectrons": 4, "AtomNumberOfProtons": 10}]', + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit)], + UnionTypeContext(), True, [Atom(2, 5), Atom(4, 10)]), + ('[{"OrbitNumberOfElectrons": 4}, {"AtomNumberOfElectrons": 4, "AtomNumberOfProtons": 10}]', + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit)], + 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().array(True), True, [Atom(2, 5), 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': '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), + + # 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}]), + + # 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']}), ]) def test_one_of_custom_type(self, input_value, input_types, input_context, expected_is_valid_output, expected_deserialized_value_output): union_type = OneOf(input_types, input_context) From f4ab0530e13ea3dc08f0640d070f09f070a8dc81 Mon Sep 17 00:00:00 2001 From: MaryamAdnan3 Date: Fri, 19 May 2023 15:33:56 +0500 Subject: [PATCH 04/49] add tests for complex type dictionary cases --- .../type_combinator_tests/test_one_of.py | 115 ++++++++++-------- 1 file changed, 63 insertions(+), 52 deletions(-) diff --git a/tests/apimatic_core/type_combinator_tests/test_one_of.py b/tests/apimatic_core/type_combinator_tests/test_one_of.py index 86138fb..e030dbf 100644 --- a/tests/apimatic_core/type_combinator_tests/test_one_of.py +++ b/tests/apimatic_core/type_combinator_tests/test_one_of.py @@ -347,71 +347,82 @@ def test_one_of_primitive_type(self, input_value, input_types, input_context, ex 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), + ({'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(int), LeafType(str)], UnionTypeContext().dict(True), False, None), + ({"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, [LeafType(int), LeafType(str)], 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': '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))], + ({'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(int, UnionTypeContext().dict(True)), LeafType(str, UnionTypeContext().dict(True))], + UnionTypeContext(), True, {'key0': Orbit(8), 'key1': Orbit(10)}), + ({'key0': {"OrbitNumberOfElectrons": 8}, 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}}, [LeafType(int, UnionTypeContext().dict(True)), + LeafType(str, UnionTypeContext().dict(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)], + ({"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': '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)], + ({'key0': {"OrbitNumberOfElectrons": 8}, 'key1': {"OrbitNumberOfElectrons": 10}}, [LeafType(Orbit, UnionTypeContext().dict(True)), LeafType(Atom)], + 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': 'abc', 'key1': 'def'}, 'key1': {'key0': 'ghi', 'key1': 'jkl'}}, - [LeafType(int, UnionTypeContext().array(True)), LeafType(str, UnionTypeContext().dict(True))], + ({'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().array(True)), LeafType(Orbit, 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))], + {'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': Atom(2, 5), 'key1': Atom(2, 10)}, 'key1': {'key0': Orbit(10), 'key1': Orbit(8)}}), + ({'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': 'abc', 'key1': 'def'}, 'key1': {'key0': 100.45, 'key1': 200.45}}, + ({'key0': {'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}, 'key1': {"OrbitNumberOfElectrons": 10}}, + 'key1': {'key0': {"OrbitNumberOfElectrons": 12}, 'key1': {"OrbitNumberOfElectrons": 14}}}, [LeafType(int, UnionTypeContext().dict(True)), LeafType(str, UnionTypeContext().dict(True))], UnionTypeContext().dict(True), False, None), From ad3b5788f9a274ff23af7b6e8c1bb4c67e1ba5d9 Mon Sep 17 00:00:00 2001 From: MaryamAdnan3 Date: Fri, 19 May 2023 16:43:33 +0500 Subject: [PATCH 05/49] added array of dictionary and dict of array complex types tests --- .../type_combinator_tests/test_one_of.py | 126 ++++++++++-------- 1 file changed, 70 insertions(+), 56 deletions(-) diff --git a/tests/apimatic_core/type_combinator_tests/test_one_of.py b/tests/apimatic_core/type_combinator_tests/test_one_of.py index e030dbf..6edc068 100644 --- a/tests/apimatic_core/type_combinator_tests/test_one_of.py +++ b/tests/apimatic_core/type_combinator_tests/test_one_of.py @@ -427,76 +427,90 @@ def test_one_of_primitive_type(self, input_value, input_types, input_context, ex 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))], + ([{'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': '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))], + ([{'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': 'abc', 'key1': 'def'}, {'key0': 'ghi', 'key1': 'jkl'}], - [LeafType(int, UnionTypeContext()), LeafType(str, UnionTypeContext())], + ([{'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': 'abc', 'key1': 'def'}, {'key0': 'ghi', 'key1': 'jkl'}]), - ([{'key0': 100, 'key1': 200}, {'key0': 300, 'key1': 400}], - [LeafType(int, UnionTypeContext()), LeafType(str, UnionTypeContext())], + [{'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': 100, 'key1': 200}, {'key0': 300, 'key1': 400}]), - ([{'key0': 'abc', 'key1': 200}, {'key0': 'def', 'key1': 400}], - [LeafType(int, UnionTypeContext()), LeafType(str, UnionTypeContext())], + [{'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": 10}, 'key1': {"OrbitNumberOfElectrons": 12}}], + [LeafType(Atom, UnionTypeContext()), LeafType(Orbit, 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())], + [{'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': 'abc', 'key1': 'def'}, {'key0': 100, 'key1': 200}]), + [{'key0': Atom(2, 10), 'key1': Atom(2, 12)}, {'key0': Orbit(10), 'key1': Orbit(12)}]), # 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))], + ({'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': [100, 200], 'key1': ['abc', 'def']}, - [LeafType(int, UnionTypeContext().dict(True).array(True)), - LeafType(str, UnionTypeContext().dict(True).array(True))], + ({'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': ['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': [{"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): union_type = OneOf(input_types, input_context) From 251dbed84d29728db9e24020d2af3ff72a952467 Mon Sep 17 00:00:00 2001 From: Hamza Shoukat <50887243+hamzashoukat94@users.noreply.github.com> Date: Wed, 24 May 2023 12:11:29 +0500 Subject: [PATCH 06/49] Update requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index cb679fb..b1eae38 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ jsonpickle~=3.0.1, >= 3.0.1 python-dateutil~=2.8.1 enum34~=1.1, >=1.1.10 -git+https://github.com/apimatic/core-interfaces-python@add-type-combinator-support +apimatic-core-interfaces~=0.1.0 requests~=2.28.1 setuptools~=67.6.0 jsonpointer~=2.3 From 3d7659cfce53afb7af2d278913fa93eba4d2ba0a Mon Sep 17 00:00:00 2001 From: Hamza Shoukat <50887243+hamzashoukat94@users.noreply.github.com> Date: Wed, 24 May 2023 12:12:19 +0500 Subject: [PATCH 07/49] Update setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e59c9ed..cdee76c 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ url='https://github.com/apimatic/core-lib-python', packages=find_packages(), install_requires=[ - 'git+https://github.com/apimatic/core-interfaces-python@add-type-combinator-support', + 'apimatic-core-interfaces~=0.1.0', 'jsonpickle~=3.0.1, >= 3.0.1', 'python-dateutil~=2.8.1', 'requests~=2.28.1', From b1a566bdde3b1a32380f52fff237230fa22700bd Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Wed, 24 May 2023 18:26:23 +0500 Subject: [PATCH 08/49] adds support for discriminator and date & time validator for OAF along with unit tests --- apimatic_core/__init__.py | 3 +- apimatic_core/exceptions/__init__.py | 4 + .../exceptions/anyof_validation_exception.py | 5 + .../exceptions/oneof_validation_exception.py | 5 + apimatic_core/types/parameter.py | 8 + apimatic_core/types/union_types/any_of.py | 14 +- apimatic_core/types/union_types/leaf_type.py | 47 +- apimatic_core/types/union_types/one_of.py | 9 +- .../types/union_types/union_type_context.py | 53 +- apimatic_core/utilities/__init__.py | 3 +- apimatic_core/utilities/api_helper.py | 62 +- apimatic_core/utilities/datetime_helper.py | 57 + tests/apimatic_core/mocks/models/deer.py | 74 ++ tests/apimatic_core/mocks/models/lion.py | 88 ++ tests/apimatic_core/mocks/models/rabbit.py | 88 ++ .../type_combinator_tests/test_any_of.py | 14 +- .../type_combinator_tests/test_one_of.py | 1107 +++++++++-------- .../utility_tests/test_datetime_helper.py | 43 + 18 files changed, 1117 insertions(+), 567 deletions(-) create mode 100644 apimatic_core/exceptions/__init__.py create mode 100644 apimatic_core/exceptions/anyof_validation_exception.py create mode 100644 apimatic_core/exceptions/oneof_validation_exception.py create mode 100644 apimatic_core/utilities/datetime_helper.py create mode 100644 tests/apimatic_core/mocks/models/deer.py create mode 100644 tests/apimatic_core/mocks/models/lion.py create mode 100644 tests/apimatic_core/mocks/models/rabbit.py create mode 100644 tests/apimatic_core/utility_tests/test_datetime_helper.py 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/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/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/any_of.py b/apimatic_core/types/union_types/any_of.py index 16ac78c..794ed42 100644 --- a/apimatic_core/types/union_types/any_of.py +++ b/apimatic_core/types/union_types/any_of.py @@ -1,6 +1,7 @@ import copy 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 class AnyOf(UnionType): @@ -41,8 +42,7 @@ def validate(self, value): else: self.is_valid = False else: - matched_count = sum(union_type.validate(value).is_valid for union_type in self._union_types) - self.is_valid = matched_count > 1 + self.is_valid = ApiHelper.get_matched_count(value, self._union_types, False) > 0 return self @@ -67,7 +67,7 @@ def deserialize(self, value): return deserialized_value def get_deserialized_value(self, value): - return [union_type.deserialize(value) for union_type in self._union_types if union_type.is_valid][0] + return [union_type for union_type in self._union_types if union_type.is_valid][0].deserialize(value) def validate_array_of_dict_case(self, array_value): if array_value is None or not array_value: @@ -93,9 +93,9 @@ def validate_dict_case(self, dict_value): nested_cases = [] for union_type in self._union_types: nested_cases.append(copy.deepcopy(union_type).validate(value)) - matched_count = sum(processed_inner_type.is_valid for processed_inner_type in nested_cases) + matched_count = ApiHelper.get_matched_count(value, nested_cases, False) if is_valid: - is_valid = matched_count > 1 + is_valid = matched_count > 0 self.collection_cases[key] = nested_cases return is_valid @@ -109,9 +109,9 @@ def validate_array_case(self, array_value): nested_cases = [] for union_type in self._union_types: nested_cases.append(copy.deepcopy(union_type).validate(item)) - matched_count = sum(processed_inner_type.is_valid for processed_inner_type in nested_cases) + matched_count = ApiHelper.get_matched_count(item, nested_cases, False) if is_valid: - is_valid = matched_count > 1 + is_valid = matched_count > 0 self.collection_cases.append(nested_cases) return is_valid diff --git a/apimatic_core/types/union_types/leaf_type.py b/apimatic_core/types/union_types/leaf_type.py index 79e9aa5..968ca82 100644 --- a/apimatic_core/types/union_types/leaf_type.py +++ b/apimatic_core/types/union_types/leaf_type.py @@ -1,7 +1,12 @@ from datetime import date, datetime + +import dateutil from apimatic_core_interfaces.types.union_type import UnionType + +from apimatic_core.types.datetime_format import DateTimeFormat 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 class LeafType(UnionType): @@ -97,15 +102,41 @@ def validate_array_of_dict_case(self, array_value): return True def validate_item(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 + + if self.type_to_match in [datetime]: + if isinstance(value, ApiHelper.RFC3339DateTime): + return context.get_date_time_format() == DateTimeFormat.RFC3339_DATE_TIME + elif isinstance(value, ApiHelper.HttpDateTime): + return context.get_date_time_format() == DateTimeFormat.HTTP_DATE_TIME + elif isinstance(value, ApiHelper.UnixDateTime): + return context.get_date_time_format() == DateTimeFormat.UNIX_DATE_TIME + else: + return DateTimeHelper.validate_datetime(value, self._union_type_context.get_date_time_format()) + + if self.type_to_match is date: + return DateTimeHelper.validate_date(value) + is_native_type = self.type_to_match in UnionType.NATIVE_TYPES - if is_native_type or self.type_to_match in [date]: + if is_native_type: return isinstance(value, self.type_to_match) - elif self.type_to_match in [datetime]: - return isinstance(value, self.type_to_match) \ - and ApiHelper.validate_date_time_case(value, self._union_type_context.get_date_time_format()) - return self.type_to_match.validate(value) + discriminator = self._union_type_context.get_discriminator() + discriminator_value = self._union_type_context.get_discriminator_value() + if discriminator and discriminator_value: + return value.get(discriminator) == discriminator_value and self.type_to_match.validate(value) + + if hasattr(self.type_to_match, 'validate'): + return self.type_to_match.validate(value) + + return False def deserialize_dict_case(self, dict_value): if not isinstance(dict_value, dict): @@ -154,13 +185,13 @@ def deserialize_array_of_dict_case(self, array_value): def deserialize_item(self, value): is_native_type = self.type_to_match in UnionType.NATIVE_TYPES - if is_native_type: - return value - elif self.type_to_match is date: + if self.type_to_match is date: return ApiHelper.date_deserialize(value) elif self.type_to_match is datetime: return ApiHelper.datetime_deserialize( value, self._union_type_context.get_date_time_format()) + elif is_native_type: + return value elif hasattr(self.type_to_match, 'from_dictionary'): return self.type_to_match.from_dictionary(value) diff --git a/apimatic_core/types/union_types/one_of.py b/apimatic_core/types/union_types/one_of.py index 2de8f80..6d14f95 100644 --- a/apimatic_core/types/union_types/one_of.py +++ b/apimatic_core/types/union_types/one_of.py @@ -1,6 +1,7 @@ import copy 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 class OneOf(UnionType): @@ -41,13 +42,11 @@ def validate(self, value): else: self.is_valid = False else: - matched_count = sum(union_type.validate(value).is_valid for union_type in self._union_types) - self.is_valid = matched_count == 1 + self.is_valid = ApiHelper.get_matched_count(value, self._union_types, True) == 1 return self def deserialize(self, value): - if value is None or not self.is_valid: return None @@ -93,7 +92,7 @@ def validate_dict_case(self, dict_value): nested_cases = [] for union_type in self._union_types: nested_cases.append(copy.deepcopy(union_type).validate(value)) - matched_count = sum(processed_inner_type.is_valid for processed_inner_type in nested_cases) + matched_count = ApiHelper.get_matched_count(value, nested_cases, True) if is_valid: is_valid = matched_count == 1 self.collection_cases[key] = nested_cases @@ -109,7 +108,7 @@ def validate_array_case(self, array_value): nested_cases = [] for union_type in self._union_types: nested_cases.append(copy.deepcopy(union_type).validate(item)) - matched_count = sum(processed_inner_type.is_valid for processed_inner_type in nested_cases) + matched_count = ApiHelper.get_matched_count(item, nested_cases, True) if is_valid: is_valid = matched_count == 1 self.collection_cases.append(nested_cases) diff --git a/apimatic_core/types/union_types/union_type_context.py b/apimatic_core/types/union_types/union_type_context.py index f2c4bc7..7b5cec6 100644 --- a/apimatic_core/types/union_types/union_type_context.py +++ b/apimatic_core/types/union_types/union_type_context.py @@ -1,47 +1,56 @@ -from apimatic_core.types.datetime_format import DateTimeFormat - 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): + 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) + def __init__(self): - self._is_array: bool = False - self._is_dict: bool = False - self._is_array_of_dict: bool = False - self._is_optional: bool = False - self._is_nullable: bool = False - self._discriminator: str | None = None - self._discriminator_values: [] = [] - self._date_time_format: DateTimeFormat | None = None - - def array(self, is_array: bool): + 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 + + 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: bool): + 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: bool): + 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: bool): + 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: bool): + def nullable(self, is_nullable): self._is_nullable = is_nullable return self @@ -51,21 +60,21 @@ def is_nullable(self): def is_nullable_or_optional(self): return self._is_nullable or self._is_optional - def discriminator(self, discriminator: str): + def discriminator(self, discriminator): self._discriminator = discriminator return self def get_discriminator(self): return self._discriminator - def discriminator_values(self, discriminator_values: []): - self._discriminator_values = discriminator_values + def discriminator_value(self, discriminator_value): + self._discriminator_value = discriminator_value return self - def get_discriminator_values(self): - return self._discriminator_values + def get_discriminator_value(self): + return self._discriminator_value - def date_time_format(self, date_time_format: DateTimeFormat): + def date_time_format(self, date_time_format): self._date_time_format = date_time_format return self diff --git a/apimatic_core/utilities/__init__.py b/apimatic_core/utilities/__init__.py index 9bac680..1b691c0 100644 --- a/apimatic_core/utilities/__init__.py +++ b/apimatic_core/utilities/__init__.py @@ -3,5 +3,6 @@ 'auth_helper', 'xml_helper', 'comparison_helper', - 'file_helper' + 'file_helper', + 'datetime_helper' ] \ No newline at end of file diff --git a/apimatic_core/utilities/api_helper.py b/apimatic_core/utilities/api_helper.py index a7bef4f..fcfd99c 100644 --- a/apimatic_core/utilities/api_helper.py +++ b/apimatic_core/utilities/api_helper.py @@ -5,6 +5,7 @@ import calendar import email.utils as eut from time import mktime +from typing import Union, List, Dict import jsonpickle import dateutil.parser @@ -199,6 +200,22 @@ def datetime_deserialize(response, datetime_format): else: return ApiHelper.RFC3339DateTime.from_value(response).datetime + @staticmethod + def validate_union_type(union_type, value): + union_type_result = union_type.validate(value) + if not union_type_result.is_valid: + raise ValueError(union_type_result.errors) + + return True + + @staticmethod + def deserialize_union_type(union_type, response): + union_type_result = union_type.validate(response) + if not union_type_result.is_valid: + raise ValueError(union_type_result.errors) + + return union_type_result.deserialize(response) + @staticmethod def get_content_type(value): """Get content type header for oneof. @@ -571,6 +588,27 @@ def resolve_template_placeholders(placeholders, values, template): return template + @staticmethod + def get_matched_count(value, union_types, is_for_one_of): + matched_count = sum(union_type.validate(value).is_valid for union_type in 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 + + # Check through normal schema validation flow when discriminator exits but still invalid + 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 matched_count == 0 and has_discriminator_cases: + for union_type in union_types: + union_type.get_context().discriminator(None) + union_type.get_context().discriminator_value(None) + matched_count = sum(union_type.validate(value).is_valid for union_type in union_types) + + return matched_count + class CustomDate(object): """ A base class for wrapper classes of datetime. @@ -635,27 +673,3 @@ def from_datetime(cls, date_time): def from_value(cls, value): dtime = dateutil.parser.parse(value) return cls(dtime, value) - - @classmethod - def validate_date_time_case(cls, value, date_time_format): - - if DateTimeFormat.HTTP_DATE_TIME == date_time_format: - try: - ApiHelper.HttpDateTime.from_value(value).datetime - return True - except Exception as e: - return False - - elif DateTimeFormat.UNIX_DATE_TIME == date_time_format: - try: - ApiHelper.UnixDateTime.from_value(value).datetime - return True - except: - return False - - elif DateTimeFormat.RFC3339_DATE_TIME == date_time_format: - try: - ApiHelper.RFC3339DateTime.from_value(value).datetime - return True - except: - return False diff --git a/apimatic_core/utilities/datetime_helper.py b/apimatic_core/utilities/datetime_helper.py new file mode 100644 index 0000000..0b9a578 --- /dev/null +++ b/apimatic_core/utilities/datetime_helper.py @@ -0,0 +1,57 @@ +from datetime import datetime, date + +import dateutil + +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): + dateutil.parser.parse(date_value.isoformat()) + return True + elif isinstance(date_value, str): + dateutil.parser.parse(date_value) + 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: + 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/tests/apimatic_core/mocks/models/deer.py b/tests/apimatic_core/mocks/models/deer.py new file mode 100644 index 0000000..1352341 --- /dev/null +++ b/tests/apimatic_core/mocks/models/deer.py @@ -0,0 +1,74 @@ +class Deer(object): + """Implementation of the 'Deer' model. + + TODO: type model description here. + + Attributes: + name (string): TODO: type description here. + weight (string): TODO: type description here. + mtype (string): 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 properties. + + Args: + dictionary: the dictionary to be validated against. + + Returns: + boolean : if value is valid for this model. + + """ + if dictionary is None: + return None + + return dictionary.get("name") is not None and \ + dictionary.get("weight") is not None and \ + dictionary.get("type") is not None diff --git a/tests/apimatic_core/mocks/models/lion.py b/tests/apimatic_core/mocks/models/lion.py new file mode 100644 index 0000000..fc288a8 --- /dev/null +++ b/tests/apimatic_core/mocks/models/lion.py @@ -0,0 +1,88 @@ +from apimatic_core.utilities.api_helper import ApiHelper + + +class Lion(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 dictionary is None: + return None + + return dictionary.get("id") is not None and \ + dictionary.get("weight") is not None and \ + dictionary.get("type") is not None diff --git a/tests/apimatic_core/mocks/models/rabbit.py b/tests/apimatic_core/mocks/models/rabbit.py new file mode 100644 index 0000000..2a71354 --- /dev/null +++ b/tests/apimatic_core/mocks/models/rabbit.py @@ -0,0 +1,88 @@ +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 dictionary is None: + return None + + return dictionary.get("id") is not None and \ + dictionary.get("weight") is not None and \ + dictionary.get("type") is not None diff --git a/tests/apimatic_core/type_combinator_tests/test_any_of.py b/tests/apimatic_core/type_combinator_tests/test_any_of.py index 90613bf..acd823d 100644 --- a/tests/apimatic_core/type_combinator_tests/test_any_of.py +++ b/tests/apimatic_core/type_combinator_tests/test_any_of.py @@ -1,13 +1,15 @@ import pytest 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.union_type_context import UnionTypeContext class TestAnyOf: - @pytest.mark.parametrize('input_value, types, expected_output', [ - (100, {int, str}, True) + @pytest.mark.parametrize('input_value, input_types, input_outer_context, expected_validity', [ + (100, [LeafType(int), LeafType(str)], UnionTypeContext(), True) ]) - def test_any_of_primitive_type(self, input_value, types, expected_output): - union_type = AnyOf() - actual_result = union_type.validate() - assert actual_result == expected_output + def test_any_of_primitive_type_validity(self, input_value, input_types, input_outer_context, expected_validity): + union_type = AnyOf(input_types, input_outer_context) + actual_result = union_type.validate(input_value) + assert actual_result.is_valid == expected_validity diff --git a/tests/apimatic_core/type_combinator_tests/test_one_of.py b/tests/apimatic_core/type_combinator_tests/test_one_of.py index 6edc068..e4fd92b 100644 --- a/tests/apimatic_core/type_combinator_tests/test_one_of.py +++ b/tests/apimatic_core/type_combinator_tests/test_one_of.py @@ -1,238 +1,242 @@ -from datetime import date -import datetime +from datetime import datetime, date import pytest + from apimatic_core.types.datetime_format import DateTimeFormat -from datetime import datetime 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 tests.apimatic_core.base import Base from tests.apimatic_core.mocks.models.atom import Atom +from tests.apimatic_core.mocks.models.deer import Deer +from tests.apimatic_core.mocks.models.lion import Lion 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), - (datetime.date(1994, 2, 13, 14, 1, 54), [LeafType(datetime, UnionTypeContext().date_time_format(DateTimeFormat.RFC3339_DATE_TIME)), LeafType(bool)], UnionTypeContext(), True, "02/13/1994 14:01:54"), - (datetime.date(1994, 11, 6, 8, 49, 37), [LeafType(datetime, UnionTypeContext().date_time_format(DateTimeFormat.HTTP_DATE_TIME)), LeafType(bool)], UnionTypeContext(), True, "Sun, 06 Nov 1994 08:49:37 GMT"), - (datetime.date(1994, 11, 6, 8, 49, 37), [LeafType(datetime, UnionTypeContext().date_time_format(DateTimeFormat.UNIX_DATE_TIME)), LeafType(bool)], UnionTypeContext(), True, 1484719381), - (date(1994, 11, 6), [LeafType(date, UnionTypeContext()), LeafType(bool)], UnionTypeContext(), True, 1484719381), - (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(), True, [100, 'abc']), - - # 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)), + @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), 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)), + 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), - - # 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)), + 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': '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), - - # 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}]), - - # 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']}), - ]) + 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), - def test_one_of_primitive_type(self, input_value, input_types, input_context, expected_is_valid_output, expected_deserialized_value_output): + # 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}]), + + # 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']}), + ]) + def test_one_of_primitive_type(self, input_value, input_types, input_context, expected_is_valid_output, + expected_deserialized_value_output): union_type = OneOf(input_types, input_context) union_type_result = union_type.validate(input_value) actual_is_valid = union_type_result.is_valid @@ -240,283 +244,400 @@ def test_one_of_primitive_type(self, input_value, input_types, input_context, ex 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_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)), - ('{"key0": {"OrbitNumberOfElectrons": 4}, "key1": {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}}', - [LeafType(Atom), LeafType(Orbit)], UnionTypeContext().dict(True), True, - {"key0": Orbit(4), "key1": Atom(2, 5)}), - - # 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)], - UnionTypeContext(), True, [Atom(2, 5), Atom(4, 10)]), - ('[{"OrbitNumberOfElectrons": 4}, {"OrbitNumberOfElectrons": 5}]', - [LeafType(Atom), LeafType(Orbit), UnionTypeContext().array(True)], - UnionTypeContext(), 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))], - 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)], - True, Orbit(4)), - ('[{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, {"AtomNumberOfElectrons": 4, "AtomNumberOfProtons": 10}]', - [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit)], - UnionTypeContext(), True, [Atom(2, 5), Atom(4, 10)]), - ('[{"OrbitNumberOfElectrons": 4}, {"AtomNumberOfElectrons": 4, "AtomNumberOfProtons": 10}]', - [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit)], - 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().array(True), True, [Atom(2, 5), 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(int), LeafType(str)], UnionTypeContext().dict(True), False, None), - ({"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, [LeafType(int), LeafType(str)], 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(int, UnionTypeContext().dict(True)), LeafType(str, UnionTypeContext().dict(True))], - UnionTypeContext(), True, {'key0': Orbit(8), 'key1': Orbit(10)}), - ({'key0': {"OrbitNumberOfElectrons": 8}, 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}}, [LeafType(int, UnionTypeContext().dict(True)), - LeafType(str, 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(Orbit, UnionTypeContext().dict(True)), LeafType(Atom)], - 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().array(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': Atom(2, 5), 'key1': Atom(2, 10)}, 'key1': {'key0': Orbit(10), 'key1': Orbit(8)}}), - ({'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(int, UnionTypeContext().dict(True)), LeafType(str, 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": 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': 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): + @pytest.mark.parametrize( + 'input_value, input_types, input_context, expected_validity', [ + (Base.get_rfc3339_datetime(datetime(1994, 11, 6, 8, 49, 37)), + [LeafType(datetime, UnionTypeContext().date_time_format(DateTimeFormat.RFC3339_DATE_TIME)), + LeafType(bool)], UnionTypeContext(), True), + (Base.get_http_datetime(datetime(1994, 11, 6, 8, 49, 37)), + [LeafType(datetime, UnionTypeContext().date_time_format(DateTimeFormat.HTTP_DATE_TIME)), LeafType(bool)], + UnionTypeContext(), True), + (1480809600, + [LeafType(datetime, UnionTypeContext().date_time_format(DateTimeFormat.UNIX_DATE_TIME)), LeafType(bool)], + UnionTypeContext(), True), + (date(1994, 11, 6), [LeafType(date), LeafType(bool)], UnionTypeContext(), True) + ]) + def test_one_of_date_and_datetime_validity(self, input_value, input_types, input_context, expected_validity): + union_type = OneOf(input_types, input_context) + union_type_result = union_type.validate(input_value) + assert union_type_result.is_valid == expected_validity + + @pytest.mark.parametrize( + 'input_value, input_types, input_context, expected_value, expected_type', [ + (Base.get_rfc3339_datetime(datetime(1994, 11, 6, 8, 49, 37)), + [LeafType(datetime, UnionTypeContext().date_time_format(DateTimeFormat.RFC3339_DATE_TIME)), + LeafType(bool)], UnionTypeContext(), datetime(1994, 11, 6, 8, 49, 37), datetime), + (Base.get_http_datetime(datetime(1994, 11, 6, 8, 49, 37)), + [LeafType(datetime, UnionTypeContext().date_time_format(DateTimeFormat.HTTP_DATE_TIME)), LeafType(bool)], + UnionTypeContext(), datetime(1994, 11, 6, 8, 49, 37), datetime), + (1480809600, + [LeafType(datetime, UnionTypeContext().date_time_format(DateTimeFormat.UNIX_DATE_TIME)), LeafType(bool)], + UnionTypeContext(), datetime.utcfromtimestamp(1480809600), datetime), + ('1994-11-06', [LeafType(date), LeafType(bool)], UnionTypeContext(), date(1994, 11, 6), date) + ]) + def test_one_of_date_and_datetime(self, input_value, input_types, input_context, expected_value, expected_type): + union_type = OneOf(input_types, input_context) + union_type_result = union_type.validate(input_value) + actual_deserialized_value = union_type_result.deserialize(input_value) + assert isinstance(actual_deserialized_value, expected_type) + 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)), + ('{"key0": {"OrbitNumberOfElectrons": 4}, "key1": {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}}', + [LeafType(Atom), LeafType(Orbit)], UnionTypeContext().dict(True), True, + {"key0": Orbit(4), "key1": Atom(2, 5)}), + + # 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(), True, Orbit(4)), + ( + '[{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, {"AtomNumberOfElectrons": 4, "AtomNumberOfProtons": 10}]', + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit)], + UnionTypeContext(), True, [Atom(2, 5), Atom(4, 10)]), + ('[{"OrbitNumberOfElectrons": 4}, {"AtomNumberOfElectrons": 4, "AtomNumberOfProtons": 10}]', + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit)], + 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().array(True), True, [Atom(2, 5), 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(int), LeafType(str)], UnionTypeContext().dict(True), False, None), + ({"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, [LeafType(int), LeafType(str)], + 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(int, UnionTypeContext().dict(True)), LeafType(str, UnionTypeContext().dict(True))], + UnionTypeContext(), True, {'key0': Orbit(8), 'key1': Orbit(10)}), + ({'key0': {"OrbitNumberOfElectrons": 8}, 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}}, + [LeafType(int, UnionTypeContext().dict(True)), + LeafType(str, 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(Orbit, UnionTypeContext().dict(True)), LeafType(Atom)], + 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().array(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': Atom(2, 5), 'key1': Atom(2, 10)}, 'key1': {'key0': Orbit(10), 'key1': Orbit(8)}}), + ({'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(int, UnionTypeContext().dict(True)), LeafType(str, 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": 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': 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): union_type = OneOf(input_types, input_context) deserialized_dict_input = ApiHelper.json_deserialize(input_value, as_dict=True) union_type_result = union_type.validate(deserialized_dict_input) actual_is_valid = union_type_result.is_valid actual_deserialized_value = union_type_result.deserialize(deserialized_dict_input) assert actual_is_valid == expected_is_valid_output - assert actual_deserialized_value == expected_deserialized_value_output \ No newline at end of file + 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(Lion, 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) + ]) + def test_one_of_custom_type(self, input_value, input_types, input_context, expected_output): + union_type = OneOf(input_types, input_context) + deserialized_dict_input = ApiHelper.json_deserialize(input_value, as_dict=True) + union_type_result = union_type.validate(deserialized_dict_input) + actual_result = union_type_result.is_valid + deserialized_value = union_type_result.deserialize(deserialized_dict_input) + assert actual_result == expected_output 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..61b8c41 --- /dev/null +++ b/tests/apimatic_core/utility_tests/test_datetime_helper.py @@ -0,0 +1,43 @@ +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), + ('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), + ('1994/11/06', True), + ('19941106', True), + ('941106', True), + (date(1994, 11, 6), True), + (date(94, 11, 6), True), + ('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 + + + + From d3da798f07462e163d5a7454283f7d2a87c9c7b3 Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Thu, 25 May 2023 12:14:16 +0500 Subject: [PATCH 09/49] added handling for case when generate models setting is false during code-gen. --- apimatic_core/types/union_types/leaf_type.py | 43 ++++++++++++------- .../type_combinator_tests/test_one_of.py | 21 ++++++++- 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/apimatic_core/types/union_types/leaf_type.py b/apimatic_core/types/union_types/leaf_type.py index 968ca82..aa990cb 100644 --- a/apimatic_core/types/union_types/leaf_type.py +++ b/apimatic_core/types/union_types/leaf_type.py @@ -111,33 +111,44 @@ def validate_item(self, value): return False if self.type_to_match in [datetime]: - if isinstance(value, ApiHelper.RFC3339DateTime): - return context.get_date_time_format() == DateTimeFormat.RFC3339_DATE_TIME - elif isinstance(value, ApiHelper.HttpDateTime): - return context.get_date_time_format() == DateTimeFormat.HTTP_DATE_TIME - elif isinstance(value, ApiHelper.UnixDateTime): - return context.get_date_time_format() == DateTimeFormat.UNIX_DATE_TIME - else: - return DateTimeHelper.validate_datetime(value, self._union_type_context.get_date_time_format()) + return self.validate_date_time(value, context) if self.type_to_match is date: return DateTimeHelper.validate_date(value) - is_native_type = self.type_to_match in UnionType.NATIVE_TYPES + 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 is_native_type: + is_native_type = self.type_to_match in UnionType.NATIVE_TYPES + if is_native_type or self.type_to_match is dict: return isinstance(value, self.type_to_match) - discriminator = self._union_type_context.get_discriminator() - discriminator_value = self._union_type_context.get_discriminator_value() - if discriminator and discriminator_value: - return value.get(discriminator) == discriminator_value and self.type_to_match.validate(value) - if hasattr(self.type_to_match, 'validate'): return self.type_to_match.validate(value) return False + def validate_with_discriminator(self, discriminator, discriminator_value, value): + if value.get(discriminator) == discriminator_value: + if hasattr(self.type_to_match, 'validate'): + return self.type_to_match.validate(value) + else: + return isinstance(value, self.type_to_match) + else: + return False + + def validate_date_time(self, value, context): + if isinstance(value, ApiHelper.RFC3339DateTime): + return context.get_date_time_format() == DateTimeFormat.RFC3339_DATE_TIME + elif isinstance(value, ApiHelper.HttpDateTime): + return context.get_date_time_format() == DateTimeFormat.HTTP_DATE_TIME + elif isinstance(value, ApiHelper.UnixDateTime): + return context.get_date_time_format() == DateTimeFormat.UNIX_DATE_TIME + else: + return DateTimeHelper.validate_datetime(value, context.get_date_time_format()) + def deserialize_dict_case(self, dict_value): if not isinstance(dict_value, dict): return None @@ -190,7 +201,7 @@ def deserialize_item(self, value): elif self.type_to_match is datetime: return ApiHelper.datetime_deserialize( value, self._union_type_context.get_date_time_format()) - elif is_native_type: + elif is_native_type or self.type_to_match is dict: return value elif hasattr(self.type_to_match, 'from_dictionary'): return self.type_to_match.from_dictionary(value) diff --git a/tests/apimatic_core/type_combinator_tests/test_one_of.py b/tests/apimatic_core/type_combinator_tests/test_one_of.py index e4fd92b..e2dc685 100644 --- a/tests/apimatic_core/type_combinator_tests/test_one_of.py +++ b/tests/apimatic_core/type_combinator_tests/test_one_of.py @@ -632,7 +632,26 @@ def test_one_of_custom_type(self, input_value, input_types, input_context, expec ('{"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) + 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_custom_type(self, input_value, input_types, input_context, expected_output): union_type = OneOf(input_types, input_context) From 48b3d86cde35b1842467d76631a6c2a495abf4d9 Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Fri, 26 May 2023 18:48:03 +0500 Subject: [PATCH 10/49] fixed array of map and map of array cases for oneof and added guard clause for deserialize method in oneOf and anyOf classes --- apimatic_core/types/union_types/any_of.py | 2 + apimatic_core/types/union_types/leaf_type.py | 3 + apimatic_core/types/union_types/one_of.py | 72 +++++++++++++------- 3 files changed, 51 insertions(+), 26 deletions(-) diff --git a/apimatic_core/types/union_types/any_of.py b/apimatic_core/types/union_types/any_of.py index 794ed42..6c3cb94 100644 --- a/apimatic_core/types/union_types/any_of.py +++ b/apimatic_core/types/union_types/any_of.py @@ -52,6 +52,8 @@ def deserialize(self, value): return None context = self._union_type_context + if value is None and context.is_nullable_or_optional(): + return None if context.is_array() and context.is_dict() and context.is_array_of_dict(): deserialized_value = self.deserialize_array_of_dict_case(value) diff --git a/apimatic_core/types/union_types/leaf_type.py b/apimatic_core/types/union_types/leaf_type.py index aa990cb..c5bfbe9 100644 --- a/apimatic_core/types/union_types/leaf_type.py +++ b/apimatic_core/types/union_types/leaf_type.py @@ -44,6 +44,9 @@ def deserialize(self, value): return None context = self._union_type_context + if value is None and context.is_nullable_or_optional(): + return None + if context.is_array() and context.is_dict() and context.is_array_of_dict(): deserialized_value = self.deserialize_array_of_dict_case(value) elif context.is_array() and context.is_dict(): diff --git a/apimatic_core/types/union_types/one_of.py b/apimatic_core/types/union_types/one_of.py index 6d14f95..2bce8e3 100644 --- a/apimatic_core/types/union_types/one_of.py +++ b/apimatic_core/types/union_types/one_of.py @@ -23,22 +23,22 @@ def validate(self, value): if context.is_array() and context.is_dict() and context.is_array_of_dict(): if isinstance(value, list): - self.is_valid = self.validate_array_of_dict_case(value) + self.is_valid, self.collection_cases = self.validate_array_of_dict_case(value) else: self.is_valid = False elif context.is_array() and context.is_dict(): if isinstance(value, dict): - self.is_valid = self.validate_dict_of_array_case(value) + self.is_valid, self.collection_cases = self.validate_dict_of_array_case(value) else: self.is_valid = False elif context.is_array(): if isinstance(value, list): - self.is_valid = self.validate_array_case(value) + self.is_valid, self.collection_cases = self.validate_array_case(value) else: self.is_valid = False elif context.is_dict(): if isinstance(value, dict): - self.is_valid = self.validate_dict_case(value) + self.is_valid, self.collection_cases = self.validate_dict_case(value) else: self.is_valid = False else: @@ -52,14 +52,17 @@ def deserialize(self, value): context = self._union_type_context + if value is None and context.is_nullable_or_optional(): + return None + if context.is_array() and context.is_dict() and context.is_array_of_dict(): deserialized_value = self.deserialize_array_of_dict_case(value) elif context.is_array() and context.is_dict(): deserialized_value = self.deserialize_dict_of_array_case(value) elif context.is_array(): - deserialized_value = self.deserialize_array_case(value) + deserialized_value = self.deserialize_array_case(value, self.collection_cases) elif context.is_dict(): - deserialized_value = self.deserialize_dict_case(value) + deserialized_value = self.deserialize_dict_case(value, self.collection_cases) else: deserialized_value = self.get_deserialized_value(value) @@ -71,23 +74,35 @@ def get_deserialized_value(self, value): def validate_array_of_dict_case(self, array_value): if array_value is None or not array_value: return False - - matched_count = sum(self.validate_dict_case(item) for item in array_value) - return matched_count == array_value.__len__() + collection_cases = [] + valid_cases = [] + for item in array_value: + case_validity, inner_dictionary = self.validate_dict_case(item) + collection_cases.append(inner_dictionary) + valid_cases.append(case_validity) + is_valid = sum(valid_cases) == array_value.__len__() + return tuple((is_valid, collection_cases)) def validate_dict_of_array_case(self, dict_value): if dict_value is None or not dict_value: - return False + return tuple((False, None)) - matched_count = sum(self.validate_array_case(item) for item in dict_value.values()) - return matched_count == dict_value.__len__() + collection_cases = {} + valid_cases = [] + for key, item in dict_value.items(): + case_validity, inner_array = self.validate_array_case(item) + collection_cases[key] = inner_array + valid_cases.append(case_validity) + + is_valid = sum(valid_cases) == dict_value.__len__() + return tuple((is_valid, collection_cases)) def validate_dict_case(self, dict_value): if dict_value is None or not dict_value: - return False + return tuple((False, None)) is_valid = True - self.collection_cases = {} + collection_cases = {} for key, value in dict_value.items(): nested_cases = [] for union_type in self._union_types: @@ -95,15 +110,15 @@ def validate_dict_case(self, dict_value): matched_count = ApiHelper.get_matched_count(value, nested_cases, True) if is_valid: is_valid = matched_count == 1 - self.collection_cases[key] = nested_cases - return is_valid + collection_cases[key] = nested_cases + return tuple((is_valid, collection_cases)) def validate_array_case(self, array_value): if array_value is None or not array_value: - return False + return tuple((False, None)) is_valid = True - self.collection_cases = [] + collection_cases = [] for item in array_value: nested_cases = [] for union_type in self._union_types: @@ -111,14 +126,17 @@ def validate_array_case(self, array_value): matched_count = ApiHelper.get_matched_count(item, nested_cases, True) if is_valid: is_valid = matched_count == 1 - self.collection_cases.append(nested_cases) - return is_valid + collection_cases.append(nested_cases) + return tuple((is_valid, collection_cases)) def deserialize_array_of_dict_case(self, array_value): if array_value is None: return False + deserialized_value = [] + for index, item in enumerate(array_value): + deserialized_value.append(self.deserialize_dict_case(item, self.collection_cases[index])) - return [self.deserialize_dict_case(item) for item in array_value] + return deserialized_value def deserialize_dict_of_array_case(self, dict_value): if dict_value is None: @@ -126,28 +144,30 @@ def deserialize_dict_of_array_case(self, dict_value): deserialized_value = {} for key, value in dict_value.items(): - deserialized_value[key] = self.deserialize_array_case(value) + deserialized_value[key] = self.deserialize_array_case(value, self.collection_cases[key]) return deserialized_value - def deserialize_dict_case(self, dict_value): + @staticmethod + def deserialize_dict_case(dict_value, collection_cases): if dict_value is None: return False deserialized_value = {} for key, value in dict_value.items(): - valid_case = [case for case in self.collection_cases[key] if case.is_valid][0] + valid_case = [case for case in collection_cases[key] if case.is_valid][0] deserialized_value[key] = valid_case.deserialize(value) return deserialized_value - def deserialize_array_case(self, array_value): + @staticmethod + def deserialize_array_case(array_value, collection_cases): if array_value is None: return False deserialized_value = [] for index, item in enumerate(array_value): - valid_case = [case for case in self.collection_cases[index] if case.is_valid][0] + valid_case = [case for case in collection_cases[index] if case.is_valid][0] deserialized_value.append(valid_case.deserialize(item)) return deserialized_value From 3fd4bcda8138105f6bf4c5e468efe0cfb2433c6d Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Fri, 26 May 2023 19:54:21 +0500 Subject: [PATCH 11/49] fixed issues in APIHelper and updated test cases accordingly --- apimatic_core/request_builder.py | 2 - apimatic_core/utilities/api_helper.py | 47 ++++++++++--------- .../test_request_builder.py | 42 ++++++++--------- .../utility_tests/test_api_helper.py | 46 +++++------------- 4 files changed, 57 insertions(+), 80 deletions(-) diff --git a/apimatic_core/request_builder.py b/apimatic_core/request_builder.py index a320aa5..516e098 100644 --- a/apimatic_core/request_builder.py +++ b/apimatic_core/request_builder.py @@ -184,8 +184,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/utilities/api_helper.py b/apimatic_core/utilities/api_helper.py index fcfd99c..d176879 100644 --- a/apimatic_core/utilities/api_helper.py +++ b/apimatic_core/utilities/api_helper.py @@ -29,26 +29,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. @@ -80,18 +60,38 @@ 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): + value.append(ApiHelper.json_serialize(item, False)) + elif 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): + value[key] = ApiHelper.json_serialize(item, False) + elif 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) @@ -209,7 +209,10 @@ def validate_union_type(union_type, value): return True @staticmethod - def deserialize_union_type(union_type, response): + 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) if not union_type_result.is_valid: raise ValueError(union_type_result.errors) 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..3510455 100644 --- a/tests/apimatic_core/request_builder_tests/test_request_builder.py +++ b/tests/apimatic_core/request_builder_tests/test_request_builder.py @@ -417,27 +417,27 @@ 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): - 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) \ - .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): + # http_request = self.new_request_builder \ + # .body_param(Parameter() + # .key(input_body_key) + # .value(input_body_param_value)) \ + # .body_serializer(ApiHelper.json_serialize_wrapped_params) \ + # .should_wrap_body_param(input_should_wrap_body_param) \ + # .build(self.global_configuration) + # assert http_request.parameters == expected_body_param_value @pytest.mark.parametrize('input_body_param_value, expected_body_param_value', [ (Base.xml_model(), '' diff --git a/tests/apimatic_core/utility_tests/test_api_helper.py b/tests/apimatic_core/utility_tests/test_api_helper.py index 09379e8..d3fefeb 100644 --- a/tests/apimatic_core/utility_tests/test_api_helper.py +++ b/tests/apimatic_core/utility_tests/test_api_helper.py @@ -16,24 +16,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, ' @@ -98,23 +80,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): From 001b2619818f7afaca4413987134f138a9479b6c Mon Sep 17 00:00:00 2001 From: MaryamAdnan3 Date: Mon, 29 May 2023 10:34:57 +0500 Subject: [PATCH 12/49] Add enum simple and array cases in One of and replicated one cases in any of --- apimatic_core/types/union_types/leaf_type.py | 2 + tests/apimatic_core/mocks/models/__init__.py | 1 + tests/apimatic_core/mocks/models/days.py | 21 +- tests/apimatic_core/mocks/models/months.py | 63 ++ .../type_combinator_tests/test_any_of.py | 716 +++++++++++++++++- .../type_combinator_tests/test_one_of.py | 111 ++- 6 files changed, 879 insertions(+), 35 deletions(-) create mode 100644 tests/apimatic_core/mocks/models/months.py diff --git a/apimatic_core/types/union_types/leaf_type.py b/apimatic_core/types/union_types/leaf_type.py index aa990cb..e770db3 100644 --- a/apimatic_core/types/union_types/leaf_type.py +++ b/apimatic_core/types/union_types/leaf_type.py @@ -205,6 +205,8 @@ def deserialize_item(self, value): return value elif hasattr(self.type_to_match, 'from_dictionary'): return self.type_to_match.from_dictionary(value) + else: + return value return None diff --git a/tests/apimatic_core/mocks/models/__init__.py b/tests/apimatic_core/mocks/models/__init__.py index da1e60f..85b5702 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', diff --git a/tests/apimatic_core/mocks/models/days.py b/tests/apimatic_core/mocks/models/days.py index f40b115..70ec83e 100644 --- a/tests/apimatic_core/mocks/models/days.py +++ b/tests/apimatic_core/mocks/models/days.py @@ -1,5 +1,5 @@ - -class Days(object): +from enum import Enum +class Days(Enum): """Implementation of the 'Days' enum. @@ -29,3 +29,20 @@ 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 + + values = [member.value for member in Days] + return value in values \ No newline at end of file diff --git a/tests/apimatic_core/mocks/models/months.py b/tests/apimatic_core/mocks/models/months.py new file mode 100644 index 0000000..bdbd298 --- /dev/null +++ b/tests/apimatic_core/mocks/models/months.py @@ -0,0 +1,63 @@ +from enum import Enum + +class Months(Enum): + + """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. + + """ + + 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 + + values = [member.value for member in Months] + return value in values \ No newline at end of file diff --git a/tests/apimatic_core/type_combinator_tests/test_any_of.py b/tests/apimatic_core/type_combinator_tests/test_any_of.py index acd823d..bd0f924 100644 --- a/tests/apimatic_core/type_combinator_tests/test_any_of.py +++ b/tests/apimatic_core/type_combinator_tests/test_any_of.py @@ -1,15 +1,719 @@ +from datetime import datetime, date import pytest + +from apimatic_core.types.datetime_format import DateTimeFormat 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.union_type_context import UnionTypeContext +from apimatic_core.utilities.api_helper import ApiHelper +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_outer_context, expected_validity', [ - (100, [LeafType(int), LeafType(str)], UnionTypeContext(), True) + @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(), 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), + + # 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), + + # 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}]), + + # 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']}), + ]) + def test_one_of_primitive_type(self, input_value, input_types, input_context, expected_is_valid_output, + expected_deserialized_value_output): + union_type = AnyOf(input_types, input_context) + union_type_result = union_type.validate(input_value) + actual_is_valid = union_type_result.is_valid + actual_deserialized_value = union_type_result.deserialize(input_value) + 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_validity, expected_value', [ + (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)), + [LeafType(datetime, UnionTypeContext().date_time_format(DateTimeFormat.HTTP_DATE_TIME)), LeafType(date)], + UnionTypeContext(), True, 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)), + ('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_types, input_context, expected_validity, expected_value): + union_type = AnyOf(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_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)), + ('{"key0": {"OrbitNumberOfElectrons": 4}, "key1": {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}}', + [LeafType(Atom), LeafType(Orbit)], UnionTypeContext().dict(True), True, + {"key0": Orbit(4), "key1": Atom(2, 5)}), + + # 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(), True, Orbit(4)), + ( + '[{"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, {"AtomNumberOfElectrons": 4, "AtomNumberOfProtons": 10}]', + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit)], + UnionTypeContext(), True, [Atom(2, 5), Atom(4, 10)]), + ('[{"OrbitNumberOfElectrons": 4}, {"AtomNumberOfElectrons": 4, "AtomNumberOfProtons": 10}]', + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit)], + 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().array(True), True, [Atom(2, 5), 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(int), LeafType(str)], UnionTypeContext().dict(True), False, None), + ({"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, [LeafType(int), LeafType(str)], + 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(int, UnionTypeContext().dict(True)), LeafType(str, UnionTypeContext().dict(True))], + UnionTypeContext(), True, {'key0': Orbit(8), 'key1': Orbit(10)}), + ({'key0': {"OrbitNumberOfElectrons": 8}, 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}}, + [LeafType(int, UnionTypeContext().dict(True)), + LeafType(str, 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(Orbit, UnionTypeContext().dict(True)), LeafType(Atom)], + 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().array(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': Atom(2, 5), 'key1': Atom(2, 10)}, 'key1': {'key0': Orbit(10), 'key1': Orbit(8)}}), + ({'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(int, UnionTypeContext().dict(True)), LeafType(str, 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": 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': 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): + union_type = AnyOf(input_types, input_context) + deserialized_dict_input = ApiHelper.json_deserialize(input_value, as_dict=True) + union_type_result = union_type.validate(deserialized_dict_input) + actual_is_valid = union_type_result.is_valid + actual_deserialized_value = union_type_result.deserialize(deserialized_dict_input) + 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(Lion, 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_custom_type(self, input_value, input_types, input_context, expected_output): + union_type = AnyOf(input_types, input_context) + deserialized_dict_input = ApiHelper.json_deserialize(input_value, as_dict=True) + union_type_result = union_type.validate(deserialized_dict_input) + actual_result = union_type_result.is_valid + assert actual_result == 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_primitive_type_validity(self, input_value, input_types, input_outer_context, expected_validity): - union_type = AnyOf(input_types, input_outer_context) - actual_result = union_type.validate(input_value) - assert actual_result.is_valid == expected_validity + def test_one_of_enum_type(self, input_value, input_types, input_context, expected_is_valid_output, expected_deserialized_value_output): + union_type = AnyOf(input_types, input_context) + union_type_result = union_type.validate(input_value) + actual_is_valid = union_type_result.is_valid + actual_deserialized_value = union_type_result.deserialize(input_value) + assert actual_is_valid == expected_is_valid_output + assert actual_deserialized_value == expected_deserialized_value_output \ No newline at end of file diff --git a/tests/apimatic_core/type_combinator_tests/test_one_of.py b/tests/apimatic_core/type_combinator_tests/test_one_of.py index e2dc685..16dccd5 100644 --- a/tests/apimatic_core/type_combinator_tests/test_one_of.py +++ b/tests/apimatic_core/type_combinator_tests/test_one_of.py @@ -8,8 +8,10 @@ from apimatic_core.utilities.api_helper import ApiHelper 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 @@ -245,43 +247,27 @@ def test_one_of_primitive_type(self, input_value, input_types, input_context, ex assert actual_deserialized_value == expected_deserialized_value_output @pytest.mark.parametrize( - 'input_value, input_types, input_context, expected_validity', [ + 'input_value, input_types, input_context, expected_validity, expected_value', [ (Base.get_rfc3339_datetime(datetime(1994, 11, 6, 8, 49, 37)), [LeafType(datetime, UnionTypeContext().date_time_format(DateTimeFormat.RFC3339_DATE_TIME)), - LeafType(bool)], UnionTypeContext(), True), + LeafType(date)], UnionTypeContext(), True, datetime(1994, 11, 6, 8, 49, 37)), (Base.get_http_datetime(datetime(1994, 11, 6, 8, 49, 37)), - [LeafType(datetime, UnionTypeContext().date_time_format(DateTimeFormat.HTTP_DATE_TIME)), LeafType(bool)], - UnionTypeContext(), True), + [LeafType(datetime, UnionTypeContext().date_time_format(DateTimeFormat.HTTP_DATE_TIME)), LeafType(date)], + UnionTypeContext(), True, datetime(1994, 11, 6, 8, 49, 37)), (1480809600, - [LeafType(datetime, UnionTypeContext().date_time_format(DateTimeFormat.UNIX_DATE_TIME)), LeafType(bool)], - UnionTypeContext(), True), - (date(1994, 11, 6), [LeafType(date), LeafType(bool)], UnionTypeContext(), True) + [LeafType(datetime, UnionTypeContext().date_time_format(DateTimeFormat.UNIX_DATE_TIME)), LeafType(date)], + UnionTypeContext(), True, datetime.utcfromtimestamp(1480809600)), + ('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_validity(self, input_value, input_types, input_context, expected_validity): + def test_one_of_date_and_datetime(self, input_value, 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 - - @pytest.mark.parametrize( - 'input_value, input_types, input_context, expected_value, expected_type', [ - (Base.get_rfc3339_datetime(datetime(1994, 11, 6, 8, 49, 37)), - [LeafType(datetime, UnionTypeContext().date_time_format(DateTimeFormat.RFC3339_DATE_TIME)), - LeafType(bool)], UnionTypeContext(), datetime(1994, 11, 6, 8, 49, 37), datetime), - (Base.get_http_datetime(datetime(1994, 11, 6, 8, 49, 37)), - [LeafType(datetime, UnionTypeContext().date_time_format(DateTimeFormat.HTTP_DATE_TIME)), LeafType(bool)], - UnionTypeContext(), datetime(1994, 11, 6, 8, 49, 37), datetime), - (1480809600, - [LeafType(datetime, UnionTypeContext().date_time_format(DateTimeFormat.UNIX_DATE_TIME)), LeafType(bool)], - UnionTypeContext(), datetime.utcfromtimestamp(1480809600), datetime), - ('1994-11-06', [LeafType(date), LeafType(bool)], UnionTypeContext(), date(1994, 11, 6), date) - ]) - def test_one_of_date_and_datetime(self, input_value, input_types, input_context, expected_value, expected_type): - union_type = OneOf(input_types, input_context) - union_type_result = union_type.validate(input_value) actual_deserialized_value = union_type_result.deserialize(input_value) - assert isinstance(actual_deserialized_value, expected_type) 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 @@ -658,5 +644,76 @@ def test_one_of_custom_type(self, input_value, input_types, input_context, expec deserialized_dict_input = ApiHelper.json_deserialize(input_value, as_dict=True) union_type_result = union_type.validate(deserialized_dict_input) actual_result = union_type_result.is_valid - deserialized_value = union_type_result.deserialize(deserialized_dict_input) assert actual_result == 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): + union_type = OneOf(input_types, input_context) + union_type_result = union_type.validate(input_value) + actual_is_valid = union_type_result.is_valid + actual_deserialized_value = union_type_result.deserialize(input_value) + assert actual_is_valid == expected_is_valid_output + assert actual_deserialized_value == expected_deserialized_value_output \ No newline at end of file From 7a63c197203c0522b551af0566ab71b1bd761500 Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Mon, 29 May 2023 18:54:17 +0500 Subject: [PATCH 13/49] added fixes and refactored code --- apimatic_core/types/union_types/any_of.py | 103 +++++++----------- apimatic_core/types/union_types/leaf_type.py | 34 +++--- apimatic_core/types/union_types/one_of.py | 68 +++--------- .../types/union_types/union_type_context.py | 13 ++- apimatic_core/utilities/__init__.py | 3 +- apimatic_core/utilities/api_helper.py | 25 +++++ apimatic_core/utilities/datetime_helper.py | 6 +- apimatic_core/utilities/union_type_helper.py | 46 ++++++++ .../type_combinator_tests/test_one_of.py | 5 +- .../utility_tests/test_datetime_helper.py | 7 +- 10 files changed, 167 insertions(+), 143 deletions(-) create mode 100644 apimatic_core/utilities/union_type_helper.py diff --git a/apimatic_core/types/union_types/any_of.py b/apimatic_core/types/union_types/any_of.py index 6c3cb94..88db319 100644 --- a/apimatic_core/types/union_types/any_of.py +++ b/apimatic_core/types/union_types/any_of.py @@ -2,6 +2,7 @@ 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.union_type_helper import UnionTypeHelper class AnyOf(UnionType): @@ -23,22 +24,22 @@ def validate(self, value): if context.is_array() and context.is_dict() and context.is_array_of_dict(): if isinstance(value, list): - self.is_valid = self.validate_array_of_dict_case(value) + self.is_valid, self.collection_cases = self.validate_array_of_dict_case(value) else: self.is_valid = False elif context.is_array() and context.is_dict(): if isinstance(value, dict): - self.is_valid = self.validate_dict_of_array_case(value) + self.is_valid, self.collection_cases = self.validate_dict_of_array_case(value) else: self.is_valid = False elif context.is_array(): if isinstance(value, list): - self.is_valid = self.validate_array_case(value) + self.is_valid, self.collection_cases = self.validate_array_case(value) else: self.is_valid = False elif context.is_dict(): if isinstance(value, dict): - self.is_valid = self.validate_dict_case(value) + self.is_valid, self.collection_cases = self.validate_dict_case(value) else: self.is_valid = False else: @@ -56,13 +57,13 @@ def deserialize(self, value): return None if context.is_array() and context.is_dict() and context.is_array_of_dict(): - deserialized_value = self.deserialize_array_of_dict_case(value) + deserialized_value = UnionTypeHelper.deserialize_array_of_dict_case(value, self.collection_cases) elif context.is_array() and context.is_dict(): - deserialized_value = self.deserialize_dict_of_array_case(value) + deserialized_value = UnionTypeHelper.deserialize_dict_of_array_case(value, self.collection_cases) elif context.is_array(): - deserialized_value = self.deserialize_array_case(value) + deserialized_value = UnionTypeHelper.deserialize_array_case(value, self.collection_cases) elif context.is_dict(): - deserialized_value = self.deserialize_dict_case(value) + deserialized_value = UnionTypeHelper.deserialize_dict_case(value, self.collection_cases) else: deserialized_value = self.get_deserialized_value(value) @@ -73,87 +74,61 @@ def get_deserialized_value(self, value): def validate_array_of_dict_case(self, array_value): if array_value is None or not array_value: - return False + return tuple((False, [])) - matched_count = sum(self.validate_dict_case(item) for item in array_value) - return matched_count == array_value.__len__() + collection_cases = [] + valid_cases = [] + for item in array_value: + case_validity, inner_dictionary = self.validate_dict_case(item) + collection_cases.append(inner_dictionary) + valid_cases.append(case_validity) + is_valid = sum(valid_cases) == array_value.__len__() + return tuple((is_valid, collection_cases)) def validate_dict_of_array_case(self, dict_value): if dict_value is None or not dict_value: - return False + return tuple((False, [])) - matched_count = sum(self.validate_array_case(item) for item in dict_value.values()) - return matched_count == dict_value.__len__() + collection_cases = {} + valid_cases = [] + for key, item in dict_value.items(): + case_validity, inner_array = self.validate_array_case(item) + collection_cases[key] = inner_array + valid_cases.append(case_validity) + is_valid = sum(valid_cases) == dict_value.__len__() + return tuple((is_valid, collection_cases)) def validate_dict_case(self, dict_value): if dict_value is None or not dict_value: - return False + return tuple((False, [])) is_valid = True - self.collection_cases = {} + collection_cases = {} for key, value in dict_value.items(): nested_cases = [] for union_type in self._union_types: nested_cases.append(copy.deepcopy(union_type).validate(value)) - matched_count = ApiHelper.get_matched_count(value, nested_cases, False) + matched_count = ApiHelper.get_matched_count(value, nested_cases, True) if is_valid: - is_valid = matched_count > 0 - self.collection_cases[key] = nested_cases - return is_valid + is_valid = matched_count >= 1 + collection_cases[key] = nested_cases + return tuple((is_valid, collection_cases)) def validate_array_case(self, array_value): if array_value is None or not array_value: - return False + return tuple((False, [])) is_valid = True - self.collection_cases = [] + collection_cases = [] for item in array_value: nested_cases = [] for union_type in self._union_types: nested_cases.append(copy.deepcopy(union_type).validate(item)) - matched_count = ApiHelper.get_matched_count(item, nested_cases, False) + matched_count = ApiHelper.get_matched_count(item, nested_cases, True) if is_valid: - is_valid = matched_count > 0 - self.collection_cases.append(nested_cases) - return is_valid - - def deserialize_array_of_dict_case(self, array_value): - if array_value is None: - return False - - return [self.deserialize_dict_case(item) for item in array_value] - - def deserialize_dict_of_array_case(self, dict_value): - if dict_value is None: - return False - - deserialized_value = {} - for key, value in dict_value.items(): - deserialized_value[key] = self.deserialize_array_case(value) - - return deserialized_value - - def deserialize_dict_case(self, dict_value): - if dict_value is None: - return False - - deserialized_value = {} - for key, value in dict_value.items(): - valid_case = [case for case in self.collection_cases[key] if case.is_valid][0] - deserialized_value[key] = valid_case.deserialize(value) - - return deserialized_value - - def deserialize_array_case(self, array_value): - if array_value is None: - return False - - deserialized_value = [] - for index, item in enumerate(array_value): - valid_case = [case for case in self.collection_cases[index] if case.is_valid][0] - deserialized_value.append(valid_case.deserialize(item)) - - return deserialized_value + is_valid = matched_count >= 1 + collection_cases.append(nested_cases) + return tuple((is_valid, collection_cases)) def __deepcopy__(self, memo={}): copy_object = AnyOf(self._union_types, self._union_type_context) diff --git a/apimatic_core/types/union_types/leaf_type.py b/apimatic_core/types/union_types/leaf_type.py index c5bfbe9..61ae060 100644 --- a/apimatic_core/types/union_types/leaf_type.py +++ b/apimatic_core/types/union_types/leaf_type.py @@ -124,24 +124,20 @@ def validate_item(self, value): if discriminator and discriminator_value: return self.validate_with_discriminator(discriminator, discriminator_value, value) - is_native_type = self.type_to_match in UnionType.NATIVE_TYPES - if is_native_type or self.type_to_match is dict: - return isinstance(value, self.type_to_match) - if hasattr(self.type_to_match, 'validate'): return self.type_to_match.validate(value) - return False + return isinstance(value, self.type_to_match) def validate_with_discriminator(self, discriminator, discriminator_value, value): - if value.get(discriminator) == discriminator_value: - if hasattr(self.type_to_match, 'validate'): - return self.type_to_match.validate(value) - else: - return isinstance(value, self.type_to_match) - else: + 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 isinstance(value, self.type_to_match) + def validate_date_time(self, value, context): if isinstance(value, ApiHelper.RFC3339DateTime): return context.get_date_time_format() == DateTimeFormat.RFC3339_DATE_TIME @@ -149,8 +145,11 @@ def validate_date_time(self, value, context): return context.get_date_time_format() == DateTimeFormat.HTTP_DATE_TIME elif isinstance(value, ApiHelper.UnixDateTime): return context.get_date_time_format() == DateTimeFormat.UNIX_DATE_TIME - else: - return DateTimeHelper.validate_datetime(value, context.get_date_time_format()) + elif isinstance(value, datetime) and context.get_date_time_converter(): + serialized_dt = ApiHelper.json_serialize(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()) def deserialize_dict_case(self, dict_value): if not isinstance(dict_value, dict): @@ -201,12 +200,15 @@ def deserialize_item(self, value): if self.type_to_match is date: return ApiHelper.date_deserialize(value) - elif self.type_to_match is datetime: + + if self.type_to_match is datetime: return ApiHelper.datetime_deserialize( value, self._union_type_context.get_date_time_format()) - elif is_native_type or self.type_to_match is dict: + + if is_native_type or self.type_to_match is dict: return value - elif hasattr(self.type_to_match, 'from_dictionary'): + + if hasattr(self.type_to_match, 'from_dictionary'): return self.type_to_match.from_dictionary(value) return None diff --git a/apimatic_core/types/union_types/one_of.py b/apimatic_core/types/union_types/one_of.py index 2bce8e3..0080146 100644 --- a/apimatic_core/types/union_types/one_of.py +++ b/apimatic_core/types/union_types/one_of.py @@ -2,6 +2,7 @@ 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.union_type_helper import UnionTypeHelper class OneOf(UnionType): @@ -56,24 +57,25 @@ def deserialize(self, value): return None if context.is_array() and context.is_dict() and context.is_array_of_dict(): - deserialized_value = self.deserialize_array_of_dict_case(value) + deserialized_value = UnionTypeHelper.deserialize_array_of_dict_case(value, self.collection_cases) elif context.is_array() and context.is_dict(): - deserialized_value = self.deserialize_dict_of_array_case(value) + deserialized_value = UnionTypeHelper.deserialize_dict_of_array_case(value, self.collection_cases) elif context.is_array(): - deserialized_value = self.deserialize_array_case(value, self.collection_cases) + deserialized_value = UnionTypeHelper.deserialize_array_case(value, self.collection_cases) elif context.is_dict(): - deserialized_value = self.deserialize_dict_case(value, self.collection_cases) + deserialized_value = UnionTypeHelper.deserialize_dict_case(value, self.collection_cases) else: deserialized_value = self.get_deserialized_value(value) return deserialized_value def get_deserialized_value(self, value): - return [union_type.deserialize(value) for union_type in self._union_types if union_type.is_valid][0] + return [union_type for union_type in self._union_types if union_type.is_valid][0].deserialize(value) def validate_array_of_dict_case(self, array_value): if array_value is None or not array_value: - return False + return tuple((False, [])) + collection_cases = [] valid_cases = [] for item in array_value: @@ -81,11 +83,12 @@ def validate_array_of_dict_case(self, array_value): collection_cases.append(inner_dictionary) valid_cases.append(case_validity) is_valid = sum(valid_cases) == array_value.__len__() + return tuple((is_valid, collection_cases)) def validate_dict_of_array_case(self, dict_value): if dict_value is None or not dict_value: - return tuple((False, None)) + return tuple((False, [])) collection_cases = {} valid_cases = [] @@ -93,13 +96,13 @@ def validate_dict_of_array_case(self, dict_value): case_validity, inner_array = self.validate_array_case(item) collection_cases[key] = inner_array valid_cases.append(case_validity) - is_valid = sum(valid_cases) == dict_value.__len__() + return tuple((is_valid, collection_cases)) def validate_dict_case(self, dict_value): if dict_value is None or not dict_value: - return tuple((False, None)) + return tuple((False, [])) is_valid = True collection_cases = {} @@ -111,11 +114,12 @@ def validate_dict_case(self, dict_value): if is_valid: is_valid = matched_count == 1 collection_cases[key] = nested_cases + return tuple((is_valid, collection_cases)) def validate_array_case(self, array_value): if array_value is None or not array_value: - return tuple((False, None)) + return tuple((False, [])) is_valid = True collection_cases = [] @@ -127,50 +131,8 @@ def validate_array_case(self, array_value): if is_valid: is_valid = matched_count == 1 collection_cases.append(nested_cases) - return tuple((is_valid, collection_cases)) - - def deserialize_array_of_dict_case(self, array_value): - if array_value is None: - return False - deserialized_value = [] - for index, item in enumerate(array_value): - deserialized_value.append(self.deserialize_dict_case(item, self.collection_cases[index])) - - return deserialized_value - - def deserialize_dict_of_array_case(self, dict_value): - if dict_value is None: - return False - - deserialized_value = {} - for key, value in dict_value.items(): - deserialized_value[key] = self.deserialize_array_case(value, self.collection_cases[key]) - return deserialized_value - - @staticmethod - def deserialize_dict_case(dict_value, collection_cases): - if dict_value is None: - return False - - 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): - if array_value is None: - return False - - 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 + return tuple((is_valid, collection_cases)) def __deepcopy__(self, memo={}): copy_object = OneOf(self._union_types, self._union_type_context) diff --git a/apimatic_core/types/union_types/union_type_context.py b/apimatic_core/types/union_types/union_type_context.py index 7b5cec6..45dc1d0 100644 --- a/apimatic_core/types/union_types/union_type_context.py +++ b/apimatic_core/types/union_types/union_type_context.py @@ -3,14 +3,15 @@ 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): + 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_format(date_time_format)\ + .date_time_converter(date_time_converter) def __init__(self): self._is_array = False @@ -21,6 +22,7 @@ def __init__(self): self._discriminator = None self._discriminator_value = None self._date_time_format = None + self._date_time_converter = None def array(self, is_array): self._is_array = is_array @@ -81,3 +83,10 @@ def date_time_format(self, date_time_format): 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 1b691c0..fbc1e4c 100644 --- a/apimatic_core/utilities/__init__.py +++ b/apimatic_core/utilities/__init__.py @@ -4,5 +4,6 @@ 'xml_helper', 'comparison_helper', 'file_helper', - 'datetime_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 d176879..9b0b41f 100644 --- a/apimatic_core/utilities/api_helper.py +++ b/apimatic_core/utilities/api_helper.py @@ -176,6 +176,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: @@ -536,6 +539,28 @@ def when_defined(func, value): def is_file_wrapper_instance(param): return isinstance(param, FileWrapper) + @staticmethod + def is_valid_array(value, type_callable): + if isinstance(value, list): + return all(ApiHelper.is_valid(item, type_callable(item)) for item in value) + elif isinstance(value, dict): + return ApiHelper.is_valid_dict(value, type_callable) + + return False + + @staticmethod + def is_valid_dict(value, type_callable): + if isinstance(value, dict): + return all(ApiHelper.is_valid(item, type_callable(item)) for item in value.values()) + elif isinstance(value, list): + return ApiHelper.is_valid_array(value, type_callable) + + return False + + @staticmethod + def is_valid(value, type_callable): + 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. diff --git a/apimatic_core/utilities/datetime_helper.py b/apimatic_core/utilities/datetime_helper.py index 0b9a578..6b80953 100644 --- a/apimatic_core/utilities/datetime_helper.py +++ b/apimatic_core/utilities/datetime_helper.py @@ -22,10 +22,10 @@ def validate_datetime(datetime_value, datetime_format): def validate_date(date_value): try: if isinstance(date_value, date): - dateutil.parser.parse(date_value.isoformat()) + datetime.strptime(date_value.isoformat(), "%Y-%m-%d") return True elif isinstance(date_value, str): - dateutil.parser.parse(date_value) + datetime.strptime(date_value, "%Y-%m-%d") return True else: return False @@ -43,6 +43,8 @@ def is_rfc_1123(datetime_value): @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): diff --git a/apimatic_core/utilities/union_type_helper.py b/apimatic_core/utilities/union_type_helper.py new file mode 100644 index 0000000..bdcc785 --- /dev/null +++ b/apimatic_core/utilities/union_type_helper.py @@ -0,0 +1,46 @@ + +class UnionTypeHelper: + @staticmethod + def deserialize_array_of_dict_case(array_value, collection_cases): + if array_value is None: + return None + 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): + if dict_value is None: + return None + + 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): + if dict_value is None: + return None + + 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): + if array_value is None: + return None + + 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 diff --git a/tests/apimatic_core/type_combinator_tests/test_one_of.py b/tests/apimatic_core/type_combinator_tests/test_one_of.py index e2dc685..5fdd1a5 100644 --- a/tests/apimatic_core/type_combinator_tests/test_one_of.py +++ b/tests/apimatic_core/type_combinator_tests/test_one_of.py @@ -266,7 +266,8 @@ def test_one_of_date_and_datetime_validity(self, input_value, input_types, input 'input_value, input_types, input_context, expected_value, expected_type', [ (Base.get_rfc3339_datetime(datetime(1994, 11, 6, 8, 49, 37)), [LeafType(datetime, UnionTypeContext().date_time_format(DateTimeFormat.RFC3339_DATE_TIME)), - LeafType(bool)], UnionTypeContext(), datetime(1994, 11, 6, 8, 49, 37), datetime), + LeafType(bool)], UnionTypeContext(), datetime(1994, 11, 6, 8, 49, 37), + datetime), (Base.get_http_datetime(datetime(1994, 11, 6, 8, 49, 37)), [LeafType(datetime, UnionTypeContext().date_time_format(DateTimeFormat.HTTP_DATE_TIME)), LeafType(bool)], UnionTypeContext(), datetime(1994, 11, 6, 8, 49, 37), datetime), @@ -280,7 +281,7 @@ def test_one_of_date_and_datetime(self, input_value, input_types, input_context, union_type_result = union_type.validate(input_value) actual_deserialized_value = union_type_result.deserialize(input_value) assert isinstance(actual_deserialized_value, expected_type) - assert actual_deserialized_value == expected_value + assert str(actual_deserialized_value) == str(expected_value) @pytest.mark.parametrize( 'input_value, input_types, input_context, expected_is_valid_output, expected_deserialized_value_output', [ diff --git a/tests/apimatic_core/utility_tests/test_datetime_helper.py b/tests/apimatic_core/utility_tests/test_datetime_helper.py index 61b8c41..9514d54 100644 --- a/tests/apimatic_core/utility_tests/test_datetime_helper.py +++ b/tests/apimatic_core/utility_tests/test_datetime_helper.py @@ -9,6 +9,7 @@ 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), @@ -25,11 +26,11 @@ def test_is_valid_datetime(self, input_dt, input_datetime_format, expected_outpu @pytest.mark.parametrize('input_date, expected_output', [ ('1994-11-06', True), - ('1994/11/06', True), - ('19941106', True), - ('941106', 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) From ba5e7d5990733695ff4dfb7aaf77c14959725336 Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Tue, 30 May 2023 14:22:25 +0500 Subject: [PATCH 14/49] added apply apply_datetime_converter utility method in ApiHelper class --- apimatic_core/utilities/api_helper.py | 28 ++++++ .../utility_tests/test_api_helper.py | 99 +++++++++++++++++++ 2 files changed, 127 insertions(+) diff --git a/apimatic_core/utilities/api_helper.py b/apimatic_core/utilities/api_helper.py index 9b0b41f..5434b51 100644 --- a/apimatic_core/utilities/api_helper.py +++ b/apimatic_core/utilities/api_helper.py @@ -531,6 +531,34 @@ def to_dictionary(obj, should_ignore_null_values=False): # Return the result return dictionary + @staticmethod + def apply_datetime_converter(value, datetime_converter_obj): + if isinstance(value, list): + converted_value = [] + for item in value: + if isinstance(value, list) or isinstance(value, dict): + converted_value.append(ApiHelper.apply_datetime_converter(item, datetime_converter_obj)) + elif isinstance(item, datetime.datetime): + converted_value.append(ApiHelper.when_defined(datetime_converter_obj, item)) + else: + converted_value.append(item) + return converted_value + if isinstance(value, dict): + converted_value = {} + for k, v in value.items(): + if isinstance(value, list) or isinstance(value, dict): + converted_value[k] = ApiHelper.apply_datetime_converter(v, datetime_converter_obj) + elif isinstance(v, datetime.datetime): + converted_value[k] = ApiHelper.when_defined(datetime_converter_obj, v) + else: + converted_value[k] = v + return converted_value + elif 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 diff --git a/tests/apimatic_core/utility_tests/test_api_helper.py b/tests/apimatic_core/utility_tests/test_api_helper.py index d3fefeb..c144bef 100644 --- a/tests/apimatic_core/utility_tests/test_api_helper.py +++ b/tests/apimatic_core/utility_tests/test_api_helper.py @@ -440,6 +440,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))), + (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), From cdd4b885f15489b5d140f8e03750b5295a570a43 Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Wed, 31 May 2023 16:45:27 +0500 Subject: [PATCH 15/49] added validation for OAF and unit tests for APIHelper --- apimatic_core/types/union_types/any_of.py | 32 +++- apimatic_core/types/union_types/leaf_type.py | 3 - apimatic_core/types/union_types/one_of.py | 35 ++++- .../types/union_types/union_type_context.py | 2 + apimatic_core/utilities/api_helper.py | 128 ++++++---------- apimatic_core/utilities/union_type_helper.py | 21 +++ tests/apimatic_core/base.py | 19 +++ tests/apimatic_core/mocks/models/__init__.py | 8 + .../mocks/models/complex_type.py | 103 +++++++++++++ .../mocks/models/inner_complex_type.py | 83 ++++++++++ .../utility_tests/test_api_helper.py | 145 +++++++++++++++++- 11 files changed, 481 insertions(+), 98 deletions(-) create mode 100644 tests/apimatic_core/mocks/models/complex_type.py create mode 100644 tests/apimatic_core/mocks/models/inner_complex_type.py diff --git a/apimatic_core/types/union_types/any_of.py b/apimatic_core/types/union_types/any_of.py index 88db319..bfeb8f2 100644 --- a/apimatic_core/types/union_types/any_of.py +++ b/apimatic_core/types/union_types/any_of.py @@ -1,7 +1,8 @@ import copy from apimatic_core_interfaces.types.union_type import UnionType +from apimatic_core.exceptions.anyof_validation_exception import AnyOfValidationException +from apimatic_core.types.union_types.leaf_type import LeafType 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 @@ -14,12 +15,16 @@ def __init__(self, union_types, union_type_context: UnionTypeContext = UnionType def validate(self, value): context = self._union_type_context + for union_type in self._union_types: + union_type.get_context().is_nested = True + if value is None and context.is_nullable_or_optional(): self.is_valid = True return self if value is None: self.is_valid = False + self.process_errors() return self if context.is_array() and context.is_dict() and context.is_array_of_dict(): @@ -43,10 +48,29 @@ def validate(self, value): else: self.is_valid = False else: - self.is_valid = ApiHelper.get_matched_count(value, self._union_types, False) > 0 + self.is_valid = UnionTypeHelper.get_matched_count(value, self._union_types, False) > 0 + + if not self.is_valid: + self.process_errors() return self + def process_errors(self): + self.error_messages = [] + + combined_types = [] + for union_type in self._union_types: + if isinstance(union_type, LeafType): + combined_types.append(union_type.type_to_match.__name__) + else: + combined_types.append(', '.join(union_type.error_messages)) + + if self._union_type_context.is_nested: + self.error_messages.append(', '.join(combined_types)) + else: + raise AnyOfValidationException('{} \nExpected Type: Any Of {}'.format( + UnionType.NONE_MATCHED_ERROR_MESSAGE, ', '.join(combined_types))) + def deserialize(self, value): if value is None or not self.is_valid: @@ -108,7 +132,7 @@ def validate_dict_case(self, dict_value): nested_cases = [] for union_type in self._union_types: nested_cases.append(copy.deepcopy(union_type).validate(value)) - matched_count = ApiHelper.get_matched_count(value, nested_cases, True) + matched_count = UnionTypeHelper.get_matched_count(value, nested_cases, True) if is_valid: is_valid = matched_count >= 1 collection_cases[key] = nested_cases @@ -124,7 +148,7 @@ def validate_array_case(self, array_value): nested_cases = [] for union_type in self._union_types: nested_cases.append(copy.deepcopy(union_type).validate(item)) - matched_count = ApiHelper.get_matched_count(item, nested_cases, True) + matched_count = UnionTypeHelper.get_matched_count(item, nested_cases, True) if is_valid: is_valid = matched_count >= 1 collection_cases.append(nested_cases) diff --git a/apimatic_core/types/union_types/leaf_type.py b/apimatic_core/types/union_types/leaf_type.py index eaa114b..b5ec178 100644 --- a/apimatic_core/types/union_types/leaf_type.py +++ b/apimatic_core/types/union_types/leaf_type.py @@ -1,8 +1,5 @@ from datetime import date, datetime - -import dateutil from apimatic_core_interfaces.types.union_type import UnionType - from apimatic_core.types.datetime_format import DateTimeFormat from apimatic_core.types.union_types.union_type_context import UnionTypeContext from apimatic_core.utilities.api_helper import ApiHelper diff --git a/apimatic_core/types/union_types/one_of.py b/apimatic_core/types/union_types/one_of.py index 0080146..3071f79 100644 --- a/apimatic_core/types/union_types/one_of.py +++ b/apimatic_core/types/union_types/one_of.py @@ -1,7 +1,8 @@ import copy from apimatic_core_interfaces.types.union_type import UnionType +from apimatic_core.exceptions.oneof_validation_exception import OneOfValidationException +from apimatic_core.types.union_types.leaf_type import LeafType 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 @@ -14,12 +15,16 @@ def __init__(self, union_types, union_type_context: UnionTypeContext = UnionType def validate(self, value): context = self._union_type_context + for union_type in self._union_types: + union_type.get_context().is_nested = True + if value is None and context.is_nullable_or_optional(): self.is_valid = True return self if value is None: self.is_valid = False + self.process_errors() return self if context.is_array() and context.is_dict() and context.is_array_of_dict(): @@ -43,10 +48,32 @@ def validate(self, value): else: self.is_valid = False else: - self.is_valid = ApiHelper.get_matched_count(value, self._union_types, True) == 1 + self.is_valid = UnionTypeHelper.get_matched_count(value, self._union_types, True) == 1 + + if not self.is_valid: + self.process_errors() return self + def process_errors(self): + self.error_messages = [] + + combined_types = [] + for union_type in self._union_types: + if isinstance(union_type, LeafType): + combined_types.append(union_type.type_to_match.__name__) + else: + combined_types.append(', '.join(union_type.error_messages)) + + if self._union_type_context.is_nested: + self.error_messages.append(', '.join(combined_types)) + else: + matched_count = sum(union_type.is_valid for union_type in self._union_types) + error_message = UnionType.MORE_THAN_1_MATCHED_ERROR_MESSAGE if matched_count > 0 \ + else UnionType.NONE_MATCHED_ERROR_MESSAGE + raise OneOfValidationException('{} \nExpected Type: One Of {}'.format( + error_message, ', '.join(combined_types))) + def deserialize(self, value): if value is None or not self.is_valid: return None @@ -110,7 +137,7 @@ def validate_dict_case(self, dict_value): nested_cases = [] for union_type in self._union_types: nested_cases.append(copy.deepcopy(union_type).validate(value)) - matched_count = ApiHelper.get_matched_count(value, nested_cases, True) + matched_count = UnionTypeHelper.get_matched_count(value, nested_cases, True) if is_valid: is_valid = matched_count == 1 collection_cases[key] = nested_cases @@ -127,7 +154,7 @@ def validate_array_case(self, array_value): nested_cases = [] for union_type in self._union_types: nested_cases.append(copy.deepcopy(union_type).validate(item)) - matched_count = ApiHelper.get_matched_count(item, nested_cases, True) + matched_count = UnionTypeHelper.get_matched_count(item, nested_cases, True) if is_valid: is_valid = matched_count == 1 collection_cases.append(nested_cases) diff --git a/apimatic_core/types/union_types/union_type_context.py b/apimatic_core/types/union_types/union_type_context.py index 45dc1d0..caf7a6f 100644 --- a/apimatic_core/types/union_types/union_type_context.py +++ b/apimatic_core/types/union_types/union_type_context.py @@ -23,6 +23,8 @@ def __init__(self): 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 diff --git a/apimatic_core/utilities/api_helper.py b/apimatic_core/utilities/api_helper.py index 5434b51..8a6a9e6 100644 --- a/apimatic_core/utilities/api_helper.py +++ b/apimatic_core/utilities/api_helper.py @@ -5,12 +5,9 @@ import calendar import email.utils as eut from time import mktime -from typing import Union, List, Dict - 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 @@ -71,9 +68,7 @@ def json_serialize(obj, should_encode=True): if isinstance(obj, list): value = list() for item in obj: - if isinstance(item, dict): - value.append(ApiHelper.json_serialize(item, False)) - elif isinstance(item, list): + 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)) @@ -83,9 +78,7 @@ def json_serialize(obj, should_encode=True): elif isinstance(obj, dict): value = dict() for key, item in obj.items(): - if isinstance(item, list): - value[key] = ApiHelper.json_serialize(item, False) - elif isinstance(item, dict): + 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) @@ -105,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 @@ -204,21 +199,11 @@ def datetime_deserialize(response, datetime_format): return ApiHelper.RFC3339DateTime.from_value(response).datetime @staticmethod - def validate_union_type(union_type, value): - union_type_result = union_type.validate(value) - if not union_type_result.is_valid: - raise ValueError(union_type_result.errors) - - return True - - @staticmethod - def deserialize_union_type(union_type, response, should_deserialize = True): + 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) - if not union_type_result.is_valid: - raise ValueError(union_type_result.errors) return union_type_result.deserialize(response) @@ -337,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: @@ -400,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: @@ -493,19 +475,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"): @@ -531,27 +519,32 @@ 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): + array = [] + for item in value: + array.append(ApiHelper.process_nested_collection(item, should_ignore_null_values)) + return array + elif isinstance(value, dict): + dictionary = {} + for k, v in value.items(): + dictionary[k] = ApiHelper.process_nested_collection(v, should_ignore_null_values) + return dictionary + else: + 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): converted_value = [] for item in value: - if isinstance(value, list) or isinstance(value, dict): - converted_value.append(ApiHelper.apply_datetime_converter(item, datetime_converter_obj)) - elif isinstance(item, datetime.datetime): - converted_value.append(ApiHelper.when_defined(datetime_converter_obj, item)) - else: - converted_value.append(item) + converted_value.append(ApiHelper.apply_datetime_converter(item, datetime_converter_obj)) return converted_value if isinstance(value, dict): converted_value = {} for k, v in value.items(): - if isinstance(value, list) or isinstance(value, dict): - converted_value[k] = ApiHelper.apply_datetime_converter(v, datetime_converter_obj) - elif isinstance(v, datetime.datetime): - converted_value[k] = ApiHelper.when_defined(datetime_converter_obj, v) - else: - converted_value[k] = v + converted_value[k] = ApiHelper.apply_datetime_converter(v, datetime_converter_obj) return converted_value elif isinstance(value, datetime.datetime): return ApiHelper.when_defined(datetime_converter_obj, value) @@ -568,25 +561,12 @@ def is_file_wrapper_instance(param): return isinstance(param, FileWrapper) @staticmethod - def is_valid_array(value, type_callable): + def is_valid_type(value, type_callable): if isinstance(value, list): - return all(ApiHelper.is_valid(item, type_callable(item)) for item in value) + return all(ApiHelper.is_valid_type(item, type_callable) for item in value) elif isinstance(value, dict): - return ApiHelper.is_valid_dict(value, type_callable) - - return False - - @staticmethod - def is_valid_dict(value, type_callable): - if isinstance(value, dict): - return all(ApiHelper.is_valid(item, type_callable(item)) for item in value.values()) - elif isinstance(value, list): - return ApiHelper.is_valid_array(value, type_callable) - - return False + return all(ApiHelper.is_valid_type(item, type_callable) for item in value.values()) - @staticmethod - def is_valid(value, type_callable): return value is not None and type_callable(value) @staticmethod @@ -644,27 +624,6 @@ def resolve_template_placeholders(placeholders, values, template): return template - @staticmethod - def get_matched_count(value, union_types, is_for_one_of): - matched_count = sum(union_type.validate(value).is_valid for union_type in 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 - - # Check through normal schema validation flow when discriminator exits but still invalid - 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 matched_count == 0 and has_discriminator_cases: - for union_type in union_types: - union_type.get_context().discriminator(None) - union_type.get_context().discriminator_value(None) - matched_count = sum(union_type.validate(value).is_valid for union_type in union_types) - - return matched_count - class CustomDate(object): """ A base class for wrapper classes of datetime. @@ -696,8 +655,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/union_type_helper.py b/apimatic_core/utilities/union_type_helper.py index bdcc785..9c6f893 100644 --- a/apimatic_core/utilities/union_type_helper.py +++ b/apimatic_core/utilities/union_type_helper.py @@ -44,3 +44,24 @@ def deserialize_array_case(array_value, collection_cases): deserialized_value.append(valid_case.deserialize(item)) return deserialized_value + + @staticmethod + def get_matched_count(value, union_types, is_for_one_of): + matched_count = sum(union_type.validate(value).is_valid for union_type in 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 + + # Check through normal schema validation flow when discriminator exits but still invalid + 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 matched_count == 0 and has_discriminator_cases: + for union_type in union_types: + union_type.get_context().discriminator(None) + union_type.get_context().discriminator_value(None) + matched_count = sum(union_type.validate(value).is_valid for union_type in union_types) + + return matched_count diff --git a/tests/apimatic_core/base.py b/tests/apimatic_core/base.py index 2a00470..8cd38f9 100644 --- a/tests/apimatic_core/base.py +++ b/tests/apimatic_core/base.py @@ -24,7 +24,9 @@ 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.wolf_model import WolfModel from tests.apimatic_core.mocks.models.xml_model import XMLModel @@ -251,3 +253,20 @@ 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'}}) diff --git a/tests/apimatic_core/mocks/models/__init__.py b/tests/apimatic_core/mocks/models/__init__.py index 85b5702..0db5e5a 100644 --- a/tests/apimatic_core/mocks/models/__init__.py +++ b/tests/apimatic_core/mocks/models/__init__.py @@ -9,4 +9,12 @@ 'cat_model', 'dog_model', 'wolf_model', + 'complex_type', + 'inner_complex_type', + 'atom', + 'deer', + 'lion', + 'orbit', + 'rabbit', + 'grand_parent_class_model' ] \ No newline at end of file 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/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/utility_tests/test_api_helper.py b/tests/apimatic_core/utility_tests/test_api_helper.py index c144bef..cfa5458 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 @@ -53,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": ' @@ -64,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) @@ -279,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 ' @@ -442,7 +540,7 @@ def test_when_defined(self, 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.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), @@ -748,3 +846,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 From e8f5c77d721217daf8cec50afe697e6f255c3e92 Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Wed, 31 May 2023 16:55:24 +0500 Subject: [PATCH 16/49] added validation for OAF types in model while serialization --- apimatic_core/utilities/api_helper.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apimatic_core/utilities/api_helper.py b/apimatic_core/utilities/api_helper.py index 8a6a9e6..1be173d 100644 --- a/apimatic_core/utilities/api_helper.py +++ b/apimatic_core/utilities/api_helper.py @@ -458,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: From 40944b3e88592ad8b4928454b0176cbb71bf1e96 Mon Sep 17 00:00:00 2001 From: MaryamAdnan3 Date: Thu, 1 Jun 2023 08:13:22 +0500 Subject: [PATCH 17/49] Fixed all tests in one of and any of, check for completion of test coverage to 100 % --- tests/apimatic_core/mocks/models/days.py | 8 +-- tests/apimatic_core/mocks/models/months.py | 7 +- .../type_combinator_tests/test_any_of.py | 66 +++++++++---------- .../type_combinator_tests/test_one_of.py | 45 ++++++------- 4 files changed, 58 insertions(+), 68 deletions(-) diff --git a/tests/apimatic_core/mocks/models/days.py b/tests/apimatic_core/mocks/models/days.py index 70ec83e..6b08a55 100644 --- a/tests/apimatic_core/mocks/models/days.py +++ b/tests/apimatic_core/mocks/models/days.py @@ -1,5 +1,4 @@ -from enum import Enum -class Days(Enum): +class Days(object): """Implementation of the 'Days' enum. @@ -16,6 +15,8 @@ class Days(Enum): """ + _all_values = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday','Saturday'] + SUNDAY = 'Sunday' MONDAY = 'Monday' @@ -44,5 +45,4 @@ def validate(cls, value): if value is None: return None - values = [member.value for member in Days] - return value in values \ No newline at end of file + return value in cls._all_values \ No newline at end of file diff --git a/tests/apimatic_core/mocks/models/months.py b/tests/apimatic_core/mocks/models/months.py index bdbd298..baba247 100644 --- a/tests/apimatic_core/mocks/models/months.py +++ b/tests/apimatic_core/mocks/models/months.py @@ -1,6 +1,5 @@ -from enum import Enum -class Months(Enum): +class Months(object): """Implementation of the 'Months' enum. @@ -20,6 +19,7 @@ class Months(Enum): DECEMBER: TODO: type description here. """ + _all_values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] JANUARY = 1 @@ -59,5 +59,4 @@ def validate(cls, value): if value is None: return None - values = [member.value for member in Months] - return value in values \ No newline at end of file + return value in cls._all_values \ No newline at end of file diff --git a/tests/apimatic_core/type_combinator_tests/test_any_of.py b/tests/apimatic_core/type_combinator_tests/test_any_of.py index e9064b9..43a5aa8 100644 --- a/tests/apimatic_core/type_combinator_tests/test_any_of.py +++ b/tests/apimatic_core/type_combinator_tests/test_any_of.py @@ -2,8 +2,8 @@ import pytest from apimatic_core.types.datetime_format import DateTimeFormat -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.any_of import AnyOf from apimatic_core.types.union_types.union_type_context import UnionTypeContext from apimatic_core.utilities.api_helper import ApiHelper from tests.apimatic_core.base import Base @@ -37,6 +37,7 @@ class TestAnyOf: (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)), @@ -237,7 +238,7 @@ class TestAnyOf: [LeafType(int, UnionTypeContext()), LeafType(str, UnionTypeContext())], UnionTypeContext().dict(True).array(True), True, {'key0': [100, 200], 'key1': ['abc', 'def']}), ]) - def test_any_of_primitive_type(self, input_value, input_types, input_context, expected_is_valid_output, + def test_one_of_primitive_type(self, input_value, input_types, input_context, expected_is_valid_output, expected_deserialized_value_output): union_type = AnyOf(input_types, input_context) union_type_result = union_type.validate(input_value) @@ -260,14 +261,13 @@ def test_any_of_primitive_type(self, input_value, input_types, input_context, ex ('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_types, input_context, expected_validity, expected_value): + def test_one_of_date_and_datetime(self, input_value, input_types, input_context, expected_validity, expected_value): union_type = AnyOf(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_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 @@ -275,15 +275,12 @@ def test_any_of_date_and_datetime(self, input_value, input_types, input_context, UnionTypeContext(), True, Atom(2, 5)), ({"OrbitNumberOfElectrons": 4}, [LeafType(Atom), LeafType(Orbit)], UnionTypeContext(), True, Orbit(4)), - ({"key0": {"OrbitNumberOfElectrons": 4}, "key1": {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}}, - [LeafType(Atom), LeafType(Orbit)], UnionTypeContext().dict(True), True, - {"key0": Orbit(4), "key1": Atom(2, 5)}), # 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)]), + [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)]), @@ -316,12 +313,11 @@ def test_any_of_date_and_datetime(self, input_value, input_types, input_context, # Partial Array Case ({"OrbitNumberOfElectrons": 4}, - [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit)], UnionTypeContext(), True, Orbit(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(), True, [Atom(2, 5), Atom(4, 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)], + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext())], UnionTypeContext(), False, None), ('{"OrbitNumberOfElectrons": 4}', [LeafType(Orbit, UnionTypeContext().array(True)), LeafType(Atom)], UnionTypeContext(), False, None), @@ -335,9 +331,9 @@ def test_any_of_date_and_datetime(self, input_value, input_types, input_context, [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().array(True), False, None), + {"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), @@ -374,11 +370,11 @@ def test_any_of_date_and_datetime(self, input_value, input_types, input_context, [{"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}, + ([[{"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), True, [[Orbit(8), Orbit(10)], [Atom(2, 5), Atom(2, 10)]]), + UnionTypeContext().array(True), False, None), # Outer Dictionary Cases ({'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, @@ -391,8 +387,8 @@ def test_any_of_date_and_datetime(self, input_value, input_types, input_context, ({'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(int), LeafType(str)], UnionTypeContext().dict(True), False, None), - ({"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, [LeafType(int), LeafType(str)], + ({"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), @@ -403,11 +399,11 @@ def test_any_of_date_and_datetime(self, input_value, input_types, input_context, [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(int, UnionTypeContext().dict(True)), LeafType(str, UnionTypeContext().dict(True))], + [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(int, UnionTypeContext().dict(True)), - LeafType(str, UnionTypeContext().dict(True))], + [LeafType(Atom, UnionTypeContext().dict(True)), + LeafType(Orbit, UnionTypeContext().dict(True))], UnionTypeContext(), False, None), # Partial Dictionary Cases @@ -425,7 +421,7 @@ def test_any_of_date_and_datetime(self, input_value, input_types, input_context, # Dictionary of Partial Dictionary Cases ({'key0': {"OrbitNumberOfElectrons": 8}, 'key1': {"OrbitNumberOfElectrons": 10}}, - [LeafType(Orbit, UnionTypeContext().dict(True)), LeafType(Atom)], + [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)], @@ -447,7 +443,7 @@ def test_any_of_date_and_datetime(self, input_value, input_types, input_context, 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}}, \ 'key1': {'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, \ 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}}}, - [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext().dict(True))], + [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}}, @@ -460,7 +456,7 @@ def test_any_of_date_and_datetime(self, input_value, input_types, input_context, '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': Orbit(10), 'key1': Orbit(8)}}), + {'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}, @@ -470,7 +466,7 @@ def test_any_of_date_and_datetime(self, input_value, input_types, input_context, ({'key0': {'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}, 'key1': {"OrbitNumberOfElectrons": 10}}, 'key1': {'key0': {"OrbitNumberOfElectrons": 12}, 'key1': {"OrbitNumberOfElectrons": 14}}}, - [LeafType(int, UnionTypeContext().dict(True)), LeafType(str, UnionTypeContext().dict(True))], + [LeafType(Atom, UnionTypeContext().dict(True)), LeafType(Orbit, UnionTypeContext().dict(True))], UnionTypeContext().dict(True), False, None), # Inner array of dictionary cases @@ -515,7 +511,7 @@ def test_any_of_date_and_datetime(self, input_value, input_types, input_context, 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": 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, @@ -571,7 +567,7 @@ def test_any_of_date_and_datetime(self, input_value, input_types, input_context, 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, + def test_one_of_custom_type(self, input_value, input_types, input_context, expected_is_valid_output, expected_deserialized_value_output): union_type = AnyOf(input_types, input_context) union_type_result = union_type.validate(input_value) @@ -607,7 +603,7 @@ def test_any_of_custom_type(self, input_value, input_types, input_context, expec LeafType(Deer, UnionTypeContext().discriminator('type').discriminator_value('deer'))], UnionTypeContext(), True), ('{"id": 123, "weight": 5, "type": "lion", "kind": "hunter"}', - [LeafType(Lion, UnionTypeContext().discriminator('type').discriminator_value('lion')), + [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"}', @@ -626,15 +622,15 @@ def test_any_of_custom_type(self, input_value, input_types, input_context, expec ('{"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), + 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(), False), + UnionTypeContext(), True), ('{"name": "sam", "weight": 5, "type": "deer", "kind": "hunter"}', - [LeafType(dict), LeafType(dict)], UnionTypeContext(), False), + [LeafType(dict), LeafType(dict)], UnionTypeContext(), True), ]) - def test_any_of_with_discriminator_custom_type(self, input_value, input_types, input_context, expected_output): + def test_one_of_with_discriminator_custom_type(self, input_value, input_types, input_context, expected_output): union_type = AnyOf(input_types, input_context) deserialized_dict_input = ApiHelper.json_deserialize(input_value, as_dict=True) union_type_result = union_type.validate(deserialized_dict_input) @@ -705,7 +701,7 @@ def test_any_of_with_discriminator_custom_type(self, input_value, input_types, i 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): + def test_one_of_enum_type(self, input_value, input_types, input_context, expected_is_valid_output, expected_deserialized_value_output): union_type = AnyOf(input_types, input_context) union_type_result = union_type.validate(input_value) actual_is_valid = union_type_result.is_valid diff --git a/tests/apimatic_core/type_combinator_tests/test_one_of.py b/tests/apimatic_core/type_combinator_tests/test_one_of.py index 175e738..1f545ec 100644 --- a/tests/apimatic_core/type_combinator_tests/test_one_of.py +++ b/tests/apimatic_core/type_combinator_tests/test_one_of.py @@ -274,16 +274,12 @@ def test_one_of_date_and_datetime(self, input_value, input_types, input_context, UnionTypeContext(), True, Atom(2, 5)), ({"OrbitNumberOfElectrons": 4}, [LeafType(Atom), LeafType(Orbit)], UnionTypeContext(), True, Orbit(4)), - ({"key0": {"OrbitNumberOfElectrons": 4}, "key1": {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}}, - [LeafType(Atom), LeafType(Orbit)], UnionTypeContext().dict(True), True, - {"key0": Orbit(4), "key1": Atom(2, 5)}), # 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)]), + [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)]), @@ -316,12 +312,11 @@ def test_one_of_date_and_datetime(self, input_value, input_types, input_context, # Partial Array Case ({"OrbitNumberOfElectrons": 4}, - [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit)], UnionTypeContext(), True, Orbit(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(), True, [Atom(2, 5), Atom(4, 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)], + [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext())], UnionTypeContext(), False, None), ('{"OrbitNumberOfElectrons": 4}', [LeafType(Orbit, UnionTypeContext().array(True)), LeafType(Atom)], UnionTypeContext(), False, None), @@ -335,9 +330,9 @@ def test_one_of_date_and_datetime(self, input_value, input_types, input_context, [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().array(True), True, [Atom(2, 5), Orbit(4)]), + {"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), @@ -374,8 +369,8 @@ def test_one_of_date_and_datetime(self, input_value, input_types, input_context, [{"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}, + ([[{"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), @@ -391,8 +386,8 @@ def test_one_of_date_and_datetime(self, input_value, input_types, input_context, ({'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(int), LeafType(str)], UnionTypeContext().dict(True), False, None), - ({"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, [LeafType(int), LeafType(str)], + ({"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), @@ -403,11 +398,11 @@ def test_one_of_date_and_datetime(self, input_value, input_types, input_context, [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(int, UnionTypeContext().dict(True)), LeafType(str, UnionTypeContext().dict(True))], + [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(int, UnionTypeContext().dict(True)), - LeafType(str, UnionTypeContext().dict(True))], + [LeafType(Atom, UnionTypeContext().dict(True)), + LeafType(Orbit, UnionTypeContext().dict(True))], UnionTypeContext(), False, None), # Partial Dictionary Cases @@ -425,7 +420,7 @@ def test_one_of_date_and_datetime(self, input_value, input_types, input_context, # Dictionary of Partial Dictionary Cases ({'key0': {"OrbitNumberOfElectrons": 8}, 'key1': {"OrbitNumberOfElectrons": 10}}, - [LeafType(Orbit, UnionTypeContext().dict(True)), LeafType(Atom)], + [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)], @@ -447,7 +442,7 @@ def test_one_of_date_and_datetime(self, input_value, input_types, input_context, 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}}, \ 'key1': {'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 5}, \ 'key1': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}}}, - [LeafType(Atom, UnionTypeContext().array(True)), LeafType(Orbit, UnionTypeContext().dict(True))], + [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}}, @@ -460,7 +455,7 @@ def test_one_of_date_and_datetime(self, input_value, input_types, input_context, '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': Orbit(10), 'key1': Orbit(8)}}), + {'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}, @@ -470,7 +465,7 @@ def test_one_of_date_and_datetime(self, input_value, input_types, input_context, ({'key0': {'key0': {"AtomNumberOfElectrons": 2, "AtomNumberOfProtons": 10}, 'key1': {"OrbitNumberOfElectrons": 10}}, 'key1': {'key0': {"OrbitNumberOfElectrons": 12}, 'key1': {"OrbitNumberOfElectrons": 14}}}, - [LeafType(int, UnionTypeContext().dict(True)), LeafType(str, UnionTypeContext().dict(True))], + [LeafType(Atom, UnionTypeContext().dict(True)), LeafType(Orbit, UnionTypeContext().dict(True))], UnionTypeContext().dict(True), False, None), # Inner array of dictionary cases @@ -515,7 +510,7 @@ def test_one_of_date_and_datetime(self, input_value, input_types, input_context, 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": 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, @@ -607,7 +602,7 @@ def test_one_of_custom_type(self, input_value, input_types, input_context, expec LeafType(Deer, UnionTypeContext().discriminator('type').discriminator_value('deer'))], UnionTypeContext(), True), ('{"id": 123, "weight": 5, "type": "lion", "kind": "hunter"}', - [LeafType(Lion, UnionTypeContext().discriminator('type').discriminator_value('lion')), + [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"}', From 7c1a68952e3112a8f95a87b44ad3b5706b41c439 Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Thu, 1 Jun 2023 11:02:00 +0500 Subject: [PATCH 18/49] added fixes to the unit tests for OAF --- tests/apimatic_core/mocks/models/atom.py | 51 +++++++------ tests/apimatic_core/mocks/models/deer.py | 35 ++++++--- tests/apimatic_core/mocks/models/lion.py | 34 +++++---- tests/apimatic_core/mocks/models/orbit.py | 34 +++++---- tests/apimatic_core/mocks/models/rabbit.py | 12 +-- .../type_combinator_tests/test_any_of.py | 73 +++++++++++-------- .../type_combinator_tests/test_one_of.py | 59 +++++++++------ 7 files changed, 180 insertions(+), 118 deletions(-) diff --git a/tests/apimatic_core/mocks/models/atom.py b/tests/apimatic_core/mocks/models/atom.py index 97507ad..254643a 100644 --- a/tests/apimatic_core/mocks/models/atom.py +++ b/tests/apimatic_core/mocks/models/atom.py @@ -2,35 +2,36 @@ class Atom(object): + """Implementation of the 'Atom' model. TODO: type model description here. Attributes: - number_of_electrons (int): TODO: type description here. - number_of_protons (int): TODO: type description here. + 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 = { - "number_of_electrons": 'AtomNumberOfElectrons', # int, bool - "number_of_protons": 'AtomNumberOfProtons' + "atom_number_of_electrons": 'AtomNumberOfElectrons', + "atom_number_of_protons": 'AtomNumberOfProtons' } _optionals = [ - 'number_of_protons', + 'atom_number_of_protons', ] def __init__(self, - number_of_electrons=None, - number_of_protons=ApiHelper.SKIP): + atom_number_of_electrons=None, + atom_number_of_protons=ApiHelper.SKIP): """Constructor for the Atom class""" # Initialize members of the class - self.number_of_electrons = number_of_electrons - if number_of_protons is not ApiHelper.SKIP: - self.number_of_protons = number_of_protons + 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, @@ -50,34 +51,40 @@ def from_dictionary(cls, return None # Extract variables from the dictionary - - number_of_electrons = dictionary.get("AtomNumberOfElectrons") if dictionary.get("AtomNumberOfElectrons") else None - number_of_protons = dictionary.get("AtomNumberOfProtons") if dictionary.get("AtomNumberOfProtons") else ApiHelper.SKIP + 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(number_of_electrons, - number_of_protons) + return cls(atom_number_of_electrons, + atom_number_of_protons) @classmethod def validate(cls, dictionary): - """Validates dictionary against class properties. + """Validates dictionary against class required properties Args: - dictionary: the dictionary to be validated against. + 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 value is valid for this model. + boolean : if dictionary is valid contains required properties. """ + if isinstance(dictionary, cls): - return True + 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 dictionary.get("AtomNumberOfElectrons") is not None and \ - ApiHelper.is_valid(dictionary.get("AtomNumberOfElectrons"), lambda value: isinstance(value, int)) + 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.number_of_electrons == other.number_of_electrons and self.number_of_protons == other.number_of_protons + 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/deer.py b/tests/apimatic_core/mocks/models/deer.py index 1352341..7c9c0d2 100644 --- a/tests/apimatic_core/mocks/models/deer.py +++ b/tests/apimatic_core/mocks/models/deer.py @@ -1,12 +1,16 @@ +from apimatic_core.utilities.api_helper import ApiHelper + + class Deer(object): + """Implementation of the 'Deer' model. TODO: type model description here. Attributes: - name (string): TODO: type description here. - weight (string): TODO: type description here. - mtype (string): TODO: type description here. + name (str): TODO: type description here. + weight (int): TODO: type description here. + mtype (str): TODO: type description here. """ @@ -46,7 +50,6 @@ def from_dictionary(cls, 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 @@ -57,18 +60,26 @@ def from_dictionary(cls, @classmethod def validate(cls, dictionary): - """Validates dictionary against class properties. + """Validates dictionary against class required properties Args: - dictionary: the dictionary to be validated against. + 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 value is valid for this model. + boolean : if dictionary is valid contains required properties. """ - if dictionary is None: - return None - return dictionary.get("name") is not None and \ - dictionary.get("weight") is not None and \ - dictionary.get("type") is not None + 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/lion.py b/tests/apimatic_core/mocks/models/lion.py index fc288a8..d3b454d 100644 --- a/tests/apimatic_core/mocks/models/lion.py +++ b/tests/apimatic_core/mocks/models/lion.py @@ -2,15 +2,16 @@ class Lion(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. + id (int): TODO: type description here. + weight (int): TODO: type description here. + mtype (str): TODO: type description here. + kind (str): TODO: type description here. """ @@ -58,7 +59,6 @@ def from_dictionary(cls, 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 @@ -71,18 +71,26 @@ def from_dictionary(cls, @classmethod def validate(cls, dictionary): - """Validates dictionary against class properties. + """Validates dictionary against class required properties Args: - dictionary: the dictionary to be validated against. + 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 value is valid for this model. + boolean : if dictionary is valid contains required properties. """ - if dictionary is None: - return None - return dictionary.get("id") is not None and \ - dictionary.get("weight") is not None and \ - dictionary.get("type") is not None + 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/orbit.py b/tests/apimatic_core/mocks/models/orbit.py index d7415a4..a124c09 100644 --- a/tests/apimatic_core/mocks/models/orbit.py +++ b/tests/apimatic_core/mocks/models/orbit.py @@ -8,21 +8,21 @@ class Orbit(object): TODO: type model description here. Attributes: - number_of_electrons (int): TODO: type description here. + orbit_number_of_electrons (int): TODO: type description here. """ # Create a mapping from Model property names to API property names _names = { - "number_of_electrons": 'OrbitNumberOfElectrons' + "orbit_number_of_electrons": 'OrbitNumberOfElectrons' } def __init__(self, - number_of_electrons=None): + orbit_number_of_electrons=None): """Constructor for the Orbit class""" # Initialize members of the class - self.number_of_electrons = number_of_electrons + self.orbit_number_of_electrons = orbit_number_of_electrons @classmethod def from_dictionary(cls, @@ -42,32 +42,36 @@ def from_dictionary(cls, return None # Extract variables from the dictionary - - number_of_electrons = dictionary.get("OrbitNumberOfElectrons") if dictionary.get("OrbitNumberOfElectrons") else None + orbit_number_of_electrons = dictionary.get("OrbitNumberOfElectrons") \ + if dictionary.get("OrbitNumberOfElectrons") else None # Return an object of this model - return cls(number_of_electrons) + return cls(orbit_number_of_electrons) @classmethod def validate(cls, dictionary): - """Validates dictionary against class properties. + """Validates dictionary against class required properties Args: - dictionary: the dictionary to be validated against. + 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 value is valid for this model. + boolean : if dictionary is valid contains required properties. """ + if isinstance(dictionary, cls): - return True + 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 dictionary.get("OrbitNumberOfElectrons") is not None and \ - ApiHelper.is_valid(dictionary.get("OrbitNumberOfElectrons"), lambda value: isinstance(value, int)) + 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.number_of_electrons == other.number_of_electrons - return False \ No newline at end of file + 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 index 2ab975a..ff1cc09 100644 --- a/tests/apimatic_core/mocks/models/rabbit.py +++ b/tests/apimatic_core/mocks/models/rabbit.py @@ -86,9 +86,9 @@ def validate(cls, dictionary): if not isinstance(dictionary, dict): return False - return dictionary.get("id") is not None and ApiHelper.is_valid(dictionary.get("id"), - lambda value: isinstance(value, str)) and \ - dictionary.get("weight") is not None and ApiHelper.is_valid(dictionary.get("weight"), - lambda value: isinstance(value, str)) and \ - dictionary.get("type") is not None and ApiHelper.is_valid(dictionary.get("type"), - lambda value: isinstance(value, str)) + 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/type_combinator_tests/test_any_of.py b/tests/apimatic_core/type_combinator_tests/test_any_of.py index 43a5aa8..2191901 100644 --- a/tests/apimatic_core/type_combinator_tests/test_any_of.py +++ b/tests/apimatic_core/type_combinator_tests/test_any_of.py @@ -1,6 +1,6 @@ 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 @@ -238,12 +238,16 @@ class TestAnyOf: [LeafType(int, UnionTypeContext()), LeafType(str, UnionTypeContext())], UnionTypeContext().dict(True).array(True), True, {'key0': [100, 200], 'key1': ['abc', 'def']}), ]) - def test_one_of_primitive_type(self, input_value, input_types, input_context, expected_is_valid_output, + def test_any_of_primitive_type(self, input_value, input_types, input_context, expected_is_valid_output, expected_deserialized_value_output): - union_type = AnyOf(input_types, input_context) - union_type_result = union_type.validate(input_value) - actual_is_valid = union_type_result.is_valid - actual_deserialized_value = union_type_result.deserialize(input_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_is_valid_output assert actual_deserialized_value == expected_deserialized_value_output @@ -261,9 +265,8 @@ def test_one_of_primitive_type(self, input_value, input_types, input_context, ex ('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_types, input_context, expected_validity, expected_value): - union_type = AnyOf(input_types, input_context) - union_type_result = union_type.validate(input_value) + def test_any_of_date_and_datetime(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 @@ -567,12 +570,16 @@ def test_one_of_date_and_datetime(self, input_value, input_types, input_context, 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, + def test_any_of_custom_type(self, input_value, input_types, input_context, expected_is_valid_output, expected_deserialized_value_output): - union_type = AnyOf(input_types, input_context) - union_type_result = union_type.validate(input_value) - actual_is_valid = union_type_result.is_valid - actual_deserialized_value = union_type_result.deserialize(input_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_is_valid_output assert actual_deserialized_value == expected_deserialized_value_output @@ -630,16 +637,20 @@ def test_one_of_custom_type(self, input_value, input_types, input_context, expec ('{"name": "sam", "weight": 5, "type": "deer", "kind": "hunter"}', [LeafType(dict), LeafType(dict)], UnionTypeContext(), True), ]) - def test_one_of_with_discriminator_custom_type(self, input_value, input_types, input_context, expected_output): - union_type = AnyOf(input_types, input_context) - deserialized_dict_input = ApiHelper.json_deserialize(input_value, as_dict=True) - union_type_result = union_type.validate(deserialized_dict_input) - actual_result = union_type_result.is_valid - assert actual_result == expected_output - - @pytest.mark.parametrize('input_value, input_types, input_context, expected_is_valid_output, expected_deserialized_value_output', [ + 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())], + ('Monday', [LeafType(Days, UnionTypeContext()), LeafType(Months, UnionTypeContext())], UnionTypeContext(), True, 'Monday'), (1, [LeafType(Days, UnionTypeContext()), LeafType(Months, UnionTypeContext())], UnionTypeContext(), True, 1), @@ -701,10 +712,14 @@ def test_one_of_with_discriminator_custom_type(self, input_value, input_types, i 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): - union_type = AnyOf(input_types, input_context) - union_type_result = union_type.validate(input_value) - actual_is_valid = union_type_result.is_valid - actual_deserialized_value = union_type_result.deserialize(input_value) + 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 \ No newline at end of file + assert actual_deserialized_value == expected_deserialized_value_output diff --git a/tests/apimatic_core/type_combinator_tests/test_one_of.py b/tests/apimatic_core/type_combinator_tests/test_one_of.py index 1f545ec..35884c0 100644 --- a/tests/apimatic_core/type_combinator_tests/test_one_of.py +++ b/tests/apimatic_core/type_combinator_tests/test_one_of.py @@ -1,6 +1,6 @@ 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 @@ -239,10 +239,14 @@ class TestOneOf: ]) def test_one_of_primitive_type(self, input_value, input_types, input_context, expected_is_valid_output, expected_deserialized_value_output): - union_type = OneOf(input_types, input_context) - union_type_result = union_type.validate(input_value) - actual_is_valid = union_type_result.is_valid - actual_deserialized_value = union_type_result.deserialize(input_value) + 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 @@ -568,10 +572,14 @@ def test_one_of_date_and_datetime(self, input_value, input_types, input_context, ]) def test_one_of_custom_type(self, input_value, input_types, input_context, expected_is_valid_output, expected_deserialized_value_output): - union_type = OneOf(input_types, input_context) - union_type_result = union_type.validate(input_value) - actual_is_valid = union_type_result.is_valid - actual_deserialized_value = union_type_result.deserialize(input_value) + 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 @@ -630,13 +638,17 @@ def test_one_of_custom_type(self, input_value, input_types, input_context, expec [LeafType(dict), LeafType(dict)], UnionTypeContext(), False), ]) def test_one_of_with_discriminator_custom_type(self, input_value, input_types, input_context, expected_output): - union_type = OneOf(input_types, input_context) - deserialized_dict_input = ApiHelper.json_deserialize(input_value, as_dict=True) - union_type_result = union_type.validate(deserialized_dict_input) - actual_result = union_type_result.is_valid - assert actual_result == 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 - @pytest.mark.parametrize('input_value, input_types, input_context, expected_is_valid_output, expected_deserialized_value_output', [ + 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'), @@ -700,10 +712,15 @@ def test_one_of_with_discriminator_custom_type(self, input_value, input_types, i 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): - union_type = OneOf(input_types, input_context) - union_type_result = union_type.validate(input_value) - actual_is_valid = union_type_result.is_valid - actual_deserialized_value = union_type_result.deserialize(input_value) + 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 \ No newline at end of file + assert actual_deserialized_value == expected_deserialized_value_output From 01397adc921e6b143f2cb6826b46d9eb0a899a35 Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Thu, 1 Jun 2023 11:48:26 +0500 Subject: [PATCH 19/49] refactored oneOf and anyOf classes and added optional or nullable test cases --- apimatic_core/types/union_types/any_of.py | 83 +++--------------- apimatic_core/types/union_types/leaf_type.py | 6 +- apimatic_core/types/union_types/one_of.py | 86 +++---------------- apimatic_core/utilities/union_type_helper.py | 69 +++++++++++++++ .../type_combinator_tests/test_any_of.py | 42 +++++++-- .../type_combinator_tests/test_one_of.py | 34 ++++++++ 6 files changed, 171 insertions(+), 149 deletions(-) diff --git a/apimatic_core/types/union_types/any_of.py b/apimatic_core/types/union_types/any_of.py index bfeb8f2..da18df7 100644 --- a/apimatic_core/types/union_types/any_of.py +++ b/apimatic_core/types/union_types/any_of.py @@ -18,7 +18,10 @@ def validate(self, value): for union_type in self._union_types: union_type.get_context().is_nested = True - if value is None and context.is_nullable_or_optional(): + is_optional_or_nullable = context.is_nullable_or_optional() or any( + union_type.get_context().is_nullable_or_optional() for union_type in self._union_types) + + if value is None and is_optional_or_nullable: self.is_valid = True return self @@ -29,22 +32,26 @@ def validate(self, value): if context.is_array() and context.is_dict() and context.is_array_of_dict(): if isinstance(value, list): - self.is_valid, self.collection_cases = self.validate_array_of_dict_case(value) + self.is_valid, self.collection_cases = UnionTypeHelper.validate_array_of_dict_case(self._union_types, + value, False) else: self.is_valid = False elif context.is_array() and context.is_dict(): if isinstance(value, dict): - self.is_valid, self.collection_cases = self.validate_dict_of_array_case(value) + self.is_valid, self.collection_cases = UnionTypeHelper.validate_dict_of_array_case(self._union_types, + value, False) else: self.is_valid = False elif context.is_array(): if isinstance(value, list): - self.is_valid, self.collection_cases = self.validate_array_case(value) + self.is_valid, self.collection_cases = UnionTypeHelper.validate_array_case(self._union_types, + value, False) else: self.is_valid = False elif context.is_dict(): if isinstance(value, dict): - self.is_valid, self.collection_cases = self.validate_dict_case(value) + self.is_valid, self.collection_cases = UnionTypeHelper.validate_dict_case(self._union_types, + value, False) else: self.is_valid = False else: @@ -68,11 +75,10 @@ def process_errors(self): if self._union_type_context.is_nested: self.error_messages.append(', '.join(combined_types)) else: - raise AnyOfValidationException('{} \nExpected Type: Any Of {}'.format( + raise AnyOfValidationException('{} \nExpected Type: Any Of {}.'.format( UnionType.NONE_MATCHED_ERROR_MESSAGE, ', '.join(combined_types))) def deserialize(self, value): - if value is None or not self.is_valid: return None @@ -89,71 +95,10 @@ def deserialize(self, value): elif context.is_dict(): deserialized_value = UnionTypeHelper.deserialize_dict_case(value, self.collection_cases) else: - deserialized_value = self.get_deserialized_value(value) + deserialized_value = UnionTypeHelper.get_deserialized_value(self._union_types, value) return deserialized_value - def get_deserialized_value(self, value): - return [union_type for union_type in self._union_types if union_type.is_valid][0].deserialize(value) - - def validate_array_of_dict_case(self, array_value): - if array_value is None or not array_value: - return tuple((False, [])) - - collection_cases = [] - valid_cases = [] - for item in array_value: - case_validity, inner_dictionary = self.validate_dict_case(item) - collection_cases.append(inner_dictionary) - valid_cases.append(case_validity) - is_valid = sum(valid_cases) == array_value.__len__() - return tuple((is_valid, collection_cases)) - - def validate_dict_of_array_case(self, dict_value): - if dict_value is None or not dict_value: - return tuple((False, [])) - - collection_cases = {} - valid_cases = [] - for key, item in dict_value.items(): - case_validity, inner_array = self.validate_array_case(item) - collection_cases[key] = inner_array - valid_cases.append(case_validity) - is_valid = sum(valid_cases) == dict_value.__len__() - return tuple((is_valid, collection_cases)) - - def validate_dict_case(self, dict_value): - if dict_value is None or not dict_value: - return tuple((False, [])) - - is_valid = True - collection_cases = {} - for key, value in dict_value.items(): - nested_cases = [] - for union_type in self._union_types: - nested_cases.append(copy.deepcopy(union_type).validate(value)) - matched_count = UnionTypeHelper.get_matched_count(value, nested_cases, True) - if is_valid: - is_valid = matched_count >= 1 - collection_cases[key] = nested_cases - return tuple((is_valid, collection_cases)) - - def validate_array_case(self, array_value): - if array_value is None or not array_value: - return tuple((False, [])) - - is_valid = True - collection_cases = [] - for item in array_value: - nested_cases = [] - for union_type in self._union_types: - nested_cases.append(copy.deepcopy(union_type).validate(item)) - matched_count = UnionTypeHelper.get_matched_count(item, nested_cases, True) - if is_valid: - is_valid = matched_count >= 1 - collection_cases.append(nested_cases) - return tuple((is_valid, collection_cases)) - def __deepcopy__(self, memo={}): copy_object = AnyOf(self._union_types, self._union_type_context) copy_object.is_valid = self.is_valid diff --git a/apimatic_core/types/union_types/leaf_type.py b/apimatic_core/types/union_types/leaf_type.py index b5ec178..d196ef7 100644 --- a/apimatic_core/types/union_types/leaf_type.py +++ b/apimatic_core/types/union_types/leaf_type.py @@ -15,13 +15,13 @@ def __init__(self, type_to_match: type, union_type_context: UnionTypeContext = U def validate(self, value): context = self._union_type_context - if value is None and context.is_nullable_or_nullable(): + if value is None and context.is_nullable_or_optional(): self.is_valid = True - return self.is_valid + return self if value is None: self.is_valid = False - return self.is_valid + return self if context.is_array() and context.is_dict() and context.is_array_of_dict(): self.is_valid = self.validate_array_of_dict_case(value) diff --git a/apimatic_core/types/union_types/one_of.py b/apimatic_core/types/union_types/one_of.py index 3071f79..bf305a8 100644 --- a/apimatic_core/types/union_types/one_of.py +++ b/apimatic_core/types/union_types/one_of.py @@ -18,7 +18,10 @@ def validate(self, value): for union_type in self._union_types: union_type.get_context().is_nested = True - if value is None and context.is_nullable_or_optional(): + is_optional_or_nullable = context.is_nullable_or_optional() or any( + union_type.get_context().is_nullable_or_optional() for union_type in self._union_types) + + if value is None and is_optional_or_nullable: self.is_valid = True return self @@ -29,22 +32,26 @@ def validate(self, value): if context.is_array() and context.is_dict() and context.is_array_of_dict(): if isinstance(value, list): - self.is_valid, self.collection_cases = self.validate_array_of_dict_case(value) + self.is_valid, self.collection_cases = UnionTypeHelper.validate_array_of_dict_case(self._union_types, + value, True) else: self.is_valid = False elif context.is_array() and context.is_dict(): if isinstance(value, dict): - self.is_valid, self.collection_cases = self.validate_dict_of_array_case(value) + self.is_valid, self.collection_cases = UnionTypeHelper.validate_dict_of_array_case(self._union_types, + value, True) else: self.is_valid = False elif context.is_array(): if isinstance(value, list): - self.is_valid, self.collection_cases = self.validate_array_case(value) + self.is_valid, self.collection_cases = UnionTypeHelper.validate_array_case(self._union_types, + value, True) else: self.is_valid = False elif context.is_dict(): if isinstance(value, dict): - self.is_valid, self.collection_cases = self.validate_dict_case(value) + self.is_valid, self.collection_cases = UnionTypeHelper.validate_dict_case(self._union_types, + value, True) else: self.is_valid = False else: @@ -71,7 +78,7 @@ def process_errors(self): matched_count = sum(union_type.is_valid for union_type in self._union_types) error_message = UnionType.MORE_THAN_1_MATCHED_ERROR_MESSAGE if matched_count > 0 \ else UnionType.NONE_MATCHED_ERROR_MESSAGE - raise OneOfValidationException('{} \nExpected Type: One Of {}'.format( + raise OneOfValidationException('{} \nExpected Type: One Of {}.'.format( error_message, ', '.join(combined_types))) def deserialize(self, value): @@ -92,75 +99,10 @@ def deserialize(self, value): elif context.is_dict(): deserialized_value = UnionTypeHelper.deserialize_dict_case(value, self.collection_cases) else: - deserialized_value = self.get_deserialized_value(value) + deserialized_value = UnionTypeHelper.get_deserialized_value(self._union_types, value) return deserialized_value - def get_deserialized_value(self, value): - return [union_type for union_type in self._union_types if union_type.is_valid][0].deserialize(value) - - def validate_array_of_dict_case(self, array_value): - if array_value is None or not array_value: - return tuple((False, [])) - - collection_cases = [] - valid_cases = [] - for item in array_value: - case_validity, inner_dictionary = self.validate_dict_case(item) - collection_cases.append(inner_dictionary) - valid_cases.append(case_validity) - is_valid = sum(valid_cases) == array_value.__len__() - - return tuple((is_valid, collection_cases)) - - def validate_dict_of_array_case(self, dict_value): - if dict_value is None or not dict_value: - return tuple((False, [])) - - collection_cases = {} - valid_cases = [] - for key, item in dict_value.items(): - case_validity, inner_array = self.validate_array_case(item) - collection_cases[key] = inner_array - valid_cases.append(case_validity) - is_valid = sum(valid_cases) == dict_value.__len__() - - return tuple((is_valid, collection_cases)) - - def validate_dict_case(self, dict_value): - if dict_value is None or not dict_value: - return tuple((False, [])) - - is_valid = True - collection_cases = {} - for key, value in dict_value.items(): - nested_cases = [] - for union_type in self._union_types: - nested_cases.append(copy.deepcopy(union_type).validate(value)) - matched_count = UnionTypeHelper.get_matched_count(value, nested_cases, True) - if is_valid: - is_valid = matched_count == 1 - collection_cases[key] = nested_cases - - return tuple((is_valid, collection_cases)) - - def validate_array_case(self, array_value): - if array_value is None or not array_value: - return tuple((False, [])) - - is_valid = True - collection_cases = [] - for item in array_value: - nested_cases = [] - for union_type in self._union_types: - nested_cases.append(copy.deepcopy(union_type).validate(item)) - matched_count = UnionTypeHelper.get_matched_count(item, nested_cases, True) - if is_valid: - is_valid = matched_count == 1 - collection_cases.append(nested_cases) - - return tuple((is_valid, collection_cases)) - def __deepcopy__(self, memo={}): copy_object = OneOf(self._union_types, self._union_type_context) copy_object.is_valid = self.is_valid diff --git a/apimatic_core/utilities/union_type_helper.py b/apimatic_core/utilities/union_type_helper.py index 9c6f893..7e58c85 100644 --- a/apimatic_core/utilities/union_type_helper.py +++ b/apimatic_core/utilities/union_type_helper.py @@ -1,5 +1,74 @@ +import copy + class UnionTypeHelper: + + @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 array_value is None or not 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 dict_value is None or not 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 dict_value is None or not dict_value: + return tuple((False, [])) + + is_valid = True + collection_cases = {} + for key, value in dict_value.items(): + nested_cases = [] + for union_type in union_types: + nested_cases.append(copy.deepcopy(union_type).validate(value)) + matched_count = UnionTypeHelper.get_matched_count(value, nested_cases, True) + if is_valid: + is_valid = matched_count == 1 if is_for_one_of else matched_count >= 1 + collection_cases[key] = nested_cases + return tuple((is_valid, collection_cases)) + + @staticmethod + def validate_array_case(union_types, array_value, is_for_one_of): + if array_value is None or not array_value: + return tuple((False, [])) + + is_valid = True + collection_cases = [] + for item in array_value: + nested_cases = [] + for union_type in union_types: + nested_cases.append(copy.deepcopy(union_type).validate(item)) + matched_count = UnionTypeHelper.get_matched_count(item, nested_cases, True) + if is_valid: + is_valid = matched_count == 1 if is_for_one_of else matched_count >= 1 + collection_cases.append(nested_cases) + return tuple((is_valid, collection_cases)) + @staticmethod def deserialize_array_of_dict_case(array_value, collection_cases): if array_value is None: diff --git a/tests/apimatic_core/type_combinator_tests/test_any_of.py b/tests/apimatic_core/type_combinator_tests/test_any_of.py index 2191901..5c89710 100644 --- a/tests/apimatic_core/type_combinator_tests/test_any_of.py +++ b/tests/apimatic_core/type_combinator_tests/test_any_of.py @@ -6,6 +6,7 @@ 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_interfaces.types.union_type import UnionType 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 @@ -19,7 +20,7 @@ class TestAnyOf: @pytest.mark.parametrize( - 'input_value, input_types, input_context, expected_is_valid_output, expected_deserialized_value_output', [ + '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), @@ -238,8 +239,8 @@ class TestAnyOf: [LeafType(int, UnionTypeContext()), LeafType(str, UnionTypeContext())], UnionTypeContext().dict(True).array(True), True, {'key0': [100, 200], 'key1': ['abc', 'def']}), ]) - def test_any_of_primitive_type(self, input_value, input_types, input_context, expected_is_valid_output, - expected_deserialized_value_output): + 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 @@ -248,8 +249,8 @@ def test_any_of_primitive_type(self, input_value, input_types, input_context, ex actual_is_valid = False actual_deserialized_value = None - assert actual_is_valid == expected_is_valid_output - assert actual_deserialized_value == expected_deserialized_value_output + assert actual_is_valid == expected_validity + assert actual_deserialized_value == expected_deserialized_value @pytest.mark.parametrize( 'input_value, input_types, input_context, expected_validity, expected_value', [ @@ -271,6 +272,25 @@ def test_any_of_date_and_datetime(self, input_value, input_types, input_context, 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_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), + ([None, None], [LeafType(int, UnionTypeContext().nullable(True)), LeafType(str)], + UnionTypeContext().array(True), True, [None, None]), + ]) + 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 @@ -723,3 +743,15 @@ def test_any_of_enum_type(self, input_value, input_types, input_context, expecte 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(), + '{} \nExpected Type: Any Of int, bool, str.'.format(UnionType.NONE_MATCHED_ERROR_MESSAGE)), + (100.5, [LeafType(int), AnyOf([LeafType(bool), LeafType(str)])], UnionTypeContext(), + '{} \nExpected Type: Any Of int, bool, str.'.format(UnionType.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/type_combinator_tests/test_one_of.py b/tests/apimatic_core/type_combinator_tests/test_one_of.py index 35884c0..ec7143b 100644 --- a/tests/apimatic_core/type_combinator_tests/test_one_of.py +++ b/tests/apimatic_core/type_combinator_tests/test_one_of.py @@ -6,6 +6,7 @@ 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_interfaces.types.union_type import UnionType 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 @@ -271,6 +272,25 @@ def test_one_of_date_and_datetime(self, input_value, input_types, input_context, 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_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), + ([None, None], [LeafType(int, UnionTypeContext().nullable(True)), LeafType(str)], + UnionTypeContext().array(True), True, [None, None]), + ]) + def test_any_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 @@ -724,3 +744,17 @@ def test_one_of_enum_type(self, input_value, input_types, input_context, expecte 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(), + '{} \nExpected Type: One Of int, int, str.'.format(UnionType.MORE_THAN_1_MATCHED_ERROR_MESSAGE)), + (100.5, [LeafType(int), LeafType(bool), LeafType(str)], UnionTypeContext(), + '{} \nExpected Type: One Of int, bool, str.'.format(UnionType.NONE_MATCHED_ERROR_MESSAGE)), + (100.5, [LeafType(int), OneOf([LeafType(bool), LeafType(str)])], UnionTypeContext(), + '{} \nExpected Type: One Of int, bool, str.'.format(UnionType.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 From a1d663b178a3a26b7aa091702f69f5a3e7928180 Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Thu, 1 Jun 2023 15:38:57 +0500 Subject: [PATCH 20/49] added value in the validation error message and handled optional nullable cases at OneOf/AnyOf level --- apimatic_core/types/union_types/any_of.py | 10 +++++----- apimatic_core/types/union_types/one_of.py | 10 +++++----- .../type_combinator_tests/test_any_of.py | 10 ++++++---- .../type_combinator_tests/test_one_of.py | 15 ++++++++++----- 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/apimatic_core/types/union_types/any_of.py b/apimatic_core/types/union_types/any_of.py index da18df7..f4fe322 100644 --- a/apimatic_core/types/union_types/any_of.py +++ b/apimatic_core/types/union_types/any_of.py @@ -27,7 +27,7 @@ def validate(self, value): if value is None: self.is_valid = False - self.process_errors() + self.process_errors(value) return self if context.is_array() and context.is_dict() and context.is_array_of_dict(): @@ -58,11 +58,11 @@ def validate(self, value): self.is_valid = UnionTypeHelper.get_matched_count(value, self._union_types, False) > 0 if not self.is_valid: - self.process_errors() + self.process_errors(value) return self - def process_errors(self): + def process_errors(self, value): self.error_messages = [] combined_types = [] @@ -75,8 +75,8 @@ def process_errors(self): if self._union_type_context.is_nested: self.error_messages.append(', '.join(combined_types)) else: - raise AnyOfValidationException('{} \nExpected Type: Any Of {}.'.format( - UnionType.NONE_MATCHED_ERROR_MESSAGE, ', '.join(combined_types))) + raise AnyOfValidationException('{} \nActual Value: {}\nExpected Type: Any Of {}.'.format( + UnionType.NONE_MATCHED_ERROR_MESSAGE, value, ', '.join(combined_types))) def deserialize(self, value): if value is None or not self.is_valid: diff --git a/apimatic_core/types/union_types/one_of.py b/apimatic_core/types/union_types/one_of.py index bf305a8..d33e2f0 100644 --- a/apimatic_core/types/union_types/one_of.py +++ b/apimatic_core/types/union_types/one_of.py @@ -27,7 +27,7 @@ def validate(self, value): if value is None: self.is_valid = False - self.process_errors() + self.process_errors(value) return self if context.is_array() and context.is_dict() and context.is_array_of_dict(): @@ -58,11 +58,11 @@ def validate(self, value): self.is_valid = UnionTypeHelper.get_matched_count(value, self._union_types, True) == 1 if not self.is_valid: - self.process_errors() + self.process_errors(value) return self - def process_errors(self): + def process_errors(self, value): self.error_messages = [] combined_types = [] @@ -78,8 +78,8 @@ def process_errors(self): matched_count = sum(union_type.is_valid for union_type in self._union_types) error_message = UnionType.MORE_THAN_1_MATCHED_ERROR_MESSAGE if matched_count > 0 \ else UnionType.NONE_MATCHED_ERROR_MESSAGE - raise OneOfValidationException('{} \nExpected Type: One Of {}.'.format( - error_message, ', '.join(combined_types))) + raise OneOfValidationException('{} \nActual Value: {}\nExpected Type: One Of {}.'.format( + error_message, value, ', '.join(combined_types))) def deserialize(self, value): if value is None or not self.is_valid: diff --git a/tests/apimatic_core/type_combinator_tests/test_any_of.py b/tests/apimatic_core/type_combinator_tests/test_any_of.py index 5c89710..f3e75a9 100644 --- a/tests/apimatic_core/type_combinator_tests/test_any_of.py +++ b/tests/apimatic_core/type_combinator_tests/test_any_of.py @@ -282,8 +282,10 @@ def test_any_of_date_and_datetime(self, input_value, input_types, input_context, (None, [LeafType(int, UnionTypeContext().nullable(True)), LeafType(str, UnionTypeContext().optional(True))], UnionTypeContext(), True, None), (None, [LeafType(int), LeafType(str)], UnionTypeContext().nullable(True), True, None), - ([None, None], [LeafType(int, UnionTypeContext().nullable(True)), LeafType(str)], - UnionTypeContext().array(True), True, [None, 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) @@ -747,9 +749,9 @@ def test_any_of_enum_type(self, input_value, input_types, input_context, expecte @pytest.mark.parametrize('input_value, input_types, input_context, expected_validation_message', [ # Simple Cases (100.5, [LeafType(int), LeafType(bool), LeafType(str)], UnionTypeContext(), - '{} \nExpected Type: Any Of int, bool, str.'.format(UnionType.NONE_MATCHED_ERROR_MESSAGE)), + '{} \nActual Value: 100.5\nExpected Type: Any Of int, bool, str.'.format(UnionType.NONE_MATCHED_ERROR_MESSAGE)), (100.5, [LeafType(int), AnyOf([LeafType(bool), LeafType(str)])], UnionTypeContext(), - '{} \nExpected Type: Any Of int, bool, str.'.format(UnionType.NONE_MATCHED_ERROR_MESSAGE)), + '{} \nActual Value: 100.5\nExpected Type: Any Of int, bool, str.'.format(UnionType.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: diff --git a/tests/apimatic_core/type_combinator_tests/test_one_of.py b/tests/apimatic_core/type_combinator_tests/test_one_of.py index ec7143b..e7686df 100644 --- a/tests/apimatic_core/type_combinator_tests/test_one_of.py +++ b/tests/apimatic_core/type_combinator_tests/test_one_of.py @@ -282,8 +282,10 @@ def test_one_of_date_and_datetime(self, input_value, input_types, input_context, (None, [LeafType(int, UnionTypeContext().nullable(True)), LeafType(str, UnionTypeContext().optional(True))], UnionTypeContext(), True, None), (None, [LeafType(int), LeafType(str)], UnionTypeContext().nullable(True), True, None), - ([None, None], [LeafType(int, UnionTypeContext().nullable(True)), LeafType(str)], - UnionTypeContext().array(True), True, [None, 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 = OneOf(input_types, input_context).validate(input_value) @@ -748,11 +750,14 @@ def test_one_of_enum_type(self, input_value, input_types, input_context, expecte @pytest.mark.parametrize('input_value, input_types, input_context, expected_validation_message', [ # Simple Cases (100, [LeafType(int), LeafType(int), LeafType(str)], UnionTypeContext(), - '{} \nExpected Type: One Of int, int, str.'.format(UnionType.MORE_THAN_1_MATCHED_ERROR_MESSAGE)), + '{} \nActual Value: 100\nExpected Type: One Of int, int, str.'.format( + UnionType.MORE_THAN_1_MATCHED_ERROR_MESSAGE)), (100.5, [LeafType(int), LeafType(bool), LeafType(str)], UnionTypeContext(), - '{} \nExpected Type: One Of int, bool, str.'.format(UnionType.NONE_MATCHED_ERROR_MESSAGE)), + '{} \nActual Value: 100.5\nExpected Type: One Of int, bool, str.'.format( + UnionType.NONE_MATCHED_ERROR_MESSAGE)), (100.5, [LeafType(int), OneOf([LeafType(bool), LeafType(str)])], UnionTypeContext(), - '{} \nExpected Type: One Of int, bool, str.'.format(UnionType.NONE_MATCHED_ERROR_MESSAGE)), + '{} \nActual Value: 100.5\nExpected Type: One Of int, bool, str.'.format( + UnionType.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: From bf2dd5847ffd8191380e07b009f8465c5741aee4 Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Thu, 1 Jun 2023 16:02:54 +0500 Subject: [PATCH 21/49] Update requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index be1e71c..04dde88 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ jsonpickle~=3.0.1, >= 3.0.1 python-dateutil~=2.8.1 enum34~=1.1, >=1.1.10 -apimatic-core-interfaces~=0.1.0 +git+https://github.com/apimatic/core-interfaces-python@add-type-combinator-support requests~=2.31 setuptools~=67.6.0 jsonpointer~=2.3 From 2b212f882b7e0bf1bbbe988ef0c9df26cc25693b Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Fri, 2 Jun 2023 18:51:32 +0500 Subject: [PATCH 22/49] Update requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 04dde88..be1e71c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ jsonpickle~=3.0.1, >= 3.0.1 python-dateutil~=2.8.1 enum34~=1.1, >=1.1.10 -git+https://github.com/apimatic/core-interfaces-python@add-type-combinator-support +apimatic-core-interfaces~=0.1.0 requests~=2.31 setuptools~=67.6.0 jsonpointer~=2.3 From 141344afe2e1ea194b32dba7e2dc9f0bd3f0e19f Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Mon, 5 Jun 2023 11:27:37 +0500 Subject: [PATCH 23/49] fixed boolean and int type matching --- apimatic_core/types/union_types/leaf_type.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apimatic_core/types/union_types/leaf_type.py b/apimatic_core/types/union_types/leaf_type.py index d196ef7..36909ec 100644 --- a/apimatic_core/types/union_types/leaf_type.py +++ b/apimatic_core/types/union_types/leaf_type.py @@ -124,7 +124,7 @@ def validate_item(self, value): if hasattr(self.type_to_match, 'validate'): return self.type_to_match.validate(value) - return isinstance(value, self.type_to_match) + 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: @@ -133,7 +133,7 @@ def validate_with_discriminator(self, discriminator, discriminator_value, value) if hasattr(self.type_to_match, 'validate'): return self.type_to_match.validate(value) - return isinstance(value, self.type_to_match) + return type(value) is self.type_to_match def validate_date_time(self, value, context): if isinstance(value, ApiHelper.RFC3339DateTime): From e0171ddf8cc43ee67f746054da638f5d44b488f6 Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Mon, 5 Jun 2023 16:41:00 +0500 Subject: [PATCH 24/49] refactored code based on result from code climate --- apimatic_core/types/union_types/leaf_type.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/apimatic_core/types/union_types/leaf_type.py b/apimatic_core/types/union_types/leaf_type.py index 36909ec..f528317 100644 --- a/apimatic_core/types/union_types/leaf_type.py +++ b/apimatic_core/types/union_types/leaf_type.py @@ -110,7 +110,7 @@ def validate_item(self, value): if value is None or isinstance(value, list): return False - if self.type_to_match in [datetime]: + if self.type_to_match is datetime: return self.validate_date_time(value, context) if self.type_to_match is date: @@ -193,7 +193,9 @@ def deserialize_array_of_dict_case(self, array_value): return deserialized_value def deserialize_item(self, value): - is_native_type = self.type_to_match in UnionType.NATIVE_TYPES + + 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) @@ -202,15 +204,7 @@ def deserialize_item(self, value): return ApiHelper.datetime_deserialize( value, self._union_type_context.get_date_time_format()) - if is_native_type or self.type_to_match is dict: - return value - - if hasattr(self.type_to_match, 'from_dictionary'): - return self.type_to_match.from_dictionary(value) - else: - return value - - return None + return value def __deepcopy__(self, memo={}): copy_object = LeafType(self.type_to_match, self._union_type_context) From 5ca19331365c11390f058c659fc2de948739ad22 Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Mon, 5 Jun 2023 16:51:25 +0500 Subject: [PATCH 25/49] applied refactoring based on code-climate --- apimatic_core/types/union_types/leaf_type.py | 37 +++++++------------- apimatic_core/utilities/union_type_helper.py | 18 ++++++++++ 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/apimatic_core/types/union_types/leaf_type.py b/apimatic_core/types/union_types/leaf_type.py index f528317..1b17bbb 100644 --- a/apimatic_core/types/union_types/leaf_type.py +++ b/apimatic_core/types/union_types/leaf_type.py @@ -4,6 +4,7 @@ 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): @@ -110,21 +111,21 @@ def validate_item(self, value): if value is None or isinstance(value, list): return False - if self.type_to_match is datetime: - return self.validate_date_time(value, context) - - if self.type_to_match is date: - return DateTimeHelper.validate_date(value) - 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) + if discriminator and discriminator_value: + is_valid = self.validate_with_discriminator(discriminator, discriminator_value, value) + elif self.type_to_match is datetime: + is_valid = UnionTypeHelper.validate_date_time(value, context) + elif self.type_to_match is date: + is_valid = DateTimeHelper.validate_date(value) + elif hasattr(self.type_to_match, 'validate'): + is_valid = self.type_to_match.validate(value) + else: + is_valid = type(value) is self.type_to_match - return type(value) is self.type_to_match + return is_valid def validate_with_discriminator(self, discriminator, discriminator_value, value): if not isinstance(value, dict) or value.get(discriminator) != discriminator_value: @@ -135,19 +136,6 @@ def validate_with_discriminator(self, discriminator, discriminator_value, value) return type(value) is self.type_to_match - def validate_date_time(self, value, context): - if isinstance(value, ApiHelper.RFC3339DateTime): - return context.get_date_time_format() == DateTimeFormat.RFC3339_DATE_TIME - elif isinstance(value, ApiHelper.HttpDateTime): - return context.get_date_time_format() == DateTimeFormat.HTTP_DATE_TIME - elif isinstance(value, ApiHelper.UnixDateTime): - return context.get_date_time_format() == DateTimeFormat.UNIX_DATE_TIME - elif isinstance(value, datetime) and context.get_date_time_converter(): - serialized_dt = ApiHelper.json_serialize(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()) - def deserialize_dict_case(self, dict_value): if not isinstance(dict_value, dict): return None @@ -193,7 +181,6 @@ def deserialize_array_of_dict_case(self, array_value): return deserialized_value def deserialize_item(self, value): - if hasattr(self.type_to_match, 'from_dictionary'): return self.type_to_match.from_dictionary(value) diff --git a/apimatic_core/utilities/union_type_helper.py b/apimatic_core/utilities/union_type_helper.py index 7e58c85..29f3c12 100644 --- a/apimatic_core/utilities/union_type_helper.py +++ b/apimatic_core/utilities/union_type_helper.py @@ -1,4 +1,8 @@ import copy +from datetime import datetime +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: @@ -134,3 +138,17 @@ def get_matched_count(value, union_types, is_for_one_of): matched_count = sum(union_type.validate(value).is_valid for union_type in union_types) return matched_count + + @staticmethod + def validate_date_time(value, context): + if isinstance(value, ApiHelper.RFC3339DateTime): + return context.get_date_time_format() == DateTimeFormat.RFC3339_DATE_TIME + elif isinstance(value, ApiHelper.HttpDateTime): + return context.get_date_time_format() == DateTimeFormat.HTTP_DATE_TIME + elif isinstance(value, ApiHelper.UnixDateTime): + return context.get_date_time_format() == DateTimeFormat.UNIX_DATE_TIME + elif context.get_date_time_converter() is not None: + serialized_dt = ApiHelper.json_serialize(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()) From e38b57eb242edf4e7f4e100654ba7bd4fa1200ce Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Mon, 5 Jun 2023 16:56:19 +0500 Subject: [PATCH 26/49] reverted refactoring --- apimatic_core/types/union_types/leaf_type.py | 22 ++++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/apimatic_core/types/union_types/leaf_type.py b/apimatic_core/types/union_types/leaf_type.py index 1b17bbb..5d099e8 100644 --- a/apimatic_core/types/union_types/leaf_type.py +++ b/apimatic_core/types/union_types/leaf_type.py @@ -111,21 +111,21 @@ def validate_item(self, value): if value is None or isinstance(value, list): return False + 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) + discriminator = context.get_discriminator() discriminator_value = context.get_discriminator_value() - if discriminator and discriminator_value: - is_valid = self.validate_with_discriminator(discriminator, discriminator_value, value) - elif self.type_to_match is datetime: - is_valid = UnionTypeHelper.validate_date_time(value, context) - elif self.type_to_match is date: - is_valid = DateTimeHelper.validate_date(value) - elif hasattr(self.type_to_match, 'validate'): - is_valid = self.type_to_match.validate(value) - else: - is_valid = type(value) is self.type_to_match + return self.validate_with_discriminator(discriminator, discriminator_value, value) - return is_valid + 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: From c3243e9e04bc080ca5d1a7972b3c192c628a063f Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Mon, 5 Jun 2023 17:07:32 +0500 Subject: [PATCH 27/49] added documentation for newly added classes in the README --- README.md | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) 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/) From f8a48f376a059dd74ebbc05498edb3254618e1b1 Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Mon, 5 Jun 2023 17:21:07 +0500 Subject: [PATCH 28/49] refactored code to reduce cognitive complexity --- apimatic_core/types/union_types/leaf_type.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apimatic_core/types/union_types/leaf_type.py b/apimatic_core/types/union_types/leaf_type.py index 5d099e8..cba3826 100644 --- a/apimatic_core/types/union_types/leaf_type.py +++ b/apimatic_core/types/union_types/leaf_type.py @@ -111,12 +111,18 @@ def validate_item(self, value): 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_custom_type_with_discriminator(value, context) + + def validate_custom_type_with_discriminator(self, value, context): discriminator = context.get_discriminator() discriminator_value = context.get_discriminator_value() if discriminator and discriminator_value: From 3aa039558a2a9c47bd5c94294a4cce1226044a86 Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Mon, 5 Jun 2023 17:40:36 +0500 Subject: [PATCH 29/49] refactored code to reduce cognitive complexity --- apimatic_core/types/union_types/leaf_type.py | 52 ++++++++++---------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/apimatic_core/types/union_types/leaf_type.py b/apimatic_core/types/union_types/leaf_type.py index cba3826..f1dd845 100644 --- a/apimatic_core/types/union_types/leaf_type.py +++ b/apimatic_core/types/union_types/leaf_type.py @@ -16,47 +16,45 @@ def __init__(self, type_to_match: type, union_type_context: UnionTypeContext = U def validate(self, value): context = self._union_type_context - if value is None and context.is_nullable_or_optional(): - self.is_valid = True - return self - if value is None: - self.is_valid = False - return self - - if context.is_array() and context.is_dict() and context.is_array_of_dict(): - self.is_valid = self.validate_array_of_dict_case(value) - elif context.is_array() and context.is_dict(): - self.is_valid = self.validate_dict_of_array_case(value) - elif context.is_array(): - self.is_valid = self.validate_array_case(value) - elif context.is_dict(): - self.is_valid = self.validate_dict_case(value) + self.is_valid = context.is_nullable_or_optional() else: - self.is_valid = self.validate_item(value) + self.is_valid = self.validate_value_against_case(value, context) return self def deserialize(self, value): - if value is None or not self.is_valid: + if value is None: return None context = self._union_type_context - if value is None and context.is_nullable_or_optional(): - return None + deserialized_value = self.deserialize_value(value, context) + return deserialized_value + + def deserialize_value(self, value, context): if context.is_array() and context.is_dict() and context.is_array_of_dict(): - deserialized_value = self.deserialize_array_of_dict_case(value) + return self.deserialize_array_of_dict_case(value) elif context.is_array() and context.is_dict(): - deserialized_value = self.deserialize_dict_of_array_case(value) + return self.deserialize_dict_of_array_case(value) elif context.is_array(): - deserialized_value = self.deserialize_array_case(value) + return self.deserialize_array_case(value) elif context.is_dict(): - deserialized_value = self.deserialize_dict_case(value) + return self.deserialize_dict_case(value) else: - deserialized_value = self.deserialize_item(value) + return self.deserialize_item(value) - 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) + elif context.is_array() and context.is_dict(): + return self.validate_dict_of_array_case(value) + elif context.is_array(): + return self.validate_array_case(value) + elif context.is_dict(): + return self.validate_dict_case(value) + else: + return self.validate_item(value) def validate_dict_case(self, dict_value): if not isinstance(dict_value, dict): @@ -120,9 +118,9 @@ def validate_value(self, value, context): if self.type_to_match is date: return DateTimeHelper.validate_date(value) - return self.validate_custom_type_with_discriminator(value, context) + return self.validate_value_with_discriminator(value, context) - def validate_custom_type_with_discriminator(self, 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: From c8636aede99f99ab38dd32605e55d711ecfcfb39 Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Mon, 5 Jun 2023 18:17:31 +0500 Subject: [PATCH 30/49] refactored code --- apimatic_core/types/union_types/leaf_type.py | 66 +++++++++----------- 1 file changed, 30 insertions(+), 36 deletions(-) diff --git a/apimatic_core/types/union_types/leaf_type.py b/apimatic_core/types/union_types/leaf_type.py index f1dd845..74ccc96 100644 --- a/apimatic_core/types/union_types/leaf_type.py +++ b/apimatic_core/types/union_types/leaf_type.py @@ -28,40 +28,31 @@ def deserialize(self, value): return None context = self._union_type_context - deserialized_value = self.deserialize_value(value, context) + deserialized_value = self.deserialize_value_against_case(value, context) return deserialized_value - def deserialize_value(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) - elif context.is_array() and context.is_dict(): - return self.deserialize_dict_of_array_case(value) - elif context.is_array(): - return self.deserialize_array_case(value) - elif context.is_dict(): - return self.deserialize_dict_case(value) - else: - return self.deserialize_item(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) - elif context.is_array() and context.is_dict(): + + if context.is_array() and context.is_dict(): return self.validate_dict_of_array_case(value) - elif context.is_array(): + + if context.is_array(): return self.validate_array_case(value) - elif context.is_dict(): + + if context.is_dict(): return self.validate_dict_case(value) - else: - return self.validate_item(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_item(value) + is_valid = self.validate_simple_case(value) if not is_valid: return False @@ -83,7 +74,7 @@ def validate_array_case(self, array_value): return False for item in array_value: - is_valid = self.validate_item(item) + is_valid = self.validate_simple_case(item) if not is_valid: return False @@ -100,7 +91,7 @@ def validate_array_of_dict_case(self, array_value): return True - def validate_item(self, value): + def validate_simple_case(self, value): context = self._union_type_context if value is None or context.is_nullable_or_optional(): @@ -140,21 +131,30 @@ def validate_with_discriminator(self, discriminator, discriminator_value, value) return type(value) is self.type_to_match - def deserialize_dict_case(self, dict_value): - if not isinstance(dict_value, dict): - return None + 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_item(value) + result_value = self.deserialize_simple_case(value) deserialized_value[key] = result_value return deserialized_value def deserialize_dict_of_array_case(self, dict_value): - if not isinstance(dict_value, dict): - return None - deserialized_value = {} for key, value in dict_value.items(): result_value = self.deserialize_array_case(value) @@ -163,20 +163,14 @@ def deserialize_dict_of_array_case(self, dict_value): return deserialized_value def deserialize_array_case(self, array_value): - if not isinstance(array_value, list): - return None - deserialized_value = [] for item in array_value: - result_value = self.deserialize_item(item) + result_value = self.deserialize_simple_case(item) deserialized_value.append(result_value) return deserialized_value def deserialize_array_of_dict_case(self, array_value): - if not isinstance(array_value, list): - return None - deserialized_value = [] for item in array_value: result_value = self.deserialize_dict_case(item) @@ -184,7 +178,7 @@ def deserialize_array_of_dict_case(self, array_value): return deserialized_value - def deserialize_item(self, value): + def deserialize_simple_case(self, value): if hasattr(self.type_to_match, 'from_dictionary'): return self.type_to_match.from_dictionary(value) From 515c759e66eb2e4cb8733c7cc8d153726c970302 Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Mon, 5 Jun 2023 19:10:23 +0500 Subject: [PATCH 31/49] refactored code to reduce cognitive complexity --- apimatic_core/types/union_types/any_of.py | 114 +++++++++--------- apimatic_core/types/union_types/leaf_type.py | 77 ++++++------ apimatic_core/types/union_types/one_of.py | 119 +++++++++---------- apimatic_core/utilities/union_type_helper.py | 28 +++-- 4 files changed, 171 insertions(+), 167 deletions(-) diff --git a/apimatic_core/types/union_types/any_of.py b/apimatic_core/types/union_types/any_of.py index f4fe322..0ba6469 100644 --- a/apimatic_core/types/union_types/any_of.py +++ b/apimatic_core/types/union_types/any_of.py @@ -1,4 +1,3 @@ -import copy from apimatic_core_interfaces.types.union_type import UnionType from apimatic_core.exceptions.anyof_validation_exception import AnyOfValidationException from apimatic_core.types.union_types.leaf_type import LeafType @@ -14,12 +13,10 @@ def __init__(self, union_types, union_type_context: UnionTypeContext = UnionType def validate(self, value): context = self._union_type_context - - for union_type in self._union_types: - union_type.get_context().is_nested = True - - is_optional_or_nullable = context.is_nullable_or_optional() or any( - union_type.get_context().is_nullable_or_optional() for union_type in self._union_types) + UnionTypeHelper.update_nested_flag_for_union_types(self._union_types) + is_optional_or_nullable = UnionTypeHelper.is_optional_or_nullable_case(self._union_type_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 @@ -27,77 +24,78 @@ def validate(self, value): if value is None: self.is_valid = False - self.process_errors(value) + self._process_errors(value) return self + self._validate_value_against_case(value, context) + + if not self.is_valid: + self._process_errors(value) + + 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(): - if isinstance(value, list): - self.is_valid, self.collection_cases = UnionTypeHelper.validate_array_of_dict_case(self._union_types, - value, False) - else: - self.is_valid = False + 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(): - if isinstance(value, dict): - self.is_valid, self.collection_cases = UnionTypeHelper.validate_dict_of_array_case(self._union_types, - value, False) - else: - self.is_valid = False + self.is_valid, self.collection_cases = UnionTypeHelper.validate_dict_of_array_case(self._union_types, value, + False) elif context.is_array(): - if isinstance(value, list): - self.is_valid, self.collection_cases = UnionTypeHelper.validate_array_case(self._union_types, - value, False) - else: - self.is_valid = False + self.is_valid, self.collection_cases = UnionTypeHelper.validate_array_case(self._union_types, value, False) elif context.is_dict(): - if isinstance(value, dict): - self.is_valid, self.collection_cases = UnionTypeHelper.validate_dict_case(self._union_types, - value, False) - else: - self.is_valid = False + 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) > 0 + self.is_valid = UnionTypeHelper.get_matched_count(value, self._union_types, False) >= 1 - if not self.is_valid: - self.process_errors(value) + def _deserialize_value_against_case(self, value, context): + if context.is_array() and context.is_dict() and context.is_array_of_dict(): + return UnionTypeHelper.deserialize_array_of_dict_case(value, self.collection_cases) - return self + if context.is_array() and context.is_dict(): + return UnionTypeHelper.deserialize_dict_of_array_case(value, self.collection_cases) - def process_errors(self, value): + if context.is_array(): + return UnionTypeHelper.deserialize_array_case(value, self.collection_cases) + + if context.is_dict(): + return UnionTypeHelper.deserialize_dict_case(value, self.collection_cases) + + return UnionTypeHelper.get_deserialized_value(self._union_types, value) + + def _process_errors(self, value): self.error_messages = [] + combined_types = self._get_combined_types() + + if self._union_type_context.is_nested: + self._append_nested_error_message(combined_types) + else: + self._raise_validation_exception(value, combined_types) + + def _get_combined_types(self): combined_types = [] for union_type in self._union_types: if isinstance(union_type, LeafType): combined_types.append(union_type.type_to_match.__name__) else: combined_types.append(', '.join(union_type.error_messages)) + return combined_types - if self._union_type_context.is_nested: - self.error_messages.append(', '.join(combined_types)) - else: - raise AnyOfValidationException('{} \nActual Value: {}\nExpected Type: Any Of {}.'.format( - UnionType.NONE_MATCHED_ERROR_MESSAGE, value, ', '.join(combined_types))) - - def deserialize(self, value): - if value is None or not self.is_valid: - return None - - context = self._union_type_context - if value is None and context.is_nullable_or_optional(): - return None + def _append_nested_error_message(self, combined_types): + self.error_messages.append(', '.join(combined_types)) - if context.is_array() and context.is_dict() and context.is_array_of_dict(): - deserialized_value = UnionTypeHelper.deserialize_array_of_dict_case(value, self.collection_cases) - elif context.is_array() and context.is_dict(): - deserialized_value = UnionTypeHelper.deserialize_dict_of_array_case(value, self.collection_cases) - elif context.is_array(): - deserialized_value = UnionTypeHelper.deserialize_array_case(value, self.collection_cases) - elif context.is_dict(): - deserialized_value = UnionTypeHelper.deserialize_dict_case(value, self.collection_cases) - else: - deserialized_value = UnionTypeHelper.get_deserialized_value(self._union_types, value) - - return deserialized_value + def _raise_validation_exception(self, value, combined_types): + raise AnyOfValidationException('{} \nActual Value: {}\nExpected Type: Any Of {}.'.format( + UnionType.NONE_MATCHED_ERROR_MESSAGE, value, ', '.join(combined_types))) def __deepcopy__(self, memo={}): copy_object = AnyOf(self._union_types, self._union_type_context) diff --git a/apimatic_core/types/union_types/leaf_type.py b/apimatic_core/types/union_types/leaf_type.py index 74ccc96..fa98916 100644 --- a/apimatic_core/types/union_types/leaf_type.py +++ b/apimatic_core/types/union_types/leaf_type.py @@ -1,6 +1,5 @@ from datetime import date, datetime from apimatic_core_interfaces.types.union_type import UnionType -from apimatic_core.types.datetime_format import DateTimeFormat 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 @@ -19,7 +18,7 @@ def validate(self, value): if value is None: self.is_valid = context.is_nullable_or_optional() else: - self.is_valid = self.validate_value_against_case(value, context) + self.is_valid = self._validate_value_against_case(value, context) return self @@ -28,70 +27,70 @@ def deserialize(self, value): return None context = self._union_type_context - deserialized_value = self.deserialize_value_against_case(value, context) + deserialized_value = self._deserialize_value_against_case(value, context) return deserialized_value - def validate_value_against_case(self, value, context): + 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) + return self._validate_array_of_dict_case(value) if context.is_array() and context.is_dict(): - return self.validate_dict_of_array_case(value) + return self._validate_dict_of_array_case(value) if context.is_array(): - return self.validate_array_case(value) + return self._validate_array_case(value) if context.is_dict(): - return self.validate_dict_case(value) + return self._validate_dict_case(value) - return self.validate_simple_case(value) + return self._validate_simple_case(value) - def validate_dict_case(self, dict_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) + is_valid = self._validate_simple_case(value) if not is_valid: return False return True - def validate_dict_of_array_case(self, dict_value): + 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) + is_valid = self._validate_array_case(value) if not is_valid: return False return True - def validate_array_case(self, array_value): + 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) + is_valid = self._validate_simple_case(item) if not is_valid: return False return True - def validate_array_of_dict_case(self, array_value): + 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) + is_valid = self._validate_dict_case(item) if not is_valid: return False return True - def validate_simple_case(self, value): + def _validate_simple_case(self, value): context = self._union_type_context if value is None or context.is_nullable_or_optional(): @@ -100,29 +99,29 @@ def validate_simple_case(self, value): if value is None or isinstance(value, list): return False - return self.validate_value(value, context) + return self._validate_value(value, context) - def validate_value(self, 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) + return self._validate_value_with_discriminator(value, context) - def validate_value_with_discriminator(self, 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) + 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): + def _validate_with_discriminator(self, discriminator, discriminator_value, value): if not isinstance(value, dict) or value.get(discriminator) != discriminator_value: return False @@ -131,54 +130,54 @@ def validate_with_discriminator(self, discriminator, discriminator_value, value) return type(value) is self.type_to_match - def deserialize_value_against_case(self, value, context): + 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) + return self._deserialize_array_of_dict_case(value) if context.is_array() and context.is_dict(): - return self.deserialize_dict_of_array_case(value) + return self._deserialize_dict_of_array_case(value) if context.is_array(): - return self.deserialize_array_case(value) + return self._deserialize_array_case(value) if context.is_dict(): - return self.deserialize_dict_case(value) + return self._deserialize_dict_case(value) - return self.deserialize_simple_case(value) + return self._deserialize_simple_case(value) - def deserialize_dict_case(self, dict_value): + def _deserialize_dict_case(self, dict_value): deserialized_value = {} for key, value in dict_value.items(): - result_value = self.deserialize_simple_case(value) + result_value = self._deserialize_simple_case(value) deserialized_value[key] = result_value return deserialized_value - def deserialize_dict_of_array_case(self, dict_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) + result_value = self._deserialize_array_case(value) deserialized_value[key] = result_value return deserialized_value - def deserialize_array_case(self, array_value): + def _deserialize_array_case(self, array_value): deserialized_value = [] for item in array_value: - result_value = self.deserialize_simple_case(item) + result_value = self._deserialize_simple_case(item) deserialized_value.append(result_value) return deserialized_value - def deserialize_array_of_dict_case(self, array_value): + def _deserialize_array_of_dict_case(self, array_value): deserialized_value = [] for item in array_value: - result_value = self.deserialize_dict_case(item) + result_value = self._deserialize_dict_case(item) deserialized_value.append(result_value) return deserialized_value - def deserialize_simple_case(self, value): + def _deserialize_simple_case(self, value): if hasattr(self.type_to_match, 'from_dictionary'): return self.type_to_match.from_dictionary(value) diff --git a/apimatic_core/types/union_types/one_of.py b/apimatic_core/types/union_types/one_of.py index d33e2f0..f30a7cf 100644 --- a/apimatic_core/types/union_types/one_of.py +++ b/apimatic_core/types/union_types/one_of.py @@ -1,4 +1,3 @@ -import copy from apimatic_core_interfaces.types.union_type import UnionType from apimatic_core.exceptions.oneof_validation_exception import OneOfValidationException from apimatic_core.types.union_types.leaf_type import LeafType @@ -14,12 +13,10 @@ def __init__(self, union_types, union_type_context: UnionTypeContext = UnionType def validate(self, value): context = self._union_type_context - - for union_type in self._union_types: - union_type.get_context().is_nested = True - - is_optional_or_nullable = context.is_nullable_or_optional() or any( - union_type.get_context().is_nullable_or_optional() for union_type in self._union_types) + UnionTypeHelper.update_nested_flag_for_union_types(self._union_types) + is_optional_or_nullable = UnionTypeHelper.is_optional_or_nullable_case(self._union_type_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 @@ -27,81 +24,81 @@ def validate(self, value): if value is None: self.is_valid = False - self.process_errors(value) + self._process_errors(value) return self + self._validate_value_against_case(value, context) + + if not self.is_valid: + self._process_errors(value) + + 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(): - if isinstance(value, list): - self.is_valid, self.collection_cases = UnionTypeHelper.validate_array_of_dict_case(self._union_types, - value, True) - else: - self.is_valid = False + 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(): - if isinstance(value, dict): - self.is_valid, self.collection_cases = UnionTypeHelper.validate_dict_of_array_case(self._union_types, - value, True) - else: - self.is_valid = False + self.is_valid, self.collection_cases = UnionTypeHelper.validate_dict_of_array_case(self._union_types, value, + True) elif context.is_array(): - if isinstance(value, list): - self.is_valid, self.collection_cases = UnionTypeHelper.validate_array_case(self._union_types, - value, True) - else: - self.is_valid = False + self.is_valid, self.collection_cases = UnionTypeHelper.validate_array_case(self._union_types, value, True) elif context.is_dict(): - if isinstance(value, dict): - self.is_valid, self.collection_cases = UnionTypeHelper.validate_dict_case(self._union_types, - value, True) - else: - self.is_valid = False + 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 - if not self.is_valid: - self.process_errors(value) + def _deserialize_value_against_case(self, value, context): + if context.is_array() and context.is_dict() and context.is_array_of_dict(): + return UnionTypeHelper.deserialize_array_of_dict_case(value, self.collection_cases) - return self + if context.is_array() and context.is_dict(): + return UnionTypeHelper.deserialize_dict_of_array_case(value, self.collection_cases) + + if context.is_array(): + return UnionTypeHelper.deserialize_array_case(value, self.collection_cases) - def process_errors(self, value): + if context.is_dict(): + return UnionTypeHelper.deserialize_dict_case(value, self.collection_cases) + + return UnionTypeHelper.get_deserialized_value(self._union_types, value) + + def _process_errors(self, value): self.error_messages = [] + combined_types = self._get_combined_types() + + if self._union_type_context.is_nested: + self._append_nested_error_message(combined_types) + else: + self._raise_validation_exception(value, combined_types) + + def _get_combined_types(self): combined_types = [] for union_type in self._union_types: if isinstance(union_type, LeafType): combined_types.append(union_type.type_to_match.__name__) else: combined_types.append(', '.join(union_type.error_messages)) + return combined_types - if self._union_type_context.is_nested: - self.error_messages.append(', '.join(combined_types)) - else: - matched_count = sum(union_type.is_valid for union_type in self._union_types) - error_message = UnionType.MORE_THAN_1_MATCHED_ERROR_MESSAGE if matched_count > 0 \ - else UnionType.NONE_MATCHED_ERROR_MESSAGE - raise OneOfValidationException('{} \nActual Value: {}\nExpected Type: One Of {}.'.format( - error_message, value, ', '.join(combined_types))) - - def deserialize(self, value): - if value is None or not self.is_valid: - return None - - context = self._union_type_context - - if value is None and context.is_nullable_or_optional(): - return None + def _append_nested_error_message(self, combined_types): + self.error_messages.append(', '.join(combined_types)) - if context.is_array() and context.is_dict() and context.is_array_of_dict(): - deserialized_value = UnionTypeHelper.deserialize_array_of_dict_case(value, self.collection_cases) - elif context.is_array() and context.is_dict(): - deserialized_value = UnionTypeHelper.deserialize_dict_of_array_case(value, self.collection_cases) - elif context.is_array(): - deserialized_value = UnionTypeHelper.deserialize_array_case(value, self.collection_cases) - elif context.is_dict(): - deserialized_value = UnionTypeHelper.deserialize_dict_case(value, self.collection_cases) - else: - deserialized_value = UnionTypeHelper.get_deserialized_value(self._union_types, value) - - return deserialized_value + def _raise_validation_exception(self, value, combined_types): + matched_count = sum(union_type.is_valid for union_type in self._union_types) + error_message = UnionType.MORE_THAN_1_MATCHED_ERROR_MESSAGE if matched_count > 0 \ + else UnionType.NONE_MATCHED_ERROR_MESSAGE + raise OneOfValidationException('{} \nActual Value: {}\nExpected Type: One Of {}.'.format( + error_message, value, ', '.join(combined_types))) def __deepcopy__(self, memo={}): copy_object = OneOf(self._union_types, self._union_type_context) diff --git a/apimatic_core/utilities/union_type_helper.py b/apimatic_core/utilities/union_type_helper.py index 29f3c12..4e15823 100644 --- a/apimatic_core/utilities/union_type_helper.py +++ b/apimatic_core/utilities/union_type_helper.py @@ -1,5 +1,4 @@ import copy -from datetime import datetime from apimatic_core.types.datetime_format import DateTimeFormat from apimatic_core.utilities.api_helper import ApiHelper from apimatic_core.utilities.datetime_helper import DateTimeHelper @@ -13,7 +12,7 @@ def get_deserialized_value(union_types, value): @staticmethod def validate_array_of_dict_case(union_types, array_value, is_for_one_of): - if array_value is None or not array_value: + if array_value is None or not isinstance(array_value, list): return tuple((False, [])) collection_cases = [] @@ -27,7 +26,7 @@ def validate_array_of_dict_case(union_types, array_value, is_for_one_of): @staticmethod def validate_dict_of_array_case(union_types, dict_value, is_for_one_of): - if dict_value is None or not dict_value: + if dict_value is None or not isinstance(dict_value, dict): return tuple((False, [])) collection_cases = {} @@ -41,7 +40,7 @@ def validate_dict_of_array_case(union_types, dict_value, is_for_one_of): @staticmethod def validate_dict_case(union_types, dict_value, is_for_one_of): - if dict_value is None or not dict_value: + if dict_value is None or not isinstance(dict_value, dict): return tuple((False, [])) is_valid = True @@ -58,7 +57,7 @@ def validate_dict_case(union_types, dict_value, is_for_one_of): @staticmethod def validate_array_case(union_types, array_value, is_for_one_of): - if array_value is None or not array_value: + if array_value is None or not isinstance(array_value, list): return tuple((False, [])) is_valid = True @@ -75,8 +74,9 @@ def validate_array_case(union_types, array_value, is_for_one_of): @staticmethod def deserialize_array_of_dict_case(array_value, collection_cases): - if array_value is None: + if array_value is None or not isinstance(array_value, list): return None + deserialized_value = [] for index, item in enumerate(array_value): deserialized_value.append(UnionTypeHelper.deserialize_dict_case(item, collection_cases[index])) @@ -85,7 +85,7 @@ def deserialize_array_of_dict_case(array_value, collection_cases): @staticmethod def deserialize_dict_of_array_case(dict_value, collection_cases): - if dict_value is None: + if dict_value is None or not isinstance(dict_value, dict): return None deserialized_value = {} @@ -96,7 +96,7 @@ def deserialize_dict_of_array_case(dict_value, collection_cases): @staticmethod def deserialize_dict_case(dict_value, collection_cases): - if dict_value is None: + if dict_value is None or not isinstance(dict_value, dict): return None deserialized_value = {} @@ -108,7 +108,7 @@ def deserialize_dict_case(dict_value, collection_cases): @staticmethod def deserialize_array_case(array_value, collection_cases): - if array_value is None: + if array_value is None or not isinstance(array_value, list): return None deserialized_value = [] @@ -152,3 +152,13 @@ def validate_date_time(value, context): 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 From dfeda280812e2b5021496c94bebe7f6382386397 Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Tue, 6 Jun 2023 11:03:54 +0500 Subject: [PATCH 32/49] refactored code --- apimatic_core/types/union_types/any_of.py | 22 ++--------- apimatic_core/types/union_types/one_of.py | 22 ++--------- apimatic_core/utilities/union_type_helper.py | 40 ++++++++++++++++---- 3 files changed, 38 insertions(+), 46 deletions(-) diff --git a/apimatic_core/types/union_types/any_of.py b/apimatic_core/types/union_types/any_of.py index 0ba6469..d4019b1 100644 --- a/apimatic_core/types/union_types/any_of.py +++ b/apimatic_core/types/union_types/any_of.py @@ -14,7 +14,7 @@ def __init__(self, union_types, union_type_context: UnionTypeContext = UnionType 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(self._union_type_context, + is_optional_or_nullable = UnionTypeHelper.is_optional_or_nullable_case(context, [nested_type.get_context() for nested_type in self._union_types]) @@ -38,9 +38,8 @@ 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 + 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(): @@ -56,21 +55,6 @@ def _validate_value_against_case(self, value, context): else: self.is_valid = UnionTypeHelper.get_matched_count(value, self._union_types, False) >= 1 - def _deserialize_value_against_case(self, value, context): - if context.is_array() and context.is_dict() and context.is_array_of_dict(): - return UnionTypeHelper.deserialize_array_of_dict_case(value, self.collection_cases) - - if context.is_array() and context.is_dict(): - return UnionTypeHelper.deserialize_dict_of_array_case(value, self.collection_cases) - - if context.is_array(): - return UnionTypeHelper.deserialize_array_case(value, self.collection_cases) - - if context.is_dict(): - return UnionTypeHelper.deserialize_dict_case(value, self.collection_cases) - - return UnionTypeHelper.get_deserialized_value(self._union_types, value) - def _process_errors(self, value): self.error_messages = [] diff --git a/apimatic_core/types/union_types/one_of.py b/apimatic_core/types/union_types/one_of.py index f30a7cf..10e8f02 100644 --- a/apimatic_core/types/union_types/one_of.py +++ b/apimatic_core/types/union_types/one_of.py @@ -14,7 +14,7 @@ def __init__(self, union_types, union_type_context: UnionTypeContext = UnionType 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(self._union_type_context, + is_optional_or_nullable = UnionTypeHelper.is_optional_or_nullable_case(context, [nested_type.get_context() for nested_type in self._union_types]) @@ -38,9 +38,8 @@ 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 + 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(): @@ -56,21 +55,6 @@ def _validate_value_against_case(self, value, context): else: self.is_valid = UnionTypeHelper.get_matched_count(value, self._union_types, True) == 1 - def _deserialize_value_against_case(self, value, context): - if context.is_array() and context.is_dict() and context.is_array_of_dict(): - return UnionTypeHelper.deserialize_array_of_dict_case(value, self.collection_cases) - - if context.is_array() and context.is_dict(): - return UnionTypeHelper.deserialize_dict_of_array_case(value, self.collection_cases) - - if context.is_array(): - return UnionTypeHelper.deserialize_array_case(value, self.collection_cases) - - if context.is_dict(): - return UnionTypeHelper.deserialize_dict_case(value, self.collection_cases) - - return UnionTypeHelper.get_deserialized_value(self._union_types, value) - def _process_errors(self, value): self.error_messages = [] diff --git a/apimatic_core/utilities/union_type_helper.py b/apimatic_core/utilities/union_type_helper.py index 4e15823..080c471 100644 --- a/apimatic_core/utilities/union_type_helper.py +++ b/apimatic_core/utilities/union_type_helper.py @@ -12,7 +12,7 @@ def get_deserialized_value(union_types, value): @staticmethod def validate_array_of_dict_case(union_types, array_value, is_for_one_of): - if array_value is None or not isinstance(array_value, list): + if not UnionTypeHelper.is_valid_array(array_value): return tuple((False, [])) collection_cases = [] @@ -26,7 +26,7 @@ def validate_array_of_dict_case(union_types, array_value, is_for_one_of): @staticmethod def validate_dict_of_array_case(union_types, dict_value, is_for_one_of): - if dict_value is None or not isinstance(dict_value, dict): + if not UnionTypeHelper.is_valid_dict(dict_value): return tuple((False, [])) collection_cases = {} @@ -40,7 +40,7 @@ def validate_dict_of_array_case(union_types, dict_value, is_for_one_of): @staticmethod def validate_dict_case(union_types, dict_value, is_for_one_of): - if dict_value is None or not isinstance(dict_value, dict): + if not UnionTypeHelper.is_valid_dict(dict_value): return tuple((False, [])) is_valid = True @@ -57,7 +57,7 @@ def validate_dict_case(union_types, dict_value, is_for_one_of): @staticmethod def validate_array_case(union_types, array_value, is_for_one_of): - if array_value is None or not isinstance(array_value, list): + if not UnionTypeHelper.is_valid_array(array_value): return tuple((False, [])) is_valid = True @@ -72,9 +72,25 @@ def validate_array_case(union_types, array_value, is_for_one_of): collection_cases.append(nested_cases) return tuple((is_valid, collection_cases)) + @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): - if array_value is None or not isinstance(array_value, list): + if not UnionTypeHelper.is_valid_array(array_value): return None deserialized_value = [] @@ -85,7 +101,7 @@ def deserialize_array_of_dict_case(array_value, collection_cases): @staticmethod def deserialize_dict_of_array_case(dict_value, collection_cases): - if dict_value is None or not isinstance(dict_value, dict): + if not UnionTypeHelper.is_valid_dict(dict_value): return None deserialized_value = {} @@ -96,7 +112,7 @@ def deserialize_dict_of_array_case(dict_value, collection_cases): @staticmethod def deserialize_dict_case(dict_value, collection_cases): - if dict_value is None or not isinstance(dict_value, dict): + if not UnionTypeHelper.is_valid_dict(dict_value): return None deserialized_value = {} @@ -108,7 +124,7 @@ def deserialize_dict_case(dict_value, collection_cases): @staticmethod def deserialize_array_case(array_value, collection_cases): - if array_value is None or not isinstance(array_value, list): + if not UnionTypeHelper.is_valid_array(array_value): return None deserialized_value = [] @@ -162,3 +178,11 @@ def is_optional_or_nullable_case(current_context, inner_contexts): 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_valid_array(value): + return value is not None and isinstance(value, list) + + @staticmethod + def is_valid_dict(value): + return value is not None and isinstance(value, dict) From 1eb097eaf4bc59181fbaca7d57e4d45b2e315018 Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Tue, 6 Jun 2023 11:36:45 +0500 Subject: [PATCH 33/49] refactored code --- apimatic_core/utilities/union_type_helper.py | 60 +++++++++++++++----- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/apimatic_core/utilities/union_type_helper.py b/apimatic_core/utilities/union_type_helper.py index 080c471..8508933 100644 --- a/apimatic_core/utilities/union_type_helper.py +++ b/apimatic_core/utilities/union_type_helper.py @@ -43,34 +43,58 @@ def validate_dict_case(union_types, dict_value, is_for_one_of): if not UnionTypeHelper.is_valid_dict(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(): - nested_cases = [] - for union_type in union_types: - nested_cases.append(copy.deepcopy(union_type).validate(value)) + nested_cases = UnionTypeHelper.validate_item(union_types, value) matched_count = UnionTypeHelper.get_matched_count(value, nested_cases, True) + if is_valid: is_valid = matched_count == 1 if is_for_one_of else matched_count >= 1 + collection_cases[key] = nested_cases - return tuple((is_valid, collection_cases)) + + return is_valid, collection_cases @staticmethod def validate_array_case(union_types, array_value, is_for_one_of): if not UnionTypeHelper.is_valid_array(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: - nested_cases = [] - for union_type in union_types: - nested_cases.append(copy.deepcopy(union_type).validate(item)) + nested_cases = UnionTypeHelper.validate_item(union_types, item) matched_count = UnionTypeHelper.get_matched_count(item, nested_cases, True) + if is_valid: is_valid = matched_count == 1 if is_for_one_of else matched_count >= 1 + collection_cases.append(nested_cases) - return tuple((is_valid, collection_cases)) + + return is_valid, collection_cases + + @staticmethod + def validate_item(union_types, item): + nested_cases = [] + for union_type in union_types: + nested_cases.append(copy.deepcopy(union_type).validate(item)) + + return nested_cases @staticmethod def deserialize_value(value, context, collection_cases, union_types): @@ -136,24 +160,34 @@ def deserialize_array_case(array_value, collection_cases): @staticmethod def get_matched_count(value, union_types, is_for_one_of): - matched_count = sum(union_type.validate(value).is_valid for union_type in union_types) + 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 - # Check through normal schema validation flow when discriminator exits but still invalid + 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 matched_count == 0 and has_discriminator_cases: + + if has_discriminator_cases: for union_type in union_types: union_type.get_context().discriminator(None) union_type.get_context().discriminator_value(None) - matched_count = sum(union_type.validate(value).is_valid for union_type in union_types) - return matched_count + return UnionTypeHelper.get_valid_cases_count(value, union_types) + + return 0 @staticmethod def validate_date_time(value, context): From 1238d570a07a208938c21f53581eebc3e5812ebd Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Tue, 6 Jun 2023 15:31:35 +0500 Subject: [PATCH 34/49] refactored code --- .../types/union_types/union_type_context.py | 2 +- apimatic_core/utilities/union_type_helper.py | 43 +++++++++++-------- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/apimatic_core/types/union_types/union_type_context.py b/apimatic_core/types/union_types/union_type_context.py index caf7a6f..4933f65 100644 --- a/apimatic_core/types/union_types/union_type_context.py +++ b/apimatic_core/types/union_types/union_type_context.py @@ -62,7 +62,7 @@ def is_nullable(self): return self._is_nullable def is_nullable_or_optional(self): - return self._is_nullable or self._is_optional + return self.is_nullable() or self.is_optional() def discriminator(self, discriminator): self._discriminator = discriminator diff --git a/apimatic_core/utilities/union_type_helper.py b/apimatic_core/utilities/union_type_helper.py index 8508933..c401dea 100644 --- a/apimatic_core/utilities/union_type_helper.py +++ b/apimatic_core/utilities/union_type_helper.py @@ -1,4 +1,5 @@ import copy +from datetime import datetime from apimatic_core.types.datetime_format import DateTimeFormat from apimatic_core.utilities.api_helper import ApiHelper from apimatic_core.utilities.datetime_helper import DateTimeHelper @@ -12,7 +13,7 @@ def get_deserialized_value(union_types, value): @staticmethod def validate_array_of_dict_case(union_types, array_value, is_for_one_of): - if not UnionTypeHelper.is_valid_array(array_value): + if UnionTypeHelper.is_invalid_array_value(array_value): return tuple((False, [])) collection_cases = [] @@ -26,7 +27,7 @@ def validate_array_of_dict_case(union_types, array_value, is_for_one_of): @staticmethod def validate_dict_of_array_case(union_types, dict_value, is_for_one_of): - if not UnionTypeHelper.is_valid_dict(dict_value): + if UnionTypeHelper.is_invalid_dict_value(dict_value): return tuple((False, [])) collection_cases = {} @@ -40,7 +41,7 @@ def validate_dict_of_array_case(union_types, dict_value, is_for_one_of): @staticmethod def validate_dict_case(union_types, dict_value, is_for_one_of): - if not UnionTypeHelper.is_valid_dict(dict_value): + 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) @@ -54,10 +55,12 @@ def process_dict_items(union_types, dict_value, is_for_one_of): for key, value in dict_value.items(): nested_cases = UnionTypeHelper.validate_item(union_types, value) - matched_count = UnionTypeHelper.get_matched_count(value, nested_cases, True) + matched_count = UnionTypeHelper.get_matched_count(value, nested_cases, is_for_one_of) - if is_valid: - is_valid = matched_count == 1 if is_for_one_of else matched_count >= 1 + if is_valid and is_for_one_of: + is_valid = matched_count == 1 + elif is_valid: + is_valid = matched_count >= 1 collection_cases[key] = nested_cases @@ -65,7 +68,7 @@ def process_dict_items(union_types, dict_value, is_for_one_of): @staticmethod def validate_array_case(union_types, array_value, is_for_one_of): - if not UnionTypeHelper.is_valid_array(array_value): + 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) @@ -79,10 +82,12 @@ def process_array_items(union_types, array_value, is_for_one_of): for item in array_value: nested_cases = UnionTypeHelper.validate_item(union_types, item) - matched_count = UnionTypeHelper.get_matched_count(item, nested_cases, True) + matched_count = UnionTypeHelper.get_matched_count(item, nested_cases, is_for_one_of) - if is_valid: - is_valid = matched_count == 1 if is_for_one_of else matched_count >= 1 + if is_valid and is_for_one_of: + is_valid = matched_count == 1 + elif is_valid: + is_valid = matched_count >= 1 collection_cases.append(nested_cases) @@ -114,7 +119,7 @@ def deserialize_value(value, context, collection_cases, union_types): @staticmethod def deserialize_array_of_dict_case(array_value, collection_cases): - if not UnionTypeHelper.is_valid_array(array_value): + if UnionTypeHelper.is_invalid_array_value(array_value): return None deserialized_value = [] @@ -125,7 +130,7 @@ def deserialize_array_of_dict_case(array_value, collection_cases): @staticmethod def deserialize_dict_of_array_case(dict_value, collection_cases): - if not UnionTypeHelper.is_valid_dict(dict_value): + if UnionTypeHelper.is_invalid_dict_value(dict_value): return None deserialized_value = {} @@ -136,7 +141,7 @@ def deserialize_dict_of_array_case(dict_value, collection_cases): @staticmethod def deserialize_dict_case(dict_value, collection_cases): - if not UnionTypeHelper.is_valid_dict(dict_value): + if UnionTypeHelper.is_invalid_dict_value(dict_value): return None deserialized_value = {} @@ -148,7 +153,7 @@ def deserialize_dict_case(dict_value, collection_cases): @staticmethod def deserialize_array_case(array_value, collection_cases): - if not UnionTypeHelper.is_valid_array(array_value): + if UnionTypeHelper.is_invalid_array_value(array_value): return None deserialized_value = [] @@ -197,7 +202,7 @@ def validate_date_time(value, context): return context.get_date_time_format() == DateTimeFormat.HTTP_DATE_TIME elif isinstance(value, ApiHelper.UnixDateTime): return context.get_date_time_format() == DateTimeFormat.UNIX_DATE_TIME - elif context.get_date_time_converter() is not None: + elif isinstance(value, datetime) and context.get_date_time_converter() is not None: serialized_dt = ApiHelper.json_serialize(ApiHelper.when_defined(context.get_date_time_converter(), value)) return DateTimeHelper.validate_datetime(serialized_dt, context.get_date_time_format()) @@ -214,9 +219,9 @@ def update_nested_flag_for_union_types(nested_union_types): union_type.get_context().is_nested = True @staticmethod - def is_valid_array(value): - return value is not None and isinstance(value, list) + def is_invalid_array_value(value): + return value is None or not isinstance(value, list) @staticmethod - def is_valid_dict(value): - return value is not None and isinstance(value, dict) + def is_invalid_dict_value(value): + return value is None or not isinstance(value, dict) From 0e9b529c5d991640b5301c1e5837646ff4007b21 Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Tue, 6 Jun 2023 15:49:56 +0500 Subject: [PATCH 35/49] refactored code --- apimatic_core/utilities/union_type_helper.py | 22 +++++++++----------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/apimatic_core/utilities/union_type_helper.py b/apimatic_core/utilities/union_type_helper.py index c401dea..2889e2a 100644 --- a/apimatic_core/utilities/union_type_helper.py +++ b/apimatic_core/utilities/union_type_helper.py @@ -56,12 +56,7 @@ def process_dict_items(union_types, dict_value, is_for_one_of): for key, value in dict_value.items(): nested_cases = UnionTypeHelper.validate_item(union_types, value) matched_count = UnionTypeHelper.get_matched_count(value, nested_cases, is_for_one_of) - - if is_valid and is_for_one_of: - is_valid = matched_count == 1 - elif is_valid: - is_valid = matched_count >= 1 - + is_valid = UnionTypeHelper.check_item_validity(is_for_one_of, is_valid, matched_count) collection_cases[key] = nested_cases return is_valid, collection_cases @@ -83,16 +78,19 @@ def process_array_items(union_types, array_value, is_for_one_of): for item in array_value: nested_cases = UnionTypeHelper.validate_item(union_types, item) matched_count = UnionTypeHelper.get_matched_count(item, nested_cases, is_for_one_of) - - if is_valid and is_for_one_of: - is_valid = matched_count == 1 - elif is_valid: - is_valid = matched_count >= 1 - + is_valid = UnionTypeHelper.check_item_validity(is_for_one_of, is_valid, matched_count) collection_cases.append(nested_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 validate_item(union_types, item): nested_cases = [] From c1a38ac3a55aa60b82323d94071cae907db87267 Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Tue, 6 Jun 2023 15:58:38 +0500 Subject: [PATCH 36/49] refactored code --- apimatic_core/utilities/api_helper.py | 32 +++++++++------------------ 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/apimatic_core/utilities/api_helper.py b/apimatic_core/utilities/api_helper.py index 1be173d..aaec592 100644 --- a/apimatic_core/utilities/api_helper.py +++ b/apimatic_core/utilities/api_helper.py @@ -525,36 +525,26 @@ def to_dictionary(obj, should_ignore_null_values=False): @staticmethod def process_nested_collection(value, should_ignore_null_values): if isinstance(value, list): - array = [] - for item in value: - array.append(ApiHelper.process_nested_collection(item, should_ignore_null_values)) - return array - elif isinstance(value, dict): - dictionary = {} - for k, v in value.items(): - dictionary[k] = ApiHelper.process_nested_collection(v, should_ignore_null_values) - return dictionary - else: - return ApiHelper.to_dictionary(value, should_ignore_null_values) if hasattr(value, "_names") else value + 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): - converted_value = [] - for item in value: - converted_value.append(ApiHelper.apply_datetime_converter(item, datetime_converter_obj)) - return converted_value + return [ApiHelper.apply_datetime_converter(item, datetime_converter_obj) for item in value] + if isinstance(value, dict): - converted_value = {} - for k, v in value.items(): - converted_value[k] = ApiHelper.apply_datetime_converter(v, datetime_converter_obj) - return converted_value - elif isinstance(value, datetime.datetime): + 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 From 3d89bf421f85b41edb56b6b6a88995989d4846e2 Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Tue, 6 Jun 2023 16:41:35 +0500 Subject: [PATCH 37/49] refactored code --- apimatic_core/utilities/union_type_helper.py | 129 ++++++++++--------- 1 file changed, 66 insertions(+), 63 deletions(-) diff --git a/apimatic_core/utilities/union_type_helper.py b/apimatic_core/utilities/union_type_helper.py index 2889e2a..1463f60 100644 --- a/apimatic_core/utilities/union_type_helper.py +++ b/apimatic_core/utilities/union_type_helper.py @@ -99,6 +99,72 @@ def validate_item(union_types, item): 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 = ApiHelper.json_serialize(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(): @@ -160,66 +226,3 @@ def deserialize_array_case(array_value, collection_cases): deserialized_value.append(valid_case.deserialize(item)) return deserialized_value - - @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 - elif isinstance(value, ApiHelper.HttpDateTime): - return context.get_date_time_format() == DateTimeFormat.HTTP_DATE_TIME - elif isinstance(value, ApiHelper.UnixDateTime): - return context.get_date_time_format() == DateTimeFormat.UNIX_DATE_TIME - elif isinstance(value, datetime) and context.get_date_time_converter() is not None: - serialized_dt = ApiHelper.json_serialize(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) From 355933add1f615bfb6aed2878a1a7584d1aa84c5 Mon Sep 17 00:00:00 2001 From: MaryamAdnan3 Date: Wed, 7 Jun 2023 07:13:25 +0500 Subject: [PATCH 38/49] added new datetime and other primitive types false negative tests to improve coverage and ignored some functions coverage calculation --- apimatic_core/types/union_types/any_of.py | 2 +- apimatic_core/types/union_types/one_of.py | 2 +- .../types/union_types/union_type_context.py | 4 +-- .../type_combinator_tests/test_any_of.py | 11 ++++++ .../type_combinator_tests/test_one_of.py | 34 +++++++++++++++---- 5 files changed, 42 insertions(+), 11 deletions(-) diff --git a/apimatic_core/types/union_types/any_of.py b/apimatic_core/types/union_types/any_of.py index d4019b1..446495d 100644 --- a/apimatic_core/types/union_types/any_of.py +++ b/apimatic_core/types/union_types/any_of.py @@ -81,7 +81,7 @@ def _raise_validation_exception(self, value, combined_types): raise AnyOfValidationException('{} \nActual Value: {}\nExpected Type: Any Of {}.'.format( UnionType.NONE_MATCHED_ERROR_MESSAGE, value, ', '.join(combined_types))) - def __deepcopy__(self, memo={}): + def __deepcopy__(self, memo={}): # pragma: no cover copy_object = AnyOf(self._union_types, self._union_type_context) copy_object.is_valid = self.is_valid copy_object.collection_cases = self.collection_cases diff --git a/apimatic_core/types/union_types/one_of.py b/apimatic_core/types/union_types/one_of.py index 10e8f02..99b6f80 100644 --- a/apimatic_core/types/union_types/one_of.py +++ b/apimatic_core/types/union_types/one_of.py @@ -84,7 +84,7 @@ def _raise_validation_exception(self, value, combined_types): raise OneOfValidationException('{} \nActual Value: {}\nExpected Type: One Of {}.'.format( error_message, value, ', '.join(combined_types))) - def __deepcopy__(self, memo={}): + def __deepcopy__(self, memo={}): # pragma: no cover copy_object = OneOf(self._union_types, self._union_type_context) copy_object.is_valid = self.is_valid copy_object.collection_cases = self.collection_cases diff --git a/apimatic_core/types/union_types/union_type_context.py b/apimatic_core/types/union_types/union_type_context.py index caf7a6f..76ad252 100644 --- a/apimatic_core/types/union_types/union_type_context.py +++ b/apimatic_core/types/union_types/union_type_context.py @@ -51,14 +51,14 @@ def optional(self, is_optional): self._is_optional = is_optional return self - def is_optional(self): + def is_optional(self): # pragma: no cover return self._is_optional def nullable(self, is_nullable): self._is_nullable = is_nullable return self - def is_nullable(self): + def is_nullable(self): # pragma: no cover return self._is_nullable def is_nullable_or_optional(self): diff --git a/tests/apimatic_core/type_combinator_tests/test_any_of.py b/tests/apimatic_core/type_combinator_tests/test_any_of.py index f3e75a9..743e2dc 100644 --- a/tests/apimatic_core/type_combinator_tests/test_any_of.py +++ b/tests/apimatic_core/type_combinator_tests/test_any_of.py @@ -188,6 +188,10 @@ class TestAnyOf: [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'}], @@ -206,6 +210,10 @@ class TestAnyOf: [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']}, @@ -238,6 +246,9 @@ class TestAnyOf: ({'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), ]) def test_any_of_primitive_type(self, input_value, input_types, input_context, expected_validity, expected_deserialized_value): diff --git a/tests/apimatic_core/type_combinator_tests/test_one_of.py b/tests/apimatic_core/type_combinator_tests/test_one_of.py index e7686df..67e7409 100644 --- a/tests/apimatic_core/type_combinator_tests/test_one_of.py +++ b/tests/apimatic_core/type_combinator_tests/test_one_of.py @@ -187,6 +187,10 @@ class TestOneOf: [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'}], @@ -205,6 +209,10 @@ class TestOneOf: [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']}, @@ -223,6 +231,10 @@ class TestOneOf: [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']}, @@ -237,6 +249,9 @@ class TestOneOf: ({'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), ]) def test_one_of_primitive_type(self, input_value, input_types, input_context, expected_is_valid_output, expected_deserialized_value_output): @@ -252,24 +267,29 @@ def test_one_of_primitive_type(self, input_value, input_types, input_context, ex assert actual_deserialized_value == expected_deserialized_value_output @pytest.mark.parametrize( - 'input_value, input_types, input_context, expected_validity, expected_value', [ - (Base.get_rfc3339_datetime(datetime(1994, 11, 6, 8, 49, 37)), + '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)), + (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)), - (1480809600, + (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)), - ('1994-11-06', [LeafType(date), LeafType(datetime, UnionTypeContext().date_time_format(DateTimeFormat.RFC3339_DATE_TIME))], UnionTypeContext(), + ('1994-11-06', '1994-11-06', + [LeafType(datetime, UnionTypeContext().date_time_converter(datetime.fromisoformat).date_time_format(DateTimeFormat.RFC3339_DATE_TIME)), LeafType(date)], + UnionTypeContext(), True, date(1994, 11, 6)), + ('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_types, input_context, expected_validity, expected_value): + 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_value) + actual_deserialized_value = union_type_result.deserialize(input_date) assert actual_deserialized_value == expected_value @pytest.mark.parametrize( From c70386f95eb8722f63fbcbd256d6006ae347f7c0 Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Wed, 7 Jun 2023 11:40:32 +0500 Subject: [PATCH 39/49] fixed datetime handling and added unit tests --- apimatic_core/utilities/union_type_helper.py | 2 +- tests/apimatic_core/type_combinator_tests/test_one_of.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apimatic_core/utilities/union_type_helper.py b/apimatic_core/utilities/union_type_helper.py index 1463f60..102632c 100644 --- a/apimatic_core/utilities/union_type_helper.py +++ b/apimatic_core/utilities/union_type_helper.py @@ -142,7 +142,7 @@ def validate_date_time(value, context): 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 = ApiHelper.json_serialize(ApiHelper.when_defined(context.get_date_time_converter(), value)) + 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()) diff --git a/tests/apimatic_core/type_combinator_tests/test_one_of.py b/tests/apimatic_core/type_combinator_tests/test_one_of.py index 67e7409..2aca6c2 100644 --- a/tests/apimatic_core/type_combinator_tests/test_one_of.py +++ b/tests/apimatic_core/type_combinator_tests/test_one_of.py @@ -279,9 +279,9 @@ def test_one_of_primitive_type(self, input_value, input_types, input_context, ex (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)), - ('1994-11-06', '1994-11-06', - [LeafType(datetime, UnionTypeContext().date_time_converter(datetime.fromisoformat).date_time_format(DateTimeFormat.RFC3339_DATE_TIME)), LeafType(date)], - UnionTypeContext(), True, date(1994, 11, 6)), + (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)) ]) @@ -307,7 +307,7 @@ def test_one_of_date_and_datetime(self, input_value, input_date, input_types, in ({'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): + 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) From 9d0466953226cc1bf7f2bad14d512155cc4bb362 Mon Sep 17 00:00:00 2001 From: MaryamAdnan3 Date: Wed, 7 Jun 2023 12:00:46 +0500 Subject: [PATCH 40/49] tests refactoring --- .../types/union_types/union_type_context.py | 4 ++-- .../type_combinator_tests/test_any_of.py | 19 ++++++++++++------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/apimatic_core/types/union_types/union_type_context.py b/apimatic_core/types/union_types/union_type_context.py index 914074a..4933f65 100644 --- a/apimatic_core/types/union_types/union_type_context.py +++ b/apimatic_core/types/union_types/union_type_context.py @@ -51,14 +51,14 @@ def optional(self, is_optional): self._is_optional = is_optional return self - def is_optional(self): # pragma: no cover + def is_optional(self): return self._is_optional def nullable(self, is_nullable): self._is_nullable = is_nullable return self - def is_nullable(self): # pragma: no cover + def is_nullable(self): return self._is_nullable def is_nullable_or_optional(self): diff --git a/tests/apimatic_core/type_combinator_tests/test_any_of.py b/tests/apimatic_core/type_combinator_tests/test_any_of.py index 743e2dc..2f131cd 100644 --- a/tests/apimatic_core/type_combinator_tests/test_any_of.py +++ b/tests/apimatic_core/type_combinator_tests/test_any_of.py @@ -264,23 +264,28 @@ def test_any_of_primitive_type(self, input_value, input_types, input_context, ex assert actual_deserialized_value == expected_deserialized_value @pytest.mark.parametrize( - 'input_value, input_types, input_context, expected_validity, expected_value', [ - (Base.get_rfc3339_datetime(datetime(1994, 11, 6, 8, 49, 37)), + '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)), + (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)), - (1480809600, + (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)), - ('1994-11-06', [LeafType(date), LeafType(datetime, UnionTypeContext().date_time_format(DateTimeFormat.RFC3339_DATE_TIME))], UnionTypeContext(), + ('1994-11-06', '1994-11-06', + [LeafType(datetime, UnionTypeContext().date_time_converter(datetime.fromisoformat).date_time_format(DateTimeFormat.RFC3339_DATE_TIME)), LeafType(date)], + UnionTypeContext(), True, date(1994, 11, 6)), + ('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_types, input_context, expected_validity, expected_value): + 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_value) + actual_deserialized_value = union_type_result.deserialize(input_date) assert actual_deserialized_value == expected_value @pytest.mark.parametrize( From 611db302e9828fbbc7fe4f058ef32eb7a5053ef1 Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Wed, 7 Jun 2023 16:51:50 +0500 Subject: [PATCH 41/49] fixed some issues --- apimatic_core/types/union_types/any_of.py | 4 +--- apimatic_core/types/union_types/one_of.py | 19 ++++++++----------- apimatic_core/utilities/union_type_helper.py | 16 ++++++++-------- .../type_combinator_tests/test_one_of.py | 8 ++++++++ 4 files changed, 25 insertions(+), 22 deletions(-) diff --git a/apimatic_core/types/union_types/any_of.py b/apimatic_core/types/union_types/any_of.py index 446495d..0b9c4b1 100644 --- a/apimatic_core/types/union_types/any_of.py +++ b/apimatic_core/types/union_types/any_of.py @@ -56,8 +56,6 @@ def _validate_value_against_case(self, value, context): self.is_valid = UnionTypeHelper.get_matched_count(value, self._union_types, False) >= 1 def _process_errors(self, value): - self.error_messages = [] - combined_types = self._get_combined_types() if self._union_type_context.is_nested: @@ -75,7 +73,7 @@ def _get_combined_types(self): return combined_types def _append_nested_error_message(self, combined_types): - self.error_messages.append(', '.join(combined_types)) + self.error_messages.add(', '.join(combined_types)) def _raise_validation_exception(self, value, combined_types): raise AnyOfValidationException('{} \nActual Value: {}\nExpected Type: Any Of {}.'.format( diff --git a/apimatic_core/types/union_types/one_of.py b/apimatic_core/types/union_types/one_of.py index 99b6f80..3a97c51 100644 --- a/apimatic_core/types/union_types/one_of.py +++ b/apimatic_core/types/union_types/one_of.py @@ -56,36 +56,33 @@ def _validate_value_against_case(self, value, context): self.is_valid = UnionTypeHelper.get_matched_count(value, self._union_types, True) == 1 def _process_errors(self, value): - self.error_messages = [] + self._append_nested_error_message(self._get_combined_types()) - combined_types = self._get_combined_types() - - if self._union_type_context.is_nested: - self._append_nested_error_message(combined_types) - else: - self._raise_validation_exception(value, combined_types) + if not self._union_type_context.is_nested: + self._raise_validation_exception(value) def _get_combined_types(self): combined_types = [] for union_type in self._union_types: if isinstance(union_type, LeafType): combined_types.append(union_type.type_to_match.__name__) - else: + elif union_type.error_messages: combined_types.append(', '.join(union_type.error_messages)) return combined_types def _append_nested_error_message(self, combined_types): - self.error_messages.append(', '.join(combined_types)) + self.error_messages.add(', '.join(combined_types)) - def _raise_validation_exception(self, value, combined_types): + def _raise_validation_exception(self, value): matched_count = sum(union_type.is_valid for union_type in self._union_types) error_message = UnionType.MORE_THAN_1_MATCHED_ERROR_MESSAGE if matched_count > 0 \ else UnionType.NONE_MATCHED_ERROR_MESSAGE raise OneOfValidationException('{} \nActual Value: {}\nExpected Type: One Of {}.'.format( - error_message, value, ', '.join(combined_types))) + error_message, value, ', '.join(self.error_messages))) def __deepcopy__(self, memo={}): # pragma: no cover 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/utilities/union_type_helper.py b/apimatic_core/utilities/union_type_helper.py index 102632c..41f18fb 100644 --- a/apimatic_core/utilities/union_type_helper.py +++ b/apimatic_core/utilities/union_type_helper.py @@ -54,10 +54,10 @@ def process_dict_items(union_types, dict_value, is_for_one_of): collection_cases = {} for key, value in dict_value.items(): - nested_cases = UnionTypeHelper.validate_item(union_types, value) - matched_count = UnionTypeHelper.get_matched_count(value, nested_cases, is_for_one_of) + 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] = nested_cases + collection_cases[key] = union_type_cases return is_valid, collection_cases @@ -76,10 +76,10 @@ def process_array_items(union_types, array_value, is_for_one_of): collection_cases = [] for item in array_value: - nested_cases = UnionTypeHelper.validate_item(union_types, item) - matched_count = UnionTypeHelper.get_matched_count(item, nested_cases, is_for_one_of) + 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(nested_cases) + collection_cases.append(union_type_cases) return is_valid, collection_cases @@ -92,10 +92,10 @@ def check_item_validity(is_for_one_of, is_valid, matched_count): return is_valid @staticmethod - def validate_item(union_types, item): + def make_deep_copies(union_types): nested_cases = [] for union_type in union_types: - nested_cases.append(copy.deepcopy(union_type).validate(item)) + nested_cases.append(copy.deepcopy(union_type)) return nested_cases diff --git a/tests/apimatic_core/type_combinator_tests/test_one_of.py b/tests/apimatic_core/type_combinator_tests/test_one_of.py index 2aca6c2..755e18a 100644 --- a/tests/apimatic_core/type_combinator_tests/test_one_of.py +++ b/tests/apimatic_core/type_combinator_tests/test_one_of.py @@ -252,6 +252,10 @@ class TestOneOf: ([{'key0': [100, 200]}, {'key1': ['abc', 'def']}], [LeafType(int, UnionTypeContext()), LeafType(str, UnionTypeContext())], UnionTypeContext().dict(True).array(True), False, 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): @@ -778,6 +782,10 @@ def test_one_of_enum_type(self, input_value, input_types, input_context, expecte (100.5, [LeafType(int), OneOf([LeafType(bool), LeafType(str)])], UnionTypeContext(), '{} \nActual Value: 100.5\nExpected Type: One Of int, bool, str.'.format( UnionType.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( + UnionType.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: From dcad4ad758a4ee05fb148bab62630fe09eabaef2 Mon Sep 17 00:00:00 2001 From: MaryamAdnan3 Date: Thu, 8 Jun 2023 07:20:17 +0500 Subject: [PATCH 42/49] Add oneof/anyof nested cases --- .../type_combinator_tests/test_any_of.py | 36 +++++++++++++++++-- .../type_combinator_tests/test_one_of.py | 30 ++++++++++++++++ 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/tests/apimatic_core/type_combinator_tests/test_any_of.py b/tests/apimatic_core/type_combinator_tests/test_any_of.py index 2f131cd..be41150 100644 --- a/tests/apimatic_core/type_combinator_tests/test_any_of.py +++ b/tests/apimatic_core/type_combinator_tests/test_any_of.py @@ -249,6 +249,36 @@ class TestAnyOf: ([{'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]), ]) def test_any_of_primitive_type(self, input_value, input_types, input_context, expected_validity, expected_deserialized_value): @@ -276,9 +306,9 @@ def test_any_of_primitive_type(self, input_value, input_types, input_context, ex (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)), - ('1994-11-06', '1994-11-06', - [LeafType(datetime, UnionTypeContext().date_time_converter(datetime.fromisoformat).date_time_format(DateTimeFormat.RFC3339_DATE_TIME)), LeafType(date)], - UnionTypeContext(), True, date(1994, 11, 6)), + (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)) ]) diff --git a/tests/apimatic_core/type_combinator_tests/test_one_of.py b/tests/apimatic_core/type_combinator_tests/test_one_of.py index 2aca6c2..2d59d94 100644 --- a/tests/apimatic_core/type_combinator_tests/test_one_of.py +++ b/tests/apimatic_core/type_combinator_tests/test_one_of.py @@ -252,6 +252,36 @@ class TestOneOf: ([{'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]), ]) def test_one_of_primitive_type(self, input_value, input_types, input_context, expected_is_valid_output, expected_deserialized_value_output): From 6149494b7d2fbb339ef15ce7cf0b58a6ed1642ec Mon Sep 17 00:00:00 2001 From: MaryamAdnan3 Date: Thu, 8 Jun 2023 09:01:14 +0500 Subject: [PATCH 43/49] complete one of/ any coverage to 100% --- tests/apimatic_core/type_combinator_tests/test_any_of.py | 4 ++++ tests/apimatic_core/type_combinator_tests/test_one_of.py | 8 ++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/apimatic_core/type_combinator_tests/test_any_of.py b/tests/apimatic_core/type_combinator_tests/test_any_of.py index be41150..7aa8d32 100644 --- a/tests/apimatic_core/type_combinator_tests/test_any_of.py +++ b/tests/apimatic_core/type_combinator_tests/test_any_of.py @@ -279,6 +279,10 @@ class TestAnyOf: 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): diff --git a/tests/apimatic_core/type_combinator_tests/test_one_of.py b/tests/apimatic_core/type_combinator_tests/test_one_of.py index 25e7196..3c3fda5 100644 --- a/tests/apimatic_core/type_combinator_tests/test_one_of.py +++ b/tests/apimatic_core/type_combinator_tests/test_one_of.py @@ -282,10 +282,10 @@ class TestOneOf: 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), + ([[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, From 0341c8b47626f00ce3164a7766996ecfdd4403b1 Mon Sep 17 00:00:00 2001 From: MaryamAdnan3 Date: Thu, 8 Jun 2023 09:51:14 +0500 Subject: [PATCH 44/49] remove no coverage checks in deepcopy method. --- apimatic_core/types/union_types/any_of.py | 2 +- apimatic_core/types/union_types/one_of.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apimatic_core/types/union_types/any_of.py b/apimatic_core/types/union_types/any_of.py index 0b9c4b1..3b87b08 100644 --- a/apimatic_core/types/union_types/any_of.py +++ b/apimatic_core/types/union_types/any_of.py @@ -79,7 +79,7 @@ def _raise_validation_exception(self, value, combined_types): raise AnyOfValidationException('{} \nActual Value: {}\nExpected Type: Any Of {}.'.format( UnionType.NONE_MATCHED_ERROR_MESSAGE, value, ', '.join(combined_types))) - def __deepcopy__(self, memo={}): # pragma: no cover + 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 diff --git a/apimatic_core/types/union_types/one_of.py b/apimatic_core/types/union_types/one_of.py index 3a97c51..9225a98 100644 --- a/apimatic_core/types/union_types/one_of.py +++ b/apimatic_core/types/union_types/one_of.py @@ -80,7 +80,7 @@ def _raise_validation_exception(self, value): raise OneOfValidationException('{} \nActual Value: {}\nExpected Type: One Of {}.'.format( error_message, value, ', '.join(self.error_messages))) - def __deepcopy__(self, memo={}): # pragma: no cover + 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 From f416f2a869c46aeac40a60f01ac5515901045901 Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Thu, 8 Jun 2023 11:26:34 +0500 Subject: [PATCH 45/49] fixed validation error messages compilation for AnyOf types and removed unreachable code from union_type_helper. --- apimatic_core/types/union_types/any_of.py | 16 +++++++--------- apimatic_core/utilities/union_type_helper.py | 12 ------------ .../type_combinator_tests/test_any_of.py | 5 +++++ 3 files changed, 12 insertions(+), 21 deletions(-) diff --git a/apimatic_core/types/union_types/any_of.py b/apimatic_core/types/union_types/any_of.py index 3b87b08..c26a621 100644 --- a/apimatic_core/types/union_types/any_of.py +++ b/apimatic_core/types/union_types/any_of.py @@ -56,31 +56,29 @@ def _validate_value_against_case(self, value, context): self.is_valid = UnionTypeHelper.get_matched_count(value, self._union_types, False) >= 1 def _process_errors(self, value): - combined_types = self._get_combined_types() - - if self._union_type_context.is_nested: - self._append_nested_error_message(combined_types) - else: - self._raise_validation_exception(value, combined_types) + self._append_nested_error_message(self._get_combined_types()) + if not self._union_type_context.is_nested: + self._raise_validation_exception(value) def _get_combined_types(self): combined_types = [] for union_type in self._union_types: if isinstance(union_type, LeafType): combined_types.append(union_type.type_to_match.__name__) - else: + elif union_type.error_messages: combined_types.append(', '.join(union_type.error_messages)) return combined_types def _append_nested_error_message(self, combined_types): self.error_messages.add(', '.join(combined_types)) - def _raise_validation_exception(self, value, combined_types): + def _raise_validation_exception(self, value): raise AnyOfValidationException('{} \nActual Value: {}\nExpected Type: Any Of {}.'.format( - UnionType.NONE_MATCHED_ERROR_MESSAGE, value, ', '.join(combined_types))) + UnionType.NONE_MATCHED_ERROR_MESSAGE, value, ', '.join(self.error_messages))) 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/utilities/union_type_helper.py b/apimatic_core/utilities/union_type_helper.py index 41f18fb..9251e3a 100644 --- a/apimatic_core/utilities/union_type_helper.py +++ b/apimatic_core/utilities/union_type_helper.py @@ -183,9 +183,6 @@ def deserialize_value(value, context, collection_cases, union_types): @staticmethod def deserialize_array_of_dict_case(array_value, collection_cases): - if UnionTypeHelper.is_invalid_array_value(array_value): - return None - deserialized_value = [] for index, item in enumerate(array_value): deserialized_value.append(UnionTypeHelper.deserialize_dict_case(item, collection_cases[index])) @@ -194,9 +191,6 @@ def deserialize_array_of_dict_case(array_value, collection_cases): @staticmethod def deserialize_dict_of_array_case(dict_value, collection_cases): - if UnionTypeHelper.is_invalid_dict_value(dict_value): - return None - deserialized_value = {} for key, value in dict_value.items(): deserialized_value[key] = UnionTypeHelper.deserialize_array_case(value, collection_cases[key]) @@ -205,9 +199,6 @@ def deserialize_dict_of_array_case(dict_value, collection_cases): @staticmethod def deserialize_dict_case(dict_value, collection_cases): - if UnionTypeHelper.is_invalid_dict_value(dict_value): - return None - deserialized_value = {} for key, value in dict_value.items(): valid_case = [case for case in collection_cases[key] if case.is_valid][0] @@ -217,9 +208,6 @@ def deserialize_dict_case(dict_value, collection_cases): @staticmethod def deserialize_array_case(array_value, collection_cases): - if UnionTypeHelper.is_invalid_array_value(array_value): - return None - deserialized_value = [] for index, item in enumerate(array_value): valid_case = [case for case in collection_cases[index] if case.is_valid][0] diff --git a/tests/apimatic_core/type_combinator_tests/test_any_of.py b/tests/apimatic_core/type_combinator_tests/test_any_of.py index 7aa8d32..ffe30b2 100644 --- a/tests/apimatic_core/type_combinator_tests/test_any_of.py +++ b/tests/apimatic_core/type_combinator_tests/test_any_of.py @@ -802,6 +802,11 @@ def test_any_of_enum_type(self, input_value, input_types, input_context, expecte '{} \nActual Value: 100.5\nExpected Type: Any Of int, bool, str.'.format(UnionType.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(UnionType.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( + UnionType.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: From bc47ef1209106cb87ffda3a153c802b0e6bc82d7 Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Thu, 8 Jun 2023 11:30:42 +0500 Subject: [PATCH 46/49] added files in init file in test package --- tests/apimatic_core/type_combinator_tests/__init__.py | 4 ++++ tests/apimatic_core/utility_tests/__init__.py | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/apimatic_core/type_combinator_tests/__init__.py b/tests/apimatic_core/type_combinator_tests/__init__.py index e69de29..519decc 100644 --- a/tests/apimatic_core/type_combinator_tests/__init__.py +++ b/tests/apimatic_core/type_combinator_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/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 From b192fd987d31096da9a160318a71ebb6cf719abc Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Thu, 8 Jun 2023 12:14:39 +0500 Subject: [PATCH 47/49] added missing unit tests and removed unused properties in request builder --- .../authentication/multiple/auth_group.py | 2 +- .../http_client_configuration.py | 2 +- apimatic_core/http/http_callback.py | 6 +- apimatic_core/http/request/http_request.py | 2 +- apimatic_core/request_builder.py | 5 - apimatic_core/utilities/api_helper.py | 2 +- tests/apimatic_core/base.py | 8 ++ tests/apimatic_core/mocks/__init__.py | 3 +- tests/apimatic_core/mocks/models/__init__.py | 3 +- .../mocks/models/union_type_scalar_model.py | 121 ++++++++++++++++++ .../apimatic_core/mocks/union_type_lookup.py | 34 +++++ .../test_request_builder.py | 34 ++--- .../utility_tests/test_api_helper.py | 2 + 13 files changed, 188 insertions(+), 36 deletions(-) create mode 100644 tests/apimatic_core/mocks/models/union_type_scalar_model.py create mode 100644 tests/apimatic_core/mocks/union_type_lookup.py 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/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 516e098..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 diff --git a/apimatic_core/utilities/api_helper.py b/apimatic_core/utilities/api_helper.py index aaec592..9ddd0a8 100644 --- a/apimatic_core/utilities/api_helper.py +++ b/apimatic_core/utilities/api_helper.py @@ -639,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): diff --git a/tests/apimatic_core/base.py b/tests/apimatic_core/base.py index 8cd38f9..1bc12cf 100644 --- a/tests/apimatic_core/base.py +++ b/tests/apimatic_core/base.py @@ -28,6 +28,7 @@ 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 @@ -270,3 +271,10 @@ def get_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 0db5e5a..173d677 100644 --- a/tests/apimatic_core/mocks/models/__init__.py +++ b/tests/apimatic_core/mocks/models/__init__.py @@ -16,5 +16,6 @@ 'lion', 'orbit', 'rabbit', - 'grand_parent_class_model' + 'grand_parent_class_model', + 'union_type_scalar_model' ] \ No newline at end of file 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 3510455..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): - # http_request = self.new_request_builder \ - # .body_param(Parameter() - # .key(input_body_key) - # .value(input_body_param_value)) \ - # .body_serializer(ApiHelper.json_serialize_wrapped_params) \ - # .should_wrap_body_param(input_should_wrap_body_param) \ - # .build(self.global_configuration) - # assert http_request.parameters == 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() + .validator(lambda value: UnionTypeLookUp.get('ScalarTypes')) + .value(input_value)) \ + .body_serializer(ApiHelper.json_serialize) \ + .build(self.global_configuration) + 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/utility_tests/test_api_helper.py b/tests/apimatic_core/utility_tests/test_api_helper.py index cfa5458..41ddedd 100644 --- a/tests/apimatic_core/utility_tests/test_api_helper.py +++ b/tests/apimatic_core/utility_tests/test_api_helper.py @@ -406,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): From 309845533b3df3ceccd0ce713c3386952a83b02f Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Fri, 9 Jun 2023 12:22:14 +0500 Subject: [PATCH 48/49] applied suggestion after the first review --- apimatic_core/types/union_types/__init__.py | 1 - apimatic_core/types/union_types/any_of.py | 29 ++------------- apimatic_core/types/union_types/one_of.py | 37 +++---------------- apimatic_core/utilities/datetime_helper.py | 3 -- apimatic_core/utilities/union_type_helper.py | 37 +++++++++++++++++++ tests/apimatic_core/__init__.py | 4 +- .../__init__.py | 0 .../test_any_of.py | 14 ++++--- .../test_one_of.py | 10 ++--- 9 files changed, 62 insertions(+), 73 deletions(-) rename tests/apimatic_core/{type_combinator_tests => union_type_tests}/__init__.py (100%) rename tests/apimatic_core/{type_combinator_tests => union_type_tests}/test_any_of.py (99%) rename tests/apimatic_core/{type_combinator_tests => union_type_tests}/test_one_of.py (99%) diff --git a/apimatic_core/types/union_types/__init__.py b/apimatic_core/types/union_types/__init__.py index 4df7d04..3bf1608 100644 --- a/apimatic_core/types/union_types/__init__.py +++ b/apimatic_core/types/union_types/__init__.py @@ -1,5 +1,4 @@ __all__ = [ - "union_type", "any_of", "one_of", "union_type_context", diff --git a/apimatic_core/types/union_types/any_of.py b/apimatic_core/types/union_types/any_of.py index c26a621..667f9b6 100644 --- a/apimatic_core/types/union_types/any_of.py +++ b/apimatic_core/types/union_types/any_of.py @@ -1,6 +1,4 @@ from apimatic_core_interfaces.types.union_type import UnionType -from apimatic_core.exceptions.anyof_validation_exception import AnyOfValidationException -from apimatic_core.types.union_types.leaf_type import LeafType from apimatic_core.types.union_types.union_type_context import UnionTypeContext from apimatic_core.utilities.union_type_helper import UnionTypeHelper @@ -24,13 +22,15 @@ def validate(self, value): if value is None: self.is_valid = False - self._process_errors(value) + 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._process_errors(value) + self.error_messages = UnionTypeHelper.process_errors(value, self._union_types, self.error_messages, + self.get_context().is_nested, False) return self @@ -55,27 +55,6 @@ def _validate_value_against_case(self, value, context): else: self.is_valid = UnionTypeHelper.get_matched_count(value, self._union_types, False) >= 1 - def _process_errors(self, value): - self._append_nested_error_message(self._get_combined_types()) - if not self._union_type_context.is_nested: - self._raise_validation_exception(value) - - def _get_combined_types(self): - combined_types = [] - for union_type in self._union_types: - if isinstance(union_type, LeafType): - combined_types.append(union_type.type_to_match.__name__) - elif union_type.error_messages: - combined_types.append(', '.join(union_type.error_messages)) - return combined_types - - def _append_nested_error_message(self, combined_types): - self.error_messages.add(', '.join(combined_types)) - - def _raise_validation_exception(self, value): - raise AnyOfValidationException('{} \nActual Value: {}\nExpected Type: Any Of {}.'.format( - UnionType.NONE_MATCHED_ERROR_MESSAGE, value, ', '.join(self.error_messages))) - def __deepcopy__(self, memo={}): copy_object = AnyOf(self._union_types, self._union_type_context) copy_object.is_valid = self.is_valid diff --git a/apimatic_core/types/union_types/one_of.py b/apimatic_core/types/union_types/one_of.py index 9225a98..e1212b5 100644 --- a/apimatic_core/types/union_types/one_of.py +++ b/apimatic_core/types/union_types/one_of.py @@ -1,6 +1,4 @@ from apimatic_core_interfaces.types.union_type import UnionType -from apimatic_core.exceptions.oneof_validation_exception import OneOfValidationException -from apimatic_core.types.union_types.leaf_type import LeafType from apimatic_core.types.union_types.union_type_context import UnionTypeContext from apimatic_core.utilities.union_type_helper import UnionTypeHelper @@ -24,13 +22,15 @@ def validate(self, value): if value is None: self.is_valid = False - self._process_errors(value) + 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._process_errors(value) + self.error_messages = UnionTypeHelper.process_errors(value, self._union_types, self.error_messages, + self.get_context().is_nested, True) return self @@ -38,8 +38,8 @@ 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) + 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(): @@ -55,31 +55,6 @@ def _validate_value_against_case(self, value, context): else: self.is_valid = UnionTypeHelper.get_matched_count(value, self._union_types, True) == 1 - def _process_errors(self, value): - self._append_nested_error_message(self._get_combined_types()) - - if not self._union_type_context.is_nested: - self._raise_validation_exception(value) - - def _get_combined_types(self): - combined_types = [] - for union_type in self._union_types: - if isinstance(union_type, LeafType): - combined_types.append(union_type.type_to_match.__name__) - elif union_type.error_messages: - combined_types.append(', '.join(union_type.error_messages)) - return combined_types - - def _append_nested_error_message(self, combined_types): - self.error_messages.add(', '.join(combined_types)) - - def _raise_validation_exception(self, value): - matched_count = sum(union_type.is_valid for union_type in self._union_types) - error_message = UnionType.MORE_THAN_1_MATCHED_ERROR_MESSAGE if matched_count > 0 \ - else UnionType.NONE_MATCHED_ERROR_MESSAGE - raise OneOfValidationException('{} \nActual Value: {}\nExpected Type: One Of {}.'.format( - error_message, value, ', '.join(self.error_messages))) - def __deepcopy__(self, memo={}): copy_object = OneOf(self._union_types, self._union_type_context) copy_object.is_valid = self.is_valid diff --git a/apimatic_core/utilities/datetime_helper.py b/apimatic_core/utilities/datetime_helper.py index 6b80953..662409e 100644 --- a/apimatic_core/utilities/datetime_helper.py +++ b/apimatic_core/utilities/datetime_helper.py @@ -1,7 +1,4 @@ from datetime import datetime, date - -import dateutil - from apimatic_core.types.datetime_format import DateTimeFormat diff --git a/apimatic_core/utilities/union_type_helper.py b/apimatic_core/utilities/union_type_helper.py index 9251e3a..61fcddf 100644 --- a/apimatic_core/utilities/union_type_helper.py +++ b/apimatic_core/utilities/union_type_helper.py @@ -1,5 +1,7 @@ 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 @@ -7,6 +9,9 @@ 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) @@ -214,3 +219,35 @@ def deserialize_array_case(array_value, collection_cases): 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/tests/apimatic_core/__init__.py b/tests/apimatic_core/__init__.py index 86629f0..672f182 100644 --- a/tests/apimatic_core/__init__.py +++ b/tests/apimatic_core/__init__.py @@ -6,5 +6,5 @@ 'mocks', 'api_call_tests', 'api_logger_tests', - 'type_combinator_tests' -] \ No newline at end of file + 'union_type_tests' +] diff --git a/tests/apimatic_core/type_combinator_tests/__init__.py b/tests/apimatic_core/union_type_tests/__init__.py similarity index 100% rename from tests/apimatic_core/type_combinator_tests/__init__.py rename to tests/apimatic_core/union_type_tests/__init__.py diff --git a/tests/apimatic_core/type_combinator_tests/test_any_of.py b/tests/apimatic_core/union_type_tests/test_any_of.py similarity index 99% rename from tests/apimatic_core/type_combinator_tests/test_any_of.py rename to tests/apimatic_core/union_type_tests/test_any_of.py index ffe30b2..690b26a 100644 --- a/tests/apimatic_core/type_combinator_tests/test_any_of.py +++ b/tests/apimatic_core/union_type_tests/test_any_of.py @@ -6,7 +6,7 @@ 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_interfaces.types.union_type import UnionType +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 @@ -515,8 +515,8 @@ def test_any_of_optional_nullable(self, input_value, input_types, input_context, # 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}}, + '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, @@ -799,14 +799,16 @@ def test_any_of_enum_type(self, input_value, input_types, input_context, expecte @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(UnionType.NONE_MATCHED_ERROR_MESSAGE)), + '{} \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(UnionType.NONE_MATCHED_ERROR_MESSAGE)), + '{} \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( - UnionType.NONE_MATCHED_ERROR_MESSAGE)), + 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: diff --git a/tests/apimatic_core/type_combinator_tests/test_one_of.py b/tests/apimatic_core/union_type_tests/test_one_of.py similarity index 99% rename from tests/apimatic_core/type_combinator_tests/test_one_of.py rename to tests/apimatic_core/union_type_tests/test_one_of.py index 3c3fda5..23af19a 100644 --- a/tests/apimatic_core/type_combinator_tests/test_one_of.py +++ b/tests/apimatic_core/union_type_tests/test_one_of.py @@ -6,7 +6,7 @@ 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_interfaces.types.union_type import UnionType +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 @@ -806,17 +806,17 @@ def test_one_of_enum_type(self, input_value, input_types, input_context, expecte # Simple Cases (100, [LeafType(int), LeafType(int), LeafType(str)], UnionTypeContext(), '{} \nActual Value: 100\nExpected Type: One Of int, int, str.'.format( - UnionType.MORE_THAN_1_MATCHED_ERROR_MESSAGE)), + 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( - UnionType.NONE_MATCHED_ERROR_MESSAGE)), + 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( - UnionType.NONE_MATCHED_ERROR_MESSAGE)), + 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( - UnionType.NONE_MATCHED_ERROR_MESSAGE)), + 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: From 6aa5b6cfa14049eab1e3e6058935fc7b4e6cc03f Mon Sep 17 00:00:00 2001 From: Muhammad Sufyan Date: Mon, 24 Jul 2023 11:46:49 +0500 Subject: [PATCH 49/49] Update setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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,