diff --git a/modules/openapi-json-schema-generator/src/main/resources/python/schemas.handlebars b/modules/openapi-json-schema-generator/src/main/resources/python/schemas.handlebars index 22999b13062..de79feab8e0 100644 --- a/modules/openapi-json-schema-generator/src/main/resources/python/schemas.handlebars +++ b/modules/openapi-json-schema-generator/src/main/resources/python/schemas.handlebars @@ -141,6 +141,19 @@ class ValidationMetadata(frozendict.frozendict): return self.get('validated_path_to_schemas') +def add_deeper_validated_schemas(validation_metadata: ValidationMetadata, path_to_schemas: dict): + # this is called if validation_ran_earlier and current and deeper locations need to be added + current_path_to_item = validation_metadata.path_to_item + other_path_to_schemas = {} + for path_to_item, schemas in validation_metadata.validated_path_to_schemas.items(): + if len(path_to_item) < len(current_path_to_item): + continue + path_begins_with_current_path = path_to_item[:len(current_path_to_item)] == current_path_to_item + if path_begins_with_current_path: + other_path_to_schemas[path_to_item] = schemas + update(path_to_schemas, other_path_to_schemas) + + class Singleton: """ Enums and singletons are the same @@ -385,9 +398,9 @@ class Schema: because value is of the correct type, and validation was run earlier when the instance was created """ _path_to_schemas = {} - if validation_metadata.validated_path_to_schemas: - update(_path_to_schemas, validation_metadata.validated_path_to_schemas) - if not validation_metadata.validation_ran_earlier(cls): + if validation_metadata.validation_ran_earlier(cls): + add_deeper_validated_schemas(validation_metadata, _path_to_schemas) + else: other_path_to_schemas = cls._validate_oapg(arg, validation_metadata=validation_metadata) update(_path_to_schemas, other_path_to_schemas) # loop through it make a new class for each entry @@ -1325,6 +1338,7 @@ class ListBase(ValidatorBase): validated_path_to_schemas=validation_metadata.validated_path_to_schemas ) if item_validation_metadata.validation_ran_earlier(item_cls): + add_deeper_validated_schemas(item_validation_metadata, path_to_schemas) continue other_path_to_schemas = item_cls._validate_oapg( value, validation_metadata=item_validation_metadata) @@ -1588,6 +1602,7 @@ class DictBase(Discriminable, ValidatorBase): validated_path_to_schemas=validation_metadata.validated_path_to_schemas ) if arg_validation_metadata.validation_ran_earlier(schema): + add_deeper_validated_schemas(arg_validation_metadata, path_to_schemas) continue other_path_to_schemas = schema._validate_oapg(value, validation_metadata=arg_validation_metadata) update(path_to_schemas, other_path_to_schemas) @@ -1676,6 +1691,7 @@ class DictBase(Discriminable, ValidatorBase): validated_path_to_schemas=validation_metadata.validated_path_to_schemas ) if updated_vm.validation_ran_earlier(discriminated_cls): + add_deeper_validated_schemas(updated_vm, _path_to_schemas) return _path_to_schemas other_path_to_schemas = discriminated_cls._validate_oapg(arg, validation_metadata=updated_vm) update(_path_to_schemas, other_path_to_schemas) @@ -1774,18 +1790,11 @@ def cast_to_allowed_types( if isinstance(arg, Schema): # store the already run validations schema_classes = set() - source_schema_was_unset = len(arg.__class__.__bases__) == 2 and UnsetAnyTypeSchema in arg.__class__.__bases__ - if not source_schema_was_unset: - """ - Do not include UnsetAnyTypeSchema and its base class because - it did not exist in the original spec schema definition - It was added to ensure that all instances are of type Schema and the allowed base types - """ - for cls in arg.__class__.__bases__: - if cls is Singleton: - # Skip Singleton - continue - schema_classes.add(cls) + for cls in arg.__class__.__bases__: + if cls is Singleton: + # Skip Singleton + continue + schema_classes.add(cls) validated_path_to_schemas[path_to_item] = schema_classes type_error = ApiTypeError(f"Invalid type. Required value type is str and passed type was {type(arg)} at {path_to_item}") @@ -1838,6 +1847,7 @@ class ComposedBase(Discriminable): path_to_schemas = defaultdict(set) for allof_cls in cls.MetaOapg.all_of(): if validation_metadata.validation_ran_earlier(allof_cls): + add_deeper_validated_schemas(validation_metadata, path_to_schemas) continue other_path_to_schemas = allof_cls._validate_oapg(arg, validation_metadata=validation_metadata) update(path_to_schemas, other_path_to_schemas) @@ -1858,6 +1868,7 @@ class ComposedBase(Discriminable): continue if validation_metadata.validation_ran_earlier(oneof_cls): oneof_classes.append(oneof_cls) + add_deeper_validated_schemas(validation_metadata, path_to_schemas) continue try: path_to_schemas = oneof_cls._validate_oapg(arg, validation_metadata=validation_metadata) @@ -1915,6 +1926,7 @@ class ComposedBase(Discriminable): for anyof_cls in cls.MetaOapg.any_of(): if validation_metadata.validation_ran_earlier(anyof_cls): anyof_classes.append(anyof_cls) + add_deeper_validated_schemas(validation_metadata, path_to_schemas) continue try: @@ -2044,6 +2056,7 @@ class ComposedBase(Discriminable): if discriminated_cls is not None and not updated_vm.validation_ran_earlier(discriminated_cls): # TODO use an exception from this package here + add_deeper_validated_schemas(updated_vm, path_to_schemas) assert discriminated_cls in path_to_schemas[updated_vm.path_to_item] return path_to_schemas diff --git a/samples/openapi3/client/3_0_3_unit_test/python/unit_test_api/schemas.py b/samples/openapi3/client/3_0_3_unit_test/python/unit_test_api/schemas.py index d6c0284ab19..7e443c6d5e6 100644 --- a/samples/openapi3/client/3_0_3_unit_test/python/unit_test_api/schemas.py +++ b/samples/openapi3/client/3_0_3_unit_test/python/unit_test_api/schemas.py @@ -148,6 +148,19 @@ def validated_path_to_schemas(self) -> typing.Dict[typing.Tuple[typing.Union[str return self.get('validated_path_to_schemas') +def add_deeper_validated_schemas(validation_metadata: ValidationMetadata, path_to_schemas: dict): + # this is called if validation_ran_earlier and current and deeper locations need to be added + current_path_to_item = validation_metadata.path_to_item + other_path_to_schemas = {} + for path_to_item, schemas in validation_metadata.validated_path_to_schemas.items(): + if len(path_to_item) < len(current_path_to_item): + continue + path_begins_with_current_path = path_to_item[:len(current_path_to_item)] == current_path_to_item + if path_begins_with_current_path: + other_path_to_schemas[path_to_item] = schemas + update(path_to_schemas, other_path_to_schemas) + + class Singleton: """ Enums and singletons are the same @@ -392,9 +405,9 @@ def __get_new_cls( because value is of the correct type, and validation was run earlier when the instance was created """ _path_to_schemas = {} - if validation_metadata.validated_path_to_schemas: - update(_path_to_schemas, validation_metadata.validated_path_to_schemas) - if not validation_metadata.validation_ran_earlier(cls): + if validation_metadata.validation_ran_earlier(cls): + add_deeper_validated_schemas(validation_metadata, _path_to_schemas) + else: other_path_to_schemas = cls._validate_oapg(arg, validation_metadata=validation_metadata) update(_path_to_schemas, other_path_to_schemas) # loop through it make a new class for each entry @@ -1332,6 +1345,7 @@ def __validate_items(cls, list_items, validation_metadata: ValidationMetadata): validated_path_to_schemas=validation_metadata.validated_path_to_schemas ) if item_validation_metadata.validation_ran_earlier(item_cls): + add_deeper_validated_schemas(item_validation_metadata, path_to_schemas) continue other_path_to_schemas = item_cls._validate_oapg( value, validation_metadata=item_validation_metadata) @@ -1595,6 +1609,7 @@ def __validate_args(cls, arg, validation_metadata: ValidationMetadata): validated_path_to_schemas=validation_metadata.validated_path_to_schemas ) if arg_validation_metadata.validation_ran_earlier(schema): + add_deeper_validated_schemas(arg_validation_metadata, path_to_schemas) continue other_path_to_schemas = schema._validate_oapg(value, validation_metadata=arg_validation_metadata) update(path_to_schemas, other_path_to_schemas) @@ -1683,6 +1698,7 @@ def _validate_oapg( validated_path_to_schemas=validation_metadata.validated_path_to_schemas ) if updated_vm.validation_ran_earlier(discriminated_cls): + add_deeper_validated_schemas(updated_vm, _path_to_schemas) return _path_to_schemas other_path_to_schemas = discriminated_cls._validate_oapg(arg, validation_metadata=updated_vm) update(_path_to_schemas, other_path_to_schemas) @@ -1781,18 +1797,11 @@ def cast_to_allowed_types( if isinstance(arg, Schema): # store the already run validations schema_classes = set() - source_schema_was_unset = len(arg.__class__.__bases__) == 2 and UnsetAnyTypeSchema in arg.__class__.__bases__ - if not source_schema_was_unset: - """ - Do not include UnsetAnyTypeSchema and its base class because - it did not exist in the original spec schema definition - It was added to ensure that all instances are of type Schema and the allowed base types - """ - for cls in arg.__class__.__bases__: - if cls is Singleton: - # Skip Singleton - continue - schema_classes.add(cls) + for cls in arg.__class__.__bases__: + if cls is Singleton: + # Skip Singleton + continue + schema_classes.add(cls) validated_path_to_schemas[path_to_item] = schema_classes type_error = ApiTypeError(f"Invalid type. Required value type is str and passed type was {type(arg)} at {path_to_item}") @@ -1845,6 +1854,7 @@ def __get_allof_classes(cls, arg, validation_metadata: ValidationMetadata): path_to_schemas = defaultdict(set) for allof_cls in cls.MetaOapg.all_of(): if validation_metadata.validation_ran_earlier(allof_cls): + add_deeper_validated_schemas(validation_metadata, path_to_schemas) continue other_path_to_schemas = allof_cls._validate_oapg(arg, validation_metadata=validation_metadata) update(path_to_schemas, other_path_to_schemas) @@ -1865,6 +1875,7 @@ def __get_oneof_class( continue if validation_metadata.validation_ran_earlier(oneof_cls): oneof_classes.append(oneof_cls) + add_deeper_validated_schemas(validation_metadata, path_to_schemas) continue try: path_to_schemas = oneof_cls._validate_oapg(arg, validation_metadata=validation_metadata) @@ -1898,6 +1909,7 @@ def __get_anyof_classes( for anyof_cls in cls.MetaOapg.any_of(): if validation_metadata.validation_ran_earlier(anyof_cls): anyof_classes.append(anyof_cls) + add_deeper_validated_schemas(validation_metadata, path_to_schemas) continue try: @@ -2011,6 +2023,7 @@ def _validate_oapg( if discriminated_cls is not None and not updated_vm.validation_ran_earlier(discriminated_cls): # TODO use an exception from this package here + add_deeper_validated_schemas(updated_vm, path_to_schemas) assert discriminated_cls in path_to_schemas[updated_vm.path_to_item] return path_to_schemas diff --git a/samples/openapi3/client/features/nonCompliantUseDiscriminatorIfCompositionFails/python/this_package/schemas.py b/samples/openapi3/client/features/nonCompliantUseDiscriminatorIfCompositionFails/python/this_package/schemas.py index a88751a643d..5e117f6df3a 100644 --- a/samples/openapi3/client/features/nonCompliantUseDiscriminatorIfCompositionFails/python/this_package/schemas.py +++ b/samples/openapi3/client/features/nonCompliantUseDiscriminatorIfCompositionFails/python/this_package/schemas.py @@ -148,6 +148,19 @@ def validated_path_to_schemas(self) -> typing.Dict[typing.Tuple[typing.Union[str return self.get('validated_path_to_schemas') +def add_deeper_validated_schemas(validation_metadata: ValidationMetadata, path_to_schemas: dict): + # this is called if validation_ran_earlier and current and deeper locations need to be added + current_path_to_item = validation_metadata.path_to_item + other_path_to_schemas = {} + for path_to_item, schemas in validation_metadata.validated_path_to_schemas.items(): + if len(path_to_item) < len(current_path_to_item): + continue + path_begins_with_current_path = path_to_item[:len(current_path_to_item)] == current_path_to_item + if path_begins_with_current_path: + other_path_to_schemas[path_to_item] = schemas + update(path_to_schemas, other_path_to_schemas) + + class Singleton: """ Enums and singletons are the same @@ -392,9 +405,9 @@ def __get_new_cls( because value is of the correct type, and validation was run earlier when the instance was created """ _path_to_schemas = {} - if validation_metadata.validated_path_to_schemas: - update(_path_to_schemas, validation_metadata.validated_path_to_schemas) - if not validation_metadata.validation_ran_earlier(cls): + if validation_metadata.validation_ran_earlier(cls): + add_deeper_validated_schemas(validation_metadata, _path_to_schemas) + else: other_path_to_schemas = cls._validate_oapg(arg, validation_metadata=validation_metadata) update(_path_to_schemas, other_path_to_schemas) # loop through it make a new class for each entry @@ -1332,6 +1345,7 @@ def __validate_items(cls, list_items, validation_metadata: ValidationMetadata): validated_path_to_schemas=validation_metadata.validated_path_to_schemas ) if item_validation_metadata.validation_ran_earlier(item_cls): + add_deeper_validated_schemas(item_validation_metadata, path_to_schemas) continue other_path_to_schemas = item_cls._validate_oapg( value, validation_metadata=item_validation_metadata) @@ -1595,6 +1609,7 @@ def __validate_args(cls, arg, validation_metadata: ValidationMetadata): validated_path_to_schemas=validation_metadata.validated_path_to_schemas ) if arg_validation_metadata.validation_ran_earlier(schema): + add_deeper_validated_schemas(arg_validation_metadata, path_to_schemas) continue other_path_to_schemas = schema._validate_oapg(value, validation_metadata=arg_validation_metadata) update(path_to_schemas, other_path_to_schemas) @@ -1683,6 +1698,7 @@ def _validate_oapg( validated_path_to_schemas=validation_metadata.validated_path_to_schemas ) if updated_vm.validation_ran_earlier(discriminated_cls): + add_deeper_validated_schemas(updated_vm, _path_to_schemas) return _path_to_schemas other_path_to_schemas = discriminated_cls._validate_oapg(arg, validation_metadata=updated_vm) update(_path_to_schemas, other_path_to_schemas) @@ -1781,18 +1797,11 @@ def cast_to_allowed_types( if isinstance(arg, Schema): # store the already run validations schema_classes = set() - source_schema_was_unset = len(arg.__class__.__bases__) == 2 and UnsetAnyTypeSchema in arg.__class__.__bases__ - if not source_schema_was_unset: - """ - Do not include UnsetAnyTypeSchema and its base class because - it did not exist in the original spec schema definition - It was added to ensure that all instances are of type Schema and the allowed base types - """ - for cls in arg.__class__.__bases__: - if cls is Singleton: - # Skip Singleton - continue - schema_classes.add(cls) + for cls in arg.__class__.__bases__: + if cls is Singleton: + # Skip Singleton + continue + schema_classes.add(cls) validated_path_to_schemas[path_to_item] = schema_classes type_error = ApiTypeError(f"Invalid type. Required value type is str and passed type was {type(arg)} at {path_to_item}") @@ -1845,6 +1854,7 @@ def __get_allof_classes(cls, arg, validation_metadata: ValidationMetadata): path_to_schemas = defaultdict(set) for allof_cls in cls.MetaOapg.all_of(): if validation_metadata.validation_ran_earlier(allof_cls): + add_deeper_validated_schemas(validation_metadata, path_to_schemas) continue other_path_to_schemas = allof_cls._validate_oapg(arg, validation_metadata=validation_metadata) update(path_to_schemas, other_path_to_schemas) @@ -1865,6 +1875,7 @@ def __get_oneof_class( continue if validation_metadata.validation_ran_earlier(oneof_cls): oneof_classes.append(oneof_cls) + add_deeper_validated_schemas(validation_metadata, path_to_schemas) continue try: path_to_schemas = oneof_cls._validate_oapg(arg, validation_metadata=validation_metadata) @@ -1914,6 +1925,7 @@ def __get_anyof_classes( for anyof_cls in cls.MetaOapg.any_of(): if validation_metadata.validation_ran_earlier(anyof_cls): anyof_classes.append(anyof_cls) + add_deeper_validated_schemas(validation_metadata, path_to_schemas) continue try: @@ -2037,6 +2049,7 @@ def _validate_oapg( if discriminated_cls is not None and not updated_vm.validation_ran_earlier(discriminated_cls): # TODO use an exception from this package here + add_deeper_validated_schemas(updated_vm, path_to_schemas) assert discriminated_cls in path_to_schemas[updated_vm.path_to_item] return path_to_schemas diff --git a/samples/openapi3/client/petstore/python/petstore_api/schemas.py b/samples/openapi3/client/petstore/python/petstore_api/schemas.py index e2c6c665dd8..33d6e5b48b3 100644 --- a/samples/openapi3/client/petstore/python/petstore_api/schemas.py +++ b/samples/openapi3/client/petstore/python/petstore_api/schemas.py @@ -148,6 +148,19 @@ def validated_path_to_schemas(self) -> typing.Dict[typing.Tuple[typing.Union[str return self.get('validated_path_to_schemas') +def add_deeper_validated_schemas(validation_metadata: ValidationMetadata, path_to_schemas: dict): + # this is called if validation_ran_earlier and current and deeper locations need to be added + current_path_to_item = validation_metadata.path_to_item + other_path_to_schemas = {} + for path_to_item, schemas in validation_metadata.validated_path_to_schemas.items(): + if len(path_to_item) < len(current_path_to_item): + continue + path_begins_with_current_path = path_to_item[:len(current_path_to_item)] == current_path_to_item + if path_begins_with_current_path: + other_path_to_schemas[path_to_item] = schemas + update(path_to_schemas, other_path_to_schemas) + + class Singleton: """ Enums and singletons are the same @@ -392,9 +405,9 @@ def __get_new_cls( because value is of the correct type, and validation was run earlier when the instance was created """ _path_to_schemas = {} - if validation_metadata.validated_path_to_schemas: - update(_path_to_schemas, validation_metadata.validated_path_to_schemas) - if not validation_metadata.validation_ran_earlier(cls): + if validation_metadata.validation_ran_earlier(cls): + add_deeper_validated_schemas(validation_metadata, _path_to_schemas) + else: other_path_to_schemas = cls._validate_oapg(arg, validation_metadata=validation_metadata) update(_path_to_schemas, other_path_to_schemas) # loop through it make a new class for each entry @@ -1332,6 +1345,7 @@ def __validate_items(cls, list_items, validation_metadata: ValidationMetadata): validated_path_to_schemas=validation_metadata.validated_path_to_schemas ) if item_validation_metadata.validation_ran_earlier(item_cls): + add_deeper_validated_schemas(item_validation_metadata, path_to_schemas) continue other_path_to_schemas = item_cls._validate_oapg( value, validation_metadata=item_validation_metadata) @@ -1595,6 +1609,7 @@ def __validate_args(cls, arg, validation_metadata: ValidationMetadata): validated_path_to_schemas=validation_metadata.validated_path_to_schemas ) if arg_validation_metadata.validation_ran_earlier(schema): + add_deeper_validated_schemas(arg_validation_metadata, path_to_schemas) continue other_path_to_schemas = schema._validate_oapg(value, validation_metadata=arg_validation_metadata) update(path_to_schemas, other_path_to_schemas) @@ -1683,6 +1698,7 @@ def _validate_oapg( validated_path_to_schemas=validation_metadata.validated_path_to_schemas ) if updated_vm.validation_ran_earlier(discriminated_cls): + add_deeper_validated_schemas(updated_vm, _path_to_schemas) return _path_to_schemas other_path_to_schemas = discriminated_cls._validate_oapg(arg, validation_metadata=updated_vm) update(_path_to_schemas, other_path_to_schemas) @@ -1781,18 +1797,11 @@ def cast_to_allowed_types( if isinstance(arg, Schema): # store the already run validations schema_classes = set() - source_schema_was_unset = len(arg.__class__.__bases__) == 2 and UnsetAnyTypeSchema in arg.__class__.__bases__ - if not source_schema_was_unset: - """ - Do not include UnsetAnyTypeSchema and its base class because - it did not exist in the original spec schema definition - It was added to ensure that all instances are of type Schema and the allowed base types - """ - for cls in arg.__class__.__bases__: - if cls is Singleton: - # Skip Singleton - continue - schema_classes.add(cls) + for cls in arg.__class__.__bases__: + if cls is Singleton: + # Skip Singleton + continue + schema_classes.add(cls) validated_path_to_schemas[path_to_item] = schema_classes type_error = ApiTypeError(f"Invalid type. Required value type is str and passed type was {type(arg)} at {path_to_item}") @@ -1845,6 +1854,7 @@ def __get_allof_classes(cls, arg, validation_metadata: ValidationMetadata): path_to_schemas = defaultdict(set) for allof_cls in cls.MetaOapg.all_of(): if validation_metadata.validation_ran_earlier(allof_cls): + add_deeper_validated_schemas(validation_metadata, path_to_schemas) continue other_path_to_schemas = allof_cls._validate_oapg(arg, validation_metadata=validation_metadata) update(path_to_schemas, other_path_to_schemas) @@ -1865,6 +1875,7 @@ def __get_oneof_class( continue if validation_metadata.validation_ran_earlier(oneof_cls): oneof_classes.append(oneof_cls) + add_deeper_validated_schemas(validation_metadata, path_to_schemas) continue try: path_to_schemas = oneof_cls._validate_oapg(arg, validation_metadata=validation_metadata) @@ -1898,6 +1909,7 @@ def __get_anyof_classes( for anyof_cls in cls.MetaOapg.any_of(): if validation_metadata.validation_ran_earlier(anyof_cls): anyof_classes.append(anyof_cls) + add_deeper_validated_schemas(validation_metadata, path_to_schemas) continue try: @@ -2011,6 +2023,7 @@ def _validate_oapg( if discriminated_cls is not None and not updated_vm.validation_ran_earlier(discriminated_cls): # TODO use an exception from this package here + add_deeper_validated_schemas(updated_vm, path_to_schemas) assert discriminated_cls in path_to_schemas[updated_vm.path_to_item] return path_to_schemas diff --git a/samples/openapi3/client/petstore/python/tests_manual/test_pet.py b/samples/openapi3/client/petstore/python/tests_manual/test_pet.py new file mode 100644 index 00000000000..726a85c2d35 --- /dev/null +++ b/samples/openapi3/client/petstore/python/tests_manual/test_pet.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +""" + OpenAPI Petstore + + This spec is mainly for testing Petstore server and contains fake endpoints, models. Please do not use this for any other purpose. Special characters: \" \\ # noqa: E501 + + The version of the OpenAPI document: 1.0.0 + Generated by: https://openapi-generator.tech +""" + +import decimal +import unittest + +from petstore_api.model import pet +from petstore_api.model import category + + +class TesttPet(unittest.TestCase): + """ParentPet unit test stubs""" + + def testPet(self): + cat = category.Category(name='hi', addprop={'a': 1}) + inst = pet.Pet(photoUrls=[], name='Katsu', category=cat) + self.assertEqual( + inst, + { + 'photoUrls': (), + 'name': 'Katsu', + 'category': { + 'name': 'hi', + 'addprop': {'a': decimal.Decimal('1')} + } + } + ) + + +if __name__ == '__main__': + unittest.main()