Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@

### Bugs Fixed

- Fixes a bug where `feature_flag_selects` could be passed in as `None` which resulted in an exception on load, doing this now results in loading the default feature flags.
- Fixes a bug where `feature_flag_selects` couldn't load snapshots.

### Other Changes

## 2.3.1 (2025-11-13)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "python",
"TagPrefix": "python/appconfiguration/azure-appconfiguration-provider",
"Tag": "python/appconfiguration/azure-appconfiguration-provider_3e69808293"
"Tag": "python/appconfiguration/azure-appconfiguration-provider_25357bbd75"
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,16 +114,6 @@ def process_load_parameters(*args, **kwargs: Any) -> Dict[str, Any]:
if kwargs.get("keyvault_credential") is not None and kwargs.get("secret_resolver") is not None:
raise ValueError("A keyvault credential and secret resolver can't both be configured.")

# Validate feature flag selectors don't use snapshots
feature_flag_selectors = kwargs.get("feature_flag_selectors")
if feature_flag_selectors:
for selector in feature_flag_selectors:
if hasattr(selector, "snapshot_name") and selector.snapshot_name is not None:
raise ValueError(
"snapshot_name cannot be used with feature_flag_selectors. "
"Use snapshot_name with regular selects instead to load feature flags from snapshots."
)

