Skip to content

Commit 7b47584

Browse files
authored
[Python] Fix for optional properties in flatten model to keep compatibility (#9129)
fix #9004 The underlying issue arises because when `properties` is optional, `_class_type` becomes wrapped twice by `functools.partial`, necessitating an additional unwrapping step to obtain the actual type rather than the wrapped class. I have included a test case in this pull request to address a similar situation.
1 parent 3c413f8 commit 7b47584

File tree

3 files changed

+82
-1
lines changed

3 files changed

+82
-1
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
changeKind: fix
3+
packages:
4+
- "@typespec/http-client-python"
5+
---
6+
7+
Fix for optional properties in flatten model to keep compatibility

packages/http-client-python/generator/pygen/codegen/templates/model_base.py.jinja2

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1023,7 +1023,11 @@ class _RestField:
10231023

10241024
@property
10251025
def _class_type(self) -> typing.Any:
1026-
return getattr(self._type, "args", [None])[0]
1026+
result = getattr(self._type, "args", [None])[0]
1027+
# type may be wrapped by nested functools.partial so we need to check for that
1028+
if isinstance(result, functools.partial):
1029+
return getattr(result, "args", [None])[0]
1030+
return result
10271031

10281032
@property
10291033
def _rest_name(self) -> str:

packages/http-client-python/generator/test/azure/mock_api_tests/test_model_base_flatten_compatibility.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@
66
from typing import (
77
Any,
88
Mapping,
9+
Optional,
910
overload,
1011
)
1112

1213
from specs.azure.clientgenerator.core.flattenproperty._utils.model_base import (
1314
Model,
1415
rest_field,
1516
)
17+
from azure.core.serialization import attribute_list
1618

1719

1820
class ModelProperty(Model):
@@ -179,3 +181,71 @@ def test_model_initialization():
179181
2023, 1, 12, 0, 0, 0, tzinfo=datetime.timezone.utc
180182
)
181183
assert model.properties["datetimeUnixTimestamp"] == 1673481600
184+
185+
186+
class FlattenModelWithOptionalProperties(Model):
187+
"""This is the model with one level of flattening and optional properties."""
188+
189+
name: str = rest_field()
190+
"""Required."""
191+
properties: Optional["ModelProperty"] = rest_field()
192+
"""Optional."""
193+
194+
__flattened_items = ["value"]
195+
196+
@overload
197+
def __init__(
198+
self,
199+
*,
200+
name: str,
201+
properties: Optional["ModelProperty"],
202+
) -> None: ...
203+
204+
@overload
205+
def __init__(self, mapping: Mapping[str, Any]) -> None:
206+
"""
207+
:param mapping: raw JSON to initialize the model.
208+
:type mapping: Mapping[str, Any]
209+
"""
210+
211+
def __init__(self, *args: Any, **kwargs: Any) -> None:
212+
_flattened_input = {k: kwargs.pop(k) for k in kwargs.keys() & self.__flattened_items}
213+
super().__init__(*args, **kwargs)
214+
for k, v in _flattened_input.items():
215+
setattr(self, k, v)
216+
217+
def __getattr__(self, name: str) -> Any:
218+
if name in self.__flattened_items:
219+
if self.properties is None:
220+
return None
221+
return getattr(self.properties, name)
222+
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
223+
224+
def __setattr__(self, key: str, value: Any) -> None:
225+
if key in self.__flattened_items:
226+
if self.properties is None:
227+
self.properties = self._attr_to_rest_field["properties"]._class_type()
228+
setattr(self.properties, key, value)
229+
else:
230+
super().__setattr__(key, value)
231+
232+
233+
def test_model_with_optional_properties_initialization():
234+
model = FlattenModelWithOptionalProperties(
235+
name="test",
236+
value="test value",
237+
)
238+
239+
assert model.name == "test"
240+
241+
assert model.value == "test value"
242+
assert model.properties.value == "test value"
243+
244+
245+
def test_model_with_optional_properties_attribute_list():
246+
model = FlattenModelWithOptionalProperties(
247+
name="test",
248+
)
249+
250+
attrs = attribute_list(model)
251+
assert sorted(attrs) == sorted(["name", "value"])

0 commit comments

Comments
 (0)