diff --git a/.vscode/cspell.json b/.vscode/cspell.json index fd8ff9124bc2..71ec3978261d 100644 --- a/.vscode/cspell.json +++ b/.vscode/cspell.json @@ -1682,6 +1682,12 @@ "azconfig" ] }, + { + "filename": "sdk/appconfiguration/azure-appconfiguration/**", + "words": [ + "kvset" + ] + }, { "filename": "sdk/personalizer/test-resources.json", "words": [ diff --git a/sdk/appconfiguration/azure-appconfiguration/CHANGELOG.md b/sdk/appconfiguration/azure-appconfiguration/CHANGELOG.md index 7c254a7effdc..a38989485545 100644 --- a/sdk/appconfiguration/azure-appconfiguration/CHANGELOG.md +++ b/sdk/appconfiguration/azure-appconfiguration/CHANGELOG.md @@ -1,16 +1,14 @@ # Release History -## 1.5.1 (Unreleased) +## 1.6.0b1 (2024-03-14) ### Features Added - -### Breaking Changes +- Exposed `send_request()` method in each client to send custom requests using the client's existing pipeline. +- Supported to get page ETag while iterating `list_configuration_setting()` result by page. ### Bugs Fixed - Fixed a bug in consuming "etag" value in sync operation `set_configuration_setting()`. -### Other Changes - ## 1.5.0 (2023-11-09) ### Other Changes diff --git a/sdk/appconfiguration/azure-appconfiguration/assets.json b/sdk/appconfiguration/azure-appconfiguration/assets.json index c374a2eea98b..1493f31a8b20 100644 --- a/sdk/appconfiguration/azure-appconfiguration/assets.json +++ b/sdk/appconfiguration/azure-appconfiguration/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "python", "TagPrefix": "python/appconfiguration/azure-appconfiguration", - "Tag": "python/appconfiguration/azure-appconfiguration_6ae662d134" + "Tag": "python/appconfiguration/azure-appconfiguration_8137b21bd0" } 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 f45b6acc9a05..beb098e4b24c 100644 --- a/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_azure_appconfiguration_client.py +++ b/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_azure_appconfiguration_client.py @@ -4,6 +4,7 @@ # license information. # ------------------------------------------------------------------------- import binascii +import functools from datetime import datetime from typing import Any, Dict, List, Mapping, Optional, Union, cast, overload from typing_extensions import Literal @@ -19,12 +20,18 @@ ResourceModifiedError, ResourceNotModifiedError, ) +from azure.core.rest import HttpRequest, HttpResponse from azure.core.utils import CaseInsensitiveDict from ._azure_appconfiguration_error import ResourceReadOnlyError from ._azure_appconfiguration_requests import AppConfigRequestsCredentialsPolicy from ._generated import AzureAppConfiguration from ._generated.models import SnapshotUpdateParameters, SnapshotStatus -from ._models import ConfigurationSetting, ConfigurationSettingsFilter, ConfigurationSnapshot +from ._models import ( + ConfigurationSetting, + ConfigurationSettingsFilter, + ConfigurationSnapshot, + ConfigurationSettingPropertiesPaged, +) from ._utils import ( prep_if_match, prep_if_none_match, @@ -110,6 +117,23 @@ def from_connection_string(cls, connection_string: str, **kwargs: Any) -> "Azure **kwargs, ) + @distributed_trace + def send_request(self, request: HttpRequest, *, stream: bool = False, **kwargs) -> HttpResponse: + """Runs a network request using the client's existing pipeline. + + The request URL can be relative to the vault URL. The service API version used for the request is the same as + the client's unless otherwise specified. This method does not raise if the response is an error; to raise an + exception, call `raise_for_status()` on the returned response object. For more information about how to send + custom requests with this method, see https://aka.ms/azsdk/dpcodegen/python/send_request. + + :param request: The network request you want to make. + :type request: ~azure.core.rest.HttpRequest + :keyword bool stream: Whether the response payload will be streamed. Defaults to False. + :return: The response of your network call. Does not do error handling on your response. + :rtype: ~azure.core.rest.HttpResponse + """ + return self._impl._send_request(request, stream=stream, **kwargs) + @overload def list_configuration_settings( self, @@ -192,14 +216,16 @@ def list_configuration_settings(self, *args, **kwargs) -> ItemPaged[Configuratio ) key_filter, kwargs = get_key_filter(*args, **kwargs) label_filter, kwargs = get_label_filter(*args, **kwargs) - return self._impl.get_key_values( # type: ignore + command = functools.partial(self._impl.get_key_values_in_one_page, **kwargs) # type: ignore[attr-defined] + return ItemPaged( + command, key=key_filter, label=label_filter, accept_datetime=accept_datetime, select=select, - cls=lambda objs: [ConfigurationSetting._from_generated(x) for x in objs], - **kwargs, + page_iterator_class=ConfigurationSettingPropertiesPaged, ) + except binascii.Error as exc: raise binascii.Error("Connection string secret has incorrect padding") from exc diff --git a/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_generated/aio/operations/_patch.py b/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_generated/aio/operations/_patch.py index f7dd32510333..0c6a25151308 100644 --- a/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_generated/aio/operations/_patch.py +++ b/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_generated/aio/operations/_patch.py @@ -6,9 +6,157 @@ Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ -from typing import List +import urllib.parse +from typing import Any, AsyncIterable, List, Optional, Union +from azure.core.exceptions import ( + ClientAuthenticationError, + HttpResponseError, + ResourceExistsError, + ResourceNotFoundError, + ResourceNotModifiedError, + map_error, +) +from azure.core.rest import HttpRequest +from azure.core.tracing.decorator_async import distributed_trace_async +from azure.core.utils import case_insensitive_dict +from ._azure_app_configuration_operations import ( + AzureAppConfigurationOperationsMixin as AzureAppConfigOpGenerated, + ClsType, + build_get_key_values_request, +) +from ... import models as _models +from ..._vendor import _convert_request -__all__: List[str] = [] # Add all objects you want publicly available to users at this package level + +class AzureAppConfigurationOperationsMixin(AzureAppConfigOpGenerated): + @distributed_trace_async + async def get_key_values_in_one_page( + self, + key: Optional[str] = None, + label: Optional[str] = None, + after: Optional[str] = None, + accept_datetime: Optional[str] = None, + select: Optional[List[Union[str, _models.KeyValueFields]]] = None, + if_match: Optional[str] = None, + if_none_match: Optional[str] = None, + continuation_token: Optional[str] = None, + **kwargs: Any + ) -> AsyncIterable["_models.KeyValue"]: + """Gets a list of key-values in one page. + + Gets a list of key-values in one page. + + :param key: A filter used to match keys. Default value is None. + :type key: str + :param label: A filter used to match labels. Default value is None. + :type label: str + :param after: Instructs the server to return elements that appear after the element referred to + by the specified token. Default value is None. + :type after: str + :param accept_datetime: Requests the server to respond with the state of the resource at the + specified time. Default value is None. + :type accept_datetime: str + :param select: Used to select what fields are present in the returned resource(s). Default + value is None. + :type select: list[str or ~azure.appconfiguration.models.KeyValueFields] + :param if_match: Used to perform an operation only if the targeted resource's etag matches the + value provided. Default value is None. + :type if_match: str + :param if_none_match: Used to perform an operation only if the targeted resource's etag does + not match the value provided. Default value is None. + :type if_none_match: str + :param str continuation_token: An opaque continuation token. + :keyword callable cls: A custom type or function that will be passed the direct response + :return: An iterator like instance of either KeyValue or the result of cls(response) + :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.appconfiguration.models.KeyValue] + :raises ~azure.core.exceptions.HttpResponseError: + """ + _headers = kwargs.pop("headers", {}) or {} + _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) + + api_version: str = kwargs.pop("api_version", _params.pop("api-version", self._config.api_version)) + cls: ClsType[_models.KeyValueListResult] = kwargs.pop("cls", None) + + error_map = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + def prepare_request(next_link=None): + if not next_link: + + _request = build_get_key_values_request( + key=key, + label=label, + after=after, + accept_datetime=accept_datetime, + select=select, + if_match=if_match, + if_none_match=if_none_match, + sync_token=self._config.sync_token, + api_version=api_version, + headers=_headers, + params=_params, + ) + _request = _convert_request(_request) + path_format_arguments = { + "endpoint": self._serialize.url( + "self._config.endpoint", self._config.endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + else: + # make call to next link with the client's api-version + _parsed_next_link = urllib.parse.urlparse(next_link) + _next_request_params = case_insensitive_dict( + { + key: [urllib.parse.quote(v) for v in value] + for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items() + } + ) + _next_request_params["api-version"] = self._config.api_version + _request = HttpRequest( + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + ) + _request = _convert_request(_request) + path_format_arguments = { + "endpoint": self._serialize.url( + "self._config.endpoint", self._config.endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + _request.method = "GET" + return _request + + _request = prepare_request(continuation_token) + + _stream = False + pipeline_response: PipelineResponse = await self._client._pipeline.run( # type: ignore # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + response = pipeline_response.http_response + + if response.status_code not in [200]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = self._deserialize.failsafe_deserialize(_models.Error, pipeline_response) + raise HttpResponseError(response=response, model=error) + + response_headers = response.headers + deserialized = self._deserialize("KeyValueListResult", pipeline_response) + + if cls: + return cls(pipeline_response, deserialized, response_headers) + + return deserialized + + +__all__: List[str] = [ + "AzureAppConfigurationOperationsMixin" +] # Add all objects you want publicly available to users at this package level def patch_sdk(): diff --git a/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_generated/operations/_patch.py b/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_generated/operations/_patch.py index f7dd32510333..64039fa2983a 100644 --- a/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_generated/operations/_patch.py +++ b/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_generated/operations/_patch.py @@ -6,9 +6,161 @@ Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ -from typing import List +import urllib.parse +from typing import Any, Iterable, List, Optional, Union +from azure.core.exceptions import ( + ClientAuthenticationError, + HttpResponseError, + ResourceExistsError, + ResourceNotFoundError, + ResourceNotModifiedError, + map_error, +) +from azure.core.pipeline import PipelineResponse +from azure.core.rest import HttpRequest +from azure.core.tracing.decorator import distributed_trace +from azure.core.utils import case_insensitive_dict +from ._azure_app_configuration_operations import ( + AzureAppConfigurationOperationsMixin as AzureAppConfigOpGenerated, + ClsType, + build_get_key_values_request, +) +from .. import models as _models +from .._vendor import _convert_request -__all__: List[str] = [] # Add all objects you want publicly available to users at this package level + +class AzureAppConfigurationOperationsMixin(AzureAppConfigOpGenerated): + @distributed_trace + def get_key_values_in_one_page( + self, + key: Optional[str] = None, + label: Optional[str] = None, + after: Optional[str] = None, + accept_datetime: Optional[str] = None, + select: Optional[List[Union[str, _models.KeyValueFields]]] = None, + if_match: Optional[str] = None, + if_none_match: Optional[str] = None, + continuation_token: Optional[str] = None, + **kwargs: Any + ) -> Iterable["_models.KeyValue"]: + """Gets a list of key-values in one page. + + Gets a list of key-values in one page. + + :param key: A filter used to match keys. Default value is None. + :type key: str + :param label: A filter used to match labels. Default value is None. + :type label: str + :param after: Instructs the server to return elements that appear after the element referred to + by the specified token. Default value is None. + :type after: str + :param accept_datetime: Requests the server to respond with the state of the resource at the + specified time. Default value is None. + :type accept_datetime: str + :param select: Used to select what fields are present in the returned resource(s). Default + value is None. + :type select: list[str or ~azure.appconfiguration.models.KeyValueFields] + :param snapshot: A filter used get key-values for a snapshot. The value should be the name of + the snapshot. Not valid when used with 'key' and 'label' filters. Default value is None. + :type snapshot: str + :param if_match: Used to perform an operation only if the targeted resource's etag matches the + value provided. Default value is None. + :type if_match: str + :param if_none_match: Used to perform an operation only if the targeted resource's etag does + not match the value provided. Default value is None. + :type if_none_match: str + :param str continuation_token: An opaque continuation token. + :keyword callable cls: A custom type or function that will be passed the direct response + :return: An iterator like instance of either KeyValue or the result of cls(response) + :rtype: ~azure.core.paging.ItemPaged[~azure.appconfiguration.models.KeyValue] + :raises ~azure.core.exceptions.HttpResponseError: + """ + _headers = kwargs.pop("headers", {}) or {} + _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) + + api_version: str = kwargs.pop("api_version", _params.pop("api-version", self._config.api_version)) + cls: ClsType[_models.KeyValueListResult] = kwargs.pop("cls", None) + + error_map = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + def prepare_request(next_link=None): + if not next_link: + + _request = build_get_key_values_request( + key=key, + label=label, + after=after, + accept_datetime=accept_datetime, + select=select, + if_match=if_match, + if_none_match=if_none_match, + sync_token=self._config.sync_token, + api_version=api_version, + headers=_headers, + params=_params, + ) + _request = _convert_request(_request) + path_format_arguments = { + "endpoint": self._serialize.url( + "self._config.endpoint", self._config.endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + else: + # make call to next link with the client's api-version + _parsed_next_link = urllib.parse.urlparse(next_link) + _next_request_params = case_insensitive_dict( + { + key: [urllib.parse.quote(v) for v in value] + for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items() + } + ) + _next_request_params["api-version"] = self._config.api_version + _request = HttpRequest( + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + ) + _request = _convert_request(_request) + path_format_arguments = { + "endpoint": self._serialize.url( + "self._config.endpoint", self._config.endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + _request.method = "GET" + return _request + + _request = prepare_request(continuation_token) + + _stream = False + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + response = pipeline_response.http_response + + if response.status_code not in [200]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = self._deserialize.failsafe_deserialize(_models.Error, pipeline_response) + raise HttpResponseError(response=response, model=error) + + response_headers = response.headers + deserialized = self._deserialize("KeyValueListResult", pipeline_response) + + if cls: + return cls(pipeline_response, deserialized, response_headers) + + return deserialized + + +__all__: List[str] = [ + "AzureAppConfigurationOperationsMixin" +] # Add all objects you want publicly available to users at this package level def patch_sdk(): diff --git a/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_models.py b/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_models.py index 10a26bb7dc89..ad515bf392d6 100644 --- a/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_models.py +++ b/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_models.py @@ -4,10 +4,13 @@ # ------------------------------------ import json from datetime import datetime -from typing import Any, Dict, List, Optional, Union, cast +from typing import Any, Dict, List, Optional, Union, cast, Callable from typing_extensions import Literal +from azure.core.async_paging import AsyncList from azure.core.rest import HttpResponse +from azure.core.paging import PageIterator +from azure.core.async_paging import AsyncPageIterator from ._generated._serialization import Model from ._generated.models import ( KeyValue, @@ -495,11 +498,11 @@ def _from_generated(cls, generated: GeneratedConfigurationSnapshot) -> "Configur return snapshot @classmethod - def _from_deserialized( # pylint:disable=unused-argument + def _from_deserialized( cls, - response: HttpResponse, + response: HttpResponse, # pylint:disable=unused-argument deserialized: GeneratedConfigurationSnapshot, - response_headers: Dict, + response_headers: Dict, # pylint:disable=unused-argument ) -> "ConfigurationSnapshot": if deserialized is None: return deserialized @@ -535,3 +538,81 @@ def _to_generated(self) -> GeneratedConfigurationSnapshot: retention_period=self.retention_period, tags=self.tags, ) + + +def _return_deserialized_and_headers(_, deserialized, response_headers): + return deserialized, response_headers + + +class ConfigurationSettingPropertiesPaged(PageIterator): + """An iterable of ConfigurationSetting properties.""" + + etag: str + """The etag of current page.""" + + def __init__(self, command: Callable, **kwargs): + super(ConfigurationSettingPropertiesPaged, self).__init__( + self._get_next_cb, + self._extract_data_cb, + continuation_token=kwargs.get("continuation_token"), + ) + self._command = command + self._key = kwargs.get("key") + self._label = kwargs.get("label") + self._accept_datetime = kwargs.get("accept_datetime") + self._select = kwargs.get("select") + self._deserializer = lambda objs: [ + ConfigurationSetting._from_generated(x) for x in objs # pylint:disable=protected-access + ] + + def _get_next_cb(self, continuation_token, **kwargs): + return self._command( + key=self._key, + label=self._label, + accept_datetime=self._accept_datetime, + select=self._select, + continuation_token=continuation_token, + cls=kwargs.pop("cls", None) or _return_deserialized_and_headers, + ) + + def _extract_data_cb(self, get_next_return): + deserialized, response_headers = get_next_return + self.etag = response_headers.pop("ETag") + return deserialized.next_link or None, iter(self._deserializer(deserialized.items)) + + +class ConfigurationSettingPropertiesPagedAsync(AsyncPageIterator): + """An iterable of ConfigurationSetting properties.""" + + etag: str + """The etag of current page.""" + + def __init__(self, command: Callable, **kwargs): + super(ConfigurationSettingPropertiesPagedAsync, self).__init__( + self._get_next_cb, + self._extract_data_cb, + continuation_token=kwargs.get("continuation_token"), + ) + self._command = command + self._key = kwargs.get("key") + self._label = kwargs.get("label") + self._accept_datetime = kwargs.get("accept_datetime") + self._select = kwargs.get("select") + self._deserializer = lambda objs: [ + ConfigurationSetting._from_generated(x) for x in objs # pylint:disable=protected-access + ] + + async def _get_next_cb(self, continuation_token, **kwargs): + return await self._command( + key=self._key, + label=self._label, + accept_datetime=self._accept_datetime, + select=self._select, + continuation_token=continuation_token, + cls=kwargs.pop("cls", None) or _return_deserialized_and_headers, + ) + + async def _extract_data_cb(self, get_next_return): + deserialized, response_headers = get_next_return + self.etag = response_headers.pop("ETag") + return deserialized.next_link or None, AsyncList(self._deserializer(deserialized.items)) diff --git a/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_version.py b/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_version.py index 26faba9eba0c..7af12c9a4dd9 100644 --- a/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_version.py +++ b/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_version.py @@ -3,4 +3,4 @@ # Licensed under the MIT License. # ------------------------------------ -VERSION = "1.5.1" +VERSION = "1.6.0b1" diff --git a/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/aio/_azure_appconfiguration_client_async.py b/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/aio/_azure_appconfiguration_client_async.py index d80fa76abfaf..c98df10a329a 100644 --- a/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/aio/_azure_appconfiguration_client_async.py +++ b/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/aio/_azure_appconfiguration_client_async.py @@ -4,6 +4,7 @@ # license information. # ------------------------------------------------------------------------- import binascii +import functools from datetime import datetime from typing import Any, Dict, List, Mapping, Optional, Union, cast, overload from typing_extensions import Literal @@ -21,13 +22,19 @@ ResourceNotFoundError, ResourceNotModifiedError, ) +from azure.core.rest import AsyncHttpResponse, HttpRequest from azure.core.utils import CaseInsensitiveDict from ._sync_token_async import AsyncSyncTokenPolicy from .._azure_appconfiguration_error import ResourceReadOnlyError from .._azure_appconfiguration_requests import AppConfigRequestsCredentialsPolicy from .._generated.aio import AzureAppConfiguration from .._generated.models import SnapshotUpdateParameters, SnapshotStatus -from .._models import ConfigurationSetting, ConfigurationSettingsFilter, ConfigurationSnapshot +from .._models import ( + ConfigurationSetting, + ConfigurationSettingsFilter, + ConfigurationSnapshot, + ConfigurationSettingPropertiesPagedAsync, +) from .._utils import ( prep_if_match, prep_if_none_match, @@ -116,6 +123,23 @@ def from_connection_string(cls, connection_string: str, **kwargs: Any) -> "Azure **kwargs, ) + @distributed_trace_async + async def send_request(self, request: HttpRequest, *, stream: bool = False, **kwargs) -> AsyncHttpResponse: + """Runs a network request using the client's existing pipeline. + + The request URL can be relative to the vault URL. The service API version used for the request is the same as + the client's unless otherwise specified. This method does not raise if the response is an error; to raise an + exception, call `raise_for_status()` on the returned response object. For more information about how to send + custom requests with this method, see https://aka.ms/azsdk/dpcodegen/python/send_request. + + :param request: The network request you want to make. + :type request: ~azure.core.rest.HttpRequest + :keyword bool stream: Whether the response payload will be streamed. Defaults to False. + :return: The response of your network call. Does not do error handling on your response. + :rtype: ~azure.core.rest.AsyncHttpResponse + """ + return await self._impl._send_request(request, stream=stream, **kwargs) + @overload def list_configuration_settings( self, @@ -200,13 +224,14 @@ def list_configuration_settings(self, *args, **kwargs) -> AsyncItemPaged[Configu ) key_filter, kwargs = get_key_filter(*args, **kwargs) label_filter, kwargs = get_label_filter(*args, **kwargs) - return self._impl.get_key_values( # type: ignore + command = functools.partial(self._impl.get_key_values_in_one_page, **kwargs) # type: ignore[attr-defined] + return AsyncItemPaged( + command, key=key_filter, label=label_filter, accept_datetime=accept_datetime, select=select, - cls=lambda objs: [ConfigurationSetting._from_generated(x) for x in objs], - **kwargs, + page_iterator_class=ConfigurationSettingPropertiesPagedAsync, ) except binascii.Error as exc: raise binascii.Error("Connection string secret has incorrect padding") from exc diff --git a/sdk/appconfiguration/azure-appconfiguration/samples/send_request_sample.py b/sdk/appconfiguration/azure-appconfiguration/samples/send_request_sample.py new file mode 100644 index 000000000000..0593582adcd3 --- /dev/null +++ b/sdk/appconfiguration/azure-appconfiguration/samples/send_request_sample.py @@ -0,0 +1,38 @@ +# coding: utf-8 + +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- + +""" +FILE: sample_send_request.py + +DESCRIPTION: + This sample demonstrates how to make custom HTTP requests through a client pipeline. + +USAGE: + python sample_send_request.py + + Set the environment variables with your own values before running the sample: + 1) APPCONFIGURATION_CONNECTION_STRING: Connection String used to access the Azure App Configuration. +""" +import os +from azure.appconfiguration import AzureAppConfigurationClient +from azure.core.rest import HttpRequest + + +def main(): + CONNECTION_STRING = os.environ["APPCONFIGURATION_CONNECTION_STRING"] + with AzureAppConfigurationClient.from_connection_string(CONNECTION_STRING) as client: + request = HttpRequest( + method="GET", + url="/kv?api-version=2023-10-01", + ) + response = client.send_request(request) + print(response.status_code) + + +if __name__ == "__main__": + main() diff --git a/sdk/appconfiguration/azure-appconfiguration/samples/send_request_sample_async.py b/sdk/appconfiguration/azure-appconfiguration/samples/send_request_sample_async.py new file mode 100644 index 000000000000..9e3a4530fb40 --- /dev/null +++ b/sdk/appconfiguration/azure-appconfiguration/samples/send_request_sample_async.py @@ -0,0 +1,39 @@ +# coding: utf-8 + +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- + +""" +FILE: sample_send_request_async.py + +DESCRIPTION: + This sample demonstrates how to make custom HTTP requests through a client pipeline. + +USAGE: + python sample_send_request_async.py + + Set the environment variables with your own values before running the sample: + 1) APPCONFIGURATION_CONNECTION_STRING: Connection String used to access the Azure App Configuration. +""" +import os +import asyncio +from azure.appconfiguration.aio import AzureAppConfigurationClient +from azure.core.rest import HttpRequest + + +async def main(): + CONNECTION_STRING = os.environ["APPCONFIGURATION_CONNECTION_STRING"] + async with AzureAppConfigurationClient.from_connection_string(CONNECTION_STRING) as client: + request = HttpRequest( + method="GET", + url="/kv?api-version=2023-10-01", + ) + response = await client.send_request(request) + print(response.status_code) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/sdk/appconfiguration/azure-appconfiguration/setup.py b/sdk/appconfiguration/azure-appconfiguration/setup.py index f4a0bfb6dde7..5dc4cc38ea40 100644 --- a/sdk/appconfiguration/azure-appconfiguration/setup.py +++ b/sdk/appconfiguration/azure-appconfiguration/setup.py @@ -53,7 +53,7 @@ url="https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/appconfiguration/azure-appconfiguration", keywords="azure, azure sdk", classifiers=[ - "Development Status :: 5 - Production/Stable", + "Development Status :: 4 - Beta", "Programming Language :: Python", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3", diff --git a/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_appconfiguration_client.py b/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_appconfiguration_client.py index 073b669af7e1..88fb537cd07a 100644 --- a/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_appconfiguration_client.py +++ b/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_appconfiguration_client.py @@ -15,6 +15,7 @@ ResourceNotFoundError, ResourceExistsError, ) +from azure.core.rest import HttpRequest from azure.appconfiguration import ( ResourceReadOnlyError, AzureAppConfigurationClient, @@ -38,7 +39,7 @@ ) from uuid import uuid4 from preparers import app_config_decorator -from devtools_testutils import recorded_by_proxy +from devtools_testutils import recorded_by_proxy, set_custom_default_matcher class TestAppConfigurationClient(AppConfigTestCase): @@ -221,6 +222,8 @@ def test_delete_configuration_setting_with_etag(self, appconfiguration_connectio @app_config_decorator @recorded_by_proxy def test_list_configuration_settings_key_label(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") self.set_up(appconfiguration_connection_string) items = list(self.client.list_configuration_settings(KEY, LABEL)) assert len(items) == 1 @@ -256,6 +259,8 @@ def test_list_configuration_settings_key_label(self, appconfiguration_connection @app_config_decorator @recorded_by_proxy def test_list_configuration_settings_only_label(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") self.set_up(appconfiguration_connection_string) items = list(self.client.list_configuration_settings(label_filter=LABEL)) assert len(items) == 1 @@ -265,6 +270,8 @@ def test_list_configuration_settings_only_label(self, appconfiguration_connectio @app_config_decorator @recorded_by_proxy def test_list_configuration_settings_only_key(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") self.set_up(appconfiguration_connection_string) items = list(self.client.list_configuration_settings(KEY)) assert len(items) == 2 @@ -274,6 +281,8 @@ def test_list_configuration_settings_only_key(self, appconfiguration_connection_ @app_config_decorator @recorded_by_proxy def test_list_configuration_settings_fields(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") self.set_up(appconfiguration_connection_string) items = list( self.client.list_configuration_settings(key_filter="*", label_filter=LABEL, fields=["key", "content_type"]) @@ -285,6 +294,8 @@ def test_list_configuration_settings_fields(self, appconfiguration_connection_st @app_config_decorator @recorded_by_proxy def test_list_configuration_settings_reserved_chars(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") client = self.create_client(appconfiguration_connection_string) reserved_char_kv = ConfigurationSetting(key=KEY, label=LABEL_RESERVED_CHARS, value=TEST_VALUE) reserved_char_kv = client.add_configuration_setting(reserved_char_kv) @@ -297,6 +308,8 @@ def test_list_configuration_settings_reserved_chars(self, appconfiguration_conne @app_config_decorator @recorded_by_proxy def test_list_configuration_settings_contains(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") self.set_up(appconfiguration_connection_string) items = list(self.client.list_configuration_settings(label_filter=LABEL + "*")) assert len(items) == 1 @@ -306,6 +319,8 @@ def test_list_configuration_settings_contains(self, appconfiguration_connection_ @app_config_decorator @recorded_by_proxy def test_list_configuration_settings_correct_etag(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") client = self.create_client(appconfiguration_connection_string) to_list_kv = self.create_config_setting() self.add_for_test(client, to_list_kv) @@ -323,6 +338,8 @@ def test_list_configuration_settings_correct_etag(self, appconfiguration_connect @app_config_decorator @recorded_by_proxy def test_list_configuration_settings_multi_pages(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") client = self.create_client(appconfiguration_connection_string) # create PAGE_SIZE+1 configuration settings to have at least two pages try: @@ -353,6 +370,8 @@ def test_list_configuration_settings_multi_pages(self, appconfiguration_connecti @app_config_decorator @recorded_by_proxy def test_list_configuration_settings_no_label(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") self.set_up(appconfiguration_connection_string) items = self.client.list_configuration_settings(label_filter="\0") assert len(list(items)) > 0 @@ -361,6 +380,8 @@ def test_list_configuration_settings_no_label(self, appconfiguration_connection_ @app_config_decorator @recorded_by_proxy def test_list_configuration_settings_only_accepttime(self, appconfiguration_connection_string, **kwargs): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") recorded_variables = kwargs.pop("variables", {}) recorded_variables.setdefault("timestamp", str(datetime.utcnow())) @@ -384,6 +405,8 @@ def test_list_configuration_settings_only_accepttime(self, appconfiguration_conn @app_config_decorator @recorded_by_proxy def test_list_revisions_key_label(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") self.set_up(appconfiguration_connection_string) to_list1 = self.create_config_setting() items = list(self.client.list_revisions(label_filter=to_list1.label, key_filter=to_list1.key)) @@ -394,6 +417,8 @@ def test_list_revisions_key_label(self, appconfiguration_connection_string): @app_config_decorator @recorded_by_proxy def test_list_revisions_only_label(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") self.set_up(appconfiguration_connection_string) items = list(self.client.list_revisions(label_filter=LABEL)) assert len(items) >= 1 @@ -403,6 +428,8 @@ def test_list_revisions_only_label(self, appconfiguration_connection_string): @app_config_decorator @recorded_by_proxy def test_list_revisions_key_no_label(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") self.set_up(appconfiguration_connection_string) items = list(self.client.list_revisions(key_filter=KEY)) assert len(items) >= 1 @@ -412,6 +439,8 @@ def test_list_revisions_key_no_label(self, appconfiguration_connection_string): @app_config_decorator @recorded_by_proxy def test_list_revisions_fields(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") self.set_up(appconfiguration_connection_string) items = list(self.client.list_revisions(key_filter="*", label_filter=LABEL, fields=["key", "content_type"])) assert all(x.key and not x.label and x.content_type and not x.tags and not x.etag for x in items) @@ -890,6 +919,8 @@ def test_breaking_with_secret_reference_configuration_setting(self, appconfigura @app_config_decorator @recorded_by_proxy def test_create_snapshot(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") self.set_up(appconfiguration_connection_string) snapshot_name = self.get_resource_name("snapshot") filters = [ConfigurationSettingsFilter(key=KEY, label=LABEL)] @@ -909,6 +940,8 @@ def test_create_snapshot(self, appconfiguration_connection_string): @app_config_decorator @recorded_by_proxy def test_update_snapshot_status(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") self.set_up(appconfiguration_connection_string) snapshot_name = self.get_resource_name("snapshot") filters = [ConfigurationSettingsFilter(key=KEY, label=LABEL)] @@ -927,6 +960,8 @@ def test_update_snapshot_status(self, appconfiguration_connection_string): @app_config_decorator @recorded_by_proxy def test_update_snapshot_status_with_etag(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") self.set_up(appconfiguration_connection_string) snapshot_name = self.get_resource_name("snapshot") filters = [ConfigurationSettingsFilter(key=KEY, label=LABEL)] @@ -947,6 +982,8 @@ def test_update_snapshot_status_with_etag(self, appconfiguration_connection_stri @app_config_decorator @recorded_by_proxy def test_list_snapshots(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") self.set_up(appconfiguration_connection_string) result = self.client.list_snapshots() @@ -971,6 +1008,8 @@ def test_list_snapshots(self, appconfiguration_connection_string): @app_config_decorator @recorded_by_proxy def test_list_snapshot_configuration_settings(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") self.set_up(appconfiguration_connection_string) snapshot_name = self.get_resource_name("snapshot") filters = [ConfigurationSettingsFilter(key=KEY, label=LABEL)] @@ -983,6 +1022,118 @@ def test_list_snapshot_configuration_settings(self, appconfiguration_connection_ self.tear_down() + @app_config_decorator + @recorded_by_proxy + def test_monitor_configuration_settings_by_page_etag(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") + with AzureAppConfigurationClient.from_connection_string(appconfiguration_connection_string) as client: + # prepare 200 configuration settings + for i in range(200): + client.add_configuration_setting( + ConfigurationSetting( + key=f"sample_key_{str(i)}", + label=f"sample_label_{str(i)}", + ) + ) + # there will have 2 pages while listing, there are 100 configuration settings per page. + + # get page etags + page_etags = [] + items = client.list_configuration_settings(key_filter="sample_key_*", label_filter="sample_label_*") + iterator = items.by_page() + for page in iterator: + etag = iterator.etag + page_etags.append(etag) + + # monitor page updates without changes + continuation_token = None + index = 0 + request = HttpRequest( + method="GET", + url="/kv?key=sample_key_%2A&label=sample_label_%2A&api-version=2023-10-01", + headers={ + "If-None-Match": page_etags[index], + "Accept": "application/vnd.microsoft.appconfig.kvset+json, application/problem+json", + }, + ) + first_page_response = client.send_request(request) + assert first_page_response.status_code == 304 + + link = first_page_response.headers.get("Link", None) + continuation_token = link[1 : link.index(">")] if link else None + index += 1 + while continuation_token: + request = HttpRequest( + method="GET", url=f"{continuation_token}", headers={"If-None-Match": page_etags[index]} + ) + index += 1 + response = client.send_request(request) + assert response.status_code == 304 + + link = response.headers.get("Link", None) + continuation_token = link[1 : link.index(">")] if link else None + + # do some changes + client.add_configuration_setting( + ConfigurationSetting( + key="sample_key_201", + label="sample_label_202", + ) + ) + # now we have three pages, 100 settings in first two pages and 1 setting in the last page + + # get page etags after updates + new_page_etags = [] + items = client.list_configuration_settings(key_filter="sample_key_*", label_filter="sample_label_*") + iterator = items.by_page() + for page in iterator: + etag = iterator.etag + new_page_etags.append(etag) + + assert page_etags[0] == new_page_etags[0] + assert page_etags[1] != new_page_etags[1] + assert page_etags[2] != new_page_etags[2] + + # monitor page after updates + continuation_token = None + index = 0 + request = HttpRequest( + method="GET", + url="/kv?key=sample_key_%2A&label=sample_label_%2A&api-version=2023-10-01", + headers={ + "If-None-Match": page_etags[index], + "Accept": "application/vnd.microsoft.appconfig.kvset+json, application/problem+json", + }, + ) + first_page_response = client.send_request(request) + # 304 means the page doesn't have changes. + assert first_page_response.status_code == 304 + + link = first_page_response.headers.get("Link", None) + continuation_token = link[1 : link.index(">")] if link else None + index += 1 + while continuation_token: + request = HttpRequest( + method="GET", url=f"{continuation_token}", headers={"If-None-Match": page_etags[index]} + ) + index += 1 + response = client.send_request(request) + + # 200 means the page has changes. + assert response.status_code == 200 + items = response.json()["items"] + for item in items: + print(f"Key: {item['key']}, Label: {item['label']}") + + link = response.headers.get("Link", None) + continuation_token = link[1 : link.index(">")] if link else None + + # clean up + config_settings = client.list_configuration_settings() + for config_setting in config_settings: + client.delete_configuration_setting(key=config_setting.key, label=config_setting.label) + class TestAppConfigurationClientUnitTest: def test_type_error(self): diff --git a/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_appconfiguration_client_aad.py b/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_appconfiguration_client_aad.py index 98e902c7a7b3..92fd27c8bcbe 100644 --- a/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_appconfiguration_client_aad.py +++ b/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_appconfiguration_client_aad.py @@ -30,7 +30,7 @@ KEY_UUID, ) from preparers import app_config_aad_decorator -from devtools_testutils import recorded_by_proxy +from devtools_testutils import recorded_by_proxy, set_custom_default_matcher import pytest import copy import datetime @@ -202,6 +202,8 @@ def test_delete_configuration_setting_with_etag(self, appconfiguration_endpoint_ @app_config_aad_decorator @recorded_by_proxy def test_list_configuration_settings_key_label(self, appconfiguration_endpoint_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") self.set_up(appconfiguration_endpoint_string, is_aad=True) items = list(self.client.list_configuration_settings(label_filter=LABEL, key_filter=KEY)) assert len(items) == 1 @@ -211,6 +213,8 @@ def test_list_configuration_settings_key_label(self, appconfiguration_endpoint_s @app_config_aad_decorator @recorded_by_proxy def test_list_configuration_settings_only_label(self, appconfiguration_endpoint_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") self.set_up(appconfiguration_endpoint_string, is_aad=True) items = list(self.client.list_configuration_settings(label_filter=LABEL)) assert len(items) == 1 @@ -220,6 +224,8 @@ def test_list_configuration_settings_only_label(self, appconfiguration_endpoint_ @app_config_aad_decorator @recorded_by_proxy def test_list_configuration_settings_only_key(self, appconfiguration_endpoint_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") self.set_up(appconfiguration_endpoint_string, is_aad=True) items = list(self.client.list_configuration_settings(key_filter=KEY)) assert len(items) == 2 @@ -229,6 +235,8 @@ def test_list_configuration_settings_only_key(self, appconfiguration_endpoint_st @app_config_aad_decorator @recorded_by_proxy def test_list_configuration_settings_fields(self, appconfiguration_endpoint_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") self.set_up(appconfiguration_endpoint_string, is_aad=True) items = list( self.client.list_configuration_settings(key_filter="*", label_filter=LABEL, fields=["key", "content_type"]) @@ -240,6 +248,8 @@ def test_list_configuration_settings_fields(self, appconfiguration_endpoint_stri @app_config_aad_decorator @recorded_by_proxy def test_list_configuration_settings_reserved_chars(self, appconfiguration_endpoint_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") client = self.create_aad_client(appconfiguration_endpoint_string) reserved_char_kv = ConfigurationSetting(key=KEY, label=LABEL_RESERVED_CHARS, value=TEST_VALUE) reserved_char_kv = client.add_configuration_setting(reserved_char_kv) @@ -252,6 +262,8 @@ def test_list_configuration_settings_reserved_chars(self, appconfiguration_endpo @app_config_aad_decorator @recorded_by_proxy def test_list_configuration_settings_contains(self, appconfiguration_endpoint_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") self.set_up(appconfiguration_endpoint_string, is_aad=True) items = list(self.client.list_configuration_settings(label_filter=LABEL + "*")) assert len(items) == 1 @@ -261,6 +273,8 @@ def test_list_configuration_settings_contains(self, appconfiguration_endpoint_st @app_config_aad_decorator @recorded_by_proxy def test_list_configuration_settings_correct_etag(self, appconfiguration_endpoint_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") client = self.create_aad_client(appconfiguration_endpoint_string) to_list_kv = self.create_config_setting() self.add_for_test(client, to_list_kv) @@ -278,6 +292,8 @@ def test_list_configuration_settings_correct_etag(self, appconfiguration_endpoin @app_config_aad_decorator @recorded_by_proxy def test_list_configuration_settings_multi_pages(self, appconfiguration_endpoint_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") client = self.create_aad_client(appconfiguration_endpoint_string) # create PAGE_SIZE+1 configuration settings to have at least two pages try: @@ -308,6 +324,8 @@ def test_list_configuration_settings_multi_pages(self, appconfiguration_endpoint @app_config_aad_decorator @recorded_by_proxy def test_list_configuration_settings_no_label(self, appconfiguration_endpoint_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") self.set_up(appconfiguration_endpoint_string, is_aad=True) items = self.client.list_configuration_settings(label_filter="\0") assert len(list(items)) > 0 @@ -316,6 +334,8 @@ def test_list_configuration_settings_no_label(self, appconfiguration_endpoint_st @app_config_aad_decorator @recorded_by_proxy def test_list_configuration_settings_only_accepttime(self, appconfiguration_endpoint_string, **kwargs): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") recorded_variables = kwargs.pop("variables", {}) self.set_up(appconfiguration_endpoint_string, is_aad=True) exclude_today = self.client.list_configuration_settings( @@ -332,6 +352,8 @@ def test_list_configuration_settings_only_accepttime(self, appconfiguration_endp @app_config_aad_decorator @recorded_by_proxy def test_list_revisions_key_label(self, appconfiguration_endpoint_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") self.set_up(appconfiguration_endpoint_string, is_aad=True) to_list = self.create_config_setting() items = list(self.client.list_revisions(label_filter=to_list.label, key_filter=to_list.key)) @@ -342,6 +364,8 @@ def test_list_revisions_key_label(self, appconfiguration_endpoint_string): @app_config_aad_decorator @recorded_by_proxy def test_list_revisions_only_label(self, appconfiguration_endpoint_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") self.set_up(appconfiguration_endpoint_string, is_aad=True) items = list(self.client.list_revisions(label_filter=LABEL)) assert len(items) >= 1 @@ -351,6 +375,8 @@ def test_list_revisions_only_label(self, appconfiguration_endpoint_string): @app_config_aad_decorator @recorded_by_proxy def test_list_revisions_key_no_label(self, appconfiguration_endpoint_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") self.set_up(appconfiguration_endpoint_string, is_aad=True) items = list(self.client.list_revisions(key_filter=KEY)) assert len(items) >= 1 @@ -360,6 +386,8 @@ def test_list_revisions_key_no_label(self, appconfiguration_endpoint_string): @app_config_aad_decorator @recorded_by_proxy def test_list_revisions_fields(self, appconfiguration_endpoint_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") self.set_up(appconfiguration_endpoint_string, is_aad=True) items = list(self.client.list_revisions(key_filter="*", label_filter=LABEL, fields=["key", "content_type"])) assert all(x.key and not x.label and x.content_type and not x.tags and not x.etag for x in items) diff --git a/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_appconfiguration_client_aad_async.py b/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_appconfiguration_client_aad_async.py index b9ba06d9b59b..8c88162d2183 100644 --- a/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_appconfiguration_client_aad_async.py +++ b/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_appconfiguration_client_aad_async.py @@ -30,6 +30,7 @@ KEY_UUID, ) from async_preparers import app_config_aad_decorator_async +from devtools_testutils import set_custom_default_matcher from devtools_testutils.aio import recorded_by_proxy_async import pytest import copy @@ -219,6 +220,8 @@ async def test_delete_configuration_setting_with_etag(self, appconfiguration_end @app_config_aad_decorator_async @recorded_by_proxy_async async def test_list_configuration_settings_key_label(self, appconfiguration_endpoint_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") await self.set_up(appconfiguration_endpoint_string, is_aad=True) items = await self.convert_to_list(self.client.list_configuration_settings(label_filter=LABEL, key_filter=KEY)) assert len(items) == 1 @@ -228,6 +231,8 @@ async def test_list_configuration_settings_key_label(self, appconfiguration_endp @app_config_aad_decorator_async @recorded_by_proxy_async async def test_list_configuration_settings_only_label(self, appconfiguration_endpoint_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") await self.set_up(appconfiguration_endpoint_string, is_aad=True) items = await self.convert_to_list(self.client.list_configuration_settings(label_filter=LABEL)) assert len(items) == 1 @@ -237,6 +242,8 @@ async def test_list_configuration_settings_only_label(self, appconfiguration_end @app_config_aad_decorator_async @recorded_by_proxy_async async def test_list_configuration_settings_only_key(self, appconfiguration_endpoint_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") await self.set_up(appconfiguration_endpoint_string, is_aad=True) items = await self.convert_to_list(self.client.list_configuration_settings(key_filter=KEY)) assert len(items) == 2 @@ -246,6 +253,8 @@ async def test_list_configuration_settings_only_key(self, appconfiguration_endpo @app_config_aad_decorator_async @recorded_by_proxy_async async def test_list_configuration_settings_fields(self, appconfiguration_endpoint_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") await self.set_up(appconfiguration_endpoint_string, is_aad=True) items = await self.convert_to_list( self.client.list_configuration_settings(key_filter="*", label_filter=LABEL, fields=["key", "content_type"]) @@ -257,6 +266,8 @@ async def test_list_configuration_settings_fields(self, appconfiguration_endpoin @app_config_aad_decorator_async @recorded_by_proxy_async async def test_list_configuration_settings_reserved_chars(self, appconfiguration_endpoint_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") async with self.create_aad_client(appconfiguration_endpoint_string) as client: reserved_char_kv = ConfigurationSetting(key=KEY, label=LABEL_RESERVED_CHARS, value=TEST_VALUE) reserved_char_kv = await client.add_configuration_setting(reserved_char_kv) @@ -270,6 +281,8 @@ async def test_list_configuration_settings_reserved_chars(self, appconfiguration @app_config_aad_decorator_async @recorded_by_proxy_async async def test_list_configuration_settings_contains(self, appconfiguration_endpoint_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") await self.set_up(appconfiguration_endpoint_string, is_aad=True) items = await self.convert_to_list(self.client.list_configuration_settings(label_filter=LABEL + "*")) assert len(items) == 1 @@ -279,6 +292,8 @@ async def test_list_configuration_settings_contains(self, appconfiguration_endpo @app_config_aad_decorator_async @recorded_by_proxy_async async def test_list_configuration_settings_correct_etag(self, appconfiguration_endpoint_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") async with self.create_aad_client(appconfiguration_endpoint_string) as client: to_list_kv = self.create_config_setting() await self.add_for_test(client, to_list_kv) @@ -296,6 +311,8 @@ async def test_list_configuration_settings_correct_etag(self, appconfiguration_e @app_config_aad_decorator_async @recorded_by_proxy_async async def test_list_configuration_settings_multi_pages(self, appconfiguration_endpoint_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") async with self.create_aad_client(appconfiguration_endpoint_string) as client: # create PAGE_SIZE+1 configuration settings to have at least two pages try: @@ -336,6 +353,8 @@ async def test_list_configuration_settings_null_label(self, appconfiguration_end @app_config_aad_decorator_async @recorded_by_proxy_async async def test_list_configuration_settings_only_accepttime(self, appconfiguration_endpoint_string, **kwargs): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") recorded_variables = kwargs.pop("variables", {}) await self.set_up(appconfiguration_endpoint_string, is_aad=True) exclude_today = await self.convert_to_list( @@ -354,6 +373,8 @@ async def test_list_configuration_settings_only_accepttime(self, appconfiguratio @app_config_aad_decorator_async @recorded_by_proxy_async async def test_list_revisions_key_label(self, appconfiguration_endpoint_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") await self.set_up(appconfiguration_endpoint_string, is_aad=True) to_list = self.create_config_setting() items = await self.convert_to_list( @@ -366,6 +387,8 @@ async def test_list_revisions_key_label(self, appconfiguration_endpoint_string): @app_config_aad_decorator_async @recorded_by_proxy_async async def test_list_revisions_only_label(self, appconfiguration_endpoint_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") await self.set_up(appconfiguration_endpoint_string, is_aad=True) items = await self.convert_to_list(self.client.list_revisions(label_filter=LABEL)) assert len(items) >= 1 @@ -375,6 +398,8 @@ async def test_list_revisions_only_label(self, appconfiguration_endpoint_string) @app_config_aad_decorator_async @recorded_by_proxy_async async def test_list_revisions_key_no_label(self, appconfiguration_endpoint_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") await self.set_up(appconfiguration_endpoint_string, is_aad=True) items = await self.convert_to_list(self.client.list_revisions(key_filter=KEY)) assert len(items) >= 1 @@ -384,6 +409,8 @@ async def test_list_revisions_key_no_label(self, appconfiguration_endpoint_strin @app_config_aad_decorator_async @recorded_by_proxy_async async def test_list_revisions_fields(self, appconfiguration_endpoint_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") await self.set_up(appconfiguration_endpoint_string, is_aad=True) items = await self.convert_to_list( self.client.list_revisions(key_filter="*", label_filter=LABEL, fields=["key", "content_type"]) diff --git a/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_appconfiguration_client_async.py b/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_appconfiguration_client_async.py index 91af95ba5794..9a9e204c1c15 100644 --- a/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_appconfiguration_client_async.py +++ b/sdk/appconfiguration/azure-appconfiguration/tests/test_azure_appconfiguration_client_async.py @@ -15,6 +15,7 @@ ResourceExistsError, AzureError, ) +from azure.core.rest import HttpRequest from azure.appconfiguration import ( ResourceReadOnlyError, ConfigurationSetting, @@ -36,6 +37,7 @@ PAGE_SIZE, KEY_UUID, ) +from devtools_testutils import set_custom_default_matcher from devtools_testutils.aio import recorded_by_proxy_async from async_preparers import app_config_decorator_async from uuid import uuid4 @@ -221,6 +223,8 @@ async def test_delete_configuration_setting_with_etag(self, appconfiguration_con @app_config_decorator_async @recorded_by_proxy_async async def test_list_configuration_settings_key_label(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") await self.set_up(appconfiguration_connection_string) items = await self.convert_to_list(self.client.list_configuration_settings(KEY, LABEL)) assert len(items) == 1 @@ -256,6 +260,8 @@ async def test_list_configuration_settings_key_label(self, appconfiguration_conn @app_config_decorator_async @recorded_by_proxy_async async def test_list_configuration_settings_only_label(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") await self.set_up(appconfiguration_connection_string) items = await self.convert_to_list(self.client.list_configuration_settings(label_filter=LABEL)) assert len(items) == 1 @@ -265,6 +271,8 @@ async def test_list_configuration_settings_only_label(self, appconfiguration_con @app_config_decorator_async @recorded_by_proxy_async async def test_list_configuration_settings_only_key(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") await self.set_up(appconfiguration_connection_string) items = await self.convert_to_list(self.client.list_configuration_settings(KEY)) assert len(items) == 2 @@ -274,6 +282,8 @@ async def test_list_configuration_settings_only_key(self, appconfiguration_conne @app_config_decorator_async @recorded_by_proxy_async async def test_list_configuration_settings_fields(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") await self.set_up(appconfiguration_connection_string) items = await self.convert_to_list( self.client.list_configuration_settings(key_filter="*", label_filter=LABEL, fields=["key", "content_type"]) @@ -285,6 +295,8 @@ async def test_list_configuration_settings_fields(self, appconfiguration_connect @app_config_decorator_async @recorded_by_proxy_async async def test_list_configuration_settings_reserved_chars(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") async with self.create_client(appconfiguration_connection_string) as client: reserved_char_kv = ConfigurationSetting(key=KEY, label=LABEL_RESERVED_CHARS, value=TEST_VALUE) reserved_char_kv = await client.add_configuration_setting(reserved_char_kv) @@ -297,6 +309,8 @@ async def test_list_configuration_settings_reserved_chars(self, appconfiguration @app_config_decorator_async @recorded_by_proxy_async async def test_list_configuration_settings_contains(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") await self.set_up(appconfiguration_connection_string) items = await self.convert_to_list(self.client.list_configuration_settings(label_filter=LABEL + "*")) assert len(items) == 1 @@ -306,6 +320,8 @@ async def test_list_configuration_settings_contains(self, appconfiguration_conne @app_config_decorator_async @recorded_by_proxy_async async def test_list_configuration_settings_correct_etag(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") async with self.create_client(appconfiguration_connection_string) as client: to_list_kv = self.create_config_setting() await self.add_for_test(client, to_list_kv) @@ -323,6 +339,8 @@ async def test_list_configuration_settings_correct_etag(self, appconfiguration_c @app_config_decorator_async @recorded_by_proxy_async async def test_list_configuration_settings_multi_pages(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") async with self.create_client(appconfiguration_connection_string) as client: # create PAGE_SIZE+1 configuration settings to have at least two pages try: @@ -355,6 +373,8 @@ async def test_list_configuration_settings_multi_pages(self, appconfiguration_co @app_config_decorator_async @recorded_by_proxy_async async def test_list_configuration_settings_no_label(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") await self.set_up(appconfiguration_connection_string) items = await self.convert_to_list(self.client.list_configuration_settings(label_filter="\0")) assert len(items) > 0 @@ -363,6 +383,8 @@ async def test_list_configuration_settings_no_label(self, appconfiguration_conne @app_config_decorator_async @recorded_by_proxy_async async def test_list_configuration_settings_only_accepttime(self, appconfiguration_connection_string, **kwargs): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") recorded_variables = kwargs.pop("variables", {}) recorded_variables.setdefault("timestamp", str(datetime.utcnow())) @@ -388,6 +410,8 @@ async def test_list_configuration_settings_only_accepttime(self, appconfiguratio @app_config_decorator_async @recorded_by_proxy_async async def test_list_revisions_key_label(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") await self.set_up(appconfiguration_connection_string) to_list1 = self.create_config_setting() items = await self.convert_to_list( @@ -400,6 +424,8 @@ async def test_list_revisions_key_label(self, appconfiguration_connection_string @app_config_decorator_async @recorded_by_proxy_async async def test_list_revisions_only_label(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") await self.set_up(appconfiguration_connection_string) items = await self.convert_to_list(self.client.list_revisions(label_filter=LABEL)) assert len(items) >= 1 @@ -409,6 +435,8 @@ async def test_list_revisions_only_label(self, appconfiguration_connection_strin @app_config_decorator_async @recorded_by_proxy_async async def test_list_revisions_key_no_label(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") await self.set_up(appconfiguration_connection_string) items = await self.convert_to_list(self.client.list_revisions(key_filter=KEY)) assert len(items) >= 1 @@ -418,6 +446,8 @@ async def test_list_revisions_key_no_label(self, appconfiguration_connection_str @app_config_decorator_async @recorded_by_proxy_async async def test_list_revisions_fields(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") await self.set_up(appconfiguration_connection_string) items = await self.convert_to_list( self.client.list_revisions(key_filter="*", label_filter=LABEL, fields=["key", "content_type"]) @@ -912,6 +942,8 @@ async def test_breaking_with_secret_reference_configuration_setting(self, appcon @app_config_decorator_async @recorded_by_proxy_async async def test_create_snapshot(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") await self.set_up(appconfiguration_connection_string) snapshot_name = self.get_resource_name("snapshot") filters = [ConfigurationSettingsFilter(key=KEY, label=LABEL)] @@ -931,6 +963,8 @@ async def test_create_snapshot(self, appconfiguration_connection_string): @app_config_decorator_async @recorded_by_proxy_async async def test_update_snapshot_status(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") await self.set_up(appconfiguration_connection_string) snapshot_name = self.get_resource_name("snapshot") filters = [ConfigurationSettingsFilter(key=KEY, label=LABEL)] @@ -949,6 +983,8 @@ async def test_update_snapshot_status(self, appconfiguration_connection_string): @app_config_decorator_async @recorded_by_proxy_async async def test_update_snapshot_status_with_etag(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") await self.set_up(appconfiguration_connection_string) snapshot_name = self.get_resource_name("snapshot") filters = [ConfigurationSettingsFilter(key=KEY, label=LABEL)] @@ -969,6 +1005,8 @@ async def test_update_snapshot_status_with_etag(self, appconfiguration_connectio @app_config_decorator_async @recorded_by_proxy_async async def test_list_snapshots(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") await self.set_up(appconfiguration_connection_string) result = await self.convert_to_list(self.client.list_snapshots()) @@ -993,6 +1031,8 @@ async def test_list_snapshots(self, appconfiguration_connection_string): @app_config_decorator_async @recorded_by_proxy_async async def test_list_snapshot_configuration_settings(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") await self.set_up(appconfiguration_connection_string) snapshot_name = self.get_resource_name("snapshot") filters = [ConfigurationSettingsFilter(key=KEY, label=LABEL)] @@ -1005,6 +1045,122 @@ async def test_list_snapshot_configuration_settings(self, appconfiguration_conne await self.tear_down() + @app_config_decorator_async + @recorded_by_proxy_async + async def test_monitor_configuration_settings_by_page_etag(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") + async with AzureAppConfigurationClient.from_connection_string(appconfiguration_connection_string) as client: + # prepare 200 configuration settings + for i in range(200): + await client.add_configuration_setting( + ConfigurationSetting( + key=f"async_sample_key_{str(i)}", + label=f"async_sample_label_{str(i)}", + ) + ) + # there will have 2 pages while listing, there are 100 configuration settings per page. + + # get page etags + page_etags = [] + items = client.list_configuration_settings( + key_filter="async_sample_key_*", label_filter="async_sample_label_*" + ) + iterator = items.by_page() + async for page in iterator: + etag = iterator.etag + page_etags.append(etag) + + # monitor page updates without changes + continuation_token = None + index = 0 + request = HttpRequest( + method="GET", + url="/kv?key=async_sample_key_%2A&label=async_sample_label_%2A&api-version=2023-10-01", + headers={ + "If-None-Match": page_etags[index], + "Accept": "application/vnd.microsoft.appconfig.kvset+json, application/problem+json", + }, + ) + first_page_response = await client.send_request(request) + assert first_page_response.status_code == 304 + + link = first_page_response.headers.get("Link", None) + continuation_token = link[1 : link.index(">")] if link else None + index += 1 + while continuation_token: + request = HttpRequest( + method="GET", url=f"{continuation_token}", headers={"If-None-Match": page_etags[index]} + ) + index += 1 + response = await client.send_request(request) + assert response.status_code == 304 + + link = response.headers.get("Link", None) + continuation_token = link[1 : link.index(">")] if link else None + + # do some changes + await client.add_configuration_setting( + ConfigurationSetting( + key="async_sample_key_201", + label="async_sample_label_202", + ) + ) + # now we have three pages, 100 settings in first two pages and 1 setting in the last page + + # get page etags after updates + new_page_etags = [] + items = client.list_configuration_settings( + key_filter="async_sample_key_*", label_filter="async_sample_label_*" + ) + iterator = items.by_page() + async for page in iterator: + etag = iterator.etag + new_page_etags.append(etag) + + assert page_etags[0] == new_page_etags[0] + assert page_etags[1] != new_page_etags[1] + assert page_etags[2] != new_page_etags[2] + + # monitor page after updates + continuation_token = None + index = 0 + request = HttpRequest( + method="GET", + url="/kv?key=async_sample_key_%2A&label=async_sample_label_%2A&api-version=2023-10-01", + headers={ + "If-None-Match": page_etags[index], + "Accept": "application/vnd.microsoft.appconfig.kvset+json, application/problem+json", + }, + ) + first_page_response = await client.send_request(request) + # 304 means the page doesn't have changes. + assert first_page_response.status_code == 304 + + link = first_page_response.headers.get("Link", None) + continuation_token = link[1 : link.index(">")] if link else None + index += 1 + while continuation_token: + request = HttpRequest( + method="GET", url=f"{continuation_token}", headers={"If-None-Match": page_etags[index]} + ) + index += 1 + response = await client.send_request(request) + + # 200 means the page has changes. + assert response.status_code == 200 + items = response.json()["items"] + for item in items: + print(f"Key: {item['key']}, Label: {item['label']}") + + link = response.headers.get("Link", None) + continuation_token = link[1 : link.index(">")] if link else None + + # clean up + config_settings = client.list_configuration_settings() + async for config_setting in config_settings: + await client.delete_configuration_setting(key=config_setting.key, label=config_setting.label) + class TestAppConfigurationClientUnitTest: @pytest.mark.asyncio diff --git a/sdk/appconfiguration/azure-appconfiguration/tests/test_consistency.py b/sdk/appconfiguration/azure-appconfiguration/tests/test_consistency.py index c60bd9f4ffdf..808e092780ac 100644 --- a/sdk/appconfiguration/azure-appconfiguration/tests/test_consistency.py +++ b/sdk/appconfiguration/azure-appconfiguration/tests/test_consistency.py @@ -9,7 +9,7 @@ ) from testcase import AppConfigTestCase from preparers import app_config_decorator -from devtools_testutils import recorded_by_proxy +from devtools_testutils import recorded_by_proxy, set_custom_default_matcher import json import pytest @@ -18,6 +18,8 @@ class TestAppConfigurationConsistency(AppConfigTestCase): @app_config_decorator @recorded_by_proxy def test_update_json_by_value(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") client = self.create_client(appconfiguration_connection_string) key = self.get_resource_name("key") feature_flag = FeatureFlagConfigurationSetting( @@ -54,6 +56,8 @@ def test_update_json_by_value(self, appconfiguration_connection_string): @app_config_decorator @recorded_by_proxy def test_feature_flag_invalid_json(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") client = self.create_client(appconfiguration_connection_string) key = self.get_resource_name("key") feature_flag = FeatureFlagConfigurationSetting(key, enabled=True) @@ -68,6 +72,8 @@ def test_feature_flag_invalid_json(self, appconfiguration_connection_string): @app_config_decorator @recorded_by_proxy def test_feature_flag_invalid_json_string(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") client = self.create_client(appconfiguration_connection_string) key = self.get_resource_name("key") feature_flag = FeatureFlagConfigurationSetting(key, enabled=True) @@ -82,6 +88,8 @@ def test_feature_flag_invalid_json_string(self, appconfiguration_connection_stri @app_config_decorator @recorded_by_proxy def test_feature_flag_invalid_json_access_properties(self, appconfiguration_connection_string): + # response header and are missing in python38. + set_custom_default_matcher(compare_bodies=False, excluded_headers="x-ms-content-sha256,x-ms-date") client = self.create_client(appconfiguration_connection_string) key = self.get_resource_name("key") feature_flag = FeatureFlagConfigurationSetting(key, enabled=True)