# Determine Key Vault usage
uses_key_vault = (
"keyvault_credential" in kwargs
Expand Down Expand Up @@ -230,7 +220,9 @@ def __init__(self, **kwargs: Any) -> None:
}
self._refresh_timer: _RefreshTimer = _RefreshTimer(**kwargs)
self._feature_flag_enabled = kwargs.pop("feature_flag_enabled", False)
self._feature_flag_selectors = kwargs.pop("feature_flag_selectors", [SettingSelector(key_filter="*")])
self._feature_flag_selectors = kwargs.pop("feature_flag_selectors", None)
if self._feature_flag_selectors is None:
self._feature_flag_selectors = [SettingSelector(key_filter="*")]
self._watched_feature_flags: Dict[Tuple[str, str], Optional[str]] = {}
self._feature_flag_refresh_timer: _RefreshTimer = _RefreshTimer(**kwargs)
self._feature_flag_refresh_enabled = kwargs.pop("feature_flag_refresh_enabled", False)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ def _check_configuration_setting(
def load_configuration_settings(self, selects: List[SettingSelector], **kwargs) -> List[ConfigurationSetting]:
configuration_settings = []
for select in selects:
configurations = []
if select.snapshot_name is not None:
# When loading from a snapshot, ignore key_filter, label_filter, and tag_filters
snapshot = self._client.get_snapshot(select.snapshot_name)
Expand All @@ -162,24 +163,27 @@ def load_configuration_settings(self, selects: List[SettingSelector], **kwargs)
def load_feature_flags(
self, feature_flag_selectors: List[SettingSelector], **kwargs
) -> List[FeatureFlagConfigurationSetting]:
loaded_feature_flags = []
loaded_feature_flags: List[FeatureFlagConfigurationSetting] = []
# Needs to be removed unknown keyword argument for list_configuration_settings
kwargs.pop("sentinel_keys", None)
for select in feature_flag_selectors:
# Handle None key_filter by converting to empty string
key_filter = select.key_filter if select.key_filter is not None else ""
feature_flags = self._client.list_configuration_settings(
key_filter=FEATURE_FLAG_PREFIX + key_filter,
label_filter=select.label_filter,
tags_filter=select.tag_filters,
**kwargs,
)
for feature_flag in feature_flags:
if not isinstance(feature_flag, FeatureFlagConfigurationSetting):
# If the feature flag is not a FeatureFlagConfigurationSetting, it means it was selected by
# mistake, so we should ignore it.
continue
loaded_feature_flags.append(feature_flag)
feature_flags = []
if select.snapshot_name is not None:
# When loading from a snapshot, ignore key_filter, label_filter, and tag_filters
snapshot = self._client.get_snapshot(select.snapshot_name)
if snapshot.composition_type != SnapshotComposition.KEY:
raise ValueError(f"Composition type for '{select.snapshot_name}' must be 'key'.")
feature_flags = self._client.list_configuration_settings(snapshot_name=select.snapshot_name, **kwargs)
else:
# Handle None key_filter by converting to empty string
key_filter = select.key_filter if select.key_filter is not None else ""
feature_flags = self._client.list_configuration_settings(
key_filter=FEATURE_FLAG_PREFIX + key_filter,
label_filter=select.label_filter,
tags_filter=select.tag_filters,
**kwargs,
)
loaded_feature_flags.extend(ff for ff in feature_flags if isinstance(ff, FeatureFlagConfigurationSetting))

return loaded_feature_flags

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from typing import Tuple, Union, Dict, List, Optional, Mapping, TYPE_CHECKING
from typing_extensions import Self
from azure.core import MatchConditions
from azure.core.async_paging import AsyncItemPaged
from azure.core.tracing.decorator import distributed_trace
from azure.core.exceptions import HttpResponseError
from azure.appconfiguration import ( # type:ignore # pylint:disable=no-name-in-module
Expand Down Expand Up @@ -164,24 +165,29 @@ async def load_configuration_settings(self, selects: List[SettingSelector], **kw
async def load_feature_flags(
self, feature_flag_selectors: List[SettingSelector], **kwargs
) -> List[FeatureFlagConfigurationSetting]:
loaded_feature_flags = []
loaded_feature_flags: List[FeatureFlagConfigurationSetting] = []
# Needs to be removed unknown keyword argument for list_configuration_settings
kwargs.pop("sentinel_keys", None)
for select in feature_flag_selectors:
# Handle None key_filter by converting to empty string
key_filter = select.key_filter if select.key_filter is not None else ""
feature_flags = self._client.list_configuration_settings(
key_filter=FEATURE_FLAG_PREFIX + key_filter,
label_filter=select.label_filter,
tags_filter=select.tag_filters,
**kwargs,
feature_flags: AsyncItemPaged[ConfigurationSetting]
if select.snapshot_name is not None:
# When loading from a snapshot, ignore key_filter, label_filter, and tag_filters
snapshot = await self._client.get_snapshot(select.snapshot_name)
if snapshot.composition_type != SnapshotComposition.KEY:
raise ValueError(f"Composition type for '{select.snapshot_name}' must be 'key'.")
feature_flags = self._client.list_configuration_settings(snapshot_name=select.snapshot_name, **kwargs)
else:
# Handle None key_filter by converting to empty string
key_filter = select.key_filter if select.key_filter is not None else ""
feature_flags = self._client.list_configuration_settings(
key_filter=FEATURE_FLAG_PREFIX + key_filter,
label_filter=select.label_filter,
tags_filter=select.tag_filters,
**kwargs,
)
loaded_feature_flags.extend(
[ff async for ff in feature_flags if isinstance(ff, FeatureFlagConfigurationSetting)]
)
async for feature_flag in feature_flags:
if not isinstance(feature_flag, FeatureFlagConfigurationSetting):
# If the feature flag is not a FeatureFlagConfigurationSetting, it means it was selected by
# mistake, so we should ignore it.
continue
loaded_feature_flags.append(feature_flag)

return loaded_feature_flags

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,15 @@

# Connection to Azure App Configuration using SettingSelector
selects = [SettingSelector(key_filter="message*")]
config = load(endpoint=endpoint, credential=credential, selects=selects, **kwargs)
config = load(
endpoint=endpoint,
credential=credential,
selects=selects,
feature_flag_enabled=True,
feature_flag_selectors=None,
**kwargs
)

print("message found: " + str("message" in config))
print("test.message found: " + str("test.message" in config))
print("feature_flag_enabled found: " + str(config.get("feature_management")))
Loading