From fedb5b9e9bec4b581e6bcf1c70f00b5070ddbade Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Wed, 29 Apr 2026 14:56:30 -0700 Subject: [PATCH 1/7] Updating readme --- .../azure-appconfiguration-provider/README.md | 277 +++++++++++++++--- .../samples/README.md | 2 +- .../samples/aad_sample.py | 23 +- .../samples/async_aad_sample.py | 8 +- .../samples/async_entra_id_sample.py | 53 ++++ ...vault_reference_provided_clients_sample.py | 2 +- .../async_key_vault_reference_sample.py | 2 +- .../samples/connection_string_sample.py | 17 +- .../samples/entra_id_sample.py | 56 ++++ ...ult_reference_customized_clients_sample.py | 6 +- .../samples/key_vault_reference_sample.py | 6 +- .../samples/refresh_sample.py | 4 + .../samples/refresh_sample_feature_flags.py | 4 + .../samples/snapshot_sample.py | 8 + 14 files changed, 411 insertions(+), 57 deletions(-) create mode 100644 sdk/appconfiguration/azure-appconfiguration-provider/samples/async_entra_id_sample.py create mode 100644 sdk/appconfiguration/azure-appconfiguration-provider/samples/entra_id_sample.py diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/README.md b/sdk/appconfiguration/azure-appconfiguration-provider/README.md index beef0a637f17..d5d7aaff89a5 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/README.md +++ b/sdk/appconfiguration/azure-appconfiguration-provider/README.md @@ -20,41 +20,59 @@ Alternatively, get the connection string from the Azure Portal. You can create a client with a connection string: + + ```python +import os from azure.appconfiguration.provider import load -config = load(connection_string="your-connection-string") +connection_string = os.environ["APPCONFIGURATION_CONNECTION_STRING"] + +# Connecting to Azure App Configuration using connection string +config = load(connection_string=connection_string) ``` -or with AAD: + + +or with Entra ID: + + ```python +import os from azure.appconfiguration.provider import load +from azure.identity import DefaultAzureCredential + +endpoint = os.environ["APPCONFIGURATION_ENDPOINT_STRING"] +credential = DefaultAzureCredential() -config = load(endpoint="your-endpoint", credential=DefaultAzureCredential()) +# Connecting to Azure App Configuration using Entra ID +config = load(endpoint=endpoint, credential=credential) ``` + + these providers will by default load all configurations with `(No Label)` from your configuration store into a dictionary of key/values. ### Features Currently the Azure App Configuration Provider enables: -* Connecting to an App Configuration Store using a connection string or Azure Active Directory. +* Connecting to an App Configuration Store using a connection string or Entra ID. * Selecting multiple sets of configurations using `SettingSelector`. +* Filtering by tags using `tag_filters` on `SettingSelector`. +* Loading from snapshots using `snapshot_name` on `SettingSelector`. * Loading Feature Flags * Dynamic Refresh -* Geo-Replication support +* Geo-Replication support with replica discovery and load balancing. * Trim prefixes off key names. -* Resolving Key Vault References, requires AAD. +* Resolving Key Vault References, requires Entra ID. * Secret Resolver, resolve Key Vault References locally without connecting to Key Vault. +* Periodic Key Vault secret refresh via `secret_refresh_interval`. * Json Content Type - -#### Future Features - -List of features we are going to add to the Python Provider in the future. - -* Configuration Placeholders +* Configuration mapper for transforming settings during load. +* Configurable startup timeout with retry. +* Async support via `azure.appconfiguration.provider.aio`. ## Examples @@ -62,44 +80,92 @@ List of features we are going to add to the Python Provider in the future. You can refine or expand the configurations loaded from your store by using `SettingSelector`s. Setting selectors provide a way to pass a key filter and label filter into the provider. + + +```python +from azure.appconfiguration.provider import load, SettingSelector + +# Connection to Azure App Configuration using SettingSelector +selects = [SettingSelector(key_filter="message*")] +config = load( + endpoint=endpoint, + credential=credential, + selects=selects, + feature_flag_enabled=True, + feature_flag_selectors=None, + **kwargs +) +``` + + + +In this example all configuration with empty label and the dev label are loaded. Because the dev selector is listed last, any configurations from dev take priority over those with `(No Label)` when duplicates are found. + +### Filtering by Tags + +You can filter configuration settings by tags using the `tag_filters` parameter on `SettingSelector`. Tag filters must follow the format `"tagName=tagValue"`. + ```python from azure.appconfiguration.provider import load, SettingSelector from azure.identity import DefaultAzureCredential -selects = {SettingSelector(key_filter="*", label_filter="\0"), SettingSelector(key_filter="*", label_filter="dev")} +selects = [SettingSelector(key_filter="*", tag_filters=["env=prod"])] config = load(endpoint=endpoint, credential=DefaultAzureCredential(), selects=selects) ``` -In this example all configuration with empty label and the dev label are loaded. Because the dev selector is listed last, any configurations from dev take priority over those with `(No Label)` when duplicates are found. +### Loading from Snapshots -## Dynamic Refresh +You can load configuration settings from a snapshot by providing `snapshot_name` on `SettingSelector`. When `snapshot_name` is specified, all configuration settings from the snapshot are loaded. Note that `snapshot_name` cannot be used together with `key_filter`, `label_filter`, or `tag_filters`. -The provider can be configured to refresh configurations from the store on a set interval. This is done by providing a `refresh_on` to the provider, which is a list of key(s) that will be watched for changes, and when they do change a refresh can happen. `refresh_interval` is the period of time in seconds between refreshes. `on_refresh_success` is a callback that will be called only if a change is detected and no error happens. `on_refresh_error` is a callback that will be called when a refresh fails. + ```python -from azure.appconfiguration.provider import load, WatchKey -import os +from azure.appconfiguration.provider import load, SettingSelector -connection_string = os.environ.get("APPCONFIGURATION_CONNECTION_STRING") +# Step 2: Loading configuration settings from the snapshot +snapshot_selects = [SettingSelector(snapshot_name=snapshot_name)] +config = load(endpoint=endpoint, credential=credential, selects=snapshot_selects) +``` + + + +You can also mix snapshot selectors with regular selectors. Later selectors take precedence when there are duplicate keys. + + -def my_callback_on_success(): - # Do something on success - ... +```python +# Step 3: Combine snapshot with regular selectors (later selectors take precedence) +mixed_selects = [ + SettingSelector(snapshot_name=snapshot_name), # Load all settings from snapshot + SettingSelector(key_filter="override.*", label_filter="prod"), # Also load specific override settings +] +config_mixed = load(endpoint=endpoint, credential=credential, selects=mixed_selects) +``` -def my_callback_on_fail(error): - # Do something on fail - ... + + +## Dynamic Refresh +The provider can be configured to refresh configurations from the store on a set interval. This is done by providing a `refresh_on` to the provider, which is a list of key(s) that will be watched for changes, and when they do change a refresh can happen. `refresh_interval` is the period of time in seconds between refreshes. `on_refresh_success` is a callback that will be called only if a change is detected and no error happens. `on_refresh_error` is a callback that will be called when a refresh fails. + + + +```python +from azure.appconfiguration.provider import load, WatchKey + +# Connecting to Azure App Configuration using connection string, and refreshing when the configuration setting message +# changes config = load( connection_string=connection_string, - refresh_on=[WatchKey("Sentinel")], - refresh_interval=60, - on_refresh_success=my_callback_on_success, + refresh_on=[watch_key], + refresh_interval=1, on_refresh_error=my_callback_on_fail, **kwargs, ) ``` + + In this example, the sentinel key will be checked for changes no sooner than every 60 seconds. In order to check for changes, the provider's `refresh` method needs to be called. ```python @@ -114,15 +180,18 @@ For additional info check out [Dynamic Refresh](https://learn.microsoft.com/azur You can trim the prefix off of keys by providing a list of trimmed key prefixes to the provider. For example, if you have the key(s) like `/application/message` in your configuration store, you could trim `/application/` from them. + + ```python from azure.appconfiguration.provider import load -from azure.identity import DefaultAzureCredential -trim_prefixes={"/application/"} -config = load(endpoint=endpoint, credential=DefaultAzureCredential(), trim_prefixes=trim_prefixes) -print(config["message"]) +# Connecting to Azure App Configuration using Entra ID and trim key prefixes +trimmed = ["test."] +config = load(endpoint=endpoint, credential=credential, trim_prefixes=trimmed, **kwargs) ``` + + ### Resolving Key Vault References Key Vault References can be resolved by providing credentials to your key vault to the provider using `AzureAppConfigurationKeyVaultOptions`. @@ -131,27 +200,38 @@ Key Vault References can be resolved by providing credentials to your key vault You can provide `AzureAppConfigurationKeyVaultOptions` with a credential and all key vault references will be resolved with it. The provider will attempt to connect to any key vault referenced with the credential provided. + + ```python -from azure.appconfiguration.provider import load, AzureAppConfigurationKeyVaultOptions -from azure.identity import DefaultAzureCredential +from azure.appconfiguration.provider import load, SettingSelector -key_vault_options = AzureAppConfigurationKeyVaultOptions(credential=DefaultAzureCredential()) -config = load(endpoint=endpoint, credential=DefaultAzureCredential(), key_vault_options=key_vault_options) +# Connection to Azure App Configuration using Entra ID and Resolving Key Vault References +selects = [SettingSelector(key_filter="*", label_filter="prod")] + +config = load(endpoint=endpoint, credential=credential, keyvault_credential=credential, selects=selects, **kwargs) ``` + + ### With Clients You can provide `AzureAppConfigurationKeyVaultOptions` with a list of `SecretClients`. + + ```python -from azure.appconfiguration.provider import load, AzureAppConfigurationKeyVaultOptions -from azure.identity import DefaultAzureCredential +from azure.appconfiguration.provider import load, SettingSelector -key_vault_options = AzureAppConfigurationKeyVaultOptions( - client_configs={key_vault_uri: {'credential': credential}}) -config = load(endpoint=endpoint, credential=DefaultAzureCredential(), key_vault_options=key_vault_options) +# Connection to Azure App Configuration using Entra ID with Provided Client +client_configs = {key_vault_uri: {"credential": credential}} +selects = [SettingSelector(key_filter="*", label_filter="prod")] +config = load( + endpoint=endpoint, credential=credential, keyvault_client_configs=client_configs, selects=selects, **kwargs +) ``` + + ### Secret Resolver If no Credentials or Clients are provided a secret resolver can be used. Secret resolver provides a way to return any value you want to a key vault reference. @@ -168,6 +248,19 @@ key_vault_options = AzureAppConfigurationKeyVaultOptions( config = load(endpoint=endpoint, credential=DefaultAzureCredential(), key_vault_options=key_vault_options) ``` +### Secret Refresh Interval + +When using Key Vault references, the provider can periodically refresh resolved secrets. By default, secrets are refreshed every 60 seconds. You can customize this with the `secret_refresh_interval` parameter (minimum 1 second). + +```python +config = load( + endpoint=endpoint, + credential=DefaultAzureCredential(), + key_vault_options=key_vault_options, + secret_refresh_interval=120, # Refresh secrets every 120 seconds +) +``` + ## Geo Replication The Azure App Configuration Provider library will automatically discover the provided configuration store's replicas and use the replicas if any issue arises. From more information see [Geo-Replication](https://learn.microsoft.com/azure/azure-app-configuration/howto-geo-replication). @@ -181,40 +274,130 @@ from azure.identity import DefaultAzureCredential config = load(endpoint=endpoint, credential=DefaultAzureCredential(), replica_discovery_enabled=False) ``` +You can also enable load balancing to distribute requests across replicas by setting `load_balancing_enabled` to `True`. + +```python +config = load(endpoint=endpoint, credential=DefaultAzureCredential(), load_balancing_enabled=True) +``` + ## Loading Feature Flags Feature Flags can be loaded from config stores using the provider. Feature flags are loaded as a dictionary of key/value pairs stored in the provider under the `feature_management`, then `feature_flags`. ```python -config = load(endpoint=endpoint, credential=DefaultAzureCredential(), feature_flags_enabled=True) +config = load(endpoint=endpoint, credential=DefaultAzureCredential(), feature_flag_enabled=True) alpha = config["feature_management"]["feature_flags"]["Alpha"] print(alpha["enabled"]) ``` -By default all feature flags with no label are loaded when `feature_flags_enabled` is set to `True`. . If you want to load feature flags with a specific label you can use `SettingSelector` to filter the feature flags. +By default all feature flags with no label are loaded when `feature_flag_enabled` is set to `True`. If you want to load feature flags with a specific label you can use `SettingSelector` to filter the feature flags. ```python from azure.appconfiguration.provider import load, SettingSelector -config = load(endpoint=endpoint, credential=DefaultAzureCredential(), feature_flags_enabled=True, feature_flag_selectors=[SettingSelector(key_filter="*", label_filter="dev")]) +config = load(endpoint=endpoint, credential=DefaultAzureCredential(), feature_flag_enabled=True, feature_flag_selectors=[SettingSelector(key_filter="*", label_filter="dev")]) alpha = config["feature_management"]["feature_flags"]["Alpha"] print(alpha["enabled"]) ``` To enable refresh for feature flags you need to enable refresh. This will allow the provider to refresh feature flags the same way it refreshes configurations. Unlike configurations, all loaded feature flags are monitored for changes and will cause a refresh. Refresh of configuration settings and feature flags are independent of each other. Both are trigged by the `refresh` method, but a feature flag changing will not cause a refresh of configurations and vice versa. Also, if refresh for configuration settings is not enabled, feature flags can still be enabled for refresh. + + ```python -config = load(endpoint=endpoint, credential=DefaultAzureCredential(), feature_flags_enabled=True, feature_flag_refresh_enabled=True) +from azure.appconfiguration.provider import load, WatchKey -... +# Connecting to Azure App Configuration using connection string, and refreshing when the configuration setting message +# changes +config = load( + connection_string=connection_string, + refresh_on=[WatchKey("message")], + refresh_on_feature_flags=True, + refresh_interval=1, + on_refresh_error=my_callback_on_fail, + feature_flag_enabled=True, + feature_flag_refresh_enabled=True, + **kwargs, +) +``` -config.refresh() + + +## JSON Content Type + +Configuration settings with a JSON content type (e.g., `application/json`) are automatically deserialized into their corresponding Python objects when loaded by the provider. + +```python +# If a setting "app/config" has value '{"timeout": 30, "retries": 3}' with content type "application/json" +config = load(endpoint=endpoint, credential=DefaultAzureCredential()) +app_config = config["app/config"] # Returns a dict: {"timeout": 30, "retries": 3} +print(app_config["timeout"]) # 30 +``` + +## Configuration Mapper + +You can provide a `configuration_mapper` callback to transform configuration settings before they are added to the provider. + +```python +from azure.appconfiguration.provider import load +from azure.identity import DefaultAzureCredential + +def my_mapper(setting): + # Transform the setting as needed + setting.value = setting.value.strip() + +config = load(endpoint=endpoint, credential=DefaultAzureCredential(), configuration_mapper=my_mapper) +``` + +## Startup Timeout + +The provider supports configurable startup timeout with automatic retry. By default, the provider allows 100 seconds for the initial load from Azure App Configuration. You can customize this with the `startup_timeout` parameter. + +```python +config = load(endpoint=endpoint, credential=DefaultAzureCredential(), startup_timeout=200) +``` + +## Async Support + +The provider includes full async support via the `azure.appconfiguration.provider.aio` module. + + + +```python +from azure.appconfiguration.provider.aio import load + +# Connecting to Azure App Configuration using Entra ID +config = await load(endpoint=endpoint, credential=credential, **kwargs) +print(config["message"]) + +await credential.close() +await config.close() ``` + + ## Key concepts +The `AzureAppConfigurationProvider` is the main object returned by `load()`. It implements the `Mapping` interface, so it can be used like a read-only dictionary. Call `refresh()` (or `await refresh()` for async) to pull the latest values from the store. + +Key types used: + +* `SettingSelector` — Filters which configuration settings to load by key, label, tags, or snapshot name. +* `WatchKey` — Identifies a sentinel key (and optional label) used to trigger configuration refresh. +* `AzureAppConfigurationKeyVaultOptions` — Groups Key Vault credential, per-vault client configs, and secret resolver options. + ## Troubleshooting +### Logging + +This library uses the standard [logging](https://docs.python.org/3/library/logging.html) library for logging. Information about configuration loading, refresh, and Key Vault resolution is logged at the `DEBUG` level. + +### Common Issues + +* **Key Vault references not resolving** — Ensure you have provided credentials via `key_vault_options` or `keyvault_credential`. Key Vault resolution requires Entra ID authentication. +* **Configuration not refreshing** — Make sure you are calling `config.refresh()` periodically (e.g., before each request in a web app). The provider does not auto-refresh in the background. +* **Startup failures** — If the store is unreachable during startup, the provider will retry until `startup_timeout` (default 100 seconds) is exceeded. Increase this value if your store is expected to have high latency. + ## Next steps Check out our Django and Flask examples to see how to use the provider in a web application. diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/samples/README.md b/sdk/appconfiguration/azure-appconfiguration-provider/samples/README.md index 23c29c22dbf8..90b21ea17f95 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/samples/README.md +++ b/sdk/appconfiguration/azure-appconfiguration-provider/samples/README.md @@ -46,7 +46,7 @@ pip install azure.appconfiguration.provider | File | Description | |-------------|-------------| -| aad_sample.py | demos connecting to app configuration with Azure Active Directory | +| entra_id_sample.py | demos connecting to app configuration with Entra ID | | connection_string_sample.py | demos connecting to app configuration with a Connection String | | key_vault_reference_sample.py | demos resolving key vault references with App Configuration | diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/samples/aad_sample.py b/sdk/appconfiguration/azure-appconfiguration-provider/samples/aad_sample.py index 451aa39ccbfb..50f7a673ec9c 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/samples/aad_sample.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/samples/aad_sample.py @@ -12,17 +12,33 @@ credential = get_credential(authority) kwargs = get_client_modifications() -# Connecting to Azure App Configuration using AAD -config = load(endpoint=endpoint, credential=credential, **kwargs) +# [START create_provider_aad] +import os +from azure.appconfiguration.provider import load +from azure.identity import DefaultAzureCredential + +endpoint = os.environ["APPCONFIGURATION_ENDPOINT_STRING"] +credential = DefaultAzureCredential() + +# Connecting to Azure App Configuration using Entra ID +config = load(endpoint=endpoint, credential=credential) +# [END create_provider_aad] print(config["message"]) -# Connecting to Azure App Configuration using AAD and trim key prefixes +# [START trim_prefixes_aad] +from azure.appconfiguration.provider import load + +# Connecting to Azure App Configuration using Entra ID and trim key prefixes trimmed = ["test."] config = load(endpoint=endpoint, credential=credential, trim_prefixes=trimmed, **kwargs) +# [END trim_prefixes_aad] print(config["message"]) +# [START setting_selector_aad] +from azure.appconfiguration.provider import load, SettingSelector + # Connection to Azure App Configuration using SettingSelector selects = [SettingSelector(key_filter="message*")] config = load( @@ -33,6 +49,7 @@ feature_flag_selectors=None, **kwargs ) +# [END setting_selector_aad] print("message found: " + str("message" in config)) print("test.message found: " + str("test.message" in config)) 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 b30cbb174dcf..71b1aa2c7ac0 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/samples/async_aad_sample.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/samples/async_aad_sample.py @@ -16,16 +16,22 @@ async def main(): credential = get_credential(authority, is_async=True) kwargs = get_client_modifications() - # Connecting to Azure App Configuration using AAD + # [START create_provider_aad_async] + from azure.appconfiguration.provider.aio import load + + # Connecting to Azure App Configuration using Entra ID config = await load(endpoint=endpoint, credential=credential, **kwargs) print(config["message"]) await credential.close() await config.close() + # [END create_provider_aad_async] + # [START trim_prefixes_aad_async] # Connecting to Azure App Configuration using AAD and trim key prefixes trimmed = ["test."] config = await load(endpoint=endpoint, credential=credential, trim_prefixes=trimmed, **kwargs) + # [END trim_prefixes_aad_async] print(config["message"]) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/samples/async_entra_id_sample.py b/sdk/appconfiguration/azure-appconfiguration-provider/samples/async_entra_id_sample.py new file mode 100644 index 000000000000..71b1aa2c7ac0 --- /dev/null +++ b/sdk/appconfiguration/azure-appconfiguration-provider/samples/async_entra_id_sample.py @@ -0,0 +1,53 @@ +# ------------------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# ------------------------------------------------------------------------- +import os +import asyncio +from sample_utilities import get_authority, get_credential, get_client_modifications +from azure.appconfiguration.provider.aio import load +from azure.appconfiguration.provider import SettingSelector + + +async def main(): + endpoint = os.environ["APPCONFIGURATION_ENDPOINT_STRING"] + authority = get_authority(endpoint) + credential = get_credential(authority, is_async=True) + kwargs = get_client_modifications() + + # [START create_provider_aad_async] + from azure.appconfiguration.provider.aio import load + + # Connecting to Azure App Configuration using Entra ID + config = await load(endpoint=endpoint, credential=credential, **kwargs) + print(config["message"]) + + await credential.close() + await config.close() + # [END create_provider_aad_async] + + # [START trim_prefixes_aad_async] + # Connecting to Azure App Configuration using AAD and trim key prefixes + trimmed = ["test."] + config = await load(endpoint=endpoint, credential=credential, trim_prefixes=trimmed, **kwargs) + # [END trim_prefixes_aad_async] + + print(config["message"]) + + await credential.close() + await config.close() + + # Connection to Azure App Configuration using SettingSelector + selects = [SettingSelector(key_filter="message*")] + config = await load(endpoint=endpoint, credential=credential, selects=selects, **kwargs) + + print("message found: " + str("message" in config)) + print("test.message found: " + str("test.message" in config)) + + await credential.close() + await config.close() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/samples/async_key_vault_reference_provided_clients_sample.py b/sdk/appconfiguration/azure-appconfiguration-provider/samples/async_key_vault_reference_provided_clients_sample.py index 0739d46128d6..60edf9a1fde1 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/samples/async_key_vault_reference_provided_clients_sample.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/samples/async_key_vault_reference_provided_clients_sample.py @@ -17,7 +17,7 @@ async def main(): credential = get_credential(authority, is_async=True) kwargs = get_client_modifications() - # Connection to Azure App Configuration using AAD with Provided Client + # Connection to Azure App Configuration using Entra ID with Provided Client client_configs = {key_vault_uri: {"credential": credential}} selects = [SettingSelector(key_filter="*", label_filter="prod")] config = await load( 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 e276705368d7..d18848e3ef93 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 @@ -16,7 +16,7 @@ async def main(): credential = get_credential(authority, is_async=True) kwargs = get_client_modifications() - # Connection to Azure App Configuration using AAD and Resolving Key Vault References + # Connection to Azure App Configuration using Entra ID and Resolving Key Vault References selects = [SettingSelector(key_filter="*", label_filter="prod")] config = await load( 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 3d4d78487d49..cce57a3a2cc8 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/samples/connection_string_sample.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/samples/connection_string_sample.py @@ -10,21 +10,36 @@ kwargs = get_client_modifications() connection_string = os.environ["APPCONFIGURATION_CONNECTION_STRING"] +# [START create_provider_connection_string] +import os +from azure.appconfiguration.provider import load + +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) +# [END create_provider_connection_string] print(config["message"]) print(config["my_json"]["key"]) +# [START trim_prefixes_connection_string] +from azure.appconfiguration.provider import load + # Connecting to Azure App Configuration using connection string and trimmed key prefixes trimmed = ["test."] config = load(connection_string=connection_string, trim_prefixes=trimmed, **kwargs) +# [END trim_prefixes_connection_string] print(config["message"]) +# [START setting_selector_connection_string] +from azure.appconfiguration.provider import load, SettingSelector + # Connection to Azure App Configuration using SettingSelector selects = [SettingSelector(key_filter="message*")] config = load(connection_string=connection_string, selects=selects, **kwargs) +# [END setting_selector_connection_string] print("message found: " + str("message" in config)) print("test.message found: " + str("test.message" in config)) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/samples/entra_id_sample.py b/sdk/appconfiguration/azure-appconfiguration-provider/samples/entra_id_sample.py new file mode 100644 index 000000000000..50f7a673ec9c --- /dev/null +++ b/sdk/appconfiguration/azure-appconfiguration-provider/samples/entra_id_sample.py @@ -0,0 +1,56 @@ +# ------------------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# ------------------------------------------------------------------------- +import os +from sample_utilities import get_authority, get_credential, get_client_modifications +from azure.appconfiguration.provider import load, SettingSelector + +endpoint = os.environ.get("APPCONFIGURATION_ENDPOINT_STRING") +authority = get_authority(endpoint) +credential = get_credential(authority) +kwargs = get_client_modifications() + +# [START create_provider_aad] +import os +from azure.appconfiguration.provider import load +from azure.identity import DefaultAzureCredential + +endpoint = os.environ["APPCONFIGURATION_ENDPOINT_STRING"] +credential = DefaultAzureCredential() + +# Connecting to Azure App Configuration using Entra ID +config = load(endpoint=endpoint, credential=credential) +# [END create_provider_aad] + +print(config["message"]) + +# [START trim_prefixes_aad] +from azure.appconfiguration.provider import load + +# Connecting to Azure App Configuration using Entra ID and trim key prefixes +trimmed = ["test."] +config = load(endpoint=endpoint, credential=credential, trim_prefixes=trimmed, **kwargs) +# [END trim_prefixes_aad] + +print(config["message"]) + +# [START setting_selector_aad] +from azure.appconfiguration.provider import load, SettingSelector + +# Connection to Azure App Configuration using SettingSelector +selects = [SettingSelector(key_filter="message*")] +config = load( + endpoint=endpoint, + credential=credential, + selects=selects, + feature_flag_enabled=True, + feature_flag_selectors=None, + **kwargs +) +# [END setting_selector_aad] + +print("message found: " + str("message" in config)) +print("test.message found: " + str("test.message" in config)) +print("feature_flag_enabled found: " + str(config.get("feature_management"))) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/samples/key_vault_reference_customized_clients_sample.py b/sdk/appconfiguration/azure-appconfiguration-provider/samples/key_vault_reference_customized_clients_sample.py index 083166a11c52..ff702db632fc 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/samples/key_vault_reference_customized_clients_sample.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/samples/key_vault_reference_customized_clients_sample.py @@ -13,11 +13,15 @@ credential = get_credential(authority) kwargs = get_client_modifications() -# Connection to Azure App Configuration using AAD with Provided Client +# [START key_vault_reference_customized_clients] +from azure.appconfiguration.provider import load, SettingSelector + +# Connection to Azure App Configuration using Entra ID with Provided Client client_configs = {key_vault_uri: {"credential": credential}} selects = [SettingSelector(key_filter="*", label_filter="prod")] config = load( endpoint=endpoint, credential=credential, keyvault_client_configs=client_configs, selects=selects, **kwargs ) +# [END key_vault_reference_customized_clients] print(config["secret"]) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/samples/key_vault_reference_sample.py b/sdk/appconfiguration/azure-appconfiguration-provider/samples/key_vault_reference_sample.py index 5454494bb1f5..08305c3e1dc4 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/samples/key_vault_reference_sample.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/samples/key_vault_reference_sample.py @@ -13,9 +13,13 @@ credential = get_credential(authority) kwargs = get_client_modifications() -# Connection to Azure App Configuration using AAD and Resolving Key Vault References +# [START key_vault_reference] +from azure.appconfiguration.provider import load, SettingSelector + +# Connection to Azure App Configuration using Entra ID and Resolving Key Vault References selects = [SettingSelector(key_filter="*", label_filter="prod")] config = load(endpoint=endpoint, credential=credential, keyvault_credential=credential, selects=selects, **kwargs) +# [END key_vault_reference] print(config["secret"]) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/samples/refresh_sample.py b/sdk/appconfiguration/azure-appconfiguration-provider/samples/refresh_sample.py index e3bd615ffc55..8ab5835c71df 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/samples/refresh_sample.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/samples/refresh_sample.py @@ -31,6 +31,9 @@ def my_callback_on_fail(_): rand = random.random() watch_key = WatchKey("message" + str(rand)) +# [START refresh_provider] +from azure.appconfiguration.provider import load, WatchKey + # Connecting to Azure App Configuration using connection string, and refreshing when the configuration setting message # changes config = load( @@ -40,6 +43,7 @@ def my_callback_on_fail(_): on_refresh_error=my_callback_on_fail, **kwargs, ) +# [END refresh_provider] print(config["message"]) print(config["my_json"]["key"]) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/samples/refresh_sample_feature_flags.py b/sdk/appconfiguration/azure-appconfiguration-provider/samples/refresh_sample_feature_flags.py index 3f4a260f32ea..3894006d58e4 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/samples/refresh_sample_feature_flags.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/samples/refresh_sample_feature_flags.py @@ -30,6 +30,9 @@ def my_callback_on_fail(_): print("Refresh failed!") +# [START refresh_feature_flags] +from azure.appconfiguration.provider import load, WatchKey + # Connecting to Azure App Configuration using connection string, and refreshing when the configuration setting message # changes config = load( @@ -42,6 +45,7 @@ def my_callback_on_fail(_): feature_flag_refresh_enabled=True, **kwargs, ) +# [END refresh_feature_flags] print(config["message"]) print(config["my_json"]["key"]) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/samples/snapshot_sample.py b/sdk/appconfiguration/azure-appconfiguration-provider/samples/snapshot_sample.py index f382c5aa752e..8abeb415f8a8 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/samples/snapshot_sample.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/samples/snapshot_sample.py @@ -70,25 +70,32 @@ print(f"Created snapshot: {created_snapshot.name} with status: {created_snapshot.status}") +# [START load_snapshot] +from azure.appconfiguration.provider import load, SettingSelector + # Step 2: Loading configuration settings from the snapshot snapshot_selects = [SettingSelector(snapshot_name=snapshot_name)] config = load(endpoint=endpoint, credential=credential, selects=snapshot_selects) +# [END load_snapshot] print("Configuration settings from snapshot:") for key, value in config.items(): print(f"{key}: {value}") +# [START load_snapshot_mixed] # Step 3: Combine snapshot with regular selectors (later selectors take precedence) mixed_selects = [ SettingSelector(snapshot_name=snapshot_name), # Load all settings from snapshot SettingSelector(key_filter="override.*", label_filter="prod"), # Also load specific override settings ] config_mixed = load(endpoint=endpoint, credential=credential, selects=mixed_selects) +# [END load_snapshot_mixed] print("\nMixed configuration (snapshot + filtered settings):") for key, value in config_mixed.items(): print(f"{key}: {value}") +# [START load_snapshot_feature_flags] # Step 4: Load feature flags from the snapshot (requires feature_flag_enabled=True) feature_flag_selects = [SettingSelector(snapshot_name=snapshot_name)] config_with_flags = load( @@ -97,6 +104,7 @@ selects=feature_flag_selects, feature_flag_enabled=True, ) +# [END load_snapshot_feature_flags] print(f"\nFeature flags loaded: {'feature_management' in config_with_flags}") if "feature_management" in config_with_flags: From e9b252685adf2589d25d53bfda7ab19fe4acb011 Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Wed, 29 Apr 2026 16:34:53 -0700 Subject: [PATCH 2/7] removing aad --- .../samples/aad_sample.py | 56 ------------------- .../samples/async_aad_sample.py | 53 ------------------ 2 files changed, 109 deletions(-) delete mode 100644 sdk/appconfiguration/azure-appconfiguration-provider/samples/aad_sample.py delete mode 100644 sdk/appconfiguration/azure-appconfiguration-provider/samples/async_aad_sample.py diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/samples/aad_sample.py b/sdk/appconfiguration/azure-appconfiguration-provider/samples/aad_sample.py deleted file mode 100644 index 50f7a673ec9c..000000000000 --- a/sdk/appconfiguration/azure-appconfiguration-provider/samples/aad_sample.py +++ /dev/null @@ -1,56 +0,0 @@ -# ------------------------------------------------------------------------ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# ------------------------------------------------------------------------- -import os -from sample_utilities import get_authority, get_credential, get_client_modifications -from azure.appconfiguration.provider import load, SettingSelector - -endpoint = os.environ.get("APPCONFIGURATION_ENDPOINT_STRING") -authority = get_authority(endpoint) -credential = get_credential(authority) -kwargs = get_client_modifications() - -# [START create_provider_aad] -import os -from azure.appconfiguration.provider import load -from azure.identity import DefaultAzureCredential - -endpoint = os.environ["APPCONFIGURATION_ENDPOINT_STRING"] -credential = DefaultAzureCredential() - -# Connecting to Azure App Configuration using Entra ID -config = load(endpoint=endpoint, credential=credential) -# [END create_provider_aad] - -print(config["message"]) - -# [START trim_prefixes_aad] -from azure.appconfiguration.provider import load - -# Connecting to Azure App Configuration using Entra ID and trim key prefixes -trimmed = ["test."] -config = load(endpoint=endpoint, credential=credential, trim_prefixes=trimmed, **kwargs) -# [END trim_prefixes_aad] - -print(config["message"]) - -# [START setting_selector_aad] -from azure.appconfiguration.provider import load, SettingSelector - -# Connection to Azure App Configuration using SettingSelector -selects = [SettingSelector(key_filter="message*")] -config = load( - endpoint=endpoint, - credential=credential, - selects=selects, - feature_flag_enabled=True, - feature_flag_selectors=None, - **kwargs -) -# [END setting_selector_aad] - -print("message found: " + str("message" in config)) -print("test.message found: " + str("test.message" in config)) -print("feature_flag_enabled found: " + str(config.get("feature_management"))) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/samples/async_aad_sample.py b/sdk/appconfiguration/azure-appconfiguration-provider/samples/async_aad_sample.py deleted file mode 100644 index 71b1aa2c7ac0..000000000000 --- a/sdk/appconfiguration/azure-appconfiguration-provider/samples/async_aad_sample.py +++ /dev/null @@ -1,53 +0,0 @@ -# ------------------------------------------------------------------------ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# ------------------------------------------------------------------------- -import os -import asyncio -from sample_utilities import get_authority, get_credential, get_client_modifications -from azure.appconfiguration.provider.aio import load -from azure.appconfiguration.provider import SettingSelector - - -async def main(): - endpoint = os.environ["APPCONFIGURATION_ENDPOINT_STRING"] - authority = get_authority(endpoint) - credential = get_credential(authority, is_async=True) - kwargs = get_client_modifications() - - # [START create_provider_aad_async] - from azure.appconfiguration.provider.aio import load - - # Connecting to Azure App Configuration using Entra ID - config = await load(endpoint=endpoint, credential=credential, **kwargs) - print(config["message"]) - - await credential.close() - await config.close() - # [END create_provider_aad_async] - - # [START trim_prefixes_aad_async] - # Connecting to Azure App Configuration using AAD and trim key prefixes - trimmed = ["test."] - config = await load(endpoint=endpoint, credential=credential, trim_prefixes=trimmed, **kwargs) - # [END trim_prefixes_aad_async] - - print(config["message"]) - - await credential.close() - await config.close() - - # Connection to Azure App Configuration using SettingSelector - selects = [SettingSelector(key_filter="message*")] - config = await load(endpoint=endpoint, credential=credential, selects=selects, **kwargs) - - print("message found: " + str("message" in config)) - print("test.message found: " + str("test.message" in config)) - - await credential.close() - await config.close() - - -if __name__ == "__main__": - asyncio.run(main()) From 848dc11f05d13c9839ceb3a874760e690e4a115f Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Wed, 29 Apr 2026 16:44:46 -0700 Subject: [PATCH 3/7] fixing a few entra id items --- .../azure-appconfiguration-provider/README.md | 8 ++++---- .../samples/async_entra_id_sample.py | 10 +++++----- .../samples/entra_id_sample.py | 12 ++++++------ 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/README.md b/sdk/appconfiguration/azure-appconfiguration-provider/README.md index d5d7aaff89a5..5162edff7973 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/README.md +++ b/sdk/appconfiguration/azure-appconfiguration-provider/README.md @@ -36,7 +36,7 @@ config = load(connection_string=connection_string) or with Entra ID: - + ```python import os @@ -80,7 +80,7 @@ Currently the Azure App Configuration Provider enables: You can refine or expand the configurations loaded from your store by using `SettingSelector`s. Setting selectors provide a way to pass a key filter and label filter into the provider. - + ```python from azure.appconfiguration.provider import load, SettingSelector @@ -180,7 +180,7 @@ For additional info check out [Dynamic Refresh](https://learn.microsoft.com/azur You can trim the prefix off of keys by providing a list of trimmed key prefixes to the provider. For example, if you have the key(s) like `/application/message` in your configuration store, you could trim `/application/` from them. - + ```python from azure.appconfiguration.provider import load @@ -361,7 +361,7 @@ config = load(endpoint=endpoint, credential=DefaultAzureCredential(), startup_ti The provider includes full async support via the `azure.appconfiguration.provider.aio` module. - + ```python from azure.appconfiguration.provider.aio import load diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/samples/async_entra_id_sample.py b/sdk/appconfiguration/azure-appconfiguration-provider/samples/async_entra_id_sample.py index 71b1aa2c7ac0..89f3189bed00 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/samples/async_entra_id_sample.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/samples/async_entra_id_sample.py @@ -16,7 +16,7 @@ async def main(): credential = get_credential(authority, is_async=True) kwargs = get_client_modifications() - # [START create_provider_aad_async] + # [START create_provider_entra_id_async] from azure.appconfiguration.provider.aio import load # Connecting to Azure App Configuration using Entra ID @@ -25,13 +25,13 @@ async def main(): await credential.close() await config.close() - # [END create_provider_aad_async] + # [END create_provider_entra_id_async] - # [START trim_prefixes_aad_async] - # Connecting to Azure App Configuration using AAD and trim key prefixes + # [START trim_prefixes_entra_id_async] + # Connecting to Azure App Configuration using Entra ID and trim key prefixes trimmed = ["test."] config = await load(endpoint=endpoint, credential=credential, trim_prefixes=trimmed, **kwargs) - # [END trim_prefixes_aad_async] + # [END trim_prefixes_entra_id_async] print(config["message"]) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/samples/entra_id_sample.py b/sdk/appconfiguration/azure-appconfiguration-provider/samples/entra_id_sample.py index 50f7a673ec9c..64c729df149f 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/samples/entra_id_sample.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/samples/entra_id_sample.py @@ -12,7 +12,7 @@ credential = get_credential(authority) kwargs = get_client_modifications() -# [START create_provider_aad] +# [START create_provider_entra_id] import os from azure.appconfiguration.provider import load from azure.identity import DefaultAzureCredential @@ -22,21 +22,21 @@ # Connecting to Azure App Configuration using Entra ID config = load(endpoint=endpoint, credential=credential) -# [END create_provider_aad] +# [END create_provider_entra_id] print(config["message"]) -# [START trim_prefixes_aad] +# [START trim_prefixes_entra_id] from azure.appconfiguration.provider import load # Connecting to Azure App Configuration using Entra ID and trim key prefixes trimmed = ["test."] config = load(endpoint=endpoint, credential=credential, trim_prefixes=trimmed, **kwargs) -# [END trim_prefixes_aad] +# [END trim_prefixes_entra_id] print(config["message"]) -# [START setting_selector_aad] +# [START setting_selector_entra_id] from azure.appconfiguration.provider import load, SettingSelector # Connection to Azure App Configuration using SettingSelector @@ -49,7 +49,7 @@ feature_flag_selectors=None, **kwargs ) -# [END setting_selector_aad] +# [END setting_selector_entra_id] print("message found: " + str("message" in config)) print("test.message found: " + str("test.message" in config)) From 144135f80e4832d24767feded745592d722041ba Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Wed, 29 Apr 2026 17:06:41 -0700 Subject: [PATCH 4/7] updating snippets and samples --- .../azure-appconfiguration-provider/README.md | 127 +++++++++++++----- .../samples/entra_id_sample.py | 73 +++++++++- ...ult_reference_customized_clients_sample.py | 6 +- .../samples/key_vault_reference_sample.py | 28 ++++ .../samples/refresh_sample.py | 4 + 5 files changed, 206 insertions(+), 32 deletions(-) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/README.md b/sdk/appconfiguration/azure-appconfiguration-provider/README.md index 5162edff7973..6b33f1e327e6 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/README.md +++ b/sdk/appconfiguration/azure-appconfiguration-provider/README.md @@ -93,7 +93,7 @@ config = load( selects=selects, feature_flag_enabled=True, feature_flag_selectors=None, - **kwargs + **kwargs, ) ``` @@ -105,14 +105,18 @@ In this example all configuration with empty label and the dev label are loaded. You can filter configuration settings by tags using the `tag_filters` parameter on `SettingSelector`. Tag filters must follow the format `"tagName=tagValue"`. + + ```python from azure.appconfiguration.provider import load, SettingSelector -from azure.identity import DefaultAzureCredential +# Filtering by tags selects = [SettingSelector(key_filter="*", tag_filters=["env=prod"])] -config = load(endpoint=endpoint, credential=DefaultAzureCredential(), selects=selects) +config = load(endpoint=endpoint, credential=credential, selects=selects, **kwargs) ``` + + ### Loading from Snapshots You can load configuration settings from a snapshot by providing `snapshot_name` on `SettingSelector`. When `snapshot_name` is specified, all configuration settings from the snapshot are loaded. Note that `snapshot_name` cannot be used together with `key_filter`, `label_filter`, or `tag_filters`. @@ -168,10 +172,14 @@ config = load( In this example, the sentinel key will be checked for changes no sooner than every 60 seconds. In order to check for changes, the provider's `refresh` method needs to be called. + + ```python config.refresh() ``` + + Once the provider is refreshed, the configurations can be accessed as normal. And if any changes have been made it will be updated with the latest values. If the `refresh_interval` hasn't passed since the last refresh check, the provider will not check for changes. For additional info check out [Dynamic Refresh](https://learn.microsoft.com/azure/azure-app-configuration/enable-dynamic-configuration-python) on MS Learn. @@ -194,11 +202,11 @@ config = load(endpoint=endpoint, credential=credential, trim_prefixes=trimmed, * ### Resolving Key Vault References -Key Vault References can be resolved by providing credentials to your key vault to the provider using `AzureAppConfigurationKeyVaultOptions`. +Key Vault References can be resolved by providing credentials to your key vault to the provider. #### With Credentials -You can provide `AzureAppConfigurationKeyVaultOptions` with a credential and all key vault references will be resolved with it. The provider will attempt to connect to any key vault referenced with the credential provided. +You can provide a `keyvault_credential` and all key vault references will be resolved with it. The provider will attempt to connect to any key vault referenced with the credential provided. @@ -213,9 +221,9 @@ config = load(endpoint=endpoint, credential=credential, keyvault_credential=cred -### With Clients +#### With Client Configs -You can provide `AzureAppConfigurationKeyVaultOptions` with a list of `SecretClients`. +You can provide `keyvault_client_configs` with a mapping of Key Vault URIs to client configurations. This is useful when different Key Vaults require different credentials. @@ -226,80 +234,122 @@ from azure.appconfiguration.provider import load, SettingSelector client_configs = {key_vault_uri: {"credential": credential}} selects = [SettingSelector(key_filter="*", label_filter="prod")] config = load( - endpoint=endpoint, credential=credential, keyvault_client_configs=client_configs, selects=selects, **kwargs + endpoint=endpoint, + credential=credential, + keyvault_client_configs=client_configs, + selects=selects, + **kwargs, ) ``` -### Secret Resolver +#### Secret Resolver + +If no credentials or client configs are provided, a `secret_resolver` can be used. Secret resolver provides a way to return any value you want for a key vault reference. -If no Credentials or Clients are provided a secret resolver can be used. Secret resolver provides a way to return any value you want to a key vault reference. + ```python -from azure.appconfiguration.provider import load, AzureAppConfigurationKeyVaultOptions -from azure.identity import DefaultAzureCredential +from azure.appconfiguration.provider import load + def secret_resolver(uri): return "From Secret Resolver" -key_vault_options = AzureAppConfigurationKeyVaultOptions( - secret_resolver=secret_resolver) -config = load(endpoint=endpoint, credential=DefaultAzureCredential(), key_vault_options=key_vault_options) + +config = load(endpoint=endpoint, credential=credential, secret_resolver=secret_resolver, **kwargs) ``` + + ### Secret Refresh Interval When using Key Vault references, the provider can periodically refresh resolved secrets. By default, secrets are refreshed every 60 seconds. You can customize this with the `secret_refresh_interval` parameter (minimum 1 second). + + ```python +from azure.appconfiguration.provider import load + +# Refresh Key Vault secrets every 120 seconds config = load( endpoint=endpoint, - credential=DefaultAzureCredential(), - key_vault_options=key_vault_options, - secret_refresh_interval=120, # Refresh secrets every 120 seconds + credential=credential, + keyvault_credential=credential, + secret_refresh_interval=120, + **kwargs, ) ``` + + ## Geo Replication The Azure App Configuration Provider library will automatically discover the provided configuration store's replicas and use the replicas if any issue arises. From more information see [Geo-Replication](https://learn.microsoft.com/azure/azure-app-configuration/howto-geo-replication). Replica discovery is enabled by default. If you want to disable it, you can set `replica_discovery_enabled` to `False`. + + ```python from azure.appconfiguration.provider import load -from azure.identity import DefaultAzureCredential -config = load(endpoint=endpoint, credential=DefaultAzureCredential(), replica_discovery_enabled=False) +# Disabling replica discovery +config = load(endpoint=endpoint, credential=credential, replica_discovery_enabled=False, **kwargs) ``` + + You can also enable load balancing to distribute requests across replicas by setting `load_balancing_enabled` to `True`. + + ```python -config = load(endpoint=endpoint, credential=DefaultAzureCredential(), load_balancing_enabled=True) +from azure.appconfiguration.provider import load + +# Enabling load balancing across replicas +config = load(endpoint=endpoint, credential=credential, load_balancing_enabled=True, **kwargs) ``` + + ## Loading Feature Flags Feature Flags can be loaded from config stores using the provider. Feature flags are loaded as a dictionary of key/value pairs stored in the provider under the `feature_management`, then `feature_flags`. + + ```python -config = load(endpoint=endpoint, credential=DefaultAzureCredential(), feature_flag_enabled=True) +from azure.appconfiguration.provider import load + +config = load(endpoint=endpoint, credential=credential, feature_flag_enabled=True, **kwargs) alpha = config["feature_management"]["feature_flags"]["Alpha"] print(alpha["enabled"]) ``` + + By default all feature flags with no label are loaded when `feature_flag_enabled` is set to `True`. If you want to load feature flags with a specific label you can use `SettingSelector` to filter the feature flags. + + ```python from azure.appconfiguration.provider import load, SettingSelector -config = load(endpoint=endpoint, credential=DefaultAzureCredential(), feature_flag_enabled=True, feature_flag_selectors=[SettingSelector(key_filter="*", label_filter="dev")]) +config = load( + endpoint=endpoint, + credential=credential, + feature_flag_enabled=True, + feature_flag_selectors=[SettingSelector(key_filter="*", label_filter="dev")], + **kwargs, +) alpha = config["feature_management"]["feature_flags"]["Alpha"] print(alpha["enabled"]) ``` + + To enable refresh for feature flags you need to enable refresh. This will allow the provider to refresh feature flags the same way it refreshes configurations. Unlike configurations, all loaded feature flags are monitored for changes and will cause a refresh. Refresh of configuration settings and feature flags are independent of each other. Both are trigged by the `refresh` method, but a feature flag changing will not cause a refresh of configurations and vice versa. Also, if refresh for configuration settings is not enabled, feature flags can still be enabled for refresh. @@ -327,36 +377,53 @@ config = load( Configuration settings with a JSON content type (e.g., `application/json`) are automatically deserialized into their corresponding Python objects when loaded by the provider. + + ```python -# If a setting "app/config" has value '{"timeout": 30, "retries": 3}' with content type "application/json" -config = load(endpoint=endpoint, credential=DefaultAzureCredential()) -app_config = config["app/config"] # Returns a dict: {"timeout": 30, "retries": 3} -print(app_config["timeout"]) # 30 +from azure.appconfiguration.provider import load + +# Settings with JSON content type are automatically deserialized +config = load(endpoint=endpoint, credential=credential, **kwargs) +app_config = config["app/config"] # Returns a dict if the value is JSON +print(app_config["timeout"]) ``` + + ## Configuration Mapper You can provide a `configuration_mapper` callback to transform configuration settings before they are added to the provider. + + ```python from azure.appconfiguration.provider import load -from azure.identity import DefaultAzureCredential + def my_mapper(setting): # Transform the setting as needed setting.value = setting.value.strip() -config = load(endpoint=endpoint, credential=DefaultAzureCredential(), configuration_mapper=my_mapper) + +config = load(endpoint=endpoint, credential=credential, configuration_mapper=my_mapper, **kwargs) ``` + + ## Startup Timeout The provider supports configurable startup timeout with automatic retry. By default, the provider allows 100 seconds for the initial load from Azure App Configuration. You can customize this with the `startup_timeout` parameter. + + ```python -config = load(endpoint=endpoint, credential=DefaultAzureCredential(), startup_timeout=200) +from azure.appconfiguration.provider import load + +config = load(endpoint=endpoint, credential=credential, startup_timeout=200, **kwargs) ``` + + ## Async Support The provider includes full async support via the `azure.appconfiguration.provider.aio` module. diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/samples/entra_id_sample.py b/sdk/appconfiguration/azure-appconfiguration-provider/samples/entra_id_sample.py index 64c729df149f..f98abee34ee3 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/samples/entra_id_sample.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/samples/entra_id_sample.py @@ -47,10 +47,81 @@ selects=selects, feature_flag_enabled=True, feature_flag_selectors=None, - **kwargs + **kwargs, ) # [END setting_selector_entra_id] print("message found: " + str("message" in config)) print("test.message found: " + str("test.message" in config)) print("feature_flag_enabled found: " + str(config.get("feature_management"))) + +# [START tag_filters] +from azure.appconfiguration.provider import load, SettingSelector + +# Filtering by tags +selects = [SettingSelector(key_filter="*", tag_filters=["env=prod"])] +config = load(endpoint=endpoint, credential=credential, selects=selects, **kwargs) +# [END tag_filters] + +# [START geo_replication_disable_discovery] +from azure.appconfiguration.provider import load + +# Disabling replica discovery +config = load(endpoint=endpoint, credential=credential, replica_discovery_enabled=False, **kwargs) +# [END geo_replication_disable_discovery] + +# [START geo_replication_load_balancing] +from azure.appconfiguration.provider import load + +# Enabling load balancing across replicas +config = load(endpoint=endpoint, credential=credential, load_balancing_enabled=True, **kwargs) +# [END geo_replication_load_balancing] + +# [START feature_flag_loading] +from azure.appconfiguration.provider import load + +config = load(endpoint=endpoint, credential=credential, feature_flag_enabled=True, **kwargs) +alpha = config["feature_management"]["feature_flags"]["Alpha"] +print(alpha["enabled"]) +# [END feature_flag_loading] + +# [START feature_flag_selector] +from azure.appconfiguration.provider import load, SettingSelector + +config = load( + endpoint=endpoint, + credential=credential, + feature_flag_enabled=True, + feature_flag_selectors=[SettingSelector(key_filter="*", label_filter="dev")], + **kwargs, +) +alpha = config["feature_management"]["feature_flags"]["Alpha"] +print(alpha["enabled"]) +# [END feature_flag_selector] + +# [START json_content_type] +from azure.appconfiguration.provider import load + +# Settings with JSON content type are automatically deserialized +config = load(endpoint=endpoint, credential=credential, **kwargs) +app_config = config["app/config"] # Returns a dict if the value is JSON +print(app_config["timeout"]) +# [END json_content_type] + +# [START configuration_mapper] +from azure.appconfiguration.provider import load + + +def my_mapper(setting): + # Transform the setting as needed + setting.value = setting.value.strip() + + +config = load(endpoint=endpoint, credential=credential, configuration_mapper=my_mapper, **kwargs) +# [END configuration_mapper] + +# [START startup_timeout] +from azure.appconfiguration.provider import load + +config = load(endpoint=endpoint, credential=credential, startup_timeout=200, **kwargs) +# [END startup_timeout] diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/samples/key_vault_reference_customized_clients_sample.py b/sdk/appconfiguration/azure-appconfiguration-provider/samples/key_vault_reference_customized_clients_sample.py index ff702db632fc..220ac725889c 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/samples/key_vault_reference_customized_clients_sample.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/samples/key_vault_reference_customized_clients_sample.py @@ -20,7 +20,11 @@ client_configs = {key_vault_uri: {"credential": credential}} selects = [SettingSelector(key_filter="*", label_filter="prod")] config = load( - endpoint=endpoint, credential=credential, keyvault_client_configs=client_configs, selects=selects, **kwargs + endpoint=endpoint, + credential=credential, + keyvault_client_configs=client_configs, + selects=selects, + **kwargs, ) # [END key_vault_reference_customized_clients] diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/samples/key_vault_reference_sample.py b/sdk/appconfiguration/azure-appconfiguration-provider/samples/key_vault_reference_sample.py index 08305c3e1dc4..e1d8fcb774c9 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/samples/key_vault_reference_sample.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/samples/key_vault_reference_sample.py @@ -23,3 +23,31 @@ # [END key_vault_reference] print(config["secret"]) + +# [START key_vault_reference_secret_resolver] +from azure.appconfiguration.provider import load + + +def secret_resolver(uri): + return "From Secret Resolver" + + +config = load(endpoint=endpoint, credential=credential, secret_resolver=secret_resolver, **kwargs) +# [END key_vault_reference_secret_resolver] + +print(config["secret"]) + +# [START key_vault_reference_secret_refresh_interval] +from azure.appconfiguration.provider import load + +# Refresh Key Vault secrets every 120 seconds +config = load( + endpoint=endpoint, + credential=credential, + keyvault_credential=credential, + secret_refresh_interval=120, + **kwargs, +) +# [END key_vault_reference_secret_refresh_interval] + +print(config["secret"]) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/samples/refresh_sample.py b/sdk/appconfiguration/azure-appconfiguration-provider/samples/refresh_sample.py index 8ab5835c71df..2836f7cc0664 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/samples/refresh_sample.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/samples/refresh_sample.py @@ -48,6 +48,10 @@ def my_callback_on_fail(_): print(config["message"]) print(config["my_json"]["key"]) +# [START refresh_call] +config.refresh() +# [END refresh_call] + # Updating the configuration setting configuration_setting.value = "Hello World Updated!" From 4e71ae0314642540c6513c8ccfe4d9dc8c7be569 Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Wed, 6 May 2026 09:34:18 -0700 Subject: [PATCH 5/7] review items --- .../azure-appconfiguration-provider/README.md | 32 +++++++++---------- .../samples/entra_id_sample.py | 6 ++-- .../samples/refresh_sample.py | 14 ++++++-- .../samples/refresh_sample_feature_flags.py | 17 ++++++++-- 4 files changed, 45 insertions(+), 24 deletions(-) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/README.md b/sdk/appconfiguration/azure-appconfiguration-provider/README.md index 6b33f1e327e6..0be2ab65b615 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/README.md +++ b/sdk/appconfiguration/azure-appconfiguration-provider/README.md @@ -99,7 +99,7 @@ config = load( -In this example all configuration with empty label and the dev label are loaded. Because the dev selector is listed last, any configurations from dev take priority over those with `(No Label)` when duplicates are found. +In this example, configuration settings with keys matching `message*` are loaded. Feature flags are also enabled, so the default feature flags (those with no label) will be loaded. ### Filtering by Tags @@ -119,7 +119,7 @@ config = load(endpoint=endpoint, credential=credential, selects=selects, **kwarg ### Loading from Snapshots -You can load configuration settings from a snapshot by providing `snapshot_name` on `SettingSelector`. When `snapshot_name` is specified, all configuration settings from the snapshot are loaded. Note that `snapshot_name` cannot be used together with `key_filter`, `label_filter`, or `tag_filters`. +You can load configuration settings from a snapshot by providing `snapshot_name` on `SettingSelector`. When `snapshot_name` is specified, all configuration settings from the snapshot are loaded. Note that `snapshot_name` cannot be used together with `key_filter`, `label_filter`, or `tag_filters`. In the examples below, `endpoint`, `credential`, and `snapshot_name` are assumed to be defined. See the [snapshot sample](samples/snapshot_sample.py) for complete setup. @@ -155,16 +155,15 @@ The provider can be configured to refresh configurations from the store on a set ```python +import os from azure.appconfiguration.provider import load, WatchKey -# Connecting to Azure App Configuration using connection string, and refreshing when the configuration setting message -# changes +connection_string = os.environ["APPCONFIGURATION_CONNECTION_STRING"] + config = load( connection_string=connection_string, - refresh_on=[watch_key], - refresh_interval=1, - on_refresh_error=my_callback_on_fail, - **kwargs, + refresh_on=[WatchKey("Sentinel")], + refresh_interval=60, ) ``` @@ -316,7 +315,7 @@ config = load(endpoint=endpoint, credential=credential, load_balancing_enabled=T ## Loading Feature Flags -Feature Flags can be loaded from config stores using the provider. Feature flags are loaded as a dictionary of key/value pairs stored in the provider under the `feature_management`, then `feature_flags`. +Feature Flags can be loaded from config stores using the provider. Feature flags are loaded as a list of feature flag objects stored in the provider under `feature_management`, then `feature_flags`. @@ -324,7 +323,8 @@ Feature Flags can be loaded from config stores using the provider. Feature flags from azure.appconfiguration.provider import load config = load(endpoint=endpoint, credential=credential, feature_flag_enabled=True, **kwargs) -alpha = config["feature_management"]["feature_flags"]["Alpha"] +feature_flags = config["feature_management"]["feature_flags"] +alpha = next(flag for flag in feature_flags if flag["id"] == "Alpha") print(alpha["enabled"]) ``` @@ -344,7 +344,8 @@ config = load( feature_flag_selectors=[SettingSelector(key_filter="*", label_filter="dev")], **kwargs, ) -alpha = config["feature_management"]["feature_flags"]["Alpha"] +feature_flags = config["feature_management"]["feature_flags"] +alpha = next(flag for flag in feature_flags if flag["id"] == "Alpha") print(alpha["enabled"]) ``` @@ -355,19 +356,18 @@ To enable refresh for feature flags you need to enable refresh. This will allow ```python +import os from azure.appconfiguration.provider import load, WatchKey -# Connecting to Azure App Configuration using connection string, and refreshing when the configuration setting message -# changes +connection_string = os.environ["APPCONFIGURATION_CONNECTION_STRING"] + config = load( connection_string=connection_string, refresh_on=[WatchKey("message")], refresh_on_feature_flags=True, - refresh_interval=1, - on_refresh_error=my_callback_on_fail, + refresh_interval=60, feature_flag_enabled=True, feature_flag_refresh_enabled=True, - **kwargs, ) ``` diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/samples/entra_id_sample.py b/sdk/appconfiguration/azure-appconfiguration-provider/samples/entra_id_sample.py index f98abee34ee3..c6869bc6a9e6 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/samples/entra_id_sample.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/samples/entra_id_sample.py @@ -81,7 +81,8 @@ from azure.appconfiguration.provider import load config = load(endpoint=endpoint, credential=credential, feature_flag_enabled=True, **kwargs) -alpha = config["feature_management"]["feature_flags"]["Alpha"] +feature_flags = config["feature_management"]["feature_flags"] +alpha = next(flag for flag in feature_flags if flag["id"] == "Alpha") print(alpha["enabled"]) # [END feature_flag_loading] @@ -95,7 +96,8 @@ feature_flag_selectors=[SettingSelector(key_filter="*", label_filter="dev")], **kwargs, ) -alpha = config["feature_management"]["feature_flags"]["Alpha"] +feature_flags = config["feature_management"]["feature_flags"] +alpha = next(flag for flag in feature_flags if flag["id"] == "Alpha") print(alpha["enabled"]) # [END feature_flag_selector] diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/samples/refresh_sample.py b/sdk/appconfiguration/azure-appconfiguration-provider/samples/refresh_sample.py index 2836f7cc0664..6943a3660f93 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/samples/refresh_sample.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/samples/refresh_sample.py @@ -32,10 +32,19 @@ def my_callback_on_fail(_): watch_key = WatchKey("message" + str(rand)) # [START refresh_provider] +import os from azure.appconfiguration.provider import load, WatchKey -# Connecting to Azure App Configuration using connection string, and refreshing when the configuration setting message -# changes +connection_string = os.environ["APPCONFIGURATION_CONNECTION_STRING"] + +config = load( + connection_string=connection_string, + refresh_on=[WatchKey("Sentinel")], + refresh_interval=60, +) +# [END refresh_provider] + +# Reload with test-specific configuration config = load( connection_string=connection_string, refresh_on=[watch_key], @@ -43,7 +52,6 @@ def my_callback_on_fail(_): on_refresh_error=my_callback_on_fail, **kwargs, ) -# [END refresh_provider] print(config["message"]) print(config["my_json"]["key"]) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/samples/refresh_sample_feature_flags.py b/sdk/appconfiguration/azure-appconfiguration-provider/samples/refresh_sample_feature_flags.py index 3894006d58e4..e87b2abc569d 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/samples/refresh_sample_feature_flags.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/samples/refresh_sample_feature_flags.py @@ -31,10 +31,22 @@ def my_callback_on_fail(_): # [START refresh_feature_flags] +import os from azure.appconfiguration.provider import load, WatchKey -# Connecting to Azure App Configuration using connection string, and refreshing when the configuration setting message -# changes +connection_string = os.environ["APPCONFIGURATION_CONNECTION_STRING"] + +config = load( + connection_string=connection_string, + refresh_on=[WatchKey("message")], + refresh_on_feature_flags=True, + refresh_interval=60, + feature_flag_enabled=True, + feature_flag_refresh_enabled=True, +) +# [END refresh_feature_flags] + +# Reload with test-specific configuration config = load( connection_string=connection_string, refresh_on=[WatchKey("message")], @@ -45,7 +57,6 @@ def my_callback_on_fail(_): feature_flag_refresh_enabled=True, **kwargs, ) -# [END refresh_feature_flags] print(config["message"]) print(config["my_json"]["key"]) From f273d3515074b57cd901d74e48bc7123d304fd68 Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Wed, 6 May 2026 10:04:47 -0700 Subject: [PATCH 6/7] Update test_azureappconfigurationproviderbase.py --- .../tests/test_azureappconfigurationproviderbase.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_azureappconfigurationproviderbase.py b/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_azureappconfigurationproviderbase.py index 91aaf423ffea..5073f2afad31 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_azureappconfigurationproviderbase.py +++ b/sdk/appconfiguration/azure-appconfiguration-provider/tests/test_azureappconfigurationproviderbase.py @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------- import unittest import time -import datetime import json import base64 from unittest.mock import patch, Mock From 69500dc530b5ae6342b13f3b29ffe94415b418b4 Mon Sep 17 00:00:00 2001 From: Matt Metcalf Date: Wed, 6 May 2026 10:10:26 -0700 Subject: [PATCH 7/7] Update README.md --- sdk/appconfiguration/azure-appconfiguration-provider/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/appconfiguration/azure-appconfiguration-provider/README.md b/sdk/appconfiguration/azure-appconfiguration-provider/README.md index 0be2ab65b615..50edd9709ebb 100644 --- a/sdk/appconfiguration/azure-appconfiguration-provider/README.md +++ b/sdk/appconfiguration/azure-appconfiguration-provider/README.md @@ -119,7 +119,7 @@ config = load(endpoint=endpoint, credential=credential, selects=selects, **kwarg ### Loading from Snapshots -You can load configuration settings from a snapshot by providing `snapshot_name` on `SettingSelector`. When `snapshot_name` is specified, all configuration settings from the snapshot are loaded. Note that `snapshot_name` cannot be used together with `key_filter`, `label_filter`, or `tag_filters`. In the examples below, `endpoint`, `credential`, and `snapshot_name` are assumed to be defined. See the [snapshot sample](samples/snapshot_sample.py) for complete setup. +You can load configuration settings from a snapshot by providing `snapshot_name` on `SettingSelector`. When `snapshot_name` is specified, all configuration settings from the snapshot are loaded. Note that `snapshot_name` cannot be used together with `key_filter`, `label_filter`, or `tag_filters`. In the examples below, `endpoint`, `credential`, and `snapshot_name` are assumed to be defined. See the [snapshot sample](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/appconfiguration/azure-appconfiguration-provider/samples/snapshot_sample.py) for complete setup.