-
Notifications
You must be signed in to change notification settings - Fork 3.2k
App Config Provider Module Level Load #28241
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…yVaultOptions to be Keyword only.
|
API change check APIView has identified API level changes in this PR and created following API reviews. |
...appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/__init__.py
Outdated
Show resolved
Hide resolved
...-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py
Outdated
Show resolved
Hide resolved
|
|
||
| class AzureAppConfigurationProvider: | ||
| #pylint:disable=protected-access | ||
| def loadProvider(*, connection_string=None, endpoint=None, credential=None, **kwargs): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Type hints should go in the method signature, and we should also add overloads.
The Provider object itself currently has no methods outside of the implemented protocols - so we can simply document the return type as a Mapping. However once we start to add things like the refresh, which may expose other methods, we would need to update this (so that the ref docs link correctly).
| def loadProvider(*, connection_string=None, endpoint=None, credential=None, **kwargs): | |
| @overload | |
| def load_provider(endpoint: str, credential: TokenCredential, **kwargs) -> Mapping[str, str]: | |
| ... | |
| @overload | |
| def load_provider(connection_string: str, **kwargs) -> Mapping[str, str]: | |
| ... | |
| def load_provider(*args, **kwargs) -> Mapping[str, str]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have made some of this modification, but something seems wrong. endpoint and the other method variables are put into kwargs, not args.
Also, it seems like Mapping isn't a typing type. I tried making a new one but it didn't work.
| return key_vault_options.secret_resolver(config.secret_id) | ||
|
|
||
| key_vault_identifier = KeyVaultSecretIdentifier(config.secret_id) | ||
| raise AttributeError( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if AttributeError is the right pick here - but we can take a closer look at that later.
...appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/__init__.py
Show resolved
Hide resolved
| def __init__(self, credential=None, secret_clients=None, secret_resolver=None): | ||
| # type: (TokenCredential, List[SecretClient], Callable) -> None | ||
| def __init__(self, *, credential=None, secret_clients=None, secret_resolver=None): | ||
| # type: (TokenCredential, List[SecretClient], Callable[[str], str]) -> None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should use inline type hints and overloads (though I can't see where the Vault endpoint comes from?):
@overload
def __init__(self, credential: TokenCredential,*, secret_resolver: Optional[Callable[[str], str]] = None) -> None
...
@overload
def __init__(self, secret_client: List[SecretClient], *, secret_resolver: Optional[Callcable[[str], str]] = None) -> None
...There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I know what you are talking about, the "vault endpoint" comes from App Configuration inside of the key vault reference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, the options here are, Credential with List of Clients, or List of Clients with Secret Resolver.
The idea is that we default either to the TokenCredential provided or the Secret Resolver.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also ran into an issue with TokenCredential it works fine locally, but for some reason it breaks in the build pipeline, I got ImportError: cannot import name 'TokenCredential'. I was using from azure.core.credential import TokenCredential. https://dev.azure.com/azure-sdk/public/_build/results?buildId=2105499&view=ms.vss-test-web.build-test-results-tab&runId=38663166&resultId=100000&paneView=debug
| sub_types = sub_type.split("+") | ||
| if "json" in sub_types: | ||
| return True | ||
| class AzureAppConfigurationProvider: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that this object holds an instance of a client, we will need to expose this as a context manger and allow it to be closeable (such that the connection can be cleaned up).
This means that we will probably need to still keep the object public so that it can be properly documented....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, this would get a bit awkward when it comes to refresh. Typically it's best to reuse the same clients as it is a costly operation to create new ones.
Also, how would this work given we have one to many clients, for App Configuration (will be with geo-replication) and Key Vault.
...-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py
Outdated
Show resolved
Hide resolved
...-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py
Show resolved
Hide resolved
...figuration-provider/azure/appconfiguration/provider/_azureappconfigurationkeyvaultoptions.py
Show resolved
Hide resolved
...-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py
Show resolved
Hide resolved
| if key_vault_options and ( | ||
| key_vault_options.credential or key_vault_options.secret_clients or key_vault_options.secret_resolver | ||
| ): | ||
| correlation_context += ",UsesKeyVault" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we are adding RequestType and UsesKeyVault tracing right now. Is there a way to get host type and dev environment tracing for python apps?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was already there. This was just moved. I can work on adding those, but not in this branch.
|
|
||
| trimmed_key = config.key | ||
| # Trim the key if it starts with one of the prefixes provided | ||
| for trim in provider._trim_prefixes: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In .NET, we process KeyVault/Json types first, then trim prefixes. Can we do the same here to maintain the same behavior? Changing the order might produce different results, especially when we start supporting feature flags.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you look down a bit, the trimmed_key is only used in the dictionary, _resolve_keyvault_reference uses the config which contains the untrimmed key.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just thinking about how feature flags would look in future - we'd want to maintain the same schema that we use in .net and spring providers, right? So for a feature "Beta", dictionary key would be something like "FeatureManagement:Beta" (or whatever separator makes sense for python).
Since feature flag flattening changes the key being added to dictionary, we shouldn't use trimmed_key as dictionary key.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see two options:
elif config.content_type == FeatureFlagConfigurationSetting._feature_flag_content_type: #pylint: disable=protected-access
feature_management = provider._dict.get("FeatureManagement", {})
feature_management[trimmed_key] = config.valueor
elif config.content_type == FeatureFlagConfigurationSetting._feature_flag_content_type: #pylint: disable=protected-access
provider._dict["FeatureManagement." + trimmed_key] = config.valueThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussed with Matt offline. Since the config dictionary structure is not limited to <string,string> in python, what you have right now should be good.
|
Closing, I think I have gotten all of the comments from here and moved them too #28497. |
Description
Moves the load method of AppConfigurationProvider from a class method to a module method.
All SDK Contribution checklist:
General Guidelines and Best Practices
Testing Guidelines