From 0fcbb0e008d62ff51f18f7a74630d107bf14843d Mon Sep 17 00:00:00 2001 From: Yuchao Yan Date: Mon, 1 Dec 2025 08:32:59 +0000 Subject: [PATCH 1/3] add test case --- .../generator/test/unittests/requirements.txt | 1 + .../test_model_base_flatten_compatibility.py | 185 ++++++++++++++++++ 2 files changed, 186 insertions(+) create mode 100644 packages/http-client-python/generator/test/unittests/test_model_base_flatten_compatibility.py diff --git a/packages/http-client-python/generator/test/unittests/requirements.txt b/packages/http-client-python/generator/test/unittests/requirements.txt index 9ef7ef8ab35..453f1c4505a 100644 --- a/packages/http-client-python/generator/test/unittests/requirements.txt +++ b/packages/http-client-python/generator/test/unittests/requirements.txt @@ -1,3 +1,4 @@ -r ../dev_requirements.txt -e ../../../generator -e ../unbranded/generated/special-words +-e ../azure/generated/azure-client-generator-core-flatten-property diff --git a/packages/http-client-python/generator/test/unittests/test_model_base_flatten_compatibility.py b/packages/http-client-python/generator/test/unittests/test_model_base_flatten_compatibility.py new file mode 100644 index 00000000000..73a750a3ce2 --- /dev/null +++ b/packages/http-client-python/generator/test/unittests/test_model_base_flatten_compatibility.py @@ -0,0 +1,185 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +import datetime +from typing import ( + Any, + Mapping, + overload, +) + +from specs.azure.clientgenerator.core.flattenproperty._utils.model_base import ( + Model, + rest_field, +) + +from collections.abc import MutableMapping + +JSON = MutableMapping[str, Any] # pylint: disable=unsubscriptable-object + + +class ModelProperty(Model): + """This is a test model.""" + + value: str = rest_field() + """Required.""" + + @overload + def __init__( + self, + *, + value: str, + ) -> 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: + super().__init__(*args, **kwargs) + + +class ChildModel(Model): + """This is the child model to be flattened. + + :ivar description: Required. + :vartype description: str + :ivar age: Required. + :vartype age: int + """ + + description: str = rest_field() + """Required.""" + age: int = rest_field() + """Required.""" + model_property: "ModelProperty" = rest_field(name="modelProperty") + """Required.""" + datetime_default: datetime.datetime = rest_field(name="datetimeDefault") + datetime_rfc3339: datetime.datetime = rest_field(name="datetimeRfc3339", format="rfc3339") + datetime_rfc7231: datetime.datetime = rest_field(name="datetimeRfc7231", format="rfc7231") + datetime_unix_timestamp: datetime.datetime = rest_field(name="datetimeUnixTimestamp", format="unix-timestamp") + + @overload + def __init__( + self, + *, + description: str, + age: int, + model_property: "ModelProperty", + datetime_default: datetime.datetime, + datetime_rfc3339: datetime.datetime, + datetime_rfc7231: datetime.datetime, + datetime_unix_timestamp: datetime.datetime, + ) -> 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: + super().__init__(*args, **kwargs) + + +class FlattenModel(Model): + """This is the model with one level of flattening.""" + + name: str = rest_field() + """Required.""" + properties: "ChildModel" = rest_field() + """Required.""" + + __flattened_items = [ + "description", + "age", + "model_property", + "datetime_default", + "datetime_rfc3339", + "datetime_rfc7231", + "datetime_unix_timestamp", + ] + + @overload + def __init__( + self, + *, + name: str, + properties: "ChildModel", + ) -> 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_initialization(): + model = FlattenModel( + name="test", + description="a description", + age=30, + model_property=ModelProperty(value="test value"), + datetime_default=datetime.datetime(2023, 1, 12, 0, 0, 0, tzinfo=datetime.timezone.utc), + datetime_rfc3339=datetime.datetime(2023, 1, 12, 0, 0, 0, tzinfo=datetime.timezone.utc), + datetime_rfc7231=datetime.datetime(2023, 1, 12, 0, 0, 0, tzinfo=datetime.timezone.utc), + datetime_unix_timestamp=datetime.datetime(2023, 1, 12, 0, 0, 0, tzinfo=datetime.timezone.utc), + ) + + assert model.name == "test" + + assert model.description == "a description" + assert model.properties.description == "a description" + + assert model.age == 30 + assert model.properties.age == 30 + + assert model.model_property.value == "test value" + assert model.properties.model_property == ModelProperty(value="test value") + assert model.properties.model_property.value == "test value" + + assert model.datetime_default == datetime.datetime(2023, 1, 12, 0, 0, 0, tzinfo=datetime.timezone.utc) + assert model.properties.datetime_default == datetime.datetime(2023, 1, 12, 0, 0, 0, tzinfo=datetime.timezone.utc) + assert model.properties["datetimeDefault"] == "2023-01-12T00:00:00Z" + + assert model.datetime_rfc3339 == datetime.datetime(2023, 1, 12, 0, 0, 0, tzinfo=datetime.timezone.utc) + assert model.properties.datetime_rfc3339 == datetime.datetime(2023, 1, 12, 0, 0, 0, tzinfo=datetime.timezone.utc) + assert model.properties["datetimeRfc3339"] == "2023-01-12T00:00:00Z" + + assert model.datetime_rfc7231 == datetime.datetime(2023, 1, 12, 0, 0, 0, tzinfo=datetime.timezone.utc) + assert model.properties.datetime_rfc7231 == datetime.datetime(2023, 1, 12, 0, 0, 0, tzinfo=datetime.timezone.utc) + assert model.properties["datetimeRfc7231"] == "Thu, 12 Jan 2023 00:00:00 GMT" + + assert model.datetime_unix_timestamp == datetime.datetime(2023, 1, 12, 0, 0, 0, tzinfo=datetime.timezone.utc) + assert model.properties.datetime_unix_timestamp == datetime.datetime( + 2023, 1, 12, 0, 0, 0, tzinfo=datetime.timezone.utc + ) + assert model.properties["datetimeUnixTimestamp"] == 1673481600 From 32a7566c2abff08be74f8f8a7997a009a3f6e00b Mon Sep 17 00:00:00 2001 From: Yuchao Yan Date: Mon, 1 Dec 2025 08:33:54 +0000 Subject: [PATCH 2/3] add changelog --- ...-flatten-model-initialization-test-2025-11-1-8-33-43.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .chronus/changes/python-flatten-model-initialization-test-2025-11-1-8-33-43.md diff --git a/.chronus/changes/python-flatten-model-initialization-test-2025-11-1-8-33-43.md b/.chronus/changes/python-flatten-model-initialization-test-2025-11-1-8-33-43.md new file mode 100644 index 00000000000..19855d95798 --- /dev/null +++ b/.chronus/changes/python-flatten-model-initialization-test-2025-11-1-8-33-43.md @@ -0,0 +1,7 @@ +--- +changeKind: internal +packages: + - "@typespec/http-client-python" +--- + +Add test case for flatten model initialization to check compatibility \ No newline at end of file From 0f66963e78fb3056437a4e112067d828835a6889 Mon Sep 17 00:00:00 2001 From: Yuchao Yan Date: Tue, 2 Dec 2025 02:50:23 +0000 Subject: [PATCH 3/3] fix test --- .../mock_api_tests}/test_model_base_flatten_compatibility.py | 4 ---- .../generator/test/unittests/requirements.txt | 1 - 2 files changed, 5 deletions(-) rename packages/http-client-python/generator/test/{unittests => azure/mock_api_tests}/test_model_base_flatten_compatibility.py (98%) diff --git a/packages/http-client-python/generator/test/unittests/test_model_base_flatten_compatibility.py b/packages/http-client-python/generator/test/azure/mock_api_tests/test_model_base_flatten_compatibility.py similarity index 98% rename from packages/http-client-python/generator/test/unittests/test_model_base_flatten_compatibility.py rename to packages/http-client-python/generator/test/azure/mock_api_tests/test_model_base_flatten_compatibility.py index 73a750a3ce2..d0f4d10aa59 100644 --- a/packages/http-client-python/generator/test/unittests/test_model_base_flatten_compatibility.py +++ b/packages/http-client-python/generator/test/azure/mock_api_tests/test_model_base_flatten_compatibility.py @@ -14,10 +14,6 @@ rest_field, ) -from collections.abc import MutableMapping - -JSON = MutableMapping[str, Any] # pylint: disable=unsubscriptable-object - class ModelProperty(Model): """This is a test model.""" diff --git a/packages/http-client-python/generator/test/unittests/requirements.txt b/packages/http-client-python/generator/test/unittests/requirements.txt index 453f1c4505a..9ef7ef8ab35 100644 --- a/packages/http-client-python/generator/test/unittests/requirements.txt +++ b/packages/http-client-python/generator/test/unittests/requirements.txt @@ -1,4 +1,3 @@ -r ../dev_requirements.txt -e ../../../generator -e ../unbranded/generated/special-words --e ../azure/generated/azure-client-generator-core-flatten-property