Skip to content

Commit 4b0a56d

Browse files
author
Yalin Li
authored
Fix de/serialization bugs in FeatureFlagConfigurationSetting (Azure#34852)
1 parent b564177 commit 4b0a56d

12 files changed

+149
-81
lines changed

.vscode/cspell.json

+6
Original file line numberDiff line numberDiff line change
@@ -1688,6 +1688,12 @@
16881688
"kvset"
16891689
]
16901690
},
1691+
{
1692+
"filename": "sdk/appconfiguration/azure-appconfiguration/tests/**",
1693+
"words": [
1694+
"adfsasdfzsd"
1695+
]
1696+
},
16911697
{
16921698
"filename": "sdk/personalizer/test-resources.json",
16931699
"words": [

sdk/appconfiguration/azure-appconfiguration/CHANGELOG.md

+3-7
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
# Release History
22

3-
## 1.6.0b2 (Unreleased)
4-
5-
### Features Added
6-
7-
### Breaking Changes
3+
## 1.6.0b2 (2024-03-21)
84

95
### Bugs Fixed
10-
11-
### Other Changes
6+
- Changed invalid default value `None` to `False` for property `enabled` in `FeatureFlagConfigurationSetting`.
7+
- Fixed the issue that `description`, `display_name` and other customer fields are missing when de/serializing `FeatureFlagConfigurationSetting` objects.
128

139
## 1.6.0b1 (2024-03-14)
1410

sdk/appconfiguration/azure-appconfiguration/assets.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
"AssetsRepo": "Azure/azure-sdk-assets",
33
"AssetsRepoPrefixPath": "python",
44
"TagPrefix": "python/appconfiguration/azure-appconfiguration",
5-
"Tag": "python/appconfiguration/azure-appconfiguration_8137b21bd0"
5+
"Tag": "python/appconfiguration/azure-appconfiguration_27c8f82a12"
66
}

sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_azure_appconfiguration_client.py

+12-14
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import binascii
77
import functools
88
from datetime import datetime
9-
from typing import Any, Dict, List, Mapping, Optional, Union, cast, overload
9+
from typing import Any, Dict, List, Optional, Union, cast, overload
1010
from typing_extensions import Literal
1111
from azure.core import MatchConditions
1212
from azure.core.paging import ItemPaged
@@ -21,7 +21,6 @@
2121
ResourceNotModifiedError,
2222
)
2323
from azure.core.rest import HttpRequest, HttpResponse
24-
from azure.core.utils import CaseInsensitiveDict
2524
from ._azure_appconfiguration_error import ResourceReadOnlyError
2625
from ._azure_appconfiguration_requests import AppConfigRequestsCredentialsPolicy
2726
from ._generated import AzureAppConfiguration
@@ -321,16 +320,15 @@ def add_configuration_setting(self, configuration_setting: ConfigurationSetting,
321320
added_config_setting = client.add_configuration_setting(config_setting)
322321
"""
323322
key_value = configuration_setting._to_generated()
324-
custom_headers: Mapping[str, Any] = CaseInsensitiveDict(kwargs.get("headers"))
325323
error_map = {412: ResourceExistsError}
326324
try:
327325
key_value_added = self._impl.put_key_value(
328326
entity=key_value,
329327
key=key_value.key, # type: ignore
330328
label=key_value.label,
331329
if_none_match="*",
332-
headers=custom_headers,
333330
error_map=error_map,
331+
**kwargs,
334332
)
335333
return ConfigurationSetting._from_generated(key_value_added)
336334
except binascii.Error as exc:
@@ -358,9 +356,9 @@ def set_configuration_setting(
358356
Will use the value from param configuration_setting if not set.
359357
:return: The ConfigurationSetting returned from the service
360358
:rtype: ~azure.appconfiguration.ConfigurationSetting
361-
:raises: :class:`~azure.core.exceptions.HttpResponseError`, \
359+
:raises: :class:`~azure.appconfiguration.ResourceReadOnlyError`, \
360+
:class:`~azure.core.exceptions.HttpResponseError`, \
362361
:class:`~azure.core.exceptions.ClientAuthenticationError`, \
363-
:class:`~azure.core.exceptions.ResourceReadOnlyError`, \
364362
:class:`~azure.core.exceptions.ResourceModifiedError`, \
365363
:class:`~azure.core.exceptions.ResourceNotModifiedError`, \
366364
:class:`~azure.core.exceptions.ResourceNotFoundError`, \
@@ -380,7 +378,6 @@ def set_configuration_setting(
380378
returned_config_setting = client.set_configuration_setting(config_setting)
381379
"""
382380
key_value = configuration_setting._to_generated()
383-
custom_headers: Mapping[str, Any] = CaseInsensitiveDict(kwargs.get("headers"))
384381
error_map: Dict[int, Any] = {409: ResourceReadOnlyError}
385382
if match_condition == MatchConditions.IfNotModified:
386383
error_map.update({412: ResourceModifiedError})
@@ -398,8 +395,8 @@ def set_configuration_setting(
398395
label=key_value.label,
399396
if_match=prep_if_match(configuration_setting.etag, match_condition),
400397
if_none_match=prep_if_none_match(etag or configuration_setting.etag, match_condition),
401-
headers=custom_headers,
402398
error_map=error_map,
399+
**kwargs,
403400
)
404401
return ConfigurationSetting._from_generated(key_value_set)
405402
except binascii.Error as exc:
@@ -414,7 +411,7 @@ def delete_configuration_setting( # pylint:disable=delete-operation-wrong-retur
414411
etag: Optional[str] = None,
415412
match_condition: MatchConditions = MatchConditions.Unconditionally,
416413
**kwargs,
417-
) -> ConfigurationSetting:
414+
) -> Union[None, ConfigurationSetting]:
418415
"""Delete a ConfigurationSetting if it exists
419416
420417
:param key: key used to identify the ConfigurationSetting
@@ -426,9 +423,9 @@ def delete_configuration_setting( # pylint:disable=delete-operation-wrong-retur
426423
:paramtype match_condition: ~azure.core.MatchConditions
427424
:return: The deleted ConfigurationSetting returned from the service, or None if it doesn't exist.
428425
:rtype: ~azure.appconfiguration.ConfigurationSetting
429-
:raises: :class:`~azure.core.exceptions.HttpResponseError`, \
426+
:raises: :class:`~azure.appconfiguration.ResourceReadOnlyError`, \
427+
:class:`~azure.core.exceptions.HttpResponseError`, \
430428
:class:`~azure.core.exceptions.ClientAuthenticationError`, \
431-
:class:`~azure.core.exceptions.ResourceReadOnlyError`, \
432429
:class:`~azure.core.exceptions.ResourceModifiedError`, \
433430
:class:`~azure.core.exceptions.ResourceNotModifiedError`, \
434431
:class:`~azure.core.exceptions.ResourceNotFoundError`, \
@@ -442,7 +439,6 @@ def delete_configuration_setting( # pylint:disable=delete-operation-wrong-retur
442439
key="MyKey", label="MyLabel"
443440
)
444441
"""
445-
custom_headers: Mapping[str, Any] = CaseInsensitiveDict(kwargs.get("headers"))
446442
error_map: Dict[int, Any] = {409: ResourceReadOnlyError}
447443
if match_condition == MatchConditions.IfNotModified:
448444
error_map.update({412: ResourceModifiedError})
@@ -458,10 +454,12 @@ def delete_configuration_setting( # pylint:disable=delete-operation-wrong-retur
458454
key=key,
459455
label=label,
460456
if_match=prep_if_match(etag, match_condition),
461-
headers=custom_headers,
462457
error_map=error_map,
458+
**kwargs,
463459
)
464-
return ConfigurationSetting._from_generated(key_value_deleted) # type: ignore
460+
if key_value_deleted:
461+
return ConfigurationSetting._from_generated(key_value_deleted)
462+
return None
465463
except binascii.Error as exc:
466464
raise binascii.Error("Connection string secret has incorrect padding") from exc
467465

sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_models.py

+29-29
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,6 @@
1919
SnapshotStatus,
2020
)
2121

22-
PolymorphicConfigurationSetting = Union[
23-
"ConfigurationSetting", "SecretReferenceConfigurationSetting", "FeatureFlagConfigurationSetting"
24-
]
25-
2622

2723
class ConfigurationSetting(Model):
2824
"""A setting, defined by a unique combination of a key and label."""
@@ -70,25 +66,23 @@ def __init__(self, **kwargs: Any) -> None:
7066
self.tags = kwargs.get("tags", {})
7167

7268
@classmethod
73-
def _from_generated(cls, key_value: KeyValue) -> PolymorphicConfigurationSetting:
74-
if key_value is None:
75-
return key_value
69+
def _from_generated(cls, key_value: KeyValue) -> "ConfigurationSetting":
70+
# pylint:disable=protected-access
7671
if key_value.content_type is not None:
7772
try:
7873
if key_value.content_type.startswith(
79-
FeatureFlagConfigurationSetting._feature_flag_content_type # pylint:disable=protected-access
74+
FeatureFlagConfigurationSetting._feature_flag_content_type
8075
) and key_value.key.startswith( # type: ignore
81-
FeatureFlagConfigurationSetting._key_prefix # pylint: disable=protected-access
76+
FeatureFlagConfigurationSetting._key_prefix
8277
):
83-
return FeatureFlagConfigurationSetting._from_generated( # pylint: disable=protected-access
84-
key_value
85-
)
78+
config_setting = FeatureFlagConfigurationSetting._from_generated(key_value)
79+
if key_value.value:
80+
config_setting.value = key_value.value
81+
return config_setting
8682
if key_value.content_type.startswith(
87-
SecretReferenceConfigurationSetting._secret_reference_content_type # pylint:disable=protected-access
83+
SecretReferenceConfigurationSetting._secret_reference_content_type
8884
):
89-
return SecretReferenceConfigurationSetting._from_generated( # pylint: disable=protected-access
90-
key_value
91-
)
85+
return SecretReferenceConfigurationSetting._from_generated(key_value)
9286
except (KeyError, AttributeError):
9387
pass
9488

