diff --git a/sdk/appconfiguration/azure-appconfiguration/CHANGELOG.md b/sdk/appconfiguration/azure-appconfiguration/CHANGELOG.md index e506fa3757d5..ea92d1abaf81 100644 --- a/sdk/appconfiguration/azure-appconfiguration/CHANGELOG.md +++ b/sdk/appconfiguration/azure-appconfiguration/CHANGELOG.md @@ -1,7 +1,13 @@ # Release History ## 1.2.0 (2021-07-06) +### Features Added +### Breaking Changes + +### Key Bugs Fixed + +### Fixed ## 1.2.0b2 (2021-06-08) diff --git a/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/__init__.py b/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/__init__.py index 2f4f1e31d339..aa7c98bd603f 100644 --- a/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/__init__.py +++ b/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/__init__.py @@ -10,7 +10,7 @@ # -------------------------------------------------------------------------- from ._azure_appconfiguration_client import AzureAppConfigurationClient -from ._constants import PERCENTAGE, TARGETING, TIME_WINDOW +from ._constants import FILTER_PERCENTAGE, FILTER_TARGETING, FILTER_TIME_WINDOW from ._models import ( ConfigurationSetting, FeatureFlagConfigurationSetting, @@ -26,7 +26,7 @@ "ResourceReadOnlyError", "FeatureFlagConfigurationSetting", "SecretReferenceConfigurationSetting", - "PERCENTAGE", - "TARGETING", - "TIME_WINDOW", + "FILTER_PERCENTAGE", + "FILTER_TARGETING", + "FILTER_TIME_WINDOW", ] diff --git a/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_azure_appconfiguration_client.py b/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_azure_appconfiguration_client.py index 542e4c6d39e7..d31fd0c0faf7 100644 --- a/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_azure_appconfiguration_client.py +++ b/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_azure_appconfiguration_client.py @@ -4,7 +4,7 @@ # license information. # ------------------------------------------------------------------------- import binascii -from typing import Optional, Any, Mapping, Union +from typing import Optional, Any, Union, Mapping, TYPE_CHECKING from requests.structures import CaseInsensitiveDict from azure.core import MatchConditions from azure.core.pipeline import Pipeline @@ -39,13 +39,9 @@ from ._sync_token import SyncTokenPolicy from ._user_agent import USER_AGENT -try: - from typing import TYPE_CHECKING -except ImportError: - TYPE_CHECKING = False - if TYPE_CHECKING: from azure.core.paging import ItemPaged + from azure.core.credentials import TokenCredential class AzureAppConfigurationClient: @@ -54,16 +50,14 @@ class AzureAppConfigurationClient: :param str base_url: base url of the service :param credential: An object which can provide secrets for the app configuration service :type credential: :class:`~azure.appconfiguration.AppConfigConnectionStringCredential` - :keyword Pipeline pipeline: If omitted, the standard pipeline is used. - :keyword HttpTransport transport: If omitted, the standard pipeline is used. - :keyword list[HTTPPolicy] policies: If omitted, the standard pipeline is used. + or :class:`~azure.core.credentials.TokenCredential` """ # pylint:disable=protected-access def __init__(self, base_url, credential, **kwargs): - # type: (str, Any, **Any) -> None + # type: (str, Union[AppConfigConnectionStringCredential, TokenCredential], **Any) -> None try: if not base_url.lower().startswith("http"): base_url = "https://" + base_url @@ -76,7 +70,7 @@ def __init__(self, base_url, credential, **kwargs): self._credential_scopes = base_url.strip("/") + "/.default" self._config = AzureAppConfigurationConfiguration( - credential, base_url, credential_scopes=self._credential_scopes, **kwargs + credential, base_url, credential_scopes=self._credential_scopes, **kwargs # type: ignore ) self._config.user_agent_policy = UserAgentPolicy( base_user_agent=USER_AGENT, **kwargs @@ -92,7 +86,7 @@ def __init__(self, base_url, credential, **kwargs): ) self._impl = AzureAppConfiguration( - credential, base_url, pipeline=pipeline, credential_scopes=self._credential_scopes + credential, base_url, pipeline=pipeline, credential_scopes=self._credential_scopes # type: ignore ) @classmethod @@ -172,7 +166,6 @@ def list_configuration_settings( :type label_filter: str :keyword datetime accept_datetime: filter out ConfigurationSetting created after this datetime :keyword list[str] fields: specify which fields to include in the results. Leave None to include all fields - :keyword dict headers: if "headers" exists, its value (a dict) will be added to the http request header :return: An iterator of :class:`ConfigurationSetting` :rtype: ~azure.core.paging.ItemPaged[ConfigurationSetting] :raises: :class:`HttpResponseError`, :class:`ClientAuthenticationError` @@ -226,7 +219,6 @@ def get_configuration_setting( match_condition=MatchConditions.Unconditionally, # type: Optional[MatchConditions] **kwargs # type: Any ): # type: (...) -> Union[None, ConfigurationSetting] - """Get the matched ConfigurationSetting from Azure App Configuration service :param key: key of the ConfigurationSetting @@ -238,7 +230,6 @@ def get_configuration_setting( :param match_condition: The match condition to use upon the etag :type match_condition: :class:`~azure.core.MatchConditions` :keyword datetime accept_datetime: the retrieved ConfigurationSetting that created no later than this datetime - :keyword dict headers: if "headers" exists, its value (a dict) will be added to the http request header :return: The matched ConfigurationSetting object :rtype: :class:`~azure.appconfiguration.ConfigurationSetting` :raises: :class:`HttpResponseError`, :class:`ClientAuthenticationError`, \ @@ -288,7 +279,6 @@ def add_configuration_setting(self, configuration_setting, **kwargs): :param configuration_setting: the ConfigurationSetting object to be added :type configuration_setting: :class:`~azure.appconfiguration.ConfigurationSetting` - :keyword dict headers: if "headers" exists, its value (a dict) will be added to the http request header :return: The ConfigurationSetting object returned from the App Configuration service :rtype: :class:`~azure.appconfiguration.ConfigurationSetting` :raises: :class:`HttpResponseError`, :class:`ClientAuthenticationError`, :class:`ResourceExistsError` @@ -342,7 +332,7 @@ def set_configuration_setting( :type configuration_setting: :class:`ConfigurationSetting` :param match_condition: The match condition to use upon the etag :type match_condition: :class:`~azure.core.MatchConditions` - :keyword dict headers: if "headers" exists, its value (a dict) will be added to the http request header + :keyword str etag: check if the ConfigurationSetting is changed. Set None to skip checking etag :return: The ConfigurationSetting returned from the service :rtype: :class:`~azure.appconfiguration.ConfigurationSetting` :raises: :class:`HttpResponseError`, :class:`ClientAuthenticationError`, \ @@ -396,7 +386,6 @@ def set_configuration_setting( @distributed_trace def delete_configuration_setting(self, key, label=None, **kwargs): # type: (str, Optional[str], **Any) -> ConfigurationSetting - """Delete a ConfigurationSetting if it exists :param key: key used to identify the ConfigurationSetting @@ -406,7 +395,6 @@ def delete_configuration_setting(self, key, label=None, **kwargs): :keyword str etag: check if the ConfigurationSetting is changed. Set None to skip checking etag :keyword match_condition: The match condition to use upon the etag :paramtype match_condition: :class:`~azure.core.MatchConditions` - :keyword dict headers: if "headers" exists, its value (a dict) will be added to the http request :return: The deleted ConfigurationSetting returned from the service, or None if it doesn't exist. :rtype: :class:`~azure.appconfiguration.ConfigurationSetting` :raises: :class:`HttpResponseError`, :class:`ClientAuthenticationError`, \ @@ -464,7 +452,6 @@ def list_revisions(self, key_filter=None, label_filter=None, **kwargs): :type label_filter: str :keyword datetime accept_datetime: filter out ConfigurationSetting created after this datetime :keyword list[str] fields: specify which fields to include in the results. Leave None to include all fields - :keyword dict headers: if "headers" exists, its value (a dict) will be added to the http request header :return: An iterator of :class:`ConfigurationSetting` :rtype: ~azure.core.paging.ItemPaged[ConfigurationSetting] :raises: :class:`HttpResponseError`, :class:`ClientAuthenticationError` @@ -521,7 +508,7 @@ def set_read_only(self, configuration_setting, read_only=True, **kwargs): :type read_only: bool :keyword match_condition: The match condition to use upon the etag :paramtype match_condition: :class:`~azure.core.MatchConditions` - :keyword dict headers: if "headers" exists, its value (a dict) will be added to the http request header + :keyword str etag: check if the ConfigurationSetting is changed. Set None to skip checking etag :return: The ConfigurationSetting returned from the service :rtype: :class:`~azure.appconfiguration.ConfigurationSetting` :raises: :class:`HttpResponseError`, :class:`ClientAuthenticationError`, :class:`ResourceNotFoundError` diff --git a/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_constants.py b/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_constants.py index 3124b7d59611..a0d12656de30 100644 --- a/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_constants.py +++ b/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_constants.py @@ -4,8 +4,8 @@ # license information. # -------------------------------------------------------------------------- -TIME_WINDOW = u"Microsoft.TimeWindow" +FILTER_TIME_WINDOW = u"Microsoft.TimeWindow" -PERCENTAGE = u"Microsoft.Percentage" +FILTER_PERCENTAGE = u"Microsoft.Percentage" -TARGETING = u"Microsoft.Targeting" +FILTER_TARGETING = u"Microsoft.Targeting" diff --git a/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_models.py b/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_models.py index e321400c2ab8..2ff870e99b16 100644 --- a/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_models.py +++ b/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_models.py @@ -3,10 +3,15 @@ # Licensed under the MIT License. # ------------------------------------ import json -from typing import Dict, Optional, Any, List, Union +from typing import Any, Union from msrest.serialization import Model from ._generated.models import KeyValue +try: + from json.decoder import JSONDecodeError +except ImportError: + JSONDecodeError = None # type: ignore + PolymorphicConfigurationSetting = Union[ "ConfigurationSetting", "SecretReferenceConfigurationSetting", "FeatureFlagConfigurationSetting" @@ -71,7 +76,9 @@ def _from_generated(cls, key_value): try: if key_value.content_type.startswith( FeatureFlagConfigurationSetting._feature_flag_content_type # pylint:disable=protected-access - ) and key_value.key.startswith(FeatureFlagConfigurationSetting.key_prefix): # type: ignore + ) and key_value.key.startswith( # type: ignore + FeatureFlagConfigurationSetting._key_prefix # pylint: disable=protected-access + ): return FeatureFlagConfigurationSetting._from_generated( # pylint: disable=protected-access key_value ) @@ -81,7 +88,7 @@ def _from_generated(cls, key_value): return SecretReferenceConfigurationSetting._from_generated( # pylint: disable=protected-access key_value ) - except (KeyError, AttributeError, TypeError): + except (KeyError, AttributeError): pass return cls( @@ -118,16 +125,20 @@ class FeatureFlagConfigurationSetting( :ivar etag: Entity tag (etag) of the object :vartype etag: str - :ivar key: - :vartype key: str + :ivar feature_id: + :vartype feature_id: str :ivar value: The value of the configuration setting :vartype value: str - :ivar enabled: - :vartype enabled: bool - :param filters: - :type filters: list[dict[str, Any]] + :keyword enabled: + :paramtype enabled: bool + :keyword filters: + :paramtype filters: list[dict[str, Any]] :param label: :type label: str + :param display_name: + :type display_name: str + :param description: + :type description: str :param content_type: :type content_type: str :ivar last_modified: @@ -140,26 +151,26 @@ class FeatureFlagConfigurationSetting( _attribute_map = { "etag": {"key": "etag", "type": "str"}, - "key": {"key": "key", "type": "str"}, + "feature_id": {"key": "feaure_id", "type": "str"}, "label": {"key": "label", "type": "str"}, - "content_type": {"key": "content_type", "type": "str"}, + "content_type": {"key": "_feature_flag_content_type", "type": "str"}, "value": {"key": "value", "type": "str"}, "last_modified": {"key": "last_modified", "type": "iso-8601"}, "read_only": {"key": "read_only", "type": "bool"}, "tags": {"key": "tags", "type": "{str}"}, } - key_prefix = ".appconfig.featureflag/" + _key_prefix = ".appconfig.featureflag/" _feature_flag_content_type = ( "application/vnd.microsoft.appconfig.ff+json;charset=utf-8" ) kind = "FeatureFlag" - def __init__(self, feature_id, enabled, filters=[], **kwargs): # pylint: disable=dangerous-default-value - # type: (str, bool, Optional[List[Dict[str, Any]]], **Any) -> None - super(FeatureFlagConfigurationSetting, self).__init__(**kwargs) - if not feature_id.startswith(self.key_prefix): - feature_id = self.key_prefix + feature_id - self.key = feature_id + def __init__(self, feature_id, **kwargs): # pylint: disable=dangerous-default-value, super-init-not-called + # type: (str, **Any) -> None + if "key" in kwargs.keys() or "value" in kwargs.keys(): + raise TypeError("Unexpected keyword argument, do not provide 'key' or 'value' as a keyword-arg") + self.feature_id = feature_id + self.key = self._key_prefix + self.feature_id self.label = kwargs.get("label", None) self.content_type = kwargs.get("content_type", self._feature_flag_content_type) self.last_modified = kwargs.get("last_modified", None) @@ -168,92 +179,73 @@ def __init__(self, feature_id, enabled, filters=[], **kwargs): # pylint: disabl self.etag = kwargs.get("etag", None) self.description = kwargs.get("description", None) self.display_name = kwargs.get("display_name", None) - self.value = kwargs.get("value", {"enabled": enabled, "conditions": {"client_filters": filters}}) - - def _validate(self): - # type: () -> None - if not self.key.startswith(self.key_prefix): - raise ValueError("All FeatureFlagConfigurationSettings should be prefixed with {}.".format(self.key_prefix)) - if not (self.value is None or isinstance(self.value, dict)): - raise ValueError("Expect 'value' to be a dictionary.") - - @property - def enabled(self): - # type: () -> Union[None, bool] - self._validate() - if self.value is None or "enabled" not in self.value: - return None - return self.value["enabled"] - - @enabled.setter - def enabled(self, new_value): - # type: (bool) -> None - self._validate() - if self.value is None: - self.value = {} - self.value["enabled"] = new_value + self.filters = kwargs.get("filters", []) + self.enabled = kwargs.get("enabled", None) + self._value = json.dumps({"enabled": self.enabled, "conditions": {"client_filters": self.filters}}) @property - def filters(self): - # type: () -> Union[None, List[Any]] - self._validate() - if self.value is None: - return None + def value(self): try: - return self.value["conditions"]["client_filters"] - except KeyError: - pass - return None - - @filters.setter - def filters(self, new_filters): - # type: (List[Dict[str, Any]]) -> None - self._validate() - if self.value is None: - self.value = {} + temp = json.loads(self._value) + temp["enabled"] = self.enabled + + if "conditions" not in temp.keys(): + temp["conditions"] = {} + temp["conditions"]["client_filters"] = self.filters + self._value = json.dumps(temp) + return self._value + except (JSONDecodeError, ValueError): + return self._value + + @value.setter + def value(self, new_value): try: - self.value["conditions"]["client_filters"] = new_filters - except KeyError: - self.value["conditions"] = { - "client_filters": new_filters - } + temp = json.loads(new_value) + self._value = new_value + self.enabled = temp.get("enabled", None) + self.filters = None + conditions = temp.get("conditions", None) + if conditions: + self.filters = conditions.get("client_filters", None) + except (JSONDecodeError, ValueError): + self._value = new_value + self.enabled = None + self.filters = None @classmethod def _from_generated(cls, key_value): # type: (KeyValue) -> Union[FeatureFlagConfigurationSetting, ConfigurationSetting] + if key_value is None: + return key_value + enabled = None + filters = None try: - if key_value is None: - return key_value - if key_value.value: - try: - key_value.value = json.loads(key_value.value) - except json.decoder.JSONDecodeError: - pass - - filters = key_value.value["conditions"]["client_filters"] # type: ignore - - return cls( - feature_id=key_value.key, # type: ignore - enabled=key_value.value["enabled"], # type: ignore - label=key_value.label, - content_type=key_value.content_type, - last_modified=key_value.last_modified, - tags=key_value.tags, - read_only=key_value.locked, - etag=key_value.etag, - filters=filters, # type: ignore - value=key_value.value, - ) - except (KeyError, AttributeError): - return ConfigurationSetting._from_generated(key_value) + temp = json.loads(key_value.value) # type: ignore + if isinstance(temp, dict): + enabled = temp.get("enabled") + if "conditions" in temp.keys(): + filters = temp["conditions"].get("client_filters") + except (ValueError, JSONDecodeError): + pass + + return cls( + feature_id=key_value.key.lstrip(".appconfig.featureflag").lstrip("/"), # type: ignore + label=key_value.label, + content_type=key_value.content_type, + last_modified=key_value.last_modified, + tags=key_value.tags, + read_only=key_value.locked, + etag=key_value.etag, + enabled=enabled, + filters=filters + ) def _to_generated(self): # type: () -> KeyValue - return KeyValue( key=self.key, label=self.label, - value=json.dumps(self.value), # NOTE: This has to be added for valid json + value=self.value, content_type=self.content_type, last_modified=self.last_modified, tags=self.tags, @@ -271,8 +263,8 @@ class SecretReferenceConfigurationSetting(ConfigurationSetting): :vartype etag: str :ivar key: :vartype key: str - :ivar secret_uri: - :vartype secret_uri: str + :ivar secret_id: + :vartype secret_id: str :param label: :type label: str :param content_type: @@ -302,12 +294,12 @@ class SecretReferenceConfigurationSetting(ConfigurationSetting): ) kind = "SecretReference" - def __init__(self, key, secret_uri, label=None, **kwargs): - # type: (str, str, Optional[str], **Any) -> None - self._secret_uri = secret_uri - super(SecretReferenceConfigurationSetting, self).__init__(**kwargs) + def __init__(self, key, secret_id, **kwargs): # pylint: disable=super-init-not-called + # type: (str, str, **Any) -> None + if "value" in kwargs.keys(): + raise TypeError("Unexpected keyword argument, do not provide 'value' as a keyword-arg") self.key = key - self.label = label + self.label = kwargs.pop("label", None) self.content_type = kwargs.get( "content_type", self._secret_reference_content_type ) @@ -315,44 +307,45 @@ def __init__(self, key, secret_uri, label=None, **kwargs): self.last_modified = kwargs.get("last_modified", None) self.read_only = kwargs.get("read_only", None) self.tags = kwargs.get("tags", {}) - self.value = {"secret_uri": self._secret_uri} + self.secret_id = secret_id + self._value = json.dumps({"secret_uri": secret_id}) @property - def secret_uri(self): - # type: () -> str - self._validate() - return self.value['secret_uri'] - - @secret_uri.setter - def secret_uri(self, value): - if self.value is None or isinstance(self.value, dict): - if self.value is None: - self.value = {} - self.value["secret_uri"] = value - else: - raise ValueError("Expect 'value' to be a dictionary.") - - def _validate(self): - # type: () -> None - if not (self.value is None or isinstance(self.value, dict)): - raise ValueError("Expect 'value' to be a dictionary or None.") + def value(self): + try: + temp = json.loads(self._value) + temp["secret_uri"] = self.secret_id + self._value = json.dumps(temp) + return self._value + except (JSONDecodeError, ValueError): + return self._value + + @value.setter + def value(self, new_value): + try: + temp = json.loads(new_value) + self._value = new_value + self.secret_id = temp.get("secret_uri") + except(JSONDecodeError, ValueError): + self._value = new_value + self.secret_id = None @classmethod def _from_generated(cls, key_value): # type: (KeyValue) -> SecretReferenceConfigurationSetting if key_value is None: return key_value - if key_value.value: - try: - key_value.value = json.loads(key_value.value) - except json.decoder.JSONDecodeError: - pass + secret_uri = None + try: + temp = json.loads(key_value.value) # type: ignore + secret_uri = temp.get("secret_uri") + except (ValueError, JSONDecodeError): + pass return cls( key=key_value.key, # type: ignore - secret_uri=key_value.value[u"secret_uri"], # type: ignore label=key_value.label, - secret_id=key_value.value, + secret_id=secret_uri, # type: ignore last_modified=key_value.last_modified, tags=key_value.tags, read_only=key_value.locked, @@ -364,7 +357,7 @@ def _to_generated(self): return KeyValue( key=self.key, label=self.label, - value=json.dumps(self.value), + value=self.value, content_type=self.content_type, last_modified=self.last_modified, tags=self.tags, diff --git a/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/aio/_azure_configuration_client_async.py b/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/aio/_azure_configuration_client_async.py index 7b9a3c4f76ff..b2ee7f98dd24 100644 --- a/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/aio/_azure_configuration_client_async.py +++ b/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/aio/_azure_configuration_client_async.py @@ -4,9 +4,10 @@ # license information. # ------------------------------------------------------------------------- import binascii -from typing import Dict, Any, Optional, Mapping, Union +from typing import Dict, Any, Optional, Mapping, Union, TYPE_CHECKING from requests.structures import CaseInsensitiveDict from azure.core import MatchConditions +from azure.core.async_paging import AsyncItemPaged from azure.core.pipeline import AsyncPipeline from azure.core.pipeline.policies import ( UserAgentPolicy, @@ -36,18 +37,12 @@ from .._generated.aio._configuration import AzureAppConfigurationConfiguration from .._azure_appconfiguration_requests import AppConfigRequestsCredentialsPolicy from .._azure_appconfiguration_credential import AppConfigConnectionStringCredential -from .._generated.models import KeyValue from .._models import ConfigurationSetting from .._user_agent import USER_AGENT from ._sync_token_async import AsyncSyncTokenPolicy -try: - from typing import TYPE_CHECKING -except ImportError: - TYPE_CHECKING = False - if TYPE_CHECKING: - from azure.core.async_paging import AsyncItemPaged + from azure.core.credentials_async import AsyncTokenCredential class AzureAppConfigurationClient: @@ -55,10 +50,8 @@ class AzureAppConfigurationClient: :param str base_url: base url of the service :param credential: An object which can provide secrets for the app configuration service - :type credential: azure.AppConfigConnectionStringCredential - :keyword Pipeline pipeline: If omitted, the standard pipeline is used. - :keyword HttpTransport transport: If omitted, the standard pipeline is used. - :keyword list[HTTPPolicy] policies: If omitted, the standard pipeline is used. + :type credential: :class:`azure.appconfiguration.AppConfigConnectionStringCredential` or + :class:`~azure.core.credentials_async.AsyncTokenCredential` This is the async version of :class:`azure.appconfiguration.AzureAppConfigurationClient` @@ -66,8 +59,12 @@ class AzureAppConfigurationClient: # pylint:disable=protected-access - def __init__(self, base_url, credential, **kwargs): - # type: (str, Any, **Any) -> None + def __init__( + self, + base_url: str, + credential: Union[AppConfigConnectionStringCredential, "AsyncTokenCredential"], + **kwargs: Any + ) -> None: try: if not base_url.lower().startswith("http"): base_url = "https://" + base_url @@ -80,7 +77,7 @@ def __init__(self, base_url, credential, **kwargs): self._credential_scopes = base_url.strip("/") + "/.default" self._config = AzureAppConfigurationConfiguration( - credential, base_url, credential_scopes=self._credential_scopes, **kwargs + credential, base_url, credential_scopes=self._credential_scopes, **kwargs # type: ignore ) self._config.user_agent_policy = UserAgentPolicy( base_user_agent=USER_AGENT, **kwargs @@ -96,12 +93,11 @@ def __init__(self, base_url, credential, **kwargs): ) self._impl = AzureAppConfiguration( - credential, base_url, credential_scopes=self._credential_scopes, pipeline=pipeline + credential, base_url, credential_scopes=self._credential_scopes, pipeline=pipeline # type: ignore ) @classmethod - def from_connection_string(cls, connection_string, **kwargs): - # type: (str, **Any) -> AzureAppConfigurationClient + def from_connection_string(cls, connection_string: str, **kwargs: Any) -> "AzureAppConfigurationClient": """Create AzureAppConfigurationClient from a Connection String. This is the async version of :class:`azure.appconfiguration.AzureAppConfigurationClient` @@ -168,8 +164,12 @@ def _create_appconfig_pipeline( ) @distributed_trace - def list_configuration_settings(self, key_filter=None, label_filter=None, **kwargs): - # type: (Optional[str], Optional[str], **Any) -> AsyncItemPaged[ConfigurationSetting] + def list_configuration_settings( + self, + key_filter: Optional[str] = None, + label_filter: Optional[str] = None, + **kwargs: Any + ) -> AsyncItemPaged[ConfigurationSetting]: """List the configuration settings stored in the configuration service, optionally filtered by label and accept_datetime @@ -182,7 +182,6 @@ def list_configuration_settings(self, key_filter=None, label_filter=None, **kwar :type label_filter: str :keyword datetime accept_datetime: filter out ConfigurationSetting created after this datetime :keyword list[str] fields: specify which fields to include in the results. Leave None to include all fields - :keyword dict headers: if "headers" exists, its value (a dict) will be added to the http request header :return: An iterator of :class:`ConfigurationSetting` :rtype: ~azure.core.async_paging.AsyncItemPaged[ConfigurationSetting] :raises: :class:`HttpResponseError`, :class:`ClientAuthenticationError` @@ -248,7 +247,6 @@ async def get_configuration_setting( :param match_condition: The match condition to use upon the etag :type match_condition: :class:`~azure.core.MatchConditions` :keyword datetime accept_datetime: the retrieved ConfigurationSetting that created no later than this datetime - :keyword dict headers: if "headers" exists, its value (a dict) will be added to the http request header :return: The matched ConfigurationSetting object :rtype: :class:`~azure.appconfiguration.ConfigurationSetting` :raises: :class:`HttpResponseError`, :class:`ClientAuthenticationError`, \ @@ -292,14 +290,16 @@ async def get_configuration_setting( raise binascii.Error("Connection string secret has incorrect padding") @distributed_trace_async - async def add_configuration_setting(self, configuration_setting, **kwargs): - # type: (ConfigurationSetting, **Any) -> ConfigurationSetting + async def add_configuration_setting( + self, + configuration_setting: ConfigurationSetting, + **kwargs: Any + ) -> ConfigurationSetting: """Add a ConfigurationSetting instance into the Azure App Configuration service. :param configuration_setting: the ConfigurationSetting object to be added - :type configuration_setting: :class:`ConfigurationSetting` - :keyword dict headers: if "headers" exists, its value (a dict) will be added to the http request header + :type configuration_setting: :class:`~azure.appconfiguration.ConfigurationSetting` :return: The ConfigurationSetting object returned from the App Configuration service :rtype: :class:`~azure.appconfiguration.ConfigurationSetting` :raises: :class:`HttpResponseError`, :class:`ClientAuthenticationError`, :class:`ResourceExistsError` @@ -341,10 +341,10 @@ async def add_configuration_setting(self, configuration_setting, **kwargs): @distributed_trace_async async def set_configuration_setting( self, - configuration_setting, - match_condition=MatchConditions.Unconditionally, - **kwargs - ): # type: (ConfigurationSetting, Optional[MatchConditions], **Any) -> ConfigurationSetting + configuration_setting: ConfigurationSetting, + match_condition: MatchConditions = MatchConditions.Unconditionally, + **kwargs: Any + ) -> ConfigurationSetting: """Add or update a ConfigurationSetting. If the configuration setting identified by key and label does not exist, this is a create. @@ -355,7 +355,7 @@ async def set_configuration_setting( :type configuration_setting: :class:`ConfigurationSetting` :param match_condition: The match condition to use upon the etag :type match_condition: :class:`~azure.core.MatchConditions` - :keyword dict headers: if "headers" exists, its value (a dict) will be added to the http request header + :keyword str etag: check if the ConfigurationSetting is changed. Set None to skip checking etag :return: The ConfigurationSetting returned from the service :rtype: :class:`~azure.appconfiguration.ConfigurationSetting` :raises: :class:`HttpResponseError`, :class:`ClientAuthenticationError`, \ @@ -376,6 +376,8 @@ async def set_configuration_setting( ) returned_config_setting = await async_client.set_configuration_setting(config_setting) """ + etag = kwargs.get("etag", configuration_setting.etag) + key_value = configuration_setting._to_generated() custom_headers = CaseInsensitiveDict(kwargs.get("headers")) # type: Mapping[str, Any] error_map = {401: ClientAuthenticationError, 409: ResourceReadOnlyError} @@ -395,7 +397,7 @@ async def set_configuration_setting( label=key_value.label, if_match=prep_if_match(configuration_setting.etag, match_condition), if_none_match=prep_if_none_match( - configuration_setting.etag, match_condition + etag, match_condition ), headers=custom_headers, error_map=error_map, @@ -409,9 +411,11 @@ async def set_configuration_setting( @distributed_trace_async async def delete_configuration_setting( - self, key, label=None, **kwargs - ): # type: (str, Optional[str], **Any) -> ConfigurationSetting - + self, + key: str, + label: Optional[str] = None, + **kwargs: Any + ) -> ConfigurationSetting: """Delete a ConfigurationSetting if it exists :param key: key used to identify the ConfigurationSetting @@ -421,7 +425,6 @@ async def delete_configuration_setting( :keyword str etag: check if the ConfigurationSetting is changed. Set None to skip checking etag :keyword match_condition: The match condition to use upon the etag :paramtype match_condition: :class:`~azure.core.MatchConditions` - :keyword dict headers: if "headers" exists, its value (a dict) will be added to the http request :return: The deleted ConfigurationSetting returned from the service, or None if it doesn't exist. :rtype: :class:`~azure.appconfiguration.ConfigurationSetting` :raises: :class:`HttpResponseError`, :class:`ClientAuthenticationError`, \ @@ -437,7 +440,6 @@ async def delete_configuration_setting( key="MyKey", label="MyLabel" ) """ - etag = kwargs.pop("etag", None) match_condition = kwargs.pop("match_condition", MatchConditions.Unconditionally) custom_headers = CaseInsensitiveDict(kwargs.get("headers")) # type: Mapping[str, Any] @@ -468,8 +470,8 @@ async def delete_configuration_setting( @distributed_trace def list_revisions( - self, key_filter=None, label_filter=None, **kwargs - ): # type: (Optional[str], Optional[str], **Any) -> AsyncItemPaged[ConfigurationSetting] + self, key_filter: Optional[str] = None, label_filter: Optional[str] = None, **kwargs: Any + ) -> AsyncItemPaged[ConfigurationSetting]: """ Find the ConfigurationSetting revision history. @@ -482,7 +484,6 @@ def list_revisions( :type label_filter: str :keyword datetime accept_datetime: filter out ConfigurationSetting created after this datetime :keyword list[str] fields: specify which fields to include in the results. Leave None to include all fields - :keyword dict headers: if "headers" exists, its value (a dict) will be added to the http request header :return: An iterator of :class:`ConfigurationSetting` :rtype: ~azure.core.async_paging.AsyncItemPaged[ConfigurationSetting] :raises: :class:`HttpResponseError`, :class:`ClientAuthenticationError` @@ -530,8 +531,8 @@ def list_revisions( @distributed_trace async def set_read_only( - self, configuration_setting, read_only=True, **kwargs - ): # type: (ConfigurationSetting, Optional[bool], **Any) -> ConfigurationSetting + self, configuration_setting: ConfigurationSetting, read_only: Optional[bool] = True, **kwargs: Any + ) -> ConfigurationSetting: """Set a configuration setting read only @@ -541,7 +542,7 @@ async def set_read_only( :type read_only: bool :keyword match_condition: The match condition to use upon the etag :paramtype match_condition: :class:`~azure.core.MatchConditions` - :keyword dict headers: if "headers" exists, its value (a dict) will be added to the http request header + :keyword str etag: check if the ConfigurationSetting is changed. Set None to skip checking etag :return: The ConfigurationSetting returned from the service :rtype: :class:`~azure.appconfiguration.ConfigurationSetting` :raises: :class:`HttpResponseError`, :class:`ClientAuthenticationError`, :class:`ResourceNotFoundError` diff --git a/sdk/appconfiguration/azure-appconfiguration/tests/recordings/test_azure_configuration_client.test_type_error.yaml b/sdk/appconfiguration/azure-appconfiguration/tests/recordings/test_azure_configuration_client.test_type_error.yaml new file mode 100644 index 000000000000..43a42d7e94d4 --- /dev/null +++ b/sdk/appconfiguration/azure-appconfiguration/tests/recordings/test_azure_configuration_client.test_type_error.yaml @@ -0,0 +1,465 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/vnd.microsoft.appconfig.kvset+json, application/json, application/problem+json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-appconfiguration/1.2.0b3 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + x-ms-content-sha256: + - 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU= + x-ms-date: + - Jun, 24 2021 21:50:50.575634 GMT + method: GET + uri: https://fake_app_config.azconfig-test.io/kv?key=PYTHON_UNIT_test_key_a6af8952-54a6-11e9-b600-2816a84d0309&label=test_label1_1d7b2b28-549e-11e9-b51c-2816a84d0309&api-version=1.0 + response: + body: + string: '{"items":[{"etag":"wenIrlKKbh4NapQN07sSHd5RtYp","key":"PYTHON_UNIT_test_key_a6af8952-54a6-11e9-b600-2816a84d0309","label":"test_label1_1d7b2b28-549e-11e9-b51c-2816a84d0309","content_type":"test + content type","value":"test value","tags":{"tag1":"tag1","tag2":"tag2"},"locked":false,"last_modified":"2021-06-24T21:50:31+00:00"}]}' + headers: + access-control-allow-credentials: + - 'true' + access-control-allow-origin: + - '*' + access-control-expose-headers: + - DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, + Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, + x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, + If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, + Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, + x-ms-effective-locale, WWW-Authenticate + connection: + - keep-alive + content-type: + - application/vnd.microsoft.appconfig.kvset+json; charset=utf-8 + date: + - Thu, 24 Jun 2021 21:50:51 GMT + server: + - openresty/1.17.8.2 + strict-transport-security: + - max-age=15724800; includeSubDomains + sync-token: + - zAJw6V16=MDoxNyM5NTA3MjIx;sn=9507221 + transfer-encoding: + - chunked + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/vnd.microsoft.appconfig.kv+json, application/json, application/problem+json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Sync-Token: + - zAJw6V16=MDoxNyM5NTA3MjIx + User-Agent: + - azsdk-python-appconfiguration/1.2.0b3 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + x-ms-content-sha256: + - 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU= + x-ms-date: + - Jun, 24 2021 21:50:51.243942 GMT + method: DELETE + uri: https://fake_app_config.azconfig-test.io/kv/PYTHON_UNIT_test_key_a6af8952-54a6-11e9-b600-2816a84d0309?label=test_label1_1d7b2b28-549e-11e9-b51c-2816a84d0309&api-version=1.0 + response: + body: + string: '{"etag":"wenIrlKKbh4NapQN07sSHd5RtYp","key":"PYTHON_UNIT_test_key_a6af8952-54a6-11e9-b600-2816a84d0309","label":"test_label1_1d7b2b28-549e-11e9-b51c-2816a84d0309","content_type":"test + content type","value":"test value","tags":{"tag1":"tag1","tag2":"tag2"},"locked":false,"last_modified":"2021-06-24T21:50:31+00:00"}' + headers: + access-control-allow-credentials: + - 'true' + access-control-allow-origin: + - '*' + access-control-expose-headers: + - DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, + Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, + x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, + If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, + Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, + x-ms-effective-locale, WWW-Authenticate + connection: + - keep-alive + content-type: + - application/vnd.microsoft.appconfig.kv+json; charset=utf-8 + date: + - Thu, 24 Jun 2021 21:50:52 GMT + etag: + - '"wenIrlKKbh4NapQN07sSHd5RtYp"' + last-modified: + - Thu, 24 Jun 2021 21:50:31 GMT + server: + - openresty/1.17.8.2 + strict-transport-security: + - max-age=15724800; includeSubDomains + sync-token: + - zAJw6V16=MDoxNyM5NTA3MjIy;sn=9507222 + transfer-encoding: + - chunked + status: + code: 200 + message: OK +- request: + body: '{"key": "PYTHON_UNIT_test_key_a6af8952-54a6-11e9-b600-2816a84d0309", "label": + "test_label1_1d7b2b28-549e-11e9-b51c-2816a84d0309", "content_type": "test content + type", "value": "test value", "tags": {"tag1": "tag1", "tag2": "tag2"}}' + headers: + Accept: + - application/vnd.microsoft.appconfig.kv+json, application/json, application/problem+json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '231' + Content-Type: + - application/vnd.microsoft.appconfig.kv+json + If-None-Match: + - '*' + Sync-Token: + - zAJw6V16=MDoxNyM5NTA3MjIy + User-Agent: + - azsdk-python-appconfiguration/1.2.0b3 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + x-ms-content-sha256: + - 5b/E4qQlXHTRod+n+f+xjK6c/tRVR8uxoC62FjvGJPw= + x-ms-date: + - Jun, 24 2021 21:50:51.431688 GMT + method: PUT + uri: https://fake_app_config.azconfig-test.io/kv/PYTHON_UNIT_test_key_a6af8952-54a6-11e9-b600-2816a84d0309?label=test_label1_1d7b2b28-549e-11e9-b51c-2816a84d0309&api-version=1.0 + response: + body: + string: '{"etag":"cc8PuncVGckYPLBhamJpQdWygZ9","key":"PYTHON_UNIT_test_key_a6af8952-54a6-11e9-b600-2816a84d0309","label":"test_label1_1d7b2b28-549e-11e9-b51c-2816a84d0309","content_type":"test + content type","value":"test value","tags":{"tag1":"tag1","tag2":"tag2"},"locked":false,"last_modified":"2021-06-24T21:50:52+00:00"}' + headers: + access-control-allow-credentials: + - 'true' + access-control-allow-origin: + - '*' + access-control-expose-headers: + - DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, + Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, + x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, + If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, + Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, + x-ms-effective-locale, WWW-Authenticate + connection: + - keep-alive + content-type: + - application/vnd.microsoft.appconfig.kv+json; charset=utf-8 + date: + - Thu, 24 Jun 2021 21:50:52 GMT + etag: + - '"cc8PuncVGckYPLBhamJpQdWygZ9"' + last-modified: + - Thu, 24 Jun 2021 21:50:52 GMT + server: + - openresty/1.17.8.2 + strict-transport-security: + - max-age=15724800; includeSubDomains + sync-token: + - zAJw6V16=MDoxNyM5NTA3MjIz;sn=9507223 + transfer-encoding: + - chunked + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/vnd.microsoft.appconfig.kvset+json, application/json, application/problem+json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Sync-Token: + - zAJw6V16=MDoxNyM5NTA3MjIz + User-Agent: + - azsdk-python-appconfiguration/1.2.0b3 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + x-ms-content-sha256: + - 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU= + x-ms-date: + - Jun, 24 2021 21:50:51.665156 GMT + method: GET + uri: https://fake_app_config.azconfig-test.io/kv?key=PYTHON_UNIT_test_key_a6af8952-54a6-11e9-b600-2816a84d0309&api-version=1.0 + response: + body: + string: '{"items":[{"etag":"hwo8MHlFR3NYRBXz5OviIT1p4j1","key":"PYTHON_UNIT_test_key_a6af8952-54a6-11e9-b600-2816a84d0309","label":null,"content_type":"test + content type","value":"test value","tags":{"tag1":"tag1","tag2":"tag2"},"locked":false,"last_modified":"2021-06-24T21:50:32+00:00"},{"etag":"cc8PuncVGckYPLBhamJpQdWygZ9","key":"PYTHON_UNIT_test_key_a6af8952-54a6-11e9-b600-2816a84d0309","label":"test_label1_1d7b2b28-549e-11e9-b51c-2816a84d0309","content_type":"test + content type","value":"test value","tags":{"tag1":"tag1","tag2":"tag2"},"locked":false,"last_modified":"2021-06-24T21:50:52+00:00"}]}' + headers: + access-control-allow-credentials: + - 'true' + access-control-allow-origin: + - '*' + access-control-expose-headers: + - DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, + Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, + x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, + If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, + Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, + x-ms-effective-locale, WWW-Authenticate + connection: + - keep-alive + content-type: + - application/vnd.microsoft.appconfig.kvset+json; charset=utf-8 + date: + - Thu, 24 Jun 2021 21:50:52 GMT + server: + - openresty/1.17.8.2 + strict-transport-security: + - max-age=15724800; includeSubDomains + sync-token: + - zAJw6V16=MDoxNyM5NTA3MjIz;sn=9507223 + transfer-encoding: + - chunked + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/vnd.microsoft.appconfig.kv+json, application/json, application/problem+json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Sync-Token: + - zAJw6V16=MDoxNyM5NTA3MjIz + User-Agent: + - azsdk-python-appconfiguration/1.2.0b3 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + x-ms-content-sha256: + - 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU= + x-ms-date: + - Jun, 24 2021 21:50:51.844728 GMT + method: DELETE + uri: https://fake_app_config.azconfig-test.io/kv/PYTHON_UNIT_test_key_a6af8952-54a6-11e9-b600-2816a84d0309?api-version=1.0 + response: + body: + string: '{"etag":"hwo8MHlFR3NYRBXz5OviIT1p4j1","key":"PYTHON_UNIT_test_key_a6af8952-54a6-11e9-b600-2816a84d0309","label":null,"content_type":"test + content type","value":"test value","tags":{"tag1":"tag1","tag2":"tag2"},"locked":false,"last_modified":"2021-06-24T21:50:32+00:00"}' + headers: + access-control-allow-credentials: + - 'true' + access-control-allow-origin: + - '*' + access-control-expose-headers: + - DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, + Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, + x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, + If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, + Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, + x-ms-effective-locale, WWW-Authenticate + connection: + - keep-alive + content-type: + - application/vnd.microsoft.appconfig.kv+json; charset=utf-8 + date: + - Thu, 24 Jun 2021 21:50:52 GMT + etag: + - '"hwo8MHlFR3NYRBXz5OviIT1p4j1"' + last-modified: + - Thu, 24 Jun 2021 21:50:32 GMT + server: + - openresty/1.17.8.2 + strict-transport-security: + - max-age=15724800; includeSubDomains + sync-token: + - zAJw6V16=MDoxNyM5NTA3MjI0;sn=9507224 + transfer-encoding: + - chunked + status: + code: 200 + message: OK +- request: + body: '{"key": "PYTHON_UNIT_test_key_a6af8952-54a6-11e9-b600-2816a84d0309", "content_type": + "test content type", "value": "test value", "tags": {"tag1": "tag1", "tag2": + "tag2"}}' + headers: + Accept: + - application/vnd.microsoft.appconfig.kv+json, application/json, application/problem+json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '170' + Content-Type: + - application/vnd.microsoft.appconfig.kv+json + If-None-Match: + - '*' + Sync-Token: + - zAJw6V16=MDoxNyM5NTA3MjI0 + User-Agent: + - azsdk-python-appconfiguration/1.2.0b3 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + x-ms-content-sha256: + - 4rt07HteiPg5NxofrgzMmlQPTVbo1no7aKxDSI5uUU4= + x-ms-date: + - Jun, 24 2021 21:50:52.018701 GMT + method: PUT + uri: https://fake_app_config.azconfig-test.io/kv/PYTHON_UNIT_test_key_a6af8952-54a6-11e9-b600-2816a84d0309?api-version=1.0 + response: + body: + string: '{"etag":"Ah1pS1pReIEAXcabySU7b1qNIZq","key":"PYTHON_UNIT_test_key_a6af8952-54a6-11e9-b600-2816a84d0309","label":null,"content_type":"test + content type","value":"test value","tags":{"tag1":"tag1","tag2":"tag2"},"locked":false,"last_modified":"2021-06-24T21:50:52+00:00"}' + headers: + access-control-allow-credentials: + - 'true' + access-control-allow-origin: + - '*' + access-control-expose-headers: + - DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, + Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, + x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, + If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, + Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, + x-ms-effective-locale, WWW-Authenticate + connection: + - keep-alive + content-type: + - application/vnd.microsoft.appconfig.kv+json; charset=utf-8 + date: + - Thu, 24 Jun 2021 21:50:53 GMT + etag: + - '"Ah1pS1pReIEAXcabySU7b1qNIZq"' + last-modified: + - Thu, 24 Jun 2021 21:50:52 GMT + server: + - openresty/1.17.8.2 + strict-transport-security: + - max-age=15724800; includeSubDomains + sync-token: + - zAJw6V16=MDoxNyM5NTA3MjI1;sn=9507225 + transfer-encoding: + - chunked + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/vnd.microsoft.appconfig.kv+json, application/json, application/problem+json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Sync-Token: + - zAJw6V16=MDoxNyM5NTA3MjI1 + User-Agent: + - azsdk-python-appconfiguration/1.2.0b3 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + x-ms-content-sha256: + - 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU= + x-ms-date: + - Jun, 24 2021 21:50:52.281596 GMT + method: DELETE + uri: https://fake_app_config.azconfig-test.io/kv/PYTHON_UNIT_test_key_a6af8952-54a6-11e9-b600-2816a84d0309?label=test_label1_1d7b2b28-549e-11e9-b51c-2816a84d0309&api-version=1.0 + response: + body: + string: '{"etag":"cc8PuncVGckYPLBhamJpQdWygZ9","key":"PYTHON_UNIT_test_key_a6af8952-54a6-11e9-b600-2816a84d0309","label":"test_label1_1d7b2b28-549e-11e9-b51c-2816a84d0309","content_type":"test + content type","value":"test value","tags":{"tag1":"tag1","tag2":"tag2"},"locked":false,"last_modified":"2021-06-24T21:50:52+00:00"}' + headers: + access-control-allow-credentials: + - 'true' + access-control-allow-origin: + - '*' + access-control-expose-headers: + - DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, + Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, + x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, + If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, + Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, + x-ms-effective-locale, WWW-Authenticate + connection: + - keep-alive + content-type: + - application/vnd.microsoft.appconfig.kv+json; charset=utf-8 + date: + - Thu, 24 Jun 2021 21:50:53 GMT + etag: + - '"cc8PuncVGckYPLBhamJpQdWygZ9"' + last-modified: + - Thu, 24 Jun 2021 21:50:52 GMT + server: + - openresty/1.17.8.2 + strict-transport-security: + - max-age=15724800; includeSubDomains + sync-token: + - zAJw6V16=MDoxNyM5NTA3MjI2;sn=9507226 + transfer-encoding: + - chunked + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/vnd.microsoft.appconfig.kv+json, application/json, application/problem+json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Sync-Token: + - zAJw6V16=MDoxNyM5NTA3MjI2 + User-Agent: + - azsdk-python-appconfiguration/1.2.0b3 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + x-ms-content-sha256: + - 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU= + x-ms-date: + - Jun, 24 2021 21:50:52.464027 GMT + method: DELETE + uri: https://fake_app_config.azconfig-test.io/kv/PYTHON_UNIT_test_key_a6af8952-54a6-11e9-b600-2816a84d0309?api-version=1.0 + response: + body: + string: '{"etag":"Ah1pS1pReIEAXcabySU7b1qNIZq","key":"PYTHON_UNIT_test_key_a6af8952-54a6-11e9-b600-2816a84d0309","label":null,"content_type":"test + content type","value":"test value","tags":{"tag1":"tag1","tag2":"tag2"},"locked":false,"last_modified":"2021-06-24T21:50:52+00:00"}' + headers: + access-control-allow-credentials: + - 'true' + access-control-allow-origin: + - '*' + access-control-expose-headers: + - DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, + Cache-Control, Content-Type, Authorization, x-ms-client-request-id, x-ms-useragent, + x-ms-content-sha256, x-ms-date, host, Accept, Accept-Datetime, Date, If-Match, + If-None-Match, Sync-Token, x-ms-return-client-request-id, ETag, Last-Modified, + Link, Memento-Datetime, retry-after-ms, x-ms-request-id, x-ms-client-session-id, + x-ms-effective-locale, WWW-Authenticate + connection: + - keep-alive + content-type: + - application/vnd.microsoft.appconfig.kv+json; charset=utf-8 + date: + - Thu, 24 Jun 2021 21:50:53 GMT + etag: + - '"Ah1pS1pReIEAXcabySU7b1qNIZq"' + last-modified: + - Thu, 24 Jun 2021 21:50:52 GMT + server: + - openresty/1.17.8.2 + strict-transport-security: + - max-age=15724800; includeSubDomains + sync-token: + - zAJw6V16=MDoxNyM5NTA3MjI3;sn=9507227 + transfer-encoding: + - chunked + status: + code: 200 + message: OK +version: 1 diff --git a/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_configuration_client.py b/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_configuration_client.py index 8ffa613bcc4a..d2956512d072 100644 --- a/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_configuration_client.py +++ b/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_configuration_client.py @@ -3,9 +3,9 @@ # Licensed under the MIT License. See License.txt in the project root for # license information. # -------------------------------------------------------------------------- +from typing import Type from azure.core import MatchConditions -from azure.core.exceptions import HttpResponseError -from devtools_testutils import AzureTestCase, PowerShellPreparer +from devtools_testutils import AzureTestCase from azure.core.exceptions import ( ResourceModifiedError, ResourceNotFoundError, @@ -18,11 +18,10 @@ ConfigurationSetting, FeatureFlagConfigurationSetting, SecretReferenceConfigurationSetting, - PERCENTAGE, - TARGETING, - TIME_WINDOW, + FILTER_PERCENTAGE, + FILTER_TARGETING, + FILTER_TIME_WINDOW, ) -from azure.identity import DefaultAzureCredential from consts import ( KEY, @@ -38,17 +37,10 @@ import pytest import copy import datetime -import os -import logging +import json import re -import functools from uuid import uuid4 -try: - from unittest import mock -except ImportError: - import mock - class AppConfigurationClientTest(AzureTestCase): def __init__(self, method_name): @@ -484,13 +476,12 @@ def _assert_same_keys(self, key1, key2): assert key1.enabled == key2.enabled assert len(key1.filters) == len(key2.filters) elif isinstance(key1, SecretReferenceConfigurationSetting): - assert key1.secret_uri == key2.secret_uri + assert key1.secret_id == key2.secret_id else: assert key1.value == key2.value @app_config_decorator def test_sync_tokens(self, client): - sync_tokens = copy.deepcopy(client._sync_token_policy._sync_tokens) sync_token_header = self._order_dict(sync_tokens) sync_token_header = ",".join(str(x) for x in sync_token_header.values()) @@ -526,7 +517,7 @@ def test_sync_tokens(self, client): @app_config_decorator def test_config_setting_feature_flag(self, client): - feature_flag = FeatureFlagConfigurationSetting("test_feature", True) + feature_flag = FeatureFlagConfigurationSetting("test_feature", enabled=True) set_flag = client.set_configuration_setting(feature_flag) self._assert_same_keys(feature_flag, set_flag) @@ -535,20 +526,21 @@ def test_config_setting_feature_flag(self, client): changed_flag = client.set_configuration_setting(set_flag) changed_flag.enabled = False - assert changed_flag.value['enabled'] == False + temp = json.loads(changed_flag.value) + assert temp['enabled'] == False - c = copy.deepcopy(changed_flag.value) + c = json.loads(changed_flag.value) c['enabled'] = True - changed_flag.value = c + changed_flag.value = json.dumps(c) assert changed_flag.enabled == True - changed_flag.value = {} + changed_flag.value = json.dumps({}) assert changed_flag.enabled == None - assert changed_flag.value == {} + assert changed_flag.value == json.dumps({'enabled': None, "conditions": {"client_filters": None}}) - with pytest.raises(ValueError): - set_flag.value = "bad_value" - _ = set_flag.enabled + set_flag.value = "bad_value" + assert set_flag.enabled == None + assert set_flag.filters == None client.delete_configuration_setting(changed_flag.key) @@ -565,15 +557,15 @@ def test_config_setting_secret_reference(self, client): assert isinstance(updated_flag, SecretReferenceConfigurationSetting) new_uri = "https://aka.ms/azsdk" new_uri2 = "https://aka.ms/azsdk/python" - updated_flag.secret_uri = new_uri - assert updated_flag.value['secret_uri'] == new_uri + updated_flag.secret_id = new_uri + temp = json.loads(updated_flag.value) + assert temp['secret_uri'] == new_uri - updated_flag.value = {'secret_uri': new_uri2} - assert updated_flag.secret_uri == new_uri2 + updated_flag.value = json.dumps({'secret_uri': new_uri2}) + assert updated_flag.secret_id == new_uri2 - with pytest.raises(ValueError): - set_flag.value = "bad_value" - _ = set_flag.secret_uri + set_flag.value = "bad_value" + assert set_flag.secret_id == None client.delete_configuration_setting(secret_reference.key) @@ -581,10 +573,10 @@ def test_config_setting_secret_reference(self, client): def test_feature_filter_targeting(self, client): new = FeatureFlagConfigurationSetting( "newflag", - True, + enabled=True, filters=[ { - "name": TARGETING, + "name": FILTER_TARGETING, "parameters": { u"Audience": { u"Users": [u"abc", u"def"], @@ -608,7 +600,7 @@ def test_feature_filter_targeting(self, client): updated_sent_config.filters.append( { - "name": TARGETING, + "name": FILTER_TARGETING, "parameters": { u"Audience": { u"Users": [u"abcd", u"defg"], @@ -618,9 +610,10 @@ def test_feature_filter_targeting(self, client): } } ) + updated_sent_config.filters.append( { - "name": TARGETING, + "name": FILTER_TARGETING, "parameters": { u"Audience": { u"Users": [u"abcde", u"defgh"], @@ -641,10 +634,35 @@ def test_feature_filter_targeting(self, client): def test_feature_filter_time_window(self, client): new = FeatureFlagConfigurationSetting( 'time_window', - True, + enabled=True, + filters=[ + { + "name": FILTER_TIME_WINDOW, + "parameters": { + "Start": "Wed, 10 Mar 2021 05:00:00 GMT", + "End": "Fri, 02 Apr 2021 04:00:00 GMT" + } + } + ] + ) + + sent = client.set_configuration_setting(new) + self._assert_same_keys(sent, new) + + sent.filters[0]["parameters"]["Start"] = "Thurs, 11 Mar 2021 05:00:00 GMT" + new_sent = client.set_configuration_setting(sent) + self._assert_same_keys(sent, new_sent) + + client.delete_configuration_setting(new_sent.key) + + @app_config_decorator + def test_feature_filter_time_window(self, client): + new = FeatureFlagConfigurationSetting( + 'time_window', + enabled=True, filters=[ { - "name": TIME_WINDOW, + "name": FILTER_TIME_WINDOW, "parameters": { "Start": "Wed, 10 Mar 2021 05:00:00 GMT", "End": "Fri, 02 Apr 2021 04:00:00 GMT" @@ -666,10 +684,10 @@ def test_feature_filter_time_window(self, client): def test_feature_filter_custom(self, client): new = FeatureFlagConfigurationSetting( 'custom', - True, + enabled=True, filters=[ { - "name": PERCENTAGE, + "name": FILTER_PERCENTAGE, "parameters": { "Value": 10, "User": "user1" @@ -691,23 +709,23 @@ def test_feature_filter_custom(self, client): def test_feature_filter_multiple(self, client): new = FeatureFlagConfigurationSetting( 'custom', - True, + enabled=True, filters=[ { - "name": PERCENTAGE, + "name": FILTER_PERCENTAGE, "parameters": { "Value": 10 } }, { - "name": TIME_WINDOW, + "name": FILTER_TIME_WINDOW, "parameters": { "Start": "Wed, 10 Mar 2021 05:00:00 GMT", "End": "Fri, 02 Apr 2021 04:00:00 GMT" } }, { - "name": TARGETING, + "name": FILTER_TARGETING, "parameters": { u"Audience": { u"Users": [u"abcde", u"defgh"], @@ -739,10 +757,10 @@ def test_feature_filter_multiple(self, client): def test_breaking1(self, client): new = FeatureFlagConfigurationSetting( 'breaking1', - True, + enabled=True, filters=[ { - "name": TIME_WINDOW, + "name": FILTER_TIME_WINDOW, "parameters": { "Start": "bababooey, 31 Mar 2021 25:00:00 GMT", "End": "Fri, 02 Apr 2021 04:00:00 GMT" @@ -755,10 +773,10 @@ def test_breaking1(self, client): new = FeatureFlagConfigurationSetting( 'breaking2', - True, + enabled=True, filters=[ { - "name": TIME_WINDOW, + "name": FILTER_TIME_WINDOW, "parameters": { "Start": "bababooey, 31 Mar 2021 25:00:00 GMT", "End": "not even trying to be a date" @@ -772,10 +790,10 @@ def test_breaking1(self, client): # This will show up as a Custom filter new = FeatureFlagConfigurationSetting( 'breaking3', - True, + enabled=True, filters=[ { - "name": TIME_WINDOW, + "name": FILTER_TIME_WINDOW, "parameters": { "Start": "bababooey, 31 Mar 2021 25:00:00 GMT", "End": "not even trying to be a date" @@ -788,10 +806,10 @@ def test_breaking1(self, client): new = FeatureFlagConfigurationSetting( 'breaking4', - True, + enabled=True, filters=[ { - "name": TIME_WINDOW, + "name": FILTER_TIME_WINDOW, "parameters": "stringystring" }, ] @@ -801,10 +819,10 @@ def test_breaking1(self, client): new = FeatureFlagConfigurationSetting( 'breaking5', - True, + enabled=True, filters=[ { - "name": TARGETING, + "name": FILTER_TARGETING, "parameters": { u"Audience": { u"Users": '123' @@ -818,10 +836,10 @@ def test_breaking1(self, client): new = FeatureFlagConfigurationSetting( 'breaking6', - True, + enabled=True, filters=[ { - "name": TARGETING, + "name": FILTER_TARGETING, "parameters": "invalidformat" } ] @@ -831,7 +849,7 @@ def test_breaking1(self, client): new = FeatureFlagConfigurationSetting( 'breaking7', - True, + enabled=True, filters=[ { 'abc': 'def' @@ -843,7 +861,7 @@ def test_breaking1(self, client): new = FeatureFlagConfigurationSetting( 'breaking8', - True, + enabled=True, filters=[ { 'abc': 'def' @@ -870,3 +888,12 @@ def test_breaking2(self, client): new.content_type = "fkaeyjfdkal;" client.set_configuration_setting(new) new1 = client.get_configuration_setting(new.key) + + @app_config_decorator + def test_type_error(self, client): + with pytest.raises(TypeError): + _ = FeatureFlagConfigurationSetting("blash", key="blash") + with pytest.raises(TypeError): + _ = FeatureFlagConfigurationSetting("blash", value="blash") + with pytest.raises(TypeError): + _ = SecretReferenceConfigurationSetting("blash", value="blash") \ No newline at end of file diff --git a/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_configuration_client_aad.py b/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_configuration_client_aad.py index bfffa59edc3d..49e3bd0e9138 100644 --- a/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_configuration_client_aad.py +++ b/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_configuration_client_aad.py @@ -17,9 +17,9 @@ ConfigurationSetting, SecretReferenceConfigurationSetting, FeatureFlagConfigurationSetting, - PERCENTAGE, - TARGETING, - TIME_WINDOW, + FILTER_PERCENTAGE, + FILTER_TARGETING, + FILTER_TIME_WINDOW, ) from consts import ( KEY, @@ -33,10 +33,8 @@ import pytest import copy import datetime -import os -import logging +import json import re -import functools from wrapper import app_config_decorator @@ -466,7 +464,7 @@ def _assert_same_keys(self, key1, key2): assert key1.enabled == key2.enabled assert len(key1.filters) == len(key2.filters) elif isinstance(key1, SecretReferenceConfigurationSetting): - assert key1.secret_uri == key2.secret_uri + assert key1.secret_id == key2.secret_id else: assert key1.value == key2.value @@ -508,30 +506,30 @@ def test_sync_tokens(self, client): @app_config_decorator def test_config_setting_feature_flag(self, client): - feature_flag = FeatureFlagConfigurationSetting("test_feature", True) + feature_flag = FeatureFlagConfigurationSetting("test_feature", enabled=True) set_flag = client.set_configuration_setting(feature_flag) self._assert_same_keys(feature_flag, set_flag) set_flag.enabled = not set_flag.enabled changed_flag = client.set_configuration_setting(set_flag) - self._assert_same_keys(set_flag, changed_flag) changed_flag.enabled = False - assert changed_flag.value['enabled'] == False + temp = json.loads(changed_flag.value) + assert temp['enabled'] == False - c = copy.deepcopy(changed_flag.value) + c = json.loads(copy.deepcopy(changed_flag.value)) c['enabled'] = True - changed_flag.value = c + changed_flag.value = json.dumps(c) assert changed_flag.enabled == True - changed_flag.value = {} + changed_flag.value = json.dumps({}) assert changed_flag.enabled == None - assert changed_flag.value == {} + assert changed_flag.value == json.dumps({'enabled': None, "conditions": {"client_filters": None}}) - with pytest.raises(ValueError): - set_flag.value = "bad_value" - _ = set_flag.enabled + set_flag.value = "bad_value" + assert set_flag.enabled == None + assert set_flag.filters == None client.delete_configuration_setting(changed_flag.key) @@ -548,15 +546,15 @@ def test_config_setting_secret_reference(self, client): assert isinstance(updated_flag, SecretReferenceConfigurationSetting) new_uri = "https://aka.ms/azsdk" new_uri2 = "https://aka.ms/azsdk/python" - updated_flag.secret_uri = new_uri - assert updated_flag.value['secret_uri'] == new_uri + updated_flag.secret_id = new_uri + temp = json.loads(updated_flag.value) + assert temp['secret_uri'] == new_uri - updated_flag.value = {'secret_uri': new_uri2} - assert updated_flag.secret_uri == new_uri2 + updated_flag.value = json.dumps({'secret_uri': new_uri2}) + assert updated_flag.secret_id == new_uri2 - with pytest.raises(ValueError): - set_flag.value = "bad_value" - _ = set_flag.secret_uri + set_flag.value = "bad_value" + assert set_flag.secret_id == None client.delete_configuration_setting(secret_reference.key) @@ -564,10 +562,10 @@ def test_config_setting_secret_reference(self, client): def test_feature_filter_targeting(self, client): new = FeatureFlagConfigurationSetting( "newflag", - True, + enabled=True, filters=[ { - "name": TARGETING, + "name": FILTER_TARGETING, "parameters": { u"Audience": { u"Users": [u"abc", u"def"], @@ -589,9 +587,10 @@ def test_feature_filter_targeting(self, client): updated_sent_config = client.set_configuration_setting(sent_config) self._assert_same_keys(sent_config, updated_sent_config) - updated_sent_config.filters.append( + filters = updated_sent_config.filters + filters.append( { - "name": TARGETING, + "name": FILTER_TARGETING, "parameters": { u"Audience": { u"Users": [u"abcd", u"defg"], @@ -601,9 +600,10 @@ def test_feature_filter_targeting(self, client): } } ) - updated_sent_config.filters.append( + + filters.append( { - "name": TARGETING, + "name": FILTER_TARGETING, "parameters": { u"Audience": { u"Users": [u"abcde", u"defgh"], @@ -613,6 +613,7 @@ def test_feature_filter_targeting(self, client): } } ) + updated_sent_config.filters = filters sent_config = client.set_configuration_setting(updated_sent_config) self._assert_same_keys(sent_config, updated_sent_config) @@ -624,10 +625,35 @@ def test_feature_filter_targeting(self, client): def test_feature_filter_time_window(self, client): new = FeatureFlagConfigurationSetting( 'time_window', - True, + enabled=True, + filters=[ + { + "name": FILTER_TIME_WINDOW, + "parameters": { + "Start": "Wed, 10 Mar 2021 05:00:00 GMT", + "End": "Fri, 02 Apr 2021 04:00:00 GMT" + } + } + ] + ) + + sent = client.set_configuration_setting(new) + self._assert_same_keys(sent, new) + + sent.filters[0]["parameters"]["Start"] = "Thurs, 11 Mar 2021 05:00:00 GMT" + new_sent = client.set_configuration_setting(sent) + self._assert_same_keys(sent, new_sent) + + client.delete_configuration_setting(new_sent.key) + + @app_config_decorator + def test_feature_filter_time_window(self, client): + new = FeatureFlagConfigurationSetting( + 'time_window', + enabled=True, filters=[ { - "name": TIME_WINDOW, + "name": FILTER_TIME_WINDOW, "parameters": { "Start": "Wed, 10 Mar 2021 05:00:00 GMT", "End": "Fri, 02 Apr 2021 04:00:00 GMT" @@ -649,10 +675,10 @@ def test_feature_filter_time_window(self, client): def test_feature_filter_custom(self, client): new = FeatureFlagConfigurationSetting( 'custom', - True, + enabled=True, filters=[ { - "name": PERCENTAGE, + "name": FILTER_PERCENTAGE, "parameters": { "Value": 10, "User": "user1" @@ -674,23 +700,23 @@ def test_feature_filter_custom(self, client): def test_feature_filter_multiple(self, client): new = FeatureFlagConfigurationSetting( 'custom', - True, + enabled=True, filters=[ { - "name": PERCENTAGE, + "name": FILTER_PERCENTAGE, "parameters": { "Value": 10 } }, { - "name": TIME_WINDOW, + "name": FILTER_TIME_WINDOW, "parameters": { "Start": "Wed, 10 Mar 2021 05:00:00 GMT", "End": "Fri, 02 Apr 2021 04:00:00 GMT" } }, { - "name": TARGETING, + "name": FILTER_TARGETING, "parameters": { u"Audience": { u"Users": [u"abcde", u"defgh"], diff --git a/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_configuration_client_aad_async.py b/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_configuration_client_aad_async.py index 20a6ddb440bf..f4ecad553be9 100644 --- a/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_configuration_client_aad_async.py +++ b/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_configuration_client_aad_async.py @@ -16,9 +16,9 @@ ConfigurationSetting, SecretReferenceConfigurationSetting, FeatureFlagConfigurationSetting, - PERCENTAGE, - TARGETING, - TIME_WINDOW, + FILTER_PERCENTAGE, + FILTER_TARGETING, + FILTER_TIME_WINDOW, ) from azure.appconfiguration.aio import AzureAppConfigurationClient from consts import ( @@ -33,9 +33,7 @@ import pytest import copy import datetime -import os -import logging -import asyncio +import json import re import copy @@ -507,37 +505,36 @@ def _assert_same_keys(self, key1, key2): assert key1.enabled == key2.enabled assert len(key1.filters) == len(key2.filters) elif isinstance(key1, SecretReferenceConfigurationSetting): - assert key1.secret_uri == key2.secret_uri + assert key1.secret_id == key2.secret_id else: assert key1.value == key2.value @app_config_decorator def test_config_setting_feature_flag(self, client): - feature_flag = FeatureFlagConfigurationSetting("test_feature", True) + feature_flag = FeatureFlagConfigurationSetting("test_feature", enabled=True) set_flag = client.set_configuration_setting(feature_flag) self._assert_same_keys(feature_flag, set_flag) set_flag.enabled = not set_flag.enabled changed_flag = client.set_configuration_setting(set_flag) - self._assert_same_keys(set_flag, changed_flag) changed_flag.enabled = False - assert changed_flag.value['enabled'] == False + temp = json.loads(changed_flag.value) + assert temp['enabled'] == False - c = copy.deepcopy(changed_flag.value) + c = json.loads(copy.deepcopy(changed_flag.value)) c['enabled'] = True - changed_flag.value = c + changed_flag.value = json.dumps(c) assert changed_flag.enabled == True - changed_flag.value = {} + changed_flag.value = json.dumps({}) assert changed_flag.enabled == None - assert changed_flag.value == {} - - with pytest.raises(ValueError): - set_flag.value = "bad_value" - _ = set_flag.enabled + assert changed_flag.value == json.dumps({'enabled': None, "conditions": {"client_filters": None}}) + set_flag.value = "bad_value" + assert set_flag.enabled == None + assert set_flag.filters == None client.delete_configuration_setting(changed_flag.key) @app_config_decorator @@ -547,22 +544,22 @@ def test_config_setting_secret_reference(self, client): set_flag = client.set_configuration_setting(secret_reference) self._assert_same_keys(secret_reference, set_flag) - set_flag.secret_uri = "https://test-test.vault.azure.net/new_secrets/connectionString" + set_flag.secret_id = "https://test-test.vault.azure.net/new_secrets/connectionString" updated_flag = client.set_configuration_setting(set_flag) self._assert_same_keys(set_flag, updated_flag) assert isinstance(updated_flag, SecretReferenceConfigurationSetting) new_uri = "https://aka.ms/azsdk" new_uri2 = "https://aka.ms/azsdk/python" - updated_flag.secret_uri = new_uri - assert updated_flag.value['secret_uri'] == new_uri + updated_flag.secret_id = new_uri + temp = json.loads(updated_flag.value) + assert temp['secret_uri'] == new_uri - updated_flag.value = {'secret_uri': new_uri2} - assert updated_flag.secret_uri == new_uri2 + updated_flag.value = json.dumps({'secret_uri': new_uri2}) + assert updated_flag.secret_id == new_uri2 - with pytest.raises(ValueError): - set_flag.value = "bad_value" - _ = set_flag.secret_uri + set_flag.value = "bad_value" + assert set_flag.secret_id == None client.delete_configuration_setting(secret_reference.key) @@ -570,10 +567,10 @@ def test_config_setting_secret_reference(self, client): def test_feature_filter_targeting(self, client): new = FeatureFlagConfigurationSetting( "newflag", - True, + enabled=True, filters=[ { - "name": TARGETING, + "name": FILTER_TARGETING, "parameters": { u"Audience": { u"Users": [u"abc", u"def"], @@ -595,9 +592,10 @@ def test_feature_filter_targeting(self, client): updated_sent_config = client.set_configuration_setting(sent_config) self._assert_same_keys(sent_config, updated_sent_config) - updated_sent_config.filters.append( + filters = updated_sent_config.filters + filters.append( { - "name": TARGETING, + "name": FILTER_TARGETING, "parameters": { u"Audience": { u"Users": [u"abcd", u"defg"], @@ -607,9 +605,10 @@ def test_feature_filter_targeting(self, client): } } ) - updated_sent_config.filters.append( + + filters.append( { - "name": TARGETING, + "name": FILTER_TARGETING, "parameters": { u"Audience": { u"Users": [u"abcde", u"defgh"], @@ -619,6 +618,7 @@ def test_feature_filter_targeting(self, client): } } ) + updated_sent_config.filters = filters sent_config = client.set_configuration_setting(updated_sent_config) self._assert_same_keys(sent_config, updated_sent_config) @@ -630,10 +630,35 @@ def test_feature_filter_targeting(self, client): def test_feature_filter_time_window(self, client): new = FeatureFlagConfigurationSetting( 'time_window', - True, + enabled=True, + filters=[ + { + "name": FILTER_TIME_WINDOW, + "parameters": { + "Start": "Wed, 10 Mar 2021 05:00:00 GMT", + "End": "Fri, 02 Apr 2021 04:00:00 GMT" + } + } + ] + ) + + sent = client.set_configuration_setting(new) + self._assert_same_keys(sent, new) + + sent.filters[0]["parameters"]["Start"] = "Thurs, 11 Mar 2021 05:00:00 GMT" + new_sent = client.set_configuration_setting(sent) + self._assert_same_keys(sent, new_sent) + + client.delete_configuration_setting(new_sent.key) + + @app_config_decorator + def test_feature_filter_time_window(self, client): + new = FeatureFlagConfigurationSetting( + 'time_window', + enabled=True, filters=[ { - "name": TIME_WINDOW, + "name": FILTER_TIME_WINDOW, "parameters": { "Start": "Wed, 10 Mar 2021 05:00:00 GMT", "End": "Fri, 02 Apr 2021 04:00:00 GMT" @@ -655,10 +680,10 @@ def test_feature_filter_time_window(self, client): def test_feature_filter_custom(self, client): new = FeatureFlagConfigurationSetting( 'custom', - True, + enabled=True, filters=[ { - "name": PERCENTAGE, + "name": FILTER_PERCENTAGE, "parameters": { "Value": 10, "User": "user1" @@ -680,23 +705,23 @@ def test_feature_filter_custom(self, client): def test_feature_filter_multiple(self, client): new = FeatureFlagConfigurationSetting( 'custom', - True, + enabled=True, filters=[ { - "name": PERCENTAGE, + "name": FILTER_PERCENTAGE, "parameters": { "Value": 10 } }, { - "name": TIME_WINDOW, + "name": FILTER_TIME_WINDOW, "parameters": { "Start": "Wed, 10 Mar 2021 05:00:00 GMT", "End": "Fri, 02 Apr 2021 04:00:00 GMT" } }, { - "name": TARGETING, + "name": FILTER_TARGETING, "parameters": { u"Audience": { u"Users": [u"abcde", u"defgh"], diff --git a/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_configuration_client_async.py b/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_configuration_client_async.py index 5042ba86a5e0..2d63ece79821 100644 --- a/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_configuration_client_async.py +++ b/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_configuration_client_async.py @@ -16,9 +16,9 @@ ConfigurationSetting, SecretReferenceConfigurationSetting, FeatureFlagConfigurationSetting, - PERCENTAGE, - TARGETING, - TIME_WINDOW, + FILTER_PERCENTAGE, + FILTER_TARGETING, + FILTER_TIME_WINDOW, ) from azure.appconfiguration.aio import AzureAppConfigurationClient from consts import ( @@ -34,11 +34,11 @@ import copy import datetime import os -import logging +import json import re -import functools import copy from uuid import uuid4 +import json from async_proxy import AzureAppConfigurationClientProxy from async_wrapper import app_config_decorator @@ -462,7 +462,7 @@ def test_sync_tokens(self, client): def test_sync_tokens(self, client): new = FeatureFlagConfigurationSetting( 'custom', - True, + enabled=True, filters = [ { "name": "Microsoft.Percentage", @@ -481,10 +481,10 @@ def test_sync_tokens(self, client): new = FeatureFlagConfigurationSetting( 'time_window', - True, + enabled=True, filters = [ { - u"name": TIME_WINDOW, + u"name": FILTER_TIME_WINDOW, u"parameters": { "Start": "Wed, 10 Mar 2021 05:00:00 GMT", "End": "Fri, 02 Apr 2021 04:00:00 GMT" @@ -500,10 +500,10 @@ def test_sync_tokens(self, client): new = FeatureFlagConfigurationSetting( "newflag", - True, + enabled=True, filters=[ { - "name": TARGETING, + "name": FILTER_TARGETING, "parameters": { u"Audience": { u"Users": [u"abc", u"def"], @@ -534,36 +534,36 @@ def _assert_same_keys(self, key1, key2): assert key1.enabled == key2.enabled assert len(key1.filters) == len(key2.filters) elif isinstance(key1, SecretReferenceConfigurationSetting): - assert key1.secret_uri == key2.secret_uri + assert key1.secret_id == key2.secret_id else: assert key1.value == key2.value @app_config_decorator def test_config_setting_feature_flag(self, client): - feature_flag = FeatureFlagConfigurationSetting("test_feature", True) + feature_flag = FeatureFlagConfigurationSetting("test_feature", enabled=True) set_flag = client.set_configuration_setting(feature_flag) self._assert_same_keys(feature_flag, set_flag) set_flag.enabled = not set_flag.enabled changed_flag = client.set_configuration_setting(set_flag) - self._assert_same_keys(set_flag, changed_flag) changed_flag.enabled = False - assert changed_flag.value['enabled'] == False + temp = json.loads(changed_flag.value) + assert temp['enabled'] == False - c = copy.deepcopy(changed_flag.value) + c = json.loads(copy.deepcopy(changed_flag.value)) c['enabled'] = True - changed_flag.value = c + changed_flag.value = json.dumps(c) assert changed_flag.enabled == True - changed_flag.value = {} + changed_flag.value = json.dumps({}) assert changed_flag.enabled == None - assert changed_flag.value == {} + assert changed_flag.value == json.dumps({'enabled': None, "conditions": {"client_filters": None}}) - with pytest.raises(ValueError): - set_flag.value = "bad_value" - _ = set_flag.enabled + set_flag.value = "bad_value" + assert set_flag.enabled == None + assert set_flag.filters == None client.delete_configuration_setting(changed_flag.key) @@ -574,22 +574,22 @@ def test_config_setting_secret_reference(self, client): set_flag = client.set_configuration_setting(secret_reference) self._assert_same_keys(secret_reference, set_flag) - set_flag.secret_uri = "https://test-test.vault.azure.net/new_secrets/connectionString" + set_flag.secret_id = "https://test-test.vault.azure.net/new_secrets/connectionString" updated_flag = client.set_configuration_setting(set_flag) self._assert_same_keys(set_flag, updated_flag) assert isinstance(updated_flag, SecretReferenceConfigurationSetting) new_uri = "https://aka.ms/azsdk" new_uri2 = "https://aka.ms/azsdk/python" - updated_flag.secret_uri = new_uri - assert updated_flag.value['secret_uri'] == new_uri + updated_flag.secret_id = new_uri + temp = json.loads(updated_flag.value) + assert temp['secret_uri'] == new_uri - updated_flag.value = {'secret_uri': new_uri2} - assert updated_flag.secret_uri == new_uri2 + updated_flag.value = json.dumps({'secret_uri': new_uri2}) + assert updated_flag.secret_id == new_uri2 - with pytest.raises(ValueError): - set_flag.value = "bad_value" - _ = set_flag.secret_uri + set_flag.value = "bad_value" + assert set_flag.secret_id == None client.delete_configuration_setting(secret_reference.key) @@ -597,10 +597,10 @@ def test_config_setting_secret_reference(self, client): def test_feature_filter_targeting(self, client): new = FeatureFlagConfigurationSetting( "newflag", - True, + enabled=True, filters=[ { - "name": TARGETING, + "name": FILTER_TARGETING, "parameters": { u"Audience": { u"Users": [u"abc", u"def"], @@ -622,9 +622,10 @@ def test_feature_filter_targeting(self, client): updated_sent_config = client.set_configuration_setting(sent_config) self._assert_same_keys(sent_config, updated_sent_config) - updated_sent_config.filters.append( + filters = updated_sent_config.filters + filters.append( { - "name": TARGETING, + "name": FILTER_TARGETING, "parameters": { u"Audience": { u"Users": [u"abcd", u"defg"], @@ -634,9 +635,10 @@ def test_feature_filter_targeting(self, client): } } ) - updated_sent_config.filters.append( + + filters.append( { - "name": TARGETING, + "name": FILTER_TARGETING, "parameters": { u"Audience": { u"Users": [u"abcde", u"defgh"], @@ -646,6 +648,7 @@ def test_feature_filter_targeting(self, client): } } ) + updated_sent_config.filters = filters sent_config = client.set_configuration_setting(updated_sent_config) self._assert_same_keys(sent_config, updated_sent_config) @@ -657,10 +660,35 @@ def test_feature_filter_targeting(self, client): def test_feature_filter_time_window(self, client): new = FeatureFlagConfigurationSetting( 'time_window', - True, + enabled=True, + filters=[ + { + "name": FILTER_TIME_WINDOW, + "parameters": { + "Start": "Wed, 10 Mar 2021 05:00:00 GMT", + "End": "Fri, 02 Apr 2021 04:00:00 GMT" + } + } + ] + ) + + sent = client.set_configuration_setting(new) + self._assert_same_keys(sent, new) + + sent.filters[0]["parameters"]["Start"] = "Thurs, 11 Mar 2021 05:00:00 GMT" + new_sent = client.set_configuration_setting(sent) + self._assert_same_keys(sent, new_sent) + + client.delete_configuration_setting(new_sent.key) + + @app_config_decorator + def test_feature_filter_time_window(self, client): + new = FeatureFlagConfigurationSetting( + 'time_window', + enabled=True, filters=[ { - "name": TIME_WINDOW, + "name": FILTER_TIME_WINDOW, "parameters": { "Start": "Wed, 10 Mar 2021 05:00:00 GMT", "End": "Fri, 02 Apr 2021 04:00:00 GMT" @@ -682,10 +710,10 @@ def test_feature_filter_time_window(self, client): def test_feature_filter_custom(self, client): new = FeatureFlagConfigurationSetting( 'custom', - True, + enabled=True, filters=[ { - "name": PERCENTAGE, + "name": FILTER_PERCENTAGE, "parameters": { "Value": 10, "User": "user1" @@ -707,23 +735,23 @@ def test_feature_filter_custom(self, client): def test_feature_filter_multiple(self, client): new = FeatureFlagConfigurationSetting( 'custom', - True, + enabled=True, filters=[ { - "name": PERCENTAGE, + "name": FILTER_PERCENTAGE, "parameters": { "Value": 10 } }, { - "name": TIME_WINDOW, + "name": FILTER_TIME_WINDOW, "parameters": { "Start": "Wed, 10 Mar 2021 05:00:00 GMT", "End": "Fri, 02 Apr 2021 04:00:00 GMT" } }, { - "name": TARGETING, + "name": FILTER_TARGETING, "parameters": { u"Audience": { u"Users": [u"abcde", u"defgh"], diff --git a/sdk/appconfiguration/azure-appconfiguration/tests/test_consistency.py b/sdk/appconfiguration/azure-appconfiguration/tests/test_consistency.py index 772facbaa876..33fe8a9a588b 100644 --- a/sdk/appconfiguration/azure-appconfiguration/tests/test_consistency.py +++ b/sdk/appconfiguration/azure-appconfiguration/tests/test_consistency.py @@ -3,39 +3,15 @@ # Licensed under the MIT License. See License.txt in the project root for # license information. # -------------------------------------------------------------------------- -from azure.core import MatchConditions -from azure.core.exceptions import HttpResponseError -from devtools_testutils import AzureTestCase, PowerShellPreparer -from azure.core.exceptions import ( - ResourceModifiedError, - ResourceNotFoundError, - ResourceExistsError, - AzureError, -) +from devtools_testutils import AzureTestCase from azure.appconfiguration import ( - ResourceReadOnlyError, - AzureAppConfigurationClient, - ConfigurationSetting, FeatureFlagConfigurationSetting, SecretReferenceConfigurationSetting, - PERCENTAGE, - TARGETING, - TIME_WINDOW, -) -from azure.identity import DefaultAzureCredential - -from consts import ( - KEY, - LABEL, - TEST_VALUE, - TEST_CONTENT_TYPE, - LABEL_RESERVED_CHARS, - PAGE_SIZE, - KEY_UUID, + FILTER_PERCENTAGE, ) from wrapper import app_config_decorator -from uuid import uuid4 +import json import pytest @@ -53,7 +29,7 @@ def _assert_same_keys(self, key1, key2): assert key1.enabled == key2.enabled assert len(key1.filters) == len(key2.filters) elif isinstance(key1, SecretReferenceConfigurationSetting): - assert key1.secret_uri == key2.secret_uri + assert key1.secret_id == key2.secret_id else: assert key1.value == key2.value @@ -62,10 +38,10 @@ def test_update_json_by_value(self, client): key = self.get_resource_name("key") feature_flag = FeatureFlagConfigurationSetting( key, - True, + enabled=True, filters=[ { - "name": PERCENTAGE, + "name": FILTER_PERCENTAGE, "parameters": { "Value": 10, "User": "user1" @@ -75,7 +51,7 @@ def test_update_json_by_value(self, client): ) set_flag = client.set_configuration_setting(feature_flag) - set_flag.value = { + set_flag.value = json.dumps({ 'conditions': { 'client_filters': [ { @@ -96,7 +72,7 @@ def test_update_json_by_value(self, client): 'description': '', 'enabled': False, 'id': key, - } + }) set_flag = client.set_configuration_setting(set_flag) assert isinstance(set_flag, FeatureFlagConfigurationSetting) @@ -106,46 +82,49 @@ def test_update_json_by_value(self, client): @app_config_decorator def test_feature_flag_invalid_json(self, client): key = self.get_resource_name("key") - feature_flag = FeatureFlagConfigurationSetting(key, True) + feature_flag = FeatureFlagConfigurationSetting(key, enabled=True) set_flag = client.set_configuration_setting(feature_flag) - set_flag.value = [] - received = client.set_configuration_setting(set_flag) + with pytest.raises(TypeError): + set_flag.value = [] + received = client.set_configuration_setting(set_flag) - assert not isinstance(received, FeatureFlagConfigurationSetting) + # assert isinstance(received, FeatureFlagConfigurationSetting) @app_config_decorator def test_feature_flag_invalid_json_string(self, client): key = self.get_resource_name("key") - feature_flag = FeatureFlagConfigurationSetting(key, True) + feature_flag = FeatureFlagConfigurationSetting(key, enabled=True) set_flag = client.set_configuration_setting(feature_flag) set_flag.value = "hello world" received = client.set_configuration_setting(set_flag) - assert not isinstance(received, FeatureFlagConfigurationSetting) + assert isinstance(received, FeatureFlagConfigurationSetting) @app_config_decorator def test_feature_flag_invalid_json_access_properties(self, client): key = self.get_resource_name("key") - feature_flag = FeatureFlagConfigurationSetting(key, True) + feature_flag = FeatureFlagConfigurationSetting(key, enabled=True) set_flag = client.set_configuration_setting(feature_flag) set_flag.value = "hello world" - with pytest.raises(ValueError): - a = set_flag.enabled - with pytest.raises(ValueError): - b = set_flag.filters + assert set_flag.enabled == None + assert set_flag.filters == None + # with pytest.raises(ValueError): + # a = set_flag.enabled + # with pytest.raises(ValueError): + # b = set_flag.filters @app_config_decorator def test_feature_flag_set_value(self, client): key = self.get_resource_name("key") feature_flag = FeatureFlagConfigurationSetting( key, - True, + enabled=True, filters=[ { - "name": PERCENTAGE, + "name": FILTER_PERCENTAGE, "parameters": { "Value": 10, "User": "user1" @@ -153,24 +132,24 @@ def test_feature_flag_set_value(self, client): } ] ) - feature_flag.value = { + feature_flag.value = json.dumps({ "conditions": { "client_filters": [] }, "enabled": False - } + }) - assert feature_flag.value["enabled"] == False + assert feature_flag.enabled == False @app_config_decorator def test_feature_flag_set_enabled(self, client): key = self.get_resource_name("key") feature_flag = FeatureFlagConfigurationSetting( key, - True, + enabled=True, filters=[ { - "name": PERCENTAGE, + "name": FILTER_PERCENTAGE, "parameters": { "Value": 10, "User": "user1" @@ -180,10 +159,11 @@ def test_feature_flag_set_enabled(self, client): ) feature_flag.enabled = False - assert feature_flag.value["enabled"] == False + temp = json.loads(feature_flag.value) + assert temp["enabled"] == False @app_config_decorator def test_feature_flag_prefix(self, client): key = self.get_resource_name("key") - feature_flag = FeatureFlagConfigurationSetting(key, True) + feature_flag = FeatureFlagConfigurationSetting(key, enabled=True) assert feature_flag.key.startswith(".appconfig.featureflag/")