From 6db8216609eb438a73c7b725988339844a679bb6 Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Fri, 5 Apr 2024 11:23:04 -0700 Subject: [PATCH 01/21] Adding Telemetry --- .../provider/_azureappconfigurationprovider.py | 7 ++++++- .../aio/_azureappconfigurationproviderasync.py | 7 ++++++- .../samples/connection_string_sample.py | 4 +++- .../tests/test_provider_feature_management.py | 4 ++-- .../azure-appconfiguration-provider/tests/testcase.py | 10 ++++++++-- 5 files changed, 25 insertions(+), 7 deletions(-) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py index 7d433a20d468..63c5bc7b4c46 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py @@ -634,7 +634,12 @@ def _load_feature_flags(self, **kwargs): key_filter=FEATURE_FLAG_PREFIX + select.key_filter, label_filter=select.label_filter, **kwargs ) for feature_flag in feature_flags: - loaded_feature_flags.append(json.loads(feature_flag.value)) + feature_flag_value = json.loads(feature_flag.value) + if "telemetry" in feature_flag_value: + if "metadata" not in feature_flag_value["telemetry"]: + feature_flag_value["telemetry"]["metadata"] = {} + feature_flag_value["telemetry"]["metadata"] ["etag"] = feature_flag.etag + loaded_feature_flags.append(feature_flag_value) if self._feature_flag_refresh_enabled: feature_flag_sentinel_keys[(feature_flag.key, feature_flag.label)] = feature_flag.etag diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py index 101eeb636880..008e4ff0335f 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py @@ -528,7 +528,12 @@ async def _load_feature_flags(self, **kwargs): key_filter=FEATURE_FLAG_PREFIX + select.key_filter, label_filter=select.label_filter, **kwargs ) async for feature_flag in feature_flags: - loaded_feature_flags.append(json.loads(feature_flag.value)) + feature_flag_value = json.loads(feature_flag.value) + if "telemetry" in feature_flag_value: + if "metadata" not in feature_flag_value["telemetry"]: + feature_flag_value["telemetry"]["metadata"] = {} + feature_flag_value["telemetry"]["metadata"] ["etag"] = feature_flag.etag + loaded_feature_flags.append(feature_flag_value) if self._feature_flag_refresh_enabled: feature_flag_sentinel_keys[(feature_flag.key, feature_flag.label)] = feature_flag.etag diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/samples/connection_string_sample.py b/sdk/appconfiguration/azure-appconfiguration-provider/samples/connection_string_sample.py index acb9e85152b8..123456940775 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/samples/connection_string_sample.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/samples/connection_string_sample.py @@ -12,7 +12,9 @@ connection_string = os.environ["APPCONFIGURATION_CONNECTION_STRING"] # Connecting to Azure App Configuration using connection string -config = load(connection_string=connection_string, **kwargs) +config = load(connection_string=connection_string, feature_flag_enabled=True, **kwargs) + +breakpoint() print(config["message"]) print(config["my_json"]["key"]) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_provider_feature_management.py b/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_provider_feature_management.py index 069fd6076f18..c1ef1e8c7b40 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_provider_feature_management.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_provider_feature_management.py @@ -7,8 +7,7 @@ from azure.appconfiguration import AzureAppConfigurationClient from devtools_testutils import recorded_by_proxy from preparers import app_config_decorator -from testcase import AppConfigTestCase, setup_configs, has_feature_flag -from unittest.mock import patch +from testcase import AppConfigTestCase, setup_configs, has_feature_flag, get_feature_flag from test_constants import FEATURE_MANAGEMENT_KEY @@ -25,6 +24,7 @@ def test_load_only_feature_flags(self, appconfiguration_connection_string): assert len(client.keys()) == 1 assert FEATURE_MANAGEMENT_KEY in client assert has_feature_flag(client, "Alpha") + assert "telemetry" not in get_feature_flag(client, "Alpha") # method: load @recorded_by_proxy diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/tests/testcase.py b/sdk/appconfiguration/azure-appconfiguration-provider/tests/testcase.py index a0b92d2faf34..d44eab01a90d 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/tests/testcase.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/tests/testcase.py @@ -185,8 +185,14 @@ def create_feature_flag_config_setting(key, label, enabled): ) -def has_feature_flag(client, feature_id, enabled=False): +def get_feature_flag(client, feature_id): for feature_flag in client[FEATURE_MANAGEMENT_KEY][FEATURE_FLAG_KEY]: if feature_flag["id"] == feature_id: - return feature_flag["enabled"] == enabled + return feature_flag + return None + +def has_feature_flag(client, feature_id, enabled=False): + feature_flag = get_feature_flag(client, feature_id) + if feature_flag: + return feature_flag["enabled"] == enabled return False From acc62d9a52733f8506171a864e2eeea3d16b71cb Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Mon, 15 Apr 2024 09:28:51 -0700 Subject: [PATCH 02/21] Telemetry Support --- .../_azureappconfigurationprovider.py | 19 +++++++++++++++++++ .../dev_requirements.txt | 2 +- .../azure-appconfiguration-provider/setup.py | 2 +- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py index 63c5bc7b4c46..ea9a61f1c90d 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py @@ -8,6 +8,8 @@ import random import time import datetime +import hashlib +import base64 from threading import Lock import logging from typing import ( @@ -623,6 +625,17 @@ def _load_configuration_settings(self, **kwargs): if (config.key, config.label) in self._refresh_on: sentinel_keys[(config.key, config.label)] = config.etag return configuration_settings, sentinel_keys + + @staticmethod + def _calculate_feature_id(key, label): + basic_value = f"{key}\n" + if label: + basic_value = basic_value + f"\n{label}" + feature_flag_id_hash_bytes = hashlib.sha256(basic_value.encode()).digest() + encoded_flag = base64.b64encode(feature_flag_id_hash_bytes) + encoded_flag = encoded_flag.replace(b'+', b'-').replace(b'/', b'_') + return encoded_flag[:encoded_flag.find(b'=')] + def _load_feature_flags(self, **kwargs): feature_flag_sentinel_keys = {} @@ -630,6 +643,7 @@ def _load_feature_flags(self, **kwargs): # Needs to be removed unknown keyword argument for list_configuration_settings kwargs.pop("sentinel_keys", None) for select in self._feature_flag_selectors: + endpoint = self._client._impl._config.endpoint feature_flags = self._client.list_configuration_settings( key_filter=FEATURE_FLAG_PREFIX + select.key_filter, label_filter=select.label_filter, **kwargs ) @@ -639,6 +653,11 @@ def _load_feature_flags(self, **kwargs): if "metadata" not in feature_flag_value["telemetry"]: feature_flag_value["telemetry"]["metadata"] = {} feature_flag_value["telemetry"]["metadata"] ["etag"] = feature_flag.etag + feature_flag_reference = f"{endpoint}/kv/{feature_flag.key}" + if feature_flag.label: + feature_flag_reference += f"?label={feature_flag.label}" + feature_flag_value["telemetry"]["metadata"]["feature_flag_reference"] = feature_flag_reference + feature_flag_value["telemetry"]["metadata"]["feature_flag_id"] = self._calculate_feature_id(feature_flag.key, feature_flag.label) loaded_feature_flags.append(feature_flag_value) if self._feature_flag_refresh_enabled: diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/dev_requirements.txt b/sdk/appconfiguration/azure-appconfiguration-provider/dev_requirements.txt index 4bd6de19acc8..01a81e7e6cd1 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/dev_requirements.txt +++ b/sdk/appconfiguration/azure-appconfiguration-provider/dev_requirements.txt @@ -1,5 +1,5 @@ -e ../../core/azure-core -../../appconfiguration/azure-appconfiguration +azure-appconfiguration==1.6.0b2 -e ../../identity/azure-identity -e ../../keyvault/azure-keyvault-secrets aiohttp>=3.0 diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/setup.py b/sdk/appconfiguration/azure-appconfiguration-provider/setup.py index 49ec53b9952c..06aee39160b8 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/setup.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/setup.py @@ -74,7 +74,7 @@ install_requires=[ "msrest>=0.6.21", "azure-core>=1.28.0", - "azure-appconfiguration>=1.5.0", + "azure-appconfiguration==1.6.0b2", "azure-keyvault-secrets>=4.3.0", ], ) From c3333115d71be366e4cde2581b5c68b3d5b4f490 Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Wed, 17 Apr 2024 15:19:37 -0700 Subject: [PATCH 03/21] fixing formatting --- .../_azureappconfigurationprovider.py | 23 ++++++++++--------- .../_azureappconfigurationproviderasync.py | 2 +- .../tests/testcase.py | 1 + 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py index ea9a61f1c90d..b05510644c95 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py @@ -78,7 +78,7 @@ def load( refresh_interval: int = 30, on_refresh_success: Optional[Callable] = None, on_refresh_error: Optional[Callable[[Exception], None]] = None, - **kwargs + **kwargs, ) -> "AzureAppConfigurationProvider": """ Loads configuration settings from Azure App Configuration into a Python application. @@ -135,7 +135,7 @@ def load( refresh_interval: int = 30, on_refresh_success: Optional[Callable] = None, on_refresh_error: Optional[Callable[[Exception], None]] = None, - **kwargs + **kwargs, ) -> "AzureAppConfigurationProvider": """ Loads configuration settings from Azure App Configuration into a Python application. @@ -307,7 +307,7 @@ def _buildprovider( user_agent=user_agent, retry_total=retry_total, retry_backoff_max=retry_backoff_max, - **kwargs + **kwargs, ) return provider if endpoint is not None and credential is not None: @@ -317,7 +317,7 @@ def _buildprovider( user_agent=user_agent, retry_total=retry_total, retry_backoff_max=retry_backoff_max, - **kwargs + **kwargs, ) return provider raise ValueError("Please pass either endpoint and credential, or a connection string.") @@ -625,7 +625,7 @@ def _load_configuration_settings(self, **kwargs): if (config.key, config.label) in self._refresh_on: sentinel_keys[(config.key, config.label)] = config.etag return configuration_settings, sentinel_keys - + @staticmethod def _calculate_feature_id(key, label): basic_value = f"{key}\n" @@ -633,9 +633,8 @@ def _calculate_feature_id(key, label): basic_value = basic_value + f"\n{label}" feature_flag_id_hash_bytes = hashlib.sha256(basic_value.encode()).digest() encoded_flag = base64.b64encode(feature_flag_id_hash_bytes) - encoded_flag = encoded_flag.replace(b'+', b'-').replace(b'/', b'_') - return encoded_flag[:encoded_flag.find(b'=')] - + encoded_flag = encoded_flag.replace(b"+", b"-").replace(b"/", b"_") + return encoded_flag[: encoded_flag.find(b"=")] def _load_feature_flags(self, **kwargs): feature_flag_sentinel_keys = {} @@ -652,12 +651,14 @@ def _load_feature_flags(self, **kwargs): if "telemetry" in feature_flag_value: if "metadata" not in feature_flag_value["telemetry"]: feature_flag_value["telemetry"]["metadata"] = {} - feature_flag_value["telemetry"]["metadata"] ["etag"] = feature_flag.etag - feature_flag_reference = f"{endpoint}/kv/{feature_flag.key}" + feature_flag_value["telemetry"]["metadata"]["etag"] = feature_flag.etag + feature_flag_reference = f"{endpoint}/kv/{feature_flag.key}" if feature_flag.label: feature_flag_reference += f"?label={feature_flag.label}" feature_flag_value["telemetry"]["metadata"]["feature_flag_reference"] = feature_flag_reference - feature_flag_value["telemetry"]["metadata"]["feature_flag_id"] = self._calculate_feature_id(feature_flag.key, feature_flag.label) + feature_flag_value["telemetry"]["metadata"]["feature_flag_id"] = self._calculate_feature_id( + feature_flag.key, feature_flag.label + ) loaded_feature_flags.append(feature_flag_value) if self._feature_flag_refresh_enabled: diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py index 008e4ff0335f..7ae5185cb36b 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py @@ -532,7 +532,7 @@ async def _load_feature_flags(self, **kwargs): if "telemetry" in feature_flag_value: if "metadata" not in feature_flag_value["telemetry"]: feature_flag_value["telemetry"]["metadata"] = {} - feature_flag_value["telemetry"]["metadata"] ["etag"] = feature_flag.etag + feature_flag_value["telemetry"]["metadata"]["etag"] = feature_flag.etag loaded_feature_flags.append(feature_flag_value) if self._feature_flag_refresh_enabled: diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/tests/testcase.py b/sdk/appconfiguration/azure-appconfiguration-provider/tests/testcase.py index d44eab01a90d..3f0d3d7f78a7 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/tests/testcase.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/tests/testcase.py @@ -191,6 +191,7 @@ def get_feature_flag(client, feature_id): return feature_flag return None + def has_feature_flag(client, feature_id, enabled=False): feature_flag = get_feature_flag(client, feature_id) if feature_flag: From 2893693c5b599a004402c59d6140d779d1d8bab1 Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Wed, 24 Apr 2024 10:40:44 -0700 Subject: [PATCH 04/21] Update _azureappconfigurationprovider.py --- .../appconfiguration/provider/_azureappconfigurationprovider.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py index b05510644c95..e526d34097df 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py @@ -642,7 +642,7 @@ def _load_feature_flags(self, **kwargs): # Needs to be removed unknown keyword argument for list_configuration_settings kwargs.pop("sentinel_keys", None) for select in self._feature_flag_selectors: - endpoint = self._client._impl._config.endpoint + endpoint = self._client._impl._config.endpoint # pylint: disable=protected-access feature_flags = self._client.list_configuration_settings( key_filter=FEATURE_FLAG_PREFIX + select.key_filter, label_filter=select.label_filter, **kwargs ) From 6ade779b49341b5fce4f6d567e320b6e341a6def Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Wed, 24 Apr 2024 10:45:05 -0700 Subject: [PATCH 05/21] Update _azureappconfigurationproviderasync.py --- .../_azureappconfigurationproviderasync.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py index 7ae5185cb36b..f87656264ae2 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py @@ -4,6 +4,8 @@ # license information. # ------------------------------------------------------------------------- import json +import hashlib +import base64 from threading import Lock import datetime import logging @@ -518,12 +520,23 @@ async def _load_configuration_settings(self, **kwargs): sentinel_keys[(config.key, config.label)] = config.etag return configuration_settings, sentinel_keys + @staticmethod + def _calculate_feature_id(key, label): + basic_value = f"{key}\n" + if label: + basic_value = basic_value + f"\n{label}" + feature_flag_id_hash_bytes = hashlib.sha256(basic_value.encode()).digest() + encoded_flag = base64.b64encode(feature_flag_id_hash_bytes) + encoded_flag = encoded_flag.replace(b"+", b"-").replace(b"/", b"_") + return encoded_flag[: encoded_flag.find(b"=")] + async def _load_feature_flags(self, **kwargs): feature_flag_sentinel_keys = {} loaded_feature_flags = [] # Needs to be removed unknown keyword argument for list_configuration_settings kwargs.pop("sentinel_keys", None) for select in self._feature_flag_selectors: + endpoint = self._client._impl._config.endpoint # pylint: disable=protected-access feature_flags = self._client.list_configuration_settings( key_filter=FEATURE_FLAG_PREFIX + select.key_filter, label_filter=select.label_filter, **kwargs ) @@ -533,6 +546,13 @@ async def _load_feature_flags(self, **kwargs): if "metadata" not in feature_flag_value["telemetry"]: feature_flag_value["telemetry"]["metadata"] = {} feature_flag_value["telemetry"]["metadata"]["etag"] = feature_flag.etag + feature_flag_reference = f"{endpoint}/kv/{feature_flag.key}" + if feature_flag.label: + feature_flag_reference += f"?label={feature_flag.label}" + feature_flag_value["telemetry"]["metadata"]["feature_flag_reference"] = feature_flag_reference + feature_flag_value["telemetry"]["metadata"]["feature_flag_id"] = self._calculate_feature_id( + feature_flag.key, feature_flag.label + ) loaded_feature_flags.append(feature_flag_value) if self._feature_flag_refresh_enabled: From 3ec51ec4b8332dfb97bad5a6e4bf078f32050c9d Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Wed, 24 Apr 2024 13:22:13 -0700 Subject: [PATCH 06/21] formatting --- .../provider/_azureappconfigurationprovider.py | 2 +- .../aio/_azureappconfigurationproviderasync.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py index e526d34097df..d9c0619a45b2 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py @@ -642,7 +642,7 @@ def _load_feature_flags(self, **kwargs): # Needs to be removed unknown keyword argument for list_configuration_settings kwargs.pop("sentinel_keys", None) for select in self._feature_flag_selectors: - endpoint = self._client._impl._config.endpoint # pylint: disable=protected-access + endpoint = self._client._impl._config.endpoint # pylint: disable=protected-access feature_flags = self._client.list_configuration_settings( key_filter=FEATURE_FLAG_PREFIX + select.key_filter, label_filter=select.label_filter, **kwargs ) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py index f87656264ae2..3dfdfe28053d 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py @@ -77,7 +77,7 @@ async def load( refresh_interval: int = 30, on_refresh_success: Optional[Callable] = None, on_refresh_error: Optional[Callable[[Exception], Awaitable[None]]] = None, - **kwargs + **kwargs, ) -> "AzureAppConfigurationProvider": """ Loads configuration settings from Azure App Configuration into a Python application. @@ -134,7 +134,7 @@ async def load( refresh_interval: int = 30, on_refresh_success: Optional[Callable] = None, on_refresh_error: Optional[Callable[[Exception], Awaitable[None]]] = None, - **kwargs + **kwargs, ) -> "AzureAppConfigurationProvider": """ Loads configuration settings from Azure App Configuration into a Python application. @@ -269,7 +269,7 @@ def _buildprovider( user_agent=user_agent, retry_total=retry_total, retry_backoff_max=retry_backoff_max, - **kwargs + **kwargs, ) return provider if endpoint is not None and credential is not None: @@ -279,7 +279,7 @@ def _buildprovider( user_agent=user_agent, retry_total=retry_total, retry_backoff_max=retry_backoff_max, - **kwargs + **kwargs, ) return provider raise ValueError("Please pass either endpoint and credential, or a connection string.") @@ -536,7 +536,7 @@ async def _load_feature_flags(self, **kwargs): # Needs to be removed unknown keyword argument for list_configuration_settings kwargs.pop("sentinel_keys", None) for select in self._feature_flag_selectors: - endpoint = self._client._impl._config.endpoint # pylint: disable=protected-access + endpoint = self._client._impl._config.endpoint # pylint: disable=protected-access feature_flags = self._client.list_configuration_settings( key_filter=FEATURE_FLAG_PREFIX + select.key_filter, label_filter=select.label_filter, **kwargs ) From d1c5a1723a75b4c90a38182393947f2554389002 Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Wed, 24 Apr 2024 14:49:40 -0700 Subject: [PATCH 07/21] changing doc style due to pylint-next --- .../provider/_azureappconfigurationprovider.py | 12 ++++-------- .../aio/_azureappconfigurationproviderasync.py | 12 ++++-------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py index d9c0619a45b2..770c03a6c22f 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py @@ -559,14 +559,10 @@ def _check_configuration_setting( """ Checks if the configuration setting have been updated since the last refresh. - :keyword key: key to check for chances - :paramtype key: str - :keyword label: label to check for changes - :paramtype label: str - :keyword etag: etag to check for changes - :paramtype etag: str - :keyword headers: headers to use for the request - :paramtype headers: Mapping[str, str] + :param str key: key to check for chances + :param str label: label to check for changes + :param str etag: etag to check for changes + :param Mapping[str, str] headers: headers to use for the request :return: A tuple with the first item being true/false if a change is detected. The second item is the updated value if a change was detected. :rtype: Tuple[bool, Union[ConfigurationSetting, None]] diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py index 3dfdfe28053d..12e52b620727 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py @@ -453,14 +453,10 @@ async def _check_configuration_setting( """ Checks if the configuration setting have been updated since the last refresh. - :keyword key: key to check for chances - :paramtype key: str - :keyword label: label to check for changes - :paramtype label: str - :keyword etag: etag to check for changes - :paramtype etag: str - :keyword headers: headers to use for the request - :paramtype headers: Mapping[str, str] + :param str key: key to check for chances + :param str label: label to check for changes + :param str etag: etag to check for changes + :param Mapping[str, str] headers: headers to use for the request :return: A tuple with the first item being true/false if a change is detected. The second item is the updated value if a change was detected. :rtype: Tuple[bool, Union[ConfigurationSetting, None]] From 5595657766a300d99a18da3aa8ded54b82ad6d2a Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Wed, 24 Apr 2024 16:39:42 -0700 Subject: [PATCH 08/21] fixing kwargs docs --- .../_azureappconfigurationprovider.py | 119 ++++++++---------- .../_azureappconfigurationproviderasync.py | 10 +- 2 files changed, 62 insertions(+), 67 deletions(-) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py index 770c03a6c22f..dd216c96a86f 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py @@ -64,7 +64,7 @@ @overload -def load( +def load( # pylint: disable=docstring-keyword-should-match-keyword-only endpoint: str, credential: "TokenCredential", *, @@ -78,51 +78,45 @@ def load( refresh_interval: int = 30, on_refresh_success: Optional[Callable] = None, on_refresh_error: Optional[Callable[[Exception], None]] = None, + feature_flag_enabled: bool = False, + feature_flag_selectors: Optional[List[SettingSelector]] = None, + feature_flag_refresh_enabled: bool = False, **kwargs, ) -> "AzureAppConfigurationProvider": """ Loads configuration settings from Azure App Configuration into a Python application. :param str endpoint: Endpoint for App Configuration resource. - :param credential: Credential for App Configuration resource. - :type credential: ~azure.core.credentials.TokenCredential - :keyword selects: List of setting selectors to filter configuration settings - :paramtype selects: Optional[List[~azure.appconfiguration.provider.SettingSelector]] - :keyword trim_prefixes: List of prefixes to trim from configuration keys - :paramtype trim_prefixes: Optional[List[str]] - :keyword keyvault_credential: A credential for authenticating with the key vault. This is optional if - keyvault_client_configs is provided. - :paramtype keyvault_credential: ~azure.core.credentials.TokenCredential - :keyword keyvault_client_configs: A Mapping of SecretClient endpoints to client configurations from - azure-keyvault-secrets. This is optional if keyvault_credential is provided. If a credential isn't provided a - credential will need to be in each set for each. - :paramtype keyvault_client_configs: Mapping[str, Mapping] - :keyword secret_resolver: A function that takes a URI and returns a value. - :paramtype secret_resolver: Callable[[str], str] - :keyword refresh_on: One or more settings whose modification will trigger a full refresh after a fixed interval. - This should be a list of Key-Label pairs for specific settings (filters and wildcards are not supported). - :paramtype refresh_on: List[Tuple[str, str]] + :param ~azure.core.credentials.TokenCredential credential: Credential for App Configuration resource. + :keyword Optional[List[~azure.appconfiguration.provider.SettingSelector]] selects: List of setting selectors to + filter configuration settings + :keyword Optional[List[str]] trim_prefixes: List of prefixes to trim from configuration keys + :keyword ~azure.core.credentials.TokenCredential keyvault_credential: A credential for authenticating with the key + vault. This is optional if keyvault_client_configs is provided. + :keyword Mapping[str, Mapping] keyvault_client_configs: A Mapping of SecretClient endpoints to client + configurations from azure-keyvault-secrets. This is optional if keyvault_credential is provided. If a credential + isn't provided a credential will need to be in each set for each. + :keyword Callable[[str], str] secret_resolver: A function that takes a URI and returns a value. + :keyword List[Tuple[str, str]] refresh_on: One or more settings whose modification will trigger a full refresh + after a fixed interval. This should be a list of Key-Label pairs for specific settings (filters and wildcards are + not supported). :keyword int refresh_interval: The minimum time in seconds between when a call to `refresh` will actually trigger a - service call to update the settings. Default value is 30 seconds. - :paramtype on_refresh_success: Optional[Callable] - :keyword on_refresh_success: Optional callback to be invoked when a change is found and a successful refresh has - happened. - :paramtype on_refresh_error: Optional[Callable[[Exception], None]] - :keyword on_refresh_error: Optional callback to be invoked when an error occurs while refreshing settings. If not - specified, errors will be raised. - :paramtype feature_flag_enabled: bool - :keyword feature_flag_enabled: Optional flag to enable or disable the loading of feature flags. Default is False. - :paramtype feature_flag_selectors: List[SettingSelector] - :keyword feature_flag_selectors: Optional list of selectors to filter feature flags. By default will load all - feature flags without a label. - :paramtype feature_flag_refresh_enabled: bool - :keyword feature_flag_refresh_enabled: Optional flag to enable or disable the refresh of feature flags. Default is - False. + service call to update the settings. Default value is 30 seconds. + :keyword Optional[Callable] on_refresh_success: Optional callback to be invoked when a change is found and a + successful refresh has happened. + :keyword Optional[Callable[[Exception], None]] on_refresh_error: Optional callback to be invoked when an error + occurs while refreshing settings. If not specified, errors will be raised. + :keyword bool feature_flag_enabled: Optional flag to enable or disable the loading of feature flags. Default is + False. + :keyword List[SettingSelector] feature_flag_selectors: Optional list of selectors to filter feature flags. By + default will load all feature flags without a label. + :keyword bool feature_flag_refresh_enabled: Optional flag to enable or disable the refresh of feature flags. + Default is False. """ @overload -def load( +def load( # pylint: disable=docstring-keyword-should-match-keyword-only *, connection_string: str, selects: Optional[List[SettingSelector]] = None, @@ -135,44 +129,39 @@ def load( refresh_interval: int = 30, on_refresh_success: Optional[Callable] = None, on_refresh_error: Optional[Callable[[Exception], None]] = None, + feature_flag_enabled: bool = False, + feature_flag_selectors: Optional[List[SettingSelector]] = None, + feature_flag_refresh_enabled: bool = False, **kwargs, ) -> "AzureAppConfigurationProvider": """ Loads configuration settings from Azure App Configuration into a Python application. :keyword str connection_string: Connection string for App Configuration resource. - :keyword selects: List of setting selectors to filter configuration settings - :paramtype selects: Optional[List[~azure.appconfiguration.provider.SettingSelector]] - :keyword trim_prefixes: List of prefixes to trim from configuration keys - :paramtype trim_prefixes: Optional[List[str]] - :keyword keyvault_credential: A credential for authenticating with the key vault. This is optional if - keyvault_client_configs is provided. - :paramtype keyvault_credential: ~azure.core.credentials.TokenCredential - :keyword keyvault_client_configs: A Mapping of SecretClient endpoints to client configurations from - azure-keyvault-secrets. This is optional if keyvault_credential is provided. If a credential isn't provided a - credential will need to be in each set for each. - :paramtype keyvault_client_configs: Mapping[str, Mapping] - :keyword secret_resolver: A function that takes a URI and returns a value. - :paramtype secret_resolver: Callable[[str], str] - :keyword refresh_on: One or more settings whose modification will trigger a full refresh after a fixed interval. - This should be a list of Key-Label pairs for specific settings (filters and wildcards are not supported). - :paramtype refresh_on: List[Tuple[str, str]] + :keyword Optional[List[~azure.appconfiguration.provider.SettingSelector]] selects: List of setting selectors to + filter configuration settings + :keyword trim_prefixes: Optional[List[str]] trim_prefixes: List of prefixes to trim from configuration keys + :keyword ~azure.core.credentials.TokenCredential keyvault_credential: A credential for authenticating with the key + vault. This is optional if keyvault_client_configs is provided. + :keyword Mapping[str, Mapping] keyvault_client_configs: A Mapping of SecretClient endpoints to client + configurations from azure-keyvault-secrets. This is optional if keyvault_credential is provided. If a credential + isn't provided a credential will need to be in each set for each. + :keyword Callable[[str], str] secret_resolver: A function that takes a URI and returns a value. + :keyword List[Tuple[str, str]] refresh_on: One or more settings whose modification will trigger a full refresh + after a fixed interval. This should be a list of Key-Label pairs for specific settings (filters and wildcards are + not supported). :keyword int refresh_interval: The minimum time in seconds between when a call to `refresh` will actually trigger a service call to update the settings. Default value is 30 seconds. - :paramtype on_refresh_success: Optional[Callable] - :keyword on_refresh_success: Optional callback to be invoked when a change is found and a successful refresh has - happened. - :paramtype on_refresh_error: Optional[Callable[[Exception], None]] - :keyword on_refresh_error: Optional callback to be invoked when an error occurs while refreshing settings. If not - specified, errors will be raised. - :paramtype feature_flag_enabled: bool - :keyword feature_flag_enabled: Optional flag to enable or disable the loading of feature flags. Default is False. - :paramtype feature_flag_selectors: List[SettingSelector] - :keyword feature_flag_selectors: Optional list of selectors to filter feature flags. By default will load all - feature flags without a label. - :paramtype feature_flag_refresh_enabled: bool - :keyword feature_flag_refresh_enabled: Optional flag to enable or disable the refresh of feature flags. Default is - False. + :keyword Optional[Callable] on_refresh_success: Optional callback to be invoked when a change is found and a + successful refresh has happened. + :keyword Optional[Callable[[Exception], None]] on_refresh_error: Optional callback to be invoked when an error + occurs while refreshing settings. If not specified, errors will be raised. + :keyword bool feature_flag_enabled: Optional flag to enable or disable the loading of feature flags. Default is + False. + :keyword List[SettingSelector] feature_flag_selectors: Optional list of selectors to filter feature flags. By + default will load all feature flags without a label. + :keyword bool feature_flag_refresh_enabled: Optional flag to enable or disable the refresh of feature flags. + Default is False. """ diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py index 12e52b620727..b4323fed5a64 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py @@ -63,7 +63,7 @@ @overload -async def load( +async def load( # pylint: disable=docstring-keyword-should-match-keyword-only endpoint: str, credential: "AsyncTokenCredential", *, @@ -77,6 +77,9 @@ async def load( refresh_interval: int = 30, on_refresh_success: Optional[Callable] = None, on_refresh_error: Optional[Callable[[Exception], Awaitable[None]]] = None, + feature_flag_enabled: bool = False, + feature_flag_selectors: Optional[List[SettingSelector]] = None, + feature_flag_refresh_enabled: bool = False, **kwargs, ) -> "AzureAppConfigurationProvider": """ @@ -121,7 +124,7 @@ async def load( @overload -async def load( +async def load( # pylint: disable=docstring-keyword-should-match-keyword-only *, connection_string: str, selects: Optional[List[SettingSelector]] = None, @@ -134,6 +137,9 @@ async def load( refresh_interval: int = 30, on_refresh_success: Optional[Callable] = None, on_refresh_error: Optional[Callable[[Exception], Awaitable[None]]] = None, + feature_flag_enabled: bool = False, + feature_flag_selectors: Optional[List[SettingSelector]] = None, + feature_flag_refresh_enabled: bool = False, **kwargs, ) -> "AzureAppConfigurationProvider": """ From 246d259a0c8a8515b6ae77cf230099ac106a86ea Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Thu, 25 Apr 2024 09:25:07 -0700 Subject: [PATCH 09/21] Formatting --- .../provider/_azureappconfigurationprovider.py | 6 +++--- .../provider/aio/_azureappconfigurationproviderasync.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py index dd216c96a86f..41b58d92a3ab 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py @@ -64,7 +64,7 @@ @overload -def load( # pylint: disable=docstring-keyword-should-match-keyword-only +def load( # pylint: disable=docstring-keyword-should-match-keyword-only endpoint: str, credential: "TokenCredential", *, @@ -116,7 +116,7 @@ def load( # pylint: disable=docstring-keyword-should-match-keyword-only @overload -def load( # pylint: disable=docstring-keyword-should-match-keyword-only +def load( # pylint: disable=docstring-keyword-should-match-keyword-only *, connection_string: str, selects: Optional[List[SettingSelector]] = None, @@ -147,7 +147,7 @@ def load( # pylint: disable=docstring-keyword-should-match-keyword-only configurations from azure-keyvault-secrets. This is optional if keyvault_credential is provided. If a credential isn't provided a credential will need to be in each set for each. :keyword Callable[[str], str] secret_resolver: A function that takes a URI and returns a value. - :keyword List[Tuple[str, str]] refresh_on: One or more settings whose modification will trigger a full refresh + :keyword List[Tuple[str, str]] refresh_on: One or more settings whose modification will trigger a full refresh after a fixed interval. This should be a list of Key-Label pairs for specific settings (filters and wildcards are not supported). :keyword int refresh_interval: The minimum time in seconds between when a call to `refresh` will actually trigger a diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py index b4323fed5a64..dffa520f2dae 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py @@ -63,7 +63,7 @@ @overload -async def load( # pylint: disable=docstring-keyword-should-match-keyword-only +async def load( # pylint: disable=docstring-keyword-should-match-keyword-only endpoint: str, credential: "AsyncTokenCredential", *, @@ -124,7 +124,7 @@ async def load( # pylint: disable=docstring-keyword-should-match-keyword-only @overload -async def load( # pylint: disable=docstring-keyword-should-match-keyword-only +async def load( # pylint: disable=docstring-keyword-should-match-keyword-only *, connection_string: str, selects: Optional[List[SettingSelector]] = None, From 07c771a866b15dd72c2c198146b0116e51850157 Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Mon, 29 Apr 2024 09:48:42 -0700 Subject: [PATCH 10/21] Review comments --- .../_azureappconfigurationprovider.py | 24 ++++++++++++------ .../appconfiguration/provider/_constants.py | 6 +++++ .../_azureappconfigurationproviderasync.py | 25 +++++++++++++------ .../dev_requirements.txt | 2 +- .../samples/connection_string_sample.py | 2 -- .../azure-appconfiguration-provider/setup.py | 2 +- 6 files changed, 41 insertions(+), 20 deletions(-) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py index 41b58d92a3ab..052186f9d092 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py @@ -50,6 +50,11 @@ ContainerAppEnvironmentVariable, KubernetesEnvironmentVariable, EMPTY_LABEL, + TELEMETRY_KEY, + METADATA_KEY, + ETAG_KEY, + FEATURE_FLAG_REFERENCE_KEY, + FEATURE_FLAG_ID_KEY, ) from ._user_agent import USER_AGENT @@ -615,7 +620,7 @@ def _load_configuration_settings(self, **kwargs): def _calculate_feature_id(key, label): basic_value = f"{key}\n" if label: - basic_value = basic_value + f"\n{label}" + basic_value += f"{label}" feature_flag_id_hash_bytes = hashlib.sha256(basic_value.encode()).digest() encoded_flag = base64.b64encode(feature_flag_id_hash_bytes) encoded_flag = encoded_flag.replace(b"+", b"-").replace(b"/", b"_") @@ -633,15 +638,18 @@ def _load_feature_flags(self, **kwargs): ) for feature_flag in feature_flags: feature_flag_value = json.loads(feature_flag.value) - if "telemetry" in feature_flag_value: - if "metadata" not in feature_flag_value["telemetry"]: - feature_flag_value["telemetry"]["metadata"] = {} - feature_flag_value["telemetry"]["metadata"]["etag"] = feature_flag.etag - feature_flag_reference = f"{endpoint}/kv/{feature_flag.key}" + if TELEMETRY_KEY in feature_flag_value: + if METADATA_KEY not in feature_flag_value[TELEMETRY_KEY]: + feature_flag_value[TELEMETRY_KEY][METADATA_KEY] = {} + feature_flag_value[TELEMETRY_KEY][METADATA_KEY][ETAG_KEY] = feature_flag.etag + + if not endpoint.endswith("/"): + endpoint += "/" + feature_flag_reference = f"{endpoint}kv/{feature_flag.key}" if feature_flag.label: feature_flag_reference += f"?label={feature_flag.label}" - feature_flag_value["telemetry"]["metadata"]["feature_flag_reference"] = feature_flag_reference - feature_flag_value["telemetry"]["metadata"]["feature_flag_id"] = self._calculate_feature_id( + feature_flag_value[TELEMETRY_KEY][METADATA_KEY][FEATURE_FLAG_REFERENCE_KEY] = feature_flag_reference + feature_flag_value[TELEMETRY_KEY][METADATA_KEY][FEATURE_FLAG_ID_KEY] = self._calculate_feature_id( feature_flag.key, feature_flag.label ) loaded_feature_flags.append(feature_flag_value) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_constants.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_constants.py index 0fdb5b2c0a20..993225ba0465 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_constants.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_constants.py @@ -16,3 +16,9 @@ ContainerAppEnvironmentVariable = "CONTAINER_APP_NAME" KubernetesEnvironmentVariable = "KUBERNETES_PORT" ServiceFabricEnvironmentVariable = "Fabric_NodeName" # cspell:disable-line + +TELEMETRY_KEY = "telemetry" +METADATA_KEY = "metadata" +ETAG_KEY = "etag" +FEATURE_FLAG_REFERENCE_KEY = "feature_flag_reference" +FEATURE_FLAG_ID_KEY = "feature_flag_id" \ No newline at end of file diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py index dffa520f2dae..dcdea03b18b4 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py @@ -45,6 +45,11 @@ FEATURE_FLAG_KEY, FEATURE_FLAG_PREFIX, EMPTY_LABEL, + TELEMETRY_KEY, + METADATA_KEY, + ETAG_KEY, + FEATURE_FLAG_REFERENCE_KEY, + FEATURE_FLAG_ID_KEY, ) from .._azureappconfigurationprovider import ( _is_json_content_type, @@ -526,7 +531,7 @@ async def _load_configuration_settings(self, **kwargs): def _calculate_feature_id(key, label): basic_value = f"{key}\n" if label: - basic_value = basic_value + f"\n{label}" + basic_value += f"{label}" feature_flag_id_hash_bytes = hashlib.sha256(basic_value.encode()).digest() encoded_flag = base64.b64encode(feature_flag_id_hash_bytes) encoded_flag = encoded_flag.replace(b"+", b"-").replace(b"/", b"_") @@ -544,15 +549,19 @@ async def _load_feature_flags(self, **kwargs): ) async for feature_flag in feature_flags: feature_flag_value = json.loads(feature_flag.value) - if "telemetry" in feature_flag_value: - if "metadata" not in feature_flag_value["telemetry"]: - feature_flag_value["telemetry"]["metadata"] = {} - feature_flag_value["telemetry"]["metadata"]["etag"] = feature_flag.etag - feature_flag_reference = f"{endpoint}/kv/{feature_flag.key}" + if TELEMETRY_KEY in feature_flag_value: + if METADATA_KEY not in feature_flag_value[TELEMETRY_KEY]: + feature_flag_value[TELEMETRY_KEY][METADATA_KEY] = {} + feature_flag_value[TELEMETRY_KEY][METADATA_KEY][ETAG_KEY] = feature_flag.etag + + if not endpoint.endswith("/"): + endpoint += "/" + + feature_flag_reference = f"{endpoint}kv/{feature_flag.key}" if feature_flag.label: feature_flag_reference += f"?label={feature_flag.label}" - feature_flag_value["telemetry"]["metadata"]["feature_flag_reference"] = feature_flag_reference - feature_flag_value["telemetry"]["metadata"]["feature_flag_id"] = self._calculate_feature_id( + feature_flag_value[TELEMETRY_KEY][METADATA_KEY][FEATURE_FLAG_REFERENCE_KEY] = feature_flag_reference + feature_flag_value[TELEMETRY_KEY][METADATA_KEY][FEATURE_FLAG_ID_KEY] = self._calculate_feature_id( feature_flag.key, feature_flag.label ) loaded_feature_flags.append(feature_flag_value) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/dev_requirements.txt b/sdk/appconfiguration/azure-appconfiguration-provider/dev_requirements.txt index 01a81e7e6cd1..75e7dc5586d8 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/dev_requirements.txt +++ b/sdk/appconfiguration/azure-appconfiguration-provider/dev_requirements.txt @@ -1,5 +1,5 @@ -e ../../core/azure-core -azure-appconfiguration==1.6.0b2 +-e ../azure-appconfiguration -e ../../identity/azure-identity -e ../../keyvault/azure-keyvault-secrets aiohttp>=3.0 diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/samples/connection_string_sample.py b/sdk/appconfiguration/azure-appconfiguration-provider/samples/connection_string_sample.py index 123456940775..ff6418e9b707 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/samples/connection_string_sample.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/samples/connection_string_sample.py @@ -14,8 +14,6 @@ # Connecting to Azure App Configuration using connection string config = load(connection_string=connection_string, feature_flag_enabled=True, **kwargs) -breakpoint() - print(config["message"]) print(config["my_json"]["key"]) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/setup.py b/sdk/appconfiguration/azure-appconfiguration-provider/setup.py index 06aee39160b8..fed374ee96f4 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/setup.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/setup.py @@ -74,7 +74,7 @@ install_requires=[ "msrest>=0.6.21", "azure-core>=1.28.0", - "azure-appconfiguration==1.6.0b2", + "azure-appconfiguration>=1.6.0", "azure-keyvault-secrets>=4.3.0", ], ) From 26f5042cb432c257365d93e676c7d31bda302616 Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Mon, 29 Apr 2024 09:54:15 -0700 Subject: [PATCH 11/21] Changed label checking. --- .../appconfiguration/provider/_azureappconfigurationprovider.py | 2 +- .../provider/aio/_azureappconfigurationproviderasync.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py index 052186f9d092..6d18165ad768 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py @@ -619,7 +619,7 @@ def _load_configuration_settings(self, **kwargs): @staticmethod def _calculate_feature_id(key, label): basic_value = f"{key}\n" - if label: + if label and not label.isspace() and label is not '': basic_value += f"{label}" feature_flag_id_hash_bytes = hashlib.sha256(basic_value.encode()).digest() encoded_flag = base64.b64encode(feature_flag_id_hash_bytes) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py index dcdea03b18b4..fac1648757b0 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py @@ -530,7 +530,7 @@ async def _load_configuration_settings(self, **kwargs): @staticmethod def _calculate_feature_id(key, label): basic_value = f"{key}\n" - if label: + if label and not label.isspace() and label is not '': basic_value += f"{label}" feature_flag_id_hash_bytes = hashlib.sha256(basic_value.encode()).digest() encoded_flag = base64.b64encode(feature_flag_id_hash_bytes) From 5d485ab645d549770495741ffa24f65a8aeafbf7 Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Mon, 29 Apr 2024 11:40:14 -0700 Subject: [PATCH 12/21] black format changes --- .../provider/_azureappconfigurationprovider.py | 2 +- .../azure/appconfiguration/provider/_constants.py | 2 +- .../provider/aio/_azureappconfigurationproviderasync.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py index 6d18165ad768..67a2ad68c818 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py @@ -619,7 +619,7 @@ def _load_configuration_settings(self, **kwargs): @staticmethod def _calculate_feature_id(key, label): basic_value = f"{key}\n" - if label and not label.isspace() and label is not '': + if label and not label.isspace() and label is not "": basic_value += f"{label}" feature_flag_id_hash_bytes = hashlib.sha256(basic_value.encode()).digest() encoded_flag = base64.b64encode(feature_flag_id_hash_bytes) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_constants.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_constants.py index 993225ba0465..861a47dbddb8 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_constants.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_constants.py @@ -21,4 +21,4 @@ METADATA_KEY = "metadata" ETAG_KEY = "etag" FEATURE_FLAG_REFERENCE_KEY = "feature_flag_reference" -FEATURE_FLAG_ID_KEY = "feature_flag_id" \ No newline at end of file +FEATURE_FLAG_ID_KEY = "feature_flag_id" diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py index fac1648757b0..382afb82c119 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py @@ -530,7 +530,7 @@ async def _load_configuration_settings(self, **kwargs): @staticmethod def _calculate_feature_id(key, label): basic_value = f"{key}\n" - if label and not label.isspace() and label is not '': + if label and not label.isspace() and label is not "": basic_value += f"{label}" feature_flag_id_hash_bytes = hashlib.sha256(basic_value.encode()).digest() encoded_flag = base64.b64encode(feature_flag_id_hash_bytes) @@ -556,7 +556,7 @@ async def _load_feature_flags(self, **kwargs): if not endpoint.endswith("/"): endpoint += "/" - + feature_flag_reference = f"{endpoint}kv/{feature_flag.key}" if feature_flag.label: feature_flag_reference += f"?label={feature_flag.label}" From c09247b406ad72edc664c38b96b1fcd2a16f8b8d Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Mon, 29 Apr 2024 11:43:02 -0700 Subject: [PATCH 13/21] pylint --- .../appconfiguration/provider/_azureappconfigurationprovider.py | 2 +- .../provider/aio/_azureappconfigurationproviderasync.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py index 67a2ad68c818..095a163aa032 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py @@ -619,7 +619,7 @@ def _load_configuration_settings(self, **kwargs): @staticmethod def _calculate_feature_id(key, label): basic_value = f"{key}\n" - if label and not label.isspace() and label is not "": + if label and not label.isspace() and label != "": basic_value += f"{label}" feature_flag_id_hash_bytes = hashlib.sha256(basic_value.encode()).digest() encoded_flag = base64.b64encode(feature_flag_id_hash_bytes) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py index 382afb82c119..e087546fe7ec 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py @@ -530,7 +530,7 @@ async def _load_configuration_settings(self, **kwargs): @staticmethod def _calculate_feature_id(key, label): basic_value = f"{key}\n" - if label and not label.isspace() and label is not "": + if label and not label.isspace() and label != "": basic_value += f"{label}" feature_flag_id_hash_bytes = hashlib.sha256(basic_value.encode()).digest() encoded_flag = base64.b64encode(feature_flag_id_hash_bytes) From 958761e7e2edb573de6219f33c1ce53d85c978b8 Mon Sep 17 00:00:00 2001 From: Matthew Metcalf Date: Tue, 30 Apr 2024 14:13:40 -0700 Subject: [PATCH 14/21] Update sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py Co-authored-by: Avani Gupta --- .../appconfiguration/provider/_azureappconfigurationprovider.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py index 095a163aa032..18c8eb7f1e0c 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py @@ -619,7 +619,7 @@ def _load_configuration_settings(self, **kwargs): @staticmethod def _calculate_feature_id(key, label): basic_value = f"{key}\n" - if label and not label.isspace() and label != "": + if label and not label.isspace(): basic_value += f"{label}" feature_flag_id_hash_bytes = hashlib.sha256(basic_value.encode()).digest() encoded_flag = base64.b64encode(feature_flag_id_hash_bytes) From fac284f7d2481fcf907574a56a1669d7394f1ece Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Tue, 30 Apr 2024 14:30:40 -0700 Subject: [PATCH 15/21] added space checks --- .../provider/_azureappconfigurationprovider.py | 2 +- .../provider/aio/_azureappconfigurationproviderasync.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py index 18c8eb7f1e0c..63cb31ae9c5d 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py @@ -646,7 +646,7 @@ def _load_feature_flags(self, **kwargs): if not endpoint.endswith("/"): endpoint += "/" feature_flag_reference = f"{endpoint}kv/{feature_flag.key}" - if feature_flag.label: + if feature_flag.label and not feature_flag.label.isspace(): feature_flag_reference += f"?label={feature_flag.label}" feature_flag_value[TELEMETRY_KEY][METADATA_KEY][FEATURE_FLAG_REFERENCE_KEY] = feature_flag_reference feature_flag_value[TELEMETRY_KEY][METADATA_KEY][FEATURE_FLAG_ID_KEY] = self._calculate_feature_id( diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py index e087546fe7ec..6f56fdfafd18 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py @@ -530,7 +530,7 @@ async def _load_configuration_settings(self, **kwargs): @staticmethod def _calculate_feature_id(key, label): basic_value = f"{key}\n" - if label and not label.isspace() and label != "": + if label and not label.isspace(): basic_value += f"{label}" feature_flag_id_hash_bytes = hashlib.sha256(basic_value.encode()).digest() encoded_flag = base64.b64encode(feature_flag_id_hash_bytes) @@ -558,7 +558,7 @@ async def _load_feature_flags(self, **kwargs): endpoint += "/" feature_flag_reference = f"{endpoint}kv/{feature_flag.key}" - if feature_flag.label: + if feature_flag.label and not feature_flag.label.isspace(): feature_flag_reference += f"?label={feature_flag.label}" feature_flag_value[TELEMETRY_KEY][METADATA_KEY][FEATURE_FLAG_REFERENCE_KEY] = feature_flag_reference feature_flag_value[TELEMETRY_KEY][METADATA_KEY][FEATURE_FLAG_ID_KEY] = self._calculate_feature_id( From 50755b0492caf420a0e25913abe98994db2cb01a Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Fri, 10 May 2024 15:08:42 -0700 Subject: [PATCH 16/21] Update conftest.py --- .../azure-appconfiguration-provider/tests/conftest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/tests/conftest.py b/sdk/appconfiguration/azure-appconfiguration-provider/tests/conftest.py index 531ff6adca92..2b8cee4063dc 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/tests/conftest.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/tests/conftest.py @@ -36,4 +36,5 @@ def add_sanitizers(test_proxy): add_general_regex_sanitizer(value="api-version=1970-01-01", regex="api-version=.+") set_custom_default_matcher(ignored_headers="x-ms-content-sha256, Accept", excluded_headers="Content-Length") + add_remove_header_sanitizer(headers="Sync-Token") add_oauth_response_sanitizer() From 33ae94e48ecde6656b06106dba5fda3d46252b2a Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Mon, 19 Aug 2024 13:39:45 -0700 Subject: [PATCH 17/21] moved telemetry to client wrapper --- .../_azureappconfigurationprovider.py | 35 ---------------- .../provider/_client_manager.py | 40 +++++++++++++++++-- 2 files changed, 37 insertions(+), 38 deletions(-) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py index 4c3c0fb5cd4a..eee938c0989b 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py @@ -8,8 +8,6 @@ import random import time import datetime -import hashlib -import base64 from importlib.metadata import version, PackageNotFoundError from threading import Lock import logging @@ -47,14 +45,6 @@ ContainerAppEnvironmentVariable, KubernetesEnvironmentVariable, EMPTY_LABEL, - TELEMETRY_KEY, - METADATA_KEY, - ETAG_KEY, - FEATURE_FLAG_REFERENCE_KEY, - FEATURE_FLAG_ID_KEY, - PERCENTAGE_FILTER_NAMES, - TIME_WINDOW_FILTER_NAMES, - TARGETING_FILTER_NAMES, CUSTOM_FILTER_KEY, PERCENTAGE_FILTER_KEY, TIME_WINDOW_FILTER_KEY, @@ -615,31 +605,6 @@ def refresh(self, **kwargs) -> None: finally: self._refresh_lock.release() - @staticmethod - def _calculate_feature_id(key, label): - basic_value = f"{key}\n" - if label and not label.isspace(): - basic_value += f"{label}" - feature_flag_id_hash_bytes = hashlib.sha256(basic_value.encode()).digest() - encoded_flag = base64.b64encode(feature_flag_id_hash_bytes) - encoded_flag = encoded_flag.replace(b"+", b"-").replace(b"/", b"_") - return encoded_flag[: encoded_flag.find(b"=")] - - if TELEMETRY_KEY in feature_flag_value: - if METADATA_KEY not in feature_flag_value[TELEMETRY_KEY]: - feature_flag_value[TELEMETRY_KEY][METADATA_KEY] = {} - feature_flag_value[TELEMETRY_KEY][METADATA_KEY][ETAG_KEY] = feature_flag.etag - - if not endpoint.endswith("/"): - endpoint += "/" - feature_flag_reference = f"{endpoint}kv/{feature_flag.key}" - if feature_flag.label and not feature_flag.label.isspace(): - feature_flag_reference += f"?label={feature_flag.label}" - feature_flag_value[TELEMETRY_KEY][METADATA_KEY][FEATURE_FLAG_REFERENCE_KEY] = feature_flag_reference - feature_flag_value[TELEMETRY_KEY][METADATA_KEY][FEATURE_FLAG_ID_KEY] = self._calculate_feature_id( - feature_flag.key, feature_flag.label - ) - def _load_all(self, **kwargs): active_clients = self._replica_client_manager.get_active_clients() diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_client_manager.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_client_manager.py index 95641b43c9ba..bd6f46adca26 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_client_manager.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_client_manager.py @@ -7,6 +7,8 @@ import json import time import random +import hashlib +import base64 from dataclasses import dataclass from typing import Tuple, Union, Dict, List, Any, Optional, Mapping from typing_extensions import Self @@ -20,8 +22,14 @@ FeatureFlagConfigurationSetting, ) from ._models import SettingSelector -from ._constants import FEATURE_FLAG_PREFIX - +from ._constants import ( + FEATURE_FLAG_PREFIX, + TELEMETRY_KEY, + METADATA_KEY, + ETAG_KEY, + FEATURE_FLAG_REFERENCE_KEY, + FEATURE_FLAG_ID_KEY, +) @dataclass class ConfigurationClientWrapper: @@ -150,6 +158,17 @@ def load_configuration_settings( sentinel_keys[(config.key, config.label)] = config.etag return configuration_settings, sentinel_keys + @staticmethod + def _calculate_feature_id(key, label): + basic_value = f"{key}\n" + if label and not label.isspace(): + basic_value += f"{label}" + feature_flag_id_hash_bytes = hashlib.sha256(basic_value.encode()).digest() + encoded_flag = base64.b64encode(feature_flag_id_hash_bytes) + encoded_flag = encoded_flag.replace(b"+", b"-").replace(b"/", b"_") + return encoded_flag[: encoded_flag.find(b"=")] + + @distributed_trace def load_feature_flags( self, feature_flag_selectors: List[SettingSelector], feature_flag_refresh_enabled: bool, **kwargs @@ -163,7 +182,22 @@ def load_feature_flags( key_filter=FEATURE_FLAG_PREFIX + select.key_filter, label_filter=select.label_filter, **kwargs ) for feature_flag in feature_flags: - loaded_feature_flags.append(json.loads(feature_flag.value)) + feature_flag_value = json.loads(feature_flag.value) + if TELEMETRY_KEY in feature_flag_value: + if METADATA_KEY not in feature_flag_value[TELEMETRY_KEY]: + feature_flag_value[TELEMETRY_KEY][METADATA_KEY] = {} + feature_flag_value[TELEMETRY_KEY][METADATA_KEY][ETAG_KEY] = feature_flag.etag + + if not endpoint.endswith("/"): + endpoint += "/" + feature_flag_reference = f"{endpoint}kv/{feature_flag.key}" + if feature_flag.label and not feature_flag.label.isspace(): + feature_flag_reference += f"?label={feature_flag.label}" + feature_flag_value[TELEMETRY_KEY][METADATA_KEY][FEATURE_FLAG_REFERENCE_KEY] = feature_flag_reference + feature_flag_value[TELEMETRY_KEY][METADATA_KEY][FEATURE_FLAG_ID_KEY] = self._calculate_feature_id( + feature_flag.key, feature_flag.label + ) + loaded_feature_flags.append(feature_flag_value) if feature_flag_refresh_enabled: feature_flag_sentinel_keys[(feature_flag.key, feature_flag.label)] = feature_flag.etag From c8f3b40693188114b044e61b717da2663790dc52 Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Mon, 19 Aug 2024 13:55:28 -0700 Subject: [PATCH 18/21] fixing format --- .../appconfiguration/provider/_client_manager.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_client_manager.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_client_manager.py index bd6f46adca26..31774f34e7a6 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_client_manager.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_client_manager.py @@ -31,6 +31,7 @@ FEATURE_FLAG_ID_KEY, ) + @dataclass class ConfigurationClientWrapper: endpoint: str @@ -47,7 +48,7 @@ def from_credential( user_agent: str, retry_total: int, retry_backoff_max: int, - **kwargs + **kwargs, ) -> Self: """ Creates a new instance of the ConfigurationClientWrapper class, using the provided credential to authenticate @@ -69,7 +70,7 @@ def from_credential( user_agent=user_agent, retry_total=retry_total, retry_backoff_max=retry_backoff_max, - **kwargs + **kwargs, ), ) @@ -96,7 +97,7 @@ def from_connection_string( user_agent=user_agent, retry_total=retry_total, retry_backoff_max=retry_backoff_max, - **kwargs + **kwargs, ), ) @@ -168,7 +169,6 @@ def _calculate_feature_id(key, label): encoded_flag = encoded_flag.replace(b"+", b"-").replace(b"/", b"_") return encoded_flag[: encoded_flag.find(b"=")] - @distributed_trace def load_feature_flags( self, feature_flag_selectors: List[SettingSelector], feature_flag_refresh_enabled: bool, **kwargs @@ -177,6 +177,7 @@ def load_feature_flags( loaded_feature_flags = [] # Needs to be removed unknown keyword argument for list_configuration_settings kwargs.pop("sentinel_keys", None) + endpoint = self._client._impl._config.endpoint # pylint: disable=protected-access for select in feature_flag_selectors: feature_flags = self._client.list_configuration_settings( key_filter=FEATURE_FLAG_PREFIX + select.key_filter, label_filter=select.label_filter, **kwargs @@ -240,7 +241,7 @@ def refresh_feature_flags( refresh_on: Mapping[Tuple[str, str], Optional[str]], feature_flag_selectors: List[SettingSelector], headers: Dict[str, str], - **kwargs + **kwargs, ) -> Tuple[bool, Optional[Dict[Tuple[str, str], str]], Optional[List[Any]]]: """ Gets the refreshed feature flags if they have changed. From 0456c6d66b32e11c701b50d5e4b6e7d999f8899e Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Fri, 6 Sep 2024 13:19:23 -0700 Subject: [PATCH 19/21] updating after merge --- .../_azureappconfigurationprovider.py | 2 +- .../provider/_client_manager.py | 57 +++++-------------- .../provider/_client_manager_base.py | 41 ++++++++++++- .../provider/aio/_async_client_manager.py | 10 +++- .../_azureappconfigurationproviderasync.py | 16 +----- 5 files changed, 64 insertions(+), 62 deletions(-) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py index 5c0fdfe92e81..1b075b58508b 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py @@ -339,7 +339,7 @@ def _buildprovider( replica_discovery_enabled, min_backoff, max_backoff, - **kwargs + **kwargs, ) provider = AzureAppConfigurationProvider(endpoint, replica_client_manager, **kwargs) return provider diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_client_manager.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_client_manager.py index c5a156b17c24..e9ae3b3b65f7 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_client_manager.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_client_manager.py @@ -6,9 +6,6 @@ from logging import getLogger import json import time -import random -import hashlib -import base64 from dataclasses import dataclass from typing import Tuple, Union, Dict, List, Any, Optional, Mapping from typing_extensions import Self @@ -21,14 +18,11 @@ AzureAppConfigurationClient, FeatureFlagConfigurationSetting, ) -from ._models import SettingSelector -from ._constants import ( - FEATURE_FLAG_PREFIX, - TELEMETRY_KEY, - METADATA_KEY, - ETAG_KEY, - FEATURE_FLAG_REFERENCE_KEY, - FEATURE_FLAG_ID_KEY, +from ._client_manager_base import ( + _ConfigurationClientWrapperBase, + ConfigurationClientManagerBase, + FALLBACK_CLIENT_REFRESH_EXPIRED_INTERVAL, + MINIMAL_CLIENT_REFRESH_INTERVAL, ) from ._models import SettingSelector from ._constants import FEATURE_FLAG_PREFIX @@ -50,7 +44,7 @@ def from_credential( user_agent: str, retry_total: int, retry_backoff_max: int, - **kwargs, + **kwargs ) -> Self: """ Creates a new instance of the _ConfigurationClientWrapper class, using the provided credential to authenticate @@ -72,7 +66,7 @@ def from_credential( user_agent=user_agent, retry_total=retry_total, retry_backoff_max=retry_backoff_max, - **kwargs, + **kwargs ), ) @@ -99,7 +93,7 @@ def from_connection_string( user_agent=user_agent, retry_total=retry_total, retry_backoff_max=retry_backoff_max, - **kwargs, + **kwargs ), ) @@ -161,16 +155,6 @@ def load_configuration_settings( sentinel_keys[(config.key, config.label)] = config.etag return configuration_settings, sentinel_keys - @staticmethod - def _calculate_feature_id(key, label): - basic_value = f"{key}\n" - if label and not label.isspace(): - basic_value += f"{label}" - feature_flag_id_hash_bytes = hashlib.sha256(basic_value.encode()).digest() - encoded_flag = base64.b64encode(feature_flag_id_hash_bytes) - encoded_flag = encoded_flag.replace(b"+", b"-").replace(b"/", b"_") - return encoded_flag[: encoded_flag.find(b"=")] - @distributed_trace def load_feature_flags( self, feature_flag_selectors: List[SettingSelector], feature_flag_refresh_enabled: bool, **kwargs @@ -186,31 +170,20 @@ def load_feature_flags( key_filter=FEATURE_FLAG_PREFIX + select.key_filter, label_filter=select.label_filter, **kwargs ) for feature_flag in feature_flags: - feature_flag_value = json.loads(feature_flag.value) - if TELEMETRY_KEY in feature_flag_value: - if METADATA_KEY not in feature_flag_value[TELEMETRY_KEY]: - feature_flag_value[TELEMETRY_KEY][METADATA_KEY] = {} - feature_flag_value[TELEMETRY_KEY][METADATA_KEY][ETAG_KEY] = feature_flag.etag - - if not endpoint.endswith("/"): - endpoint += "/" - feature_flag_reference = f"{endpoint}kv/{feature_flag.key}" - if feature_flag.label and not feature_flag.label.isspace(): - feature_flag_reference += f"?label={feature_flag.label}" - feature_flag_value[TELEMETRY_KEY][METADATA_KEY][FEATURE_FLAG_REFERENCE_KEY] = feature_flag_reference - feature_flag_value[TELEMETRY_KEY][METADATA_KEY][FEATURE_FLAG_ID_KEY] = self._calculate_feature_id( - feature_flag.key, feature_flag.label - ) - loaded_feature_flags.append(feature_flag_value) - loaded_feature_flags.append(json.loads(feature_flag.value)) 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 + feature_flag_value = json.loads(feature_flag.value) + + self._feature_flag_telemetry(endpoint, feature_flag, feature_flag_value) + self._feature_flag_appconfig_telemetry(feature_flag, filters_used) + + loaded_feature_flags.append(feature_flag_value) + if feature_flag_refresh_enabled: feature_flag_sentinel_keys[(feature_flag.key, feature_flag.label)] = feature_flag.etag - self._feature_flag_telemetry(feature_flag, filters_used) return loaded_feature_flags, feature_flag_sentinel_keys, filters_used @distributed_trace diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_client_manager_base.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_client_manager_base.py index 72704f759050..cf7870a4681f 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_client_manager_base.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_client_manager_base.py @@ -5,6 +5,8 @@ # ------------------------------------------------------------------------- import time import random +import hashlib +import base64 from dataclasses import dataclass from typing import Dict, List from azure.appconfiguration import ( # type:ignore # pylint:disable=no-name-in-module @@ -18,6 +20,11 @@ PERCENTAGE_FILTER_KEY, TIME_WINDOW_FILTER_KEY, TARGETING_FILTER_KEY, + TELEMETRY_KEY, + METADATA_KEY, + ETAG_KEY, + FEATURE_FLAG_REFERENCE_KEY, + FEATURE_FLAG_ID_KEY, ) FALLBACK_CLIENT_REFRESH_EXPIRED_INTERVAL = 3600 # 1 hour in seconds @@ -28,7 +35,37 @@ class _ConfigurationClientWrapperBase: endpoint: str - def _feature_flag_telemetry(self, feature_flag: FeatureFlagConfigurationSetting, filters_used: Dict[str, bool]): + @staticmethod + def _calculate_feature_id(key, label): + basic_value = f"{key}\n" + if label and not label.isspace(): + basic_value += f"{label}" + feature_flag_id_hash_bytes = hashlib.sha256(basic_value.encode()).digest() + encoded_flag = base64.b64encode(feature_flag_id_hash_bytes) + encoded_flag = encoded_flag.replace(b"+", b"-").replace(b"/", b"_") + return encoded_flag[: encoded_flag.find(b"=")] + + def _feature_flag_telemetry( + self, endpoint: str, feature_flag: FeatureFlagConfigurationSetting, feature_flag_value: Dict + ): + if TELEMETRY_KEY in feature_flag_value: + if METADATA_KEY not in feature_flag_value[TELEMETRY_KEY]: + feature_flag_value[TELEMETRY_KEY][METADATA_KEY] = {} + feature_flag_value[TELEMETRY_KEY][METADATA_KEY][ETAG_KEY] = feature_flag.etag + + if not endpoint.endswith("/"): + endpoint += "/" + feature_flag_reference = f"{endpoint}kv/{feature_flag.key}" + if feature_flag.label and not feature_flag.label.isspace(): + feature_flag_reference += f"?label={feature_flag.label}" + feature_flag_value[TELEMETRY_KEY][METADATA_KEY][FEATURE_FLAG_REFERENCE_KEY] = feature_flag_reference + feature_flag_value[TELEMETRY_KEY][METADATA_KEY][FEATURE_FLAG_ID_KEY] = self._calculate_feature_id( + feature_flag.key, feature_flag.label + ) + + def _feature_flag_appconfig_telemetry( + self, feature_flag: FeatureFlagConfigurationSetting, filters_used: Dict[str, bool] + ): if feature_flag.filters: for filter in feature_flag.filters: if filter.get("name") in PERCENTAGE_FILTER_NAMES: @@ -51,7 +88,7 @@ def __init__( replica_discovery_enabled, min_backoff_sec, max_backoff_sec, - **kwargs + **kwargs, ): self._replica_clients: List[_ConfigurationClientWrapperBase] = [] self._original_endpoint = endpoint diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_async_client_manager.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_async_client_manager.py index 966251cda7fb..6ba0fa43d1e9 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_async_client_manager.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_async_client_manager.py @@ -165,21 +165,27 @@ async def load_feature_flags( loaded_feature_flags = [] # Needs to be removed unknown keyword argument for list_configuration_settings kwargs.pop("sentinel_keys", None) + endpoint = self._client._impl._config.endpoint # pylint: disable=protected-access filters_used: Dict[str, bool] = {} for select in feature_flag_selectors: feature_flags = self._client.list_configuration_settings( key_filter=FEATURE_FLAG_PREFIX + select.key_filter, label_filter=select.label_filter, **kwargs ) async for feature_flag in feature_flags: - loaded_feature_flags.append(json.loads(feature_flag.value)) 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 + feature_flag_value = json.loads(feature_flag.value) + + self._feature_flag_telemetry(endpoint, feature_flag, feature_flag_value) + self._feature_flag_appconfig_telemetry(feature_flag, filters_used) + + loaded_feature_flags.append(feature_flag_value) + if feature_flag_refresh_enabled: feature_flag_sentinel_keys[(feature_flag.key, feature_flag.label)] = feature_flag.etag - self._feature_flag_telemetry(feature_flag, filters_used) return loaded_feature_flags, feature_flag_sentinel_keys, filters_used @distributed_trace diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py index 127d029e88af..fe992d5b75fa 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py @@ -4,8 +4,6 @@ # license information. # ------------------------------------------------------------------------- import json -import hashlib -import base64 import datetime from threading import Lock import logging @@ -40,18 +38,6 @@ FEATURE_MANAGEMENT_KEY, FEATURE_FLAG_KEY, EMPTY_LABEL, - TELEMETRY_KEY, - METADATA_KEY, - ETAG_KEY, - FEATURE_FLAG_REFERENCE_KEY, - FEATURE_FLAG_ID_KEY, - PERCENTAGE_FILTER_NAMES, - TIME_WINDOW_FILTER_NAMES, - TARGETING_FILTER_NAMES, - CUSTOM_FILTER_KEY, - PERCENTAGE_FILTER_KEY, - TIME_WINDOW_FILTER_KEY, - TARGETING_FILTER_KEY, ) from ._async_client_manager import AsyncConfigurationClientManager from .._azureappconfigurationprovider import ( @@ -292,7 +278,7 @@ async def _buildprovider( replica_discovery_enabled, min_backoff, max_backoff, - **kwargs + **kwargs, ) await replica_client_manager.setup_initial_clients() provider = AzureAppConfigurationProvider(endpoint, replica_client_manager, **kwargs) From 7e33ecbf54e88c422a1633da502b041f11eeba5d Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Mon, 9 Sep 2024 16:51:56 -0700 Subject: [PATCH 20/21] fixing black issue --- .../provider/aio/_azureappconfigurationproviderasync.py | 4 ++-- .../samples/connection_string_sample.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py index 883cdecce40b..17de89c6f4e5 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py @@ -132,7 +132,7 @@ async def load( # pylint: disable=docstring-keyword-should-match-keyword-only feature_flag_enabled: bool = False, feature_flag_selectors: Optional[List[SettingSelector]] = None, feature_flag_refresh_enabled: bool = False, - **kwargs + **kwargs, ) -> "AzureAppConfigurationProvider": """ Loads configuration settings from Azure App Configuration into a Python application. @@ -337,7 +337,7 @@ def __init__(self, **kwargs) -> None: replica_discovery_enabled=kwargs.pop("replica_discovery_enabled", True), min_backoff_sec=min_backoff, max_backoff_sec=max_backoff, - **kwargs + **kwargs, ) self._dict: Dict[str, Any] = {} self._secret_clients: Dict[str, SecretClient] = {} diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/samples/connection_string_sample.py b/sdk/appconfiguration/azure-appconfiguration-provider/samples/connection_string_sample.py index ff6418e9b707..acb9e85152b8 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/samples/connection_string_sample.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/samples/connection_string_sample.py @@ -12,7 +12,7 @@ connection_string = os.environ["APPCONFIGURATION_CONNECTION_STRING"] # Connecting to Azure App Configuration using connection string -config = load(connection_string=connection_string, feature_flag_enabled=True, **kwargs) +config = load(connection_string=connection_string, **kwargs) print(config["message"]) print(config["my_json"]["key"]) From 126937a0889087e2804ac65abdfdd31048d9e0d9 Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Mon, 9 Sep 2024 16:54:22 -0700 Subject: [PATCH 21/21] removing unused imports --- .../samples/async_aad_sample.py | 2 +- .../samples/async_key_vault_reference_sample.py | 2 +- .../samples/sample_utilities.py | 2 -- .../tests/test_async_provider_feature_management.py | 3 --- .../azure-appconfiguration-provider/tests/test_discovery.py | 2 +- .../tests/test_provider_backoff.py | 3 +-- .../tests/test_provider_refresh.py | 1 - 7 files changed, 4 insertions(+), 11 deletions(-) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/samples/async_aad_sample.py b/sdk/appconfiguration/azure-appconfiguration-provider/samples/async_aad_sample.py index 59af883f55a8..61c51dc50ef8 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/samples/async_aad_sample.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/samples/async_aad_sample.py @@ -8,7 +8,7 @@ from azure.appconfiguration.provider.aio import load from azure.appconfiguration.provider import SettingSelector import os -from sample_utilities import get_authority, get_audience, get_credential, get_client_modifications +from sample_utilities import get_authority, get_credential, get_client_modifications async def main(): diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/samples/async_key_vault_reference_sample.py b/sdk/appconfiguration/azure-appconfiguration-provider/samples/async_key_vault_reference_sample.py index e81b6975811a..384793ad495e 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/samples/async_key_vault_reference_sample.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/samples/async_key_vault_reference_sample.py @@ -8,7 +8,7 @@ from azure.appconfiguration.provider.aio import load from azure.appconfiguration.provider import SettingSelector import os -from sample_utilities import get_authority, get_audience, get_credential, get_client_modifications +from sample_utilities import get_authority, get_credential, get_client_modifications async def main(): diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/samples/sample_utilities.py b/sdk/appconfiguration/azure-appconfiguration-provider/samples/sample_utilities.py index 813f2300a196..7e09ee8de8fa 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/samples/sample_utilities.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/samples/sample_utilities.py @@ -13,8 +13,6 @@ - get_credential(): get credential of the ConfigurationClient It is not a file expected to run independently. """ - -import os from azure.identity import AzureAuthorityHosts, DefaultAzureCredential from azure.identity.aio import DefaultAzureCredential as AsyncDefaultAzureCredential diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_async_provider_feature_management.py b/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_async_provider_feature_management.py index 8dcb5dbf147b..60833d449212 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_async_provider_feature_management.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_async_provider_feature_management.py @@ -12,9 +12,6 @@ from test_constants import FEATURE_MANAGEMENT_KEY -from azure.appconfiguration.provider._azureappconfigurationprovider import _delay_failure - - class TestAppConfigurationProviderFeatureManagement(AppConfigTestCase): # method: load @app_config_decorator_async diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_discovery.py b/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_discovery.py index dd47bc47a82d..d14898c205b7 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_discovery.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_discovery.py @@ -12,7 +12,7 @@ _find_origin, find_auto_failover_endpoints, ) -from dns.resolver import NXDOMAIN, YXDOMAIN, LifetimeTimeout, NoNameservers, Answer # cspell:disable-line +from dns.resolver import NXDOMAIN, YXDOMAIN, LifetimeTimeout, NoNameservers # cspell:disable-line AZCONFIG_IO = ".azconfig.io" # cspell:disable-line APPCONFIG_IO = ".appconfig.io" # cspell:disable-line diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_provider_backoff.py b/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_provider_backoff.py index e9bc5c68fb75..fd925b59c502 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_provider_backoff.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_provider_backoff.py @@ -3,8 +3,7 @@ # Licensed under the MIT License. See License.txt in the project root for # license information. # -------------------------------------------------------------------------- -from azure.appconfiguration.provider import load, SettingSelector -from devtools_testutils import AzureRecordedTestCase, recorded_by_proxy +from devtools_testutils import recorded_by_proxy from preparers import app_config_decorator from testcase import AppConfigTestCase diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_provider_refresh.py b/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_provider_refresh.py index a72aecf14243..a60a83d661fa 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_provider_refresh.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_provider_refresh.py @@ -5,7 +5,6 @@ # -------------------------------------------------------------------------- import time import unittest -import pytest from unittest.mock import Mock from azure.appconfiguration.provider import WatchKey from devtools_testutils import recorded_by_proxy