Skip to content

Commit 0650a1e

Browse files
authored
[core] set value of dict property not working (#44435)
1 parent fdac744 commit 0650a1e

9 files changed

Lines changed: 1003 additions & 145 deletions

File tree

sdk/core/azure-core/tests/specs/modeltypes/main.tsp

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ model Scratch {
1717
model FlattenModel {
1818
name: string;
1919

20-
#suppress "deprecated" "@flattenProperty decorator is not recommended to use."
21-
@flattenProperty
20+
#suppress "@azure-tools/typespec-azure-core/no-legacy-usage" "Testing backcompat"
21+
@Azure.ClientGenerator.Core.Legacy.flattenProperty
2222
properties: PropertiesModel;
2323
}
2424

@@ -86,3 +86,21 @@ model RecursiveElement extends Element {
8686
model Element {
8787
recursiveElement?: RecursiveElement[];
8888
}
89+
90+
model BackcompatModel {
91+
keys: Record<string>;
92+
}
93+
94+
@alternateType(
95+
{
96+
identity: "geojson.Feature",
97+
package: "geojson",
98+
minVersion: "3.2.0",
99+
},
100+
"python"
101+
)
102+
model Feature {
103+
type: "Feature";
104+
properties: Record<unknown>;
105+
id?: string | numeric;
106+
}

sdk/core/azure-core/tests/specs_sdk/modeltypes/apiview-properties.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"CrossLanguagePackageId": "",
33
"CrossLanguageDefinitionId": {
4+
"modeltypes.models.BackcompatModel": "ModelTypes.BackcompatModel",
45
"modeltypes.models.ClientNameAndJsonEncodedNameModel": "ModelTypes.ClientNameAndJsonEncodedNameModel",
56
"modeltypes.models.Element": "ModelTypes.Element",
67
"modeltypes.models.Fish": "ModelTypes.Fish",

sdk/core/azure-core/tests/specs_sdk/modeltypes/modeltypes/_utils/model_base.py

Lines changed: 134 additions & 44 deletions
Large diffs are not rendered by default.

sdk/core/azure-core/tests/specs_sdk/modeltypes/modeltypes/_utils/serialization.py

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import sys
2222
import codecs
2323
from typing import (
24-
Dict,
2524
Any,
2625
cast,
2726
Optional,
@@ -31,7 +30,6 @@
3130
Mapping,
3231
Callable,
3332
MutableMapping,
34-
List,
3533
)
3634

3735
try:
@@ -229,12 +227,12 @@ class Model:
229227
serialization and deserialization.
230228
"""
231229

232-
_subtype_map: Dict[str, Dict[str, Any]] = {}
233-
_attribute_map: Dict[str, Dict[str, Any]] = {}
234-
_validation: Dict[str, Dict[str, Any]] = {}
230+
_subtype_map: dict[str, dict[str, Any]] = {}
231+
_attribute_map: dict[str, dict[str, Any]] = {}
232+
_validation: dict[str, dict[str, Any]] = {}
235233

236234
def __init__(self, **kwargs: Any) -> None:
237-
self.additional_properties: Optional[Dict[str, Any]] = {}
235+
self.additional_properties: Optional[dict[str, Any]] = {}
238236
for k in kwargs: # pylint: disable=consider-using-dict-items
239237
if k not in self._attribute_map:
240238
_LOGGER.warning("%s is not a known attribute of class %s and will be ignored", k, self.__class__)
@@ -311,7 +309,7 @@ def serialize(self, keep_readonly: bool = False, **kwargs: Any) -> JSON:
311309
def as_dict(
312310
self,
313311
keep_readonly: bool = True,
314-
key_transformer: Callable[[str, Dict[str, Any], Any], Any] = attribute_transformer,
312+
key_transformer: Callable[[str, dict[str, Any], Any], Any] = attribute_transformer,
315313
**kwargs: Any
316314
) -> JSON:
317315
"""Return a dict that can be serialized using json.dump.
@@ -380,7 +378,7 @@ def deserialize(cls, data: Any, content_type: Optional[str] = None) -> Self:
380378
def from_dict(
381379
cls,
382380
data: Any,
383-
key_extractors: Optional[Callable[[str, Dict[str, Any], Any], Any]] = None,
381+
key_extractors: Optional[Callable[[str, dict[str, Any], Any], Any]] = None,
384382
content_type: Optional[str] = None,
385383
) -> Self:
386384
"""Parse a dict using given key extractor return a model.
@@ -414,7 +412,7 @@ def _flatten_subtype(cls, key, objects):
414412
return {}
415413
result = dict(cls._subtype_map[key])
416414
for valuetype in cls._subtype_map[key].values():
417-
result.update(objects[valuetype]._flatten_subtype(key, objects)) # pylint: disable=protected-access
415+
result |= objects[valuetype]._flatten_subtype(key, objects) # pylint: disable=protected-access
418416
return result
419417

420418
@classmethod
@@ -528,7 +526,7 @@ def __init__(self, classes: Optional[Mapping[str, type]] = None) -> None:
528526
"[]": self.serialize_iter,
529527
"{}": self.serialize_dict,
530528
}
531-
self.dependencies: Dict[str, type] = dict(classes) if classes else {}
529+
self.dependencies: dict[str, type] = dict(classes) if classes else {}
532530
self.key_transformer = full_restapi_key_transformer
533531
self.client_side_validation = True
534532

@@ -579,7 +577,7 @@ def _serialize( # pylint: disable=too-many-nested-blocks, too-many-branches, to
579577

580578
if attr_name == "additional_properties" and attr_desc["key"] == "":
581579
if target_obj.additional_properties is not None:
582-
serialized.update(target_obj.additional_properties)
580+
serialized |= target_obj.additional_properties
583581
continue
584582
try:
585583

@@ -789,7 +787,7 @@ def serialize_data(self, data, data_type, **kwargs):
789787

790788
# If dependencies is empty, try with current data class
791789
# It has to be a subclass of Enum anyway
792-
enum_type = self.dependencies.get(data_type, data.__class__)
790+
enum_type = self.dependencies.get(data_type, cast(type, data.__class__))
793791
if issubclass(enum_type, Enum):
794792
return Serializer.serialize_enum(data, enum_obj=enum_type)
795793

@@ -823,13 +821,20 @@ def serialize_basic(cls, data, data_type, **kwargs):
823821
:param str data_type: Type of object in the iterable.
824822
:rtype: str, int, float, bool
825823
:return: serialized object
824+
:raises TypeError: raise if data_type is not one of str, int, float, bool.
826825
"""
827826
custom_serializer = cls._get_custom_serializers(data_type, **kwargs)
828827
if custom_serializer:
829828
return custom_serializer(data)
830829
if data_type == "str":
831830
return cls.serialize_unicode(data)
832-
return eval(data_type)(data) # nosec # pylint: disable=eval-used
831+
if data_type == "int":
832+
return int(data)
833+
if data_type == "float":
834+
return float(data)
835+
if data_type == "bool":
836+
return bool(data)
837+
raise TypeError("Unknown basic data type: {}".format(data_type))
833838

834839
@classmethod
835840
def serialize_unicode(cls, data):
@@ -1184,7 +1189,7 @@ def rest_key_extractor(attr, attr_desc, data): # pylint: disable=unused-argumen
11841189

11851190
while "." in key:
11861191
# Need the cast, as for some reasons "split" is typed as list[str | Any]
1187-
dict_keys = cast(List[str], _FLATTEN.split(key))
1192+
dict_keys = cast(list[str], _FLATTEN.split(key))
11881193
if len(dict_keys) == 1:
11891194
key = _decode_attribute_map_key(dict_keys[0])
11901195
break
@@ -1386,7 +1391,7 @@ def __init__(self, classes: Optional[Mapping[str, type]] = None) -> None:
13861391
"duration": (isodate.Duration, datetime.timedelta),
13871392
"iso-8601": (datetime.datetime),
13881393
}
1389-
self.dependencies: Dict[str, type] = dict(classes) if classes else {}
1394+
self.dependencies: dict[str, type] = dict(classes) if classes else {}
13901395
self.key_extractors = [rest_key_extractor, xml_key_extractor]
13911396
# Additional properties only works if the "rest_key_extractor" is used to
13921397
# extract the keys. Making it to work whatever the key extractor is too much
@@ -1759,7 +1764,7 @@ def deserialize_basic(self, attr, data_type): # pylint: disable=too-many-return
17591764
:param str data_type: deserialization data type.
17601765
:return: Deserialized basic type.
17611766
:rtype: str, int, float or bool
1762-
:raises TypeError: if string format is not valid.
1767+
:raises TypeError: if string format is not valid or data_type is not one of str, int, float, bool.
17631768
"""
17641769
# If we're here, data is supposed to be a basic type.
17651770
# If it's still an XML node, take the text
@@ -1785,7 +1790,11 @@ def deserialize_basic(self, attr, data_type): # pylint: disable=too-many-return
17851790

17861791
if data_type == "str":
17871792
return self.deserialize_unicode(attr)
1788-
return eval(data_type)(attr) # nosec # pylint: disable=eval-used
1793+
if data_type == "int":
1794+
return int(attr)
1795+
if data_type == "float":
1796+
return float(attr)
1797+
raise TypeError("Unknown basic data type: {}".format(data_type))
17891798

17901799
@staticmethod
17911800
def deserialize_unicode(data):

sdk/core/azure-core/tests/specs_sdk/modeltypes/modeltypes/models/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515

1616
from ._models import ( # type: ignore
17+
BackcompatModel,
1718
ClientNameAndJsonEncodedNameModel,
1819
Element,
1920
Fish,
@@ -32,6 +33,7 @@
3233
from ._patch import patch_sdk as _patch_sdk
3334

3435
__all__ = [
36+
"BackcompatModel",
3537
"ClientNameAndJsonEncodedNameModel",
3638
"Element",
3739
"Fish",

sdk/core/azure-core/tests/specs_sdk/modeltypes/modeltypes/models/_models.py

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,44 @@
88
# --------------------------------------------------------------------------
99
# pylint: disable=useless-super-delegation
1010

11-
from typing import Any, Dict, List, Literal, Mapping, Optional, TYPE_CHECKING, overload
11+
from typing import Any, Literal, Mapping, Optional, TYPE_CHECKING, overload
1212

1313
from .._utils.model_base import Model as _Model, rest_discriminator, rest_field
1414

1515
if TYPE_CHECKING:
1616
from .. import models as _models
1717

1818

19+
class BackcompatModel(_Model):
20+
"""BackcompatModel.
21+
22+
:ivar keys_property: Required.
23+
:vartype keys_property: dict[str, str]
24+
"""
25+
26+
keys_property: dict[str, str] = rest_field(
27+
name="keys", visibility=["read", "create", "update", "delete", "query"], original_tsp_name="keys"
28+
)
29+
"""Required."""
30+
31+
@overload
32+
def __init__(
33+
self,
34+
*,
35+
keys_property: dict[str, str],
36+
) -> None: ...
37+
38+
@overload
39+
def __init__(self, mapping: Mapping[str, Any]) -> None:
40+
"""
41+
:param mapping: raw JSON to initialize the model.
42+
:type mapping: Mapping[str, Any]
43+
"""
44+
45+
def __init__(self, *args: Any, **kwargs: Any) -> None:
46+
super().__init__(*args, **kwargs)
47+
48+
1949
class ClientNameAndJsonEncodedNameModel(_Model):
2050
"""Model with a property that has a client name.
2151
@@ -51,15 +81,15 @@ class Element(_Model):
5181
:vartype recursive_element: list[~modeltypes.models.RecursiveElement]
5282
"""
5383

54-
recursive_element: Optional[List["_models.RecursiveElement"]] = rest_field(
84+
recursive_element: Optional[list["_models.RecursiveElement"]] = rest_field(
5585
name="recursiveElement", visibility=["read", "create", "update", "delete", "query"]
5686
)
5787

5888
@overload
5989
def __init__(
6090
self,
6191
*,
62-
recursive_element: Optional[List["_models.RecursiveElement"]] = None,
92+
recursive_element: Optional[list["_models.RecursiveElement"]] = None,
6393
) -> None: ...
6494

6595
@overload
@@ -85,7 +115,7 @@ class Fish(_Model):
85115
:vartype age: int
86116
"""
87117

88-
__mapping__: Dict[str, _Model] = {}
118+
__mapping__: dict[str, _Model] = {}
89119
kind: str = rest_discriminator(name="kind")
90120
"""Discriminator property for Fish. Required. Default value is None."""
91121
age: int = rest_field(visibility=["read", "create", "update", "delete", "query"])
@@ -178,7 +208,7 @@ class Shark(Fish, discriminator="shark"):
178208
:vartype shark_type: str
179209
"""
180210

181-
__mapping__: Dict[str, _Model] = {}
211+
__mapping__: dict[str, _Model] = {}
182212
kind: Literal["shark"] = rest_discriminator(name="kind", visibility=["read", "create", "update", "delete", "query"]) # type: ignore
183213
"""Required. Default value is \"shark\"."""
184214
shark_type: str = rest_discriminator(name="sharkType", visibility=["read", "create", "update", "delete", "query"])
@@ -200,7 +230,8 @@ def __init__(self, mapping: Mapping[str, Any]) -> None:
200230
"""
201231

202232
def __init__(self, *args: Any, **kwargs: Any) -> None:
203-
super().__init__(*args, kind="shark", **kwargs)
233+
super().__init__(*args, **kwargs)
234+
self.kind = "shark" # type: ignore
204235

205236

206237
class GoblinShark(Shark, discriminator="goblin"):
@@ -214,7 +245,7 @@ class GoblinShark(Shark, discriminator="goblin"):
214245
:vartype shark_type: str
215246
"""
216247

217-
__mapping__: Dict[str, _Model] = {}
248+
__mapping__: dict[str, _Model] = {}
218249
shark_type: Literal["goblin"] = rest_discriminator(name="sharkType", visibility=["read", "create", "update", "delete", "query"]) # type: ignore
219250
"""Required. Default value is \"goblin\"."""
220251

@@ -233,7 +264,8 @@ def __init__(self, mapping: Mapping[str, Any]) -> None:
233264
"""
234265

235266
def __init__(self, *args: Any, **kwargs: Any) -> None:
236-
super().__init__(*args, shark_type="goblin", **kwargs)
267+
super().__init__(*args, **kwargs)
268+
self.shark_type = "goblin" # type: ignore
237269

238270

239271
class PropertiesModel(_Model):
@@ -326,8 +358,8 @@ class Salmon(Fish, discriminator="salmon"):
326358

327359
kind: Literal["salmon"] = rest_discriminator(name="kind", visibility=["read", "create", "update", "delete", "query"]) # type: ignore
328360
"""Required. Default value is \"salmon\"."""
329-
friends: Optional[List["_models.Fish"]] = rest_field(visibility=["read", "create", "update", "delete", "query"])
330-
hate: Optional[Dict[str, "_models.Fish"]] = rest_field(visibility=["read", "create", "update", "delete", "query"])
361+
friends: Optional[list["_models.Fish"]] = rest_field(visibility=["read", "create", "update", "delete", "query"])
362+
hate: Optional[dict[str, "_models.Fish"]] = rest_field(visibility=["read", "create", "update", "delete", "query"])
331363
life_partner: Optional["_models.Fish"] = rest_field(
332364
name="lifePartner", visibility=["read", "create", "update", "delete", "query"]
333365
)
@@ -337,8 +369,8 @@ def __init__(
337369
self,
338370
*,
339371
age: int,
340-
friends: Optional[List["_models.Fish"]] = None,
341-
hate: Optional[Dict[str, "_models.Fish"]] = None,
372+
friends: Optional[list["_models.Fish"]] = None,
373+
hate: Optional[dict[str, "_models.Fish"]] = None,
342374
life_partner: Optional["_models.Fish"] = None,
343375
) -> None: ...
344376

@@ -350,7 +382,8 @@ def __init__(self, mapping: Mapping[str, Any]) -> None:
350382
"""
351383

352384
def __init__(self, *args: Any, **kwargs: Any) -> None:
353-
super().__init__(*args, kind="salmon", **kwargs)
385+
super().__init__(*args, **kwargs)
386+
self.kind = "salmon" # type: ignore
354387

355388

356389
class SawShark(Shark, discriminator="saw"):
@@ -364,7 +397,7 @@ class SawShark(Shark, discriminator="saw"):
364397
:vartype shark_type: str
365398
"""
366399

367-
__mapping__: Dict[str, _Model] = {}
400+
__mapping__: dict[str, _Model] = {}
368401
shark_type: Literal["saw"] = rest_discriminator(name="sharkType", visibility=["read", "create", "update", "delete", "query"]) # type: ignore
369402
"""Required. Default value is \"saw\"."""
370403

@@ -383,7 +416,8 @@ def __init__(self, mapping: Mapping[str, Any]) -> None:
383416
"""
384417

385418
def __init__(self, *args: Any, **kwargs: Any) -> None:
386-
super().__init__(*args, shark_type="saw", **kwargs)
419+
super().__init__(*args, **kwargs)
420+
self.shark_type = "saw" # type: ignore
387421

388422

389423
class Scratch(_Model):

0 commit comments

Comments
 (0)