From f6cec9ea6eef25f141d5f7bf9580dc6c03fe45c1 Mon Sep 17 00:00:00 2001 From: Yuchao Yan Date: Wed, 3 Dec 2025 16:15:42 +0800 Subject: [PATCH 1/2] fix for optional properties in flatten model --- .../codegen/templates/model_base.py.jinja2 | 6 +- .../test_model_base_flatten_compatibility.py | 70 +++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/packages/http-client-python/generator/pygen/codegen/templates/model_base.py.jinja2 b/packages/http-client-python/generator/pygen/codegen/templates/model_base.py.jinja2 index 4a830e1dfcd..3f31fc5d9ff 100644 --- a/packages/http-client-python/generator/pygen/codegen/templates/model_base.py.jinja2 +++ b/packages/http-client-python/generator/pygen/codegen/templates/model_base.py.jinja2 @@ -1023,7 +1023,11 @@ class _RestField: @property def _class_type(self) -> typing.Any: - return getattr(self._type, "args", [None])[0] + result = getattr(self._type, "args", [None])[0] + # type may be wrapped by nested functools.partial so we need to check for that + if isinstance(result, functools.partial): + return getattr(result, "args", [None])[0] + return result @property def _rest_name(self) -> str: diff --git a/packages/http-client-python/generator/test/azure/mock_api_tests/test_model_base_flatten_compatibility.py b/packages/http-client-python/generator/test/azure/mock_api_tests/test_model_base_flatten_compatibility.py index d0f4d10aa59..538b03a953b 100644 --- a/packages/http-client-python/generator/test/azure/mock_api_tests/test_model_base_flatten_compatibility.py +++ b/packages/http-client-python/generator/test/azure/mock_api_tests/test_model_base_flatten_compatibility.py @@ -6,6 +6,7 @@ from typing import ( Any, Mapping, + Optional, overload, ) @@ -13,6 +14,7 @@ Model, rest_field, ) +from azure.core.serialization import attribute_list class ModelProperty(Model): @@ -179,3 +181,71 @@ def test_model_initialization(): 2023, 1, 12, 0, 0, 0, tzinfo=datetime.timezone.utc ) assert model.properties["datetimeUnixTimestamp"] == 1673481600 + + +class FlattenModelWithOptionalProperties(Model): + """This is the model with one level of flattening and optional properties.""" + + name: str = rest_field() + """Required.""" + properties: Optional["ModelProperty"] = rest_field() + """Optional.""" + + __flattened_items = ["value"] + + @overload + def __init__( + self, + *, + name: str, + properties: Optional["ModelProperty"], + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + _flattened_input = {k: kwargs.pop(k) for k in kwargs.keys() & self.__flattened_items} + super().__init__(*args, **kwargs) + for k, v in _flattened_input.items(): + setattr(self, k, v) + + def __getattr__(self, name: str) -> Any: + if name in self.__flattened_items: + if self.properties is None: + return None + return getattr(self.properties, name) + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") + + def __setattr__(self, key: str, value: Any) -> None: + if key in self.__flattened_items: + if self.properties is None: + self.properties = self._attr_to_rest_field["properties"]._class_type() + setattr(self.properties, key, value) + else: + super().__setattr__(key, value) + + +def test_model_with_optional_properties_initialization(): + model = FlattenModelWithOptionalProperties( + name="test", + value="test value", + ) + + assert model.name == "test" + + assert model.value == "test value" + assert model.properties.value == "test value" + + +def test_model_with_optional_properties_attribute_list(): + model = FlattenModelWithOptionalProperties( + name="test", + ) + + attrs = attribute_list(model) + assert sorted(attrs) == sorted(["name", "value"]) From 8a7cff7a8cc705f96b20abab40b76208269fc516 Mon Sep 17 00:00:00 2001 From: Yuchao Yan Date: Wed, 3 Dec 2025 16:16:45 +0800 Subject: [PATCH 2/2] add changelog --- .../python-fix-optional-properties-2025-11-3-16-16-35.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .chronus/changes/python-fix-optional-properties-2025-11-3-16-16-35.md diff --git a/.chronus/changes/python-fix-optional-properties-2025-11-3-16-16-35.md b/.chronus/changes/python-fix-optional-properties-2025-11-3-16-16-35.md new file mode 100644 index 00000000000..ac948c2b08e --- /dev/null +++ b/.chronus/changes/python-fix-optional-properties-2025-11-3-16-16-35.md @@ -0,0 +1,7 @@ +--- +changeKind: fix +packages: + - "@typespec/http-client-python" +--- + +Fix for optional properties in flatten model to keep compatibility \ No newline at end of file