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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
apiMdSha256: 1b252a78094b95bf515ebe5ec67fc2de12ce5b595bad25438a58241ca7153b84
parserVersion: 0.3.28
parserVersion: 0.3.30
pythonVersion: 3.14.3
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
from ._user_agent import USER_AGENT
from ._utils import get_startup_backoff

JSON = Mapping[str, Any]
logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -96,7 +95,19 @@ def __init__(self, **kwargs: Any) -> None:
self._on_refresh_error: Optional[Callable[[Exception], None]] = kwargs.pop("on_refresh_error", None)
self._configuration_mapper: Optional[Callable] = kwargs.pop("configuration_mapper", None)

def _attempt_refresh(self, client: ConfigurationClient, replica_count: int, is_failover_request: bool, **kwargs):
def _attempt_refresh(
self, client: ConfigurationClient, replica_count: int, is_failover_request: bool, **kwargs
) -> None:
"""
Attempts to refresh configuration settings and feature flags using a single client.

:param client: The configuration client to attempt the refresh against.
:type client: ~azure.appconfiguration.provider.ConfigurationClient
:param replica_count: The number of replica clients available, used for correlation telemetry.
:type replica_count: int
:param is_failover_request: Whether this attempt is a failover from a previously failed client.
:type is_failover_request: bool
"""
settings_refreshed = False
headers = self._update_correlation_context_header(
kwargs.pop("headers", {}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ def _generate_allocation_id(feature_flag_value: Dict[str, JSON]) -> Optional[str
seed=123abc\ndefault_when_enabled=Control\npercentiles=0,Control,20;20,Test,100\nvariants=Control,standard;Test,special # pylint:disable=line-too-long

:param Dict[str, JSON] feature_flag_value: The feature to generate an allocation ID for.
:rtype: str
:rtype: Optional[str]
:return: The allocation ID.
"""

Expand Down Expand Up @@ -303,11 +303,11 @@ def values(self) -> ValuesView[Union[str, Mapping[str, Any]]]:
resolved.

:return: A list of values loaded from Azure App Configuration. The values are either Strings or JSON objects,
based on there content type.
based on their content type.
:rtype: ValuesView[Union[str, Mapping[str, Any]]]
"""
with self._update_lock:
return (self._dict).values()
return self._dict.values()

@overload
def get(self, key: str, default: None = None) -> Union[str, JSON, None]: ...
Expand Down Expand Up @@ -440,7 +440,9 @@ def _update_correlation_context_header(

def _deduplicate_settings(self, configuration_settings: List[ConfigurationSetting]) -> List[ConfigurationSetting]:
"""
Deduplicates configuration settings by key.
Deduplicates configuration settings by key, ignoring label. The provider exposes a flat
key->value mapping, so when multiple selectors return the same key the last one wins,
regardless of label.

:param List[ConfigurationSetting] configuration_settings: The list of configuration settings to deduplicate
:return: A list of unique configuration settings
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import time
import random
from dataclasses import dataclass
from typing import Tuple, Union, Dict, List, Optional, Mapping
from typing import Tuple, Union, List, Optional, Mapping
from typing_extensions import Self
from azure.core import MatchConditions
from azure.core.tracing.decorator import distributed_trace
Expand Down Expand Up @@ -101,14 +101,14 @@ def from_connection_string(
)

def _check_configuration_setting(
self, key: str, label: str, etag: Optional[str], headers: Dict[str, str], **kwargs
self, key: str, label: str, etag: Optional[str], headers: Mapping[str, str], **kwargs
) -> Tuple[bool, Union[ConfigurationSetting, None]]:
"""
Checks if the configuration setting have been updated since the last refresh.

:param str key: key to check for chances
:param str key: key to check for changes
:param str label: label to check for changes
:param str etag: etag to check for changes
:param Optional[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.
Expand Down Expand Up @@ -284,16 +284,16 @@ def check_feature_flag_page_etags(

@distributed_trace
def get_updated_watched_settings(
self, watched_settings: Mapping[Tuple[str, str], Optional[str]], headers: Dict[str, str], **kwargs
self, watched_settings: Mapping[Tuple[str, str], Optional[str]], headers: Mapping[str, str], **kwargs
) -> Mapping[Tuple[str, str], Optional[str]]:
"""
Checks if any of the watch keys have changed, and updates them if they have.

:param Mapping[Tuple[str, str], Optional[str]] watched_settings: The configuration settings to check for changes
:param Mapping[str, str] headers: The headers to use for the request

:return: Updated value of the configuration watched settings.
:rtype: Union[Dict[Tuple[str, str], str], None]
:return: Updated value of the configuration watched settings. Empty if no change was detected.
:rtype: Mapping[Tuple[str, str], Optional[str]]
"""
updated_watched_settings = dict(watched_settings)
trigger_refresh = False
Expand All @@ -319,8 +319,8 @@ def get_configuration_setting(self, key: str, label: str, **kwargs) -> Optional[

:param str key: The key of the configuration setting
:param str label: The label of the configuration setting
:return: The configuration setting
:rtype: ConfigurationSetting
:return: The configuration setting, or None when the supplied ETag has not been modified.
:rtype: Optional[ConfigurationSetting]
"""
return self._client.get_configuration_setting(key=key, label=label, **kwargs)

Expand Down Expand Up @@ -403,12 +403,12 @@ def __init__(
endpoint: str,
credential: Optional["TokenCredential"],
user_agent: str,
retry_total,
retry_backoff_max,
replica_discovery_enabled,
min_backoff_sec,
max_backoff_sec,
load_balancing_enabled,
retry_total: int,
retry_backoff_max: int,
replica_discovery_enabled: bool,
min_backoff_sec: int,
max_backoff_sec: int,
load_balancing_enabled: bool,
**kwargs,
):
super(ConfigurationClientManager, self).__init__(
Expand Down Expand Up @@ -445,6 +445,7 @@ def get_next_active_client(self) -> Optional[_ConfigurationClientWrapper]:
method returns None.

:return: The next client to be used for the request.
:rtype: Optional[_ConfigurationClientWrapper]
"""
if not self._active_clients:
self._last_active_client_name = ""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@
# -------------------------------------------------------------------------
import random
from dataclasses import dataclass
from typing import Optional, Mapping, Any
from typing import Optional

FALLBACK_CLIENT_REFRESH_EXPIRED_INTERVAL = 3600 # 1 hour in seconds
MINIMAL_CLIENT_REFRESH_INTERVAL = 30 # 30 seconds

JSON = Mapping[str, Any]


@dataclass
class _ConfigurationClientWrapperBase:
Expand All @@ -23,12 +21,12 @@ def __init__(
self,
endpoint: str,
user_agent: str,
retry_total,
retry_backoff_max,
replica_discovery_enabled,
min_backoff_sec,
max_backoff_sec,
_load_balancing_enabled: bool,
retry_total: int,
retry_backoff_max: int,
replica_discovery_enabled: bool,
min_backoff_sec: int,
max_backoff_sec: int,
load_balancing_enabled: bool,
**kwargs,
):
self._last_active_client_name = ""
Expand All @@ -41,9 +39,10 @@ def __init__(
self._args = dict(kwargs)
self._min_backoff_sec = min_backoff_sec
self._max_backoff_sec = max_backoff_sec
self._load_balancing_enabled = _load_balancing_enabled
self._load_balancing_enabled = load_balancing_enabled

def _calculate_backoff(self, attempts: int) -> float:

max_attempts = 63
ms_per_second = 1000 # 1 Second in milliseconds

Expand All @@ -55,8 +54,7 @@ def _calculate_backoff(self, attempts: int) -> float:

calculated_milliseconds = max(1, min_backoff_milliseconds) * (1 << min(attempts, max_attempts))

if calculated_milliseconds > max_backoff_milliseconds or calculated_milliseconds <= 0:
calculated_milliseconds = max_backoff_milliseconds
calculated_milliseconds = min(calculated_milliseconds, max_backoff_milliseconds)

return min_backoff_milliseconds + (
random.uniform(0.0, 1.0) * (calculated_milliseconds - min_backoff_milliseconds) # nosec
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@
# Environment Variable Constants
# ------------------------------------------------------------------------
REQUEST_TRACING_DISABLED_ENVIRONMENT_VARIABLE = "AZURE_APP_CONFIGURATION_TRACING_DISABLED"
AzureFunctionEnvironmentVariable = "FUNCTIONS_EXTENSION_VERSION"
AzureWebAppEnvironmentVariable = "WEBSITE_SITE_NAME"
ContainerAppEnvironmentVariable = "CONTAINER_APP_NAME"
KubernetesEnvironmentVariable = "KUBERNETES_PORT"
ServiceFabricEnvironmentVariable = "Fabric_NodeName" # cspell:disable-line
AZURE_FUNCTION_ENVIRONMENT_VARIABLE = "FUNCTIONS_EXTENSION_VERSION"
AZURE_WEB_APP_ENVIRONMENT_VARIABLE = "WEBSITE_SITE_NAME"
CONTAINER_APP_ENVIRONMENT_VARIABLE = "CONTAINER_APP_NAME"
KUBERNETES_ENVIRONMENT_VARIABLE = "KUBERNETES_PORT"
SERVICE_FABRIC_ENVIRONMENT_VARIABLE = "Fabric_NodeName" # cspell:disable-line

# ------------------------------------------------------------------------
# Telemetry and Tracing Constants
Expand All @@ -38,9 +38,9 @@
APP_CONFIG_AI_MIME_PROFILE = "https://azconfig.io/mime-profiles/ai/"
APP_CONFIG_AICC_MIME_PROFILE = "https://azconfig.io/mime-profiles/ai/chat-completion"

# =============================================================================
# ------------------------------------------------------------------------
# Startup Retry Constants
# =============================================================================
# ------------------------------------------------------------------------
# Timeout
DEFAULT_STARTUP_TIMEOUT = 100 # seconds

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def _find_string_end(text: str, index: int) -> int:

def remove_json_comments(text: str) -> str:
"""
Removes comments from a JSON file. Supports //, and /* ... */ comments.
Removes comments from a JSON string. Supports //, and /* ... */ comments.
Returns as string.

:param text: The input JSON string with comments.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,12 @@
# Licensed under the MIT License. See License.txt in the project root for
# license information.
# -------------------------------------------------------------------------
from typing import Mapping, Any, Dict
from typing import Any, Dict
from azure.appconfiguration import SecretReferenceConfigurationSetting # type:ignore # pylint:disable=no-name-in-module
from azure.keyvault.secrets import SecretClient, KeyVaultSecretIdentifier
from azure.core.exceptions import ServiceRequestError
from ._secret_provider_base import _SecretProviderBase

JSON = Mapping[str, Any]


class SecretProvider(_SecretProviderBase):

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
# license information.
# -------------------------------------------------------------------------
from typing import (
Mapping,
Any,
TypeVar,
Optional,
Dict,
Tuple,
Expand All @@ -15,9 +13,6 @@
from azure.keyvault.secrets import KeyVaultSecretIdentifier
from .._azureappconfigurationproviderbase import _RefreshTimer

JSON = Mapping[str, Any]
_T = TypeVar("_T")


class _SecretProviderBase:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# -------------------------------------------------------------------------
import datetime
from typing import (
Any,
Callable,
List,
Mapping,
Expand All @@ -24,10 +25,11 @@
)
from ._azureappconfigurationprovider import (
AzureAppConfigurationProvider,
JSON,
_buildprovider,
)

JSON = Mapping[str, Any]


@overload
def load( # pylint: disable=docstring-keyword-should-match-keyword-only
Expand Down Expand Up @@ -132,7 +134,7 @@ def load( # pylint: disable=docstring-keyword-should-match-keyword-only
:keyword str connection_string: Connection string for App Configuration resource.
: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 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
Expand All @@ -145,9 +147,6 @@ def load( # pylint: disable=docstring-keyword-should-match-keyword-only
: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 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 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.
:keyword refresh_enabled: Optional flag to enable or disable refreshing of configuration settings. Defaults to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def __init__(
:keyword client_configs: A Mapping of SecretClient endpoints to client configurations from
azure-keyvault-secrets. This is optional if credential is provided. If a credential isn't provided a
credential will need to be in each set for each.
:paramtype client_configs: Mapping[Url, Mapping]
:paramtype client_configs: Mapping[str, Mapping[str, Any]]
:keyword secret_resolver: A function that takes a URI and returns a value.
:paramtype secret_resolver: Callable[[str], str]
"""
Expand All @@ -52,20 +52,20 @@ class SettingSelector:

:keyword key_filter: A filter to select configuration settings and feature flags based on their keys.
Cannot be used with snapshot_name.
:type key_filter: str
:paramtype key_filter: Optional[str]
:keyword label_filter: A filter to select configuration settings and feature flags based on their labels. Default
value is \0 i.e. (No Label) as seen in the portal. Cannot be used with snapshot_name.
:type label_filter: Optional[str]
:paramtype label_filter: Optional[str]
:keyword tag_filters: A filter to select configuration settings and feature flags based on their tags. This is a
list of strings that will be used to match tags on the configuration settings. Reserved characters (\\*, \\, ,)
must be escaped with backslash if they are part of the value. Tag filters must follow the format
"tagName=tagValue", for empty values use "tagName=" and for null values use "tagName=\\0".
Cannot be used with snapshot_name.
:type tag_filters: Optional[List[str]]
:paramtype tag_filters: Optional[List[str]]
:keyword snapshot_name: The name of the snapshot to load configuration settings from. When specified,
all configuration settings from the snapshot will be loaded. Cannot be used with key_filter, label_filter,
or tag_filters.
:type snapshot_name: Optional[str]
:paramtype snapshot_name: Optional[str]
"""

def __init__(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,7 @@ def _calculate_backoff(self) -> float:

calculated_milliseconds = max(1, min_backoff_milliseconds) * (1 << min(self._attempts, max_attempts))

if calculated_milliseconds > max_backoff_milliseconds or calculated_milliseconds <= 0:
calculated_milliseconds = max_backoff_milliseconds
calculated_milliseconds = min(calculated_milliseconds, max_backoff_milliseconds)

return min_backoff_milliseconds + (
random.uniform(0.0, 1.0) * (calculated_milliseconds - min_backoff_milliseconds) # nosec
Expand Down
Loading
Loading