@@ -125,7 +119,7 @@ class FeatureFlagConfigurationSetting(ConfigurationSetting): # pylint: disable=
125119
"""The identity of the configuration setting."""
126120
key: str
127121
"""The key of the configuration setting."""
128-
enabled: Optional[bool]
122+
enabled: bool
129123
"""The value indicating whether the feature flag is enabled. A feature is OFF if enabled is false.
130124
If enabled is true, then the feature is ON if there are no conditions or if all conditions are satisfied."""
131125
filters: Optional[List[Dict[str, Any]]]
@@ -164,7 +158,7 @@ def __init__( # pylint: disable=super-init-not-called
164158
self,
165159
feature_id: str,
166160
*,
167-
enabled: Optional[bool] = None,
161+
enabled: bool = False,
168162
filters: Optional[List[Dict[str, Any]]] = None,
169163
**kwargs: Any,
170164
) -> None:
@@ -173,8 +167,8 @@ def __init__( # pylint: disable=super-init-not-called
173167
:type feature_id: str
174168
:keyword enabled: The value indicating whether the feature flag is enabled.
175169
A feature is OFF if enabled is false. If enabled is true, then the feature is ON
176-
if there are no conditions or if all conditions are satisfied.
177-
:paramtype enabled: bool or None
170+
if there are no conditions or if all conditions are satisfied. Default value of this property is False.
171+
:paramtype enabled: bool
178172
:keyword filters: Filters that must run on the client and be evaluated as true for the feature
179173
to be considered enabled.
180174
:paramtype filters: list[dict[str, Any]] or None
@@ -207,6 +201,8 @@ def value(self) -> str:
207201
temp = json.loads(self._value)
208202
temp["id"] = self.feature_id
209203
temp["enabled"] = self.enabled
204+
temp["display_name"] = self.display_name
205+
temp["description"] = self.description
210206
if "conditions" not in temp.keys():
211207
temp["conditions"] = {}
212208
temp["conditions"]["client_filters"] = self.filters
@@ -221,26 +217,30 @@ def value(self, new_value: str) -> None:
221217
temp = json.loads(new_value)
222218
temp["id"] = self.feature_id
223219
self._value = json.dumps(temp)
224-
self.enabled = temp.get("enabled", None)
220+
self.enabled = temp.get("enabled", False)
221+
self.display_name = temp.get("display_name", None)
222+
self.description = temp.get("description", None)
225223
self.filters = None
226224
conditions = temp.get("conditions", None)
227225
if conditions:
228226
self.filters = conditions.get("client_filters", None)
229227
except (json.JSONDecodeError, ValueError):
230228
self._value = new_value
231-
self.enabled = None
229+
self.enabled = False
232230
self.filters = None
233231

234232
@classmethod
235-
def _from_generated(cls, key_value: KeyValue) -> Union["FeatureFlagConfigurationSetting", ConfigurationSetting]:
236-
if key_value is None:
237-
return key_value
238-
enabled = None
233+
def _from_generated(cls, key_value: KeyValue) -> "FeatureFlagConfigurationSetting":
234+
enabled = False
239235
filters = None
236+
display_name = None
237+
description = None
240238
try:
241239
temp = json.loads(key_value.value) # type: ignore
242240
if isinstance(temp, dict):
243-
enabled = temp.get("enabled")
241+
enabled = temp.get("enabled", False)
242+
display_name = temp.get("display_name")
243+
description = temp.get("description")
244244
if "conditions" in temp.keys():
245245
filters = temp["conditions"].get("client_filters")
246246
except (ValueError, json.JSONDecodeError):
@@ -256,6 +256,8 @@ def _from_generated(cls, key_value: KeyValue) -> Union["FeatureFlagConfiguration
256256
etag=key_value.etag,
257257
enabled=enabled,
258258
filters=filters,
259+
display_name=display_name,
260+
description=description,
259261
)
260262

261263
def _to_generated(self) -> KeyValue:
@@ -349,8 +351,6 @@ def value(self, new_value: str) -> None:
349351

350352
@classmethod
351353
def _from_generated(cls, key_value: KeyValue) -> "SecretReferenceConfigurationSetting":
352-
if key_value is None:
353-
return key_value
354354
secret_uri = None
355355
try:
356356
temp = json.loads(key_value.value) # type: ignore

sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/aio/_azure_appconfiguration_client_async.py

+12-14
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import binascii
77
import functools
88
from datetime import datetime
9-
from typing import Any, Dict, List, Mapping, Optional, Union, cast, overload
9+
from typing import Any, Dict, List, Optional, Union, cast, overload
1010
from typing_extensions import Literal
1111
from azure.core import MatchConditions
1212
from azure.core.async_paging import AsyncItemPaged
@@ -23,7 +23,6 @@
2323
ResourceNotModifiedError,
2424
)
2525
from azure.core.rest import AsyncHttpResponse, HttpRequest
26-
from azure.core.utils import CaseInsensitiveDict
2726
from ._sync_token_async import AsyncSyncTokenPolicy
2827
from .._azure_appconfiguration_error import ResourceReadOnlyError
2928
from .._azure_appconfiguration_requests import AppConfigRequestsCredentialsPolicy
@@ -334,7 +333,6 @@ async def add_configuration_setting(
334333
added_config_setting = await async_client.add_configuration_setting(config_setting)
335334
"""
336335
key_value = configuration_setting._to_generated()
337-
custom_headers: Mapping[str, Any] = CaseInsensitiveDict(kwargs.get("headers"))
338336
error_map = {412: ResourceExistsError}
339337

340338
try:
@@ -343,8 +341,8 @@ async def add_configuration_setting(
343341
key=key_value.key, # type: ignore
344342
label=key_value.label,
345343
if_none_match="*",
346-
headers=custom_headers,
347344
error_map=error_map,
345+
**kwargs,
348346
)
349347
return ConfigurationSetting._from_generated(key_value_added)
350348
except binascii.Error as exc:
@@ -373,9 +371,9 @@ async def set_configuration_setting(
373371
Will use the value from param configuration_setting if not set.
374372
:return: The ConfigurationSetting returned from the service
375373
:rtype: ~azure.appconfiguration.ConfigurationSetting
376-
:raises: :class:`~azure.core.exceptions.HttpResponseError`, \
374+
:raises: :class:`~azure.appconfiguration.ResourceReadOnlyError`, \
375+
:class:`~azure.core.exceptions.HttpResponseError`, \
377376
:class:`~azure.core.exceptions.ClientAuthenticationError`, \
378-
:class:`~azure.core.exceptions.ResourceReadOnlyError`, \
379377
:class:`~azure.core.exceptions.ResourceModifiedError`, \
380378
:class:`~azure.core.exceptions.ResourceNotModifiedError`, \
381379
:class:`~azure.core.exceptions.ResourceNotFoundError`, \
@@ -396,7 +394,6 @@ async def set_configuration_setting(
396394
returned_config_setting = await async_client.set_configuration_setting(config_setting)
397395
"""
398396
key_value = configuration_setting._to_generated()
399-
custom_headers: Mapping[str, Any] = CaseInsensitiveDict(kwargs.get("headers"))
400397
error_map: Dict[int, Any] = {409: ResourceReadOnlyError}
401398
if match_condition == MatchConditions.IfNotModified:
402399
error_map.update({412: ResourceModifiedError})
@@ -414,8 +411,8 @@ async def set_configuration_setting(
414411
label=key_value.label,
415412
if_match=prep_if_match(configuration_setting.etag, match_condition),
416413
if_none_match=prep_if_none_match(etag or configuration_setting.etag, match_condition),
417-
headers=custom_headers,
418414
error_map=error_map,
415+
**kwargs,
419416
)
420417
return ConfigurationSetting._from_generated(key_value_set)
421418
except binascii.Error as exc:
@@ -430,7 +427,7 @@ async def delete_configuration_setting(
430427
etag: Optional[str] = None,
431428
match_condition: MatchConditions = MatchConditions.Unconditionally,
432429
**kwargs,
433-
) -> ConfigurationSetting:
430+
) -> Union[None, ConfigurationSetting]:
434431
"""Delete a ConfigurationSetting if it exists
435432
436433
:param key: key used to identify the ConfigurationSetting
@@ -442,9 +439,9 @@ async def delete_configuration_setting(
442439
:paramtype match_condition: ~azure.core.MatchConditions
443440
:return: The deleted ConfigurationSetting returned from the service, or None if it doesn't exist.
444441
:rtype: ~azure.appconfiguration.ConfigurationSetting
445-
:raises: :class:`~azure.core.exceptions.HttpResponseError`, \
442+
:raises: :class:`~azure.appconfiguration.ResourceReadOnlyError`, \
443+
:class:`~azure.core.exceptions.HttpResponseError`, \
446444
:class:`~azure.core.exceptions.ClientAuthenticationError`, \
447-
:class:`~azure.core.exceptions.ResourceReadOnlyError`, \
448445
:class:`~azure.core.exceptions.ResourceModifiedError`, \
449446
:class:`~azure.core.exceptions.ResourceNotModifiedError`, \
450447
:class:`~azure.core.exceptions.ResourceNotFoundError`, \
@@ -459,7 +456,6 @@ async def delete_configuration_setting(
459456
key="MyKey", label="MyLabel"
460457
)
461458
"""
462-
custom_headers: Mapping[str, Any] = CaseInsensitiveDict(kwargs.get("headers"))
463459
error_map: Dict[int, Any] = {409: ResourceReadOnlyError}
464460
if match_condition == MatchConditions.IfNotModified:
465461
error_map.update({412: ResourceModifiedError})
@@ -475,10 +471,12 @@ async def delete_configuration_setting(
475471
key=key,
476472
label=label,
477473
if_match=prep_if_match(etag, match_condition),
478-
headers=custom_headers,
479474
error_map=error_map,
475+
**kwargs,
480476
)
481-
return ConfigurationSetting._from_generated(key_value_deleted) # type: ignore
477+
if key_value_deleted:
478+
return ConfigurationSetting._from_generated(key_value_deleted)
479+
return None
482480
except binascii.Error as exc:
483481
raise binascii.Error("Connection string secret has incorrect padding") from exc
484482

0 commit comments

Comments
 (0)