From 5e7f2f274c2f98b3cbc5445dd47708ca05a7c5ec Mon Sep 17 00:00:00 2001 From: Robert Schweizer Date: Sat, 18 Nov 2023 03:46:01 +0100 Subject: [PATCH] fix: Annotate free-form object as dict in Python (#17082) --- .../codegen/languages/AbstractPythonCodegen.java | 14 +------------- .../models/inner_dict_with_property.py | 4 ++-- .../petstore_api/models/nullable_class.py | 14 +++++++------- .../models/inner_dict_with_property.py | 4 ++-- .../python/petstore_api/models/nullable_class.py | 14 +++++++------- .../client/petstore/python/tests/test_model.py | 15 +++++++++++++++ 6 files changed, 34 insertions(+), 31 deletions(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPythonCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPythonCodegen.java index e4adee10dd48..80b4cea7798e 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPythonCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPythonCodegen.java @@ -2024,16 +2024,6 @@ public abstract class AbstractPythonCodegen extends DefaultCodegen implements Co return new PythonType(cp.getDataType()); } - private PythonType freeFormType(IJsonSchemaValidationProperties cp) { - typingImports.add("Dict"); - typingImports.add("Any"); - typingImports.add("Union"); - PythonType pt = new PythonType("Union"); - pt.addTypeParam(new PythonType("str")); - pt.addTypeParam(new PythonType("Any")); - return pt; - } - private PythonType modelType(IJsonSchemaValidationProperties cp) { // add model prefix hasModelsToImport = true; @@ -2056,7 +2046,7 @@ public abstract class AbstractPythonCodegen extends DefaultCodegen implements Co if (cp.getIsArray()) { return arrayType(cp); - } else if (cp.getIsMap()) { + } else if (cp.getIsMap() || cp.getIsFreeFormObject()) { return mapType(cp); } else if (cp.getIsString()) { return stringType(cp); @@ -2076,8 +2066,6 @@ public abstract class AbstractPythonCodegen extends DefaultCodegen implements Co return dateType(cp); } else if (cp.getIsUuid()) { return uuidType(cp); - } else if (cp.getIsFreeFormObject()) { // type: object - return freeFormType(cp); } return null; diff --git a/samples/openapi3/client/petstore/python-aiohttp/petstore_api/models/inner_dict_with_property.py b/samples/openapi3/client/petstore/python-aiohttp/petstore_api/models/inner_dict_with_property.py index 106fbbbb9889..f93d19de93c2 100644 --- a/samples/openapi3/client/petstore/python-aiohttp/petstore_api/models/inner_dict_with_property.py +++ b/samples/openapi3/client/petstore/python-aiohttp/petstore_api/models/inner_dict_with_property.py @@ -18,7 +18,7 @@ import re # noqa: F401 import json -from typing import Any, ClassVar, Dict, List, Optional, Union +from typing import Any, ClassVar, Dict, List, Optional from pydantic import BaseModel from pydantic import Field try: @@ -30,7 +30,7 @@ class InnerDictWithProperty(BaseModel): """ InnerDictWithProperty """ # noqa: E501 - a_property: Optional[Union[str, Any]] = Field(default=None, alias="aProperty") + a_property: Optional[Dict[str, Any]] = Field(default=None, alias="aProperty") __properties: ClassVar[List[str]] = ["aProperty"] model_config = { diff --git a/samples/openapi3/client/petstore/python-aiohttp/petstore_api/models/nullable_class.py b/samples/openapi3/client/petstore/python-aiohttp/petstore_api/models/nullable_class.py index 67d7f00f9e6b..d30916976ba2 100644 --- a/samples/openapi3/client/petstore/python-aiohttp/petstore_api/models/nullable_class.py +++ b/samples/openapi3/client/petstore/python-aiohttp/petstore_api/models/nullable_class.py @@ -18,7 +18,7 @@ import re # noqa: F401 import json from datetime import date, datetime -from typing import Any, ClassVar, Dict, List, Optional, Union +from typing import Any, ClassVar, Dict, List, Optional from pydantic import BaseModel, StrictBool, StrictInt, StrictStr try: from typing import Self @@ -36,12 +36,12 @@ class NullableClass(BaseModel): string_prop: Optional[StrictStr] = None date_prop: Optional[date] = None datetime_prop: Optional[datetime] = None - array_nullable_prop: Optional[List[Union[str, Any]]] = None - array_and_items_nullable_prop: Optional[List[Union[str, Any]]] = None - array_items_nullable: Optional[List[Union[str, Any]]] = None - object_nullable_prop: Optional[Dict[str, Union[str, Any]]] = None - object_and_items_nullable_prop: Optional[Dict[str, Union[str, Any]]] = None - object_items_nullable: Optional[Dict[str, Union[str, Any]]] = None + array_nullable_prop: Optional[List[Dict[str, Any]]] = None + array_and_items_nullable_prop: Optional[List[Dict[str, Any]]] = None + array_items_nullable: Optional[List[Dict[str, Any]]] = None + object_nullable_prop: Optional[Dict[str, Dict[str, Any]]] = None + object_and_items_nullable_prop: Optional[Dict[str, Dict[str, Any]]] = None + object_items_nullable: Optional[Dict[str, Dict[str, Any]]] = None additional_properties: Dict[str, Any] = {} __properties: ClassVar[List[str]] = ["required_integer_prop", "integer_prop", "number_prop", "boolean_prop", "string_prop", "date_prop", "datetime_prop", "array_nullable_prop", "array_and_items_nullable_prop", "array_items_nullable", "object_nullable_prop", "object_and_items_nullable_prop", "object_items_nullable"] diff --git a/samples/openapi3/client/petstore/python/petstore_api/models/inner_dict_with_property.py b/samples/openapi3/client/petstore/python/petstore_api/models/inner_dict_with_property.py index 08fceba1ad30..c4e52c32e4d7 100644 --- a/samples/openapi3/client/petstore/python/petstore_api/models/inner_dict_with_property.py +++ b/samples/openapi3/client/petstore/python/petstore_api/models/inner_dict_with_property.py @@ -18,7 +18,7 @@ import re # noqa: F401 import json -from typing import Any, ClassVar, Dict, List, Optional, Union +from typing import Any, ClassVar, Dict, List, Optional from pydantic import BaseModel from pydantic import Field try: @@ -30,7 +30,7 @@ class InnerDictWithProperty(BaseModel): """ InnerDictWithProperty """ # noqa: E501 - a_property: Optional[Union[str, Any]] = Field(default=None, alias="aProperty") + a_property: Optional[Dict[str, Any]] = Field(default=None, alias="aProperty") additional_properties: Dict[str, Any] = {} __properties: ClassVar[List[str]] = ["aProperty"] diff --git a/samples/openapi3/client/petstore/python/petstore_api/models/nullable_class.py b/samples/openapi3/client/petstore/python/petstore_api/models/nullable_class.py index fc38db22ac01..88c5ee219988 100644 --- a/samples/openapi3/client/petstore/python/petstore_api/models/nullable_class.py +++ b/samples/openapi3/client/petstore/python/petstore_api/models/nullable_class.py @@ -18,7 +18,7 @@ import re # noqa: F401 import json from datetime import date, datetime -from typing import Any, ClassVar, Dict, List, Optional, Union +from typing import Any, ClassVar, Dict, List, Optional from pydantic import BaseModel, StrictBool, StrictFloat, StrictInt, StrictStr try: from typing import Self @@ -36,12 +36,12 @@ class NullableClass(BaseModel): string_prop: Optional[StrictStr] = None date_prop: Optional[date] = None datetime_prop: Optional[datetime] = None - array_nullable_prop: Optional[List[Union[str, Any]]] = None - array_and_items_nullable_prop: Optional[List[Union[str, Any]]] = None - array_items_nullable: Optional[List[Union[str, Any]]] = None - object_nullable_prop: Optional[Dict[str, Union[str, Any]]] = None - object_and_items_nullable_prop: Optional[Dict[str, Union[str, Any]]] = None - object_items_nullable: Optional[Dict[str, Union[str, Any]]] = None + array_nullable_prop: Optional[List[Dict[str, Any]]] = None + array_and_items_nullable_prop: Optional[List[Dict[str, Any]]] = None + array_items_nullable: Optional[List[Dict[str, Any]]] = None + object_nullable_prop: Optional[Dict[str, Dict[str, Any]]] = None + object_and_items_nullable_prop: Optional[Dict[str, Dict[str, Any]]] = None + object_items_nullable: Optional[Dict[str, Dict[str, Any]]] = None additional_properties: Dict[str, Any] = {} __properties: ClassVar[List[str]] = ["required_integer_prop", "integer_prop", "number_prop", "boolean_prop", "string_prop", "date_prop", "datetime_prop", "array_nullable_prop", "array_and_items_nullable_prop", "array_items_nullable", "object_nullable_prop", "object_and_items_nullable_prop", "object_items_nullable"] diff --git a/samples/openapi3/client/petstore/python/tests/test_model.py b/samples/openapi3/client/petstore/python/tests/test_model.py index c96ec890acc2..646755b7f1dd 100644 --- a/samples/openapi3/client/petstore/python/tests/test_model.py +++ b/samples/openapi3/client/petstore/python/tests/test_model.py @@ -8,8 +8,10 @@ import time import unittest from pydantic import ValidationError +import pytest import petstore_api +from petstore_api import InnerDictWithProperty class ModelTests(unittest.TestCase): @@ -508,6 +510,19 @@ class ModelTests(unittest.TestCase): self.assertFalse(b is None) self.assertEqual(b.optional_dict["key"].a_property["a"], "b") + def test_freeform_object(self): + # Allows dict[str, Any] and is nullable + a = InnerDictWithProperty.from_dict({"aProperty": {"a": 12}}) + a = InnerDictWithProperty.from_dict({"aProperty": None}) + + # Allows no other values + with pytest.raises(ValidationError): + a = InnerDictWithProperty.from_dict({"aProperty": {123: 45}}) + with pytest.raises(ValidationError): + a = InnerDictWithProperty.from_dict({"aProperty": "abc"}) + with pytest.raises(ValidationError): + a = InnerDictWithProperty.from_dict({"aProperty": 12}) + def test_object_with_dict_of_dict_of_object(self): # for https://github.com/OpenAPITools/openapi-generator/issues/15135 d = {"optionalDict": {"a": {"b": {"aProperty": "value"}}}}