Skip to content

Initial commit for feature flag support. #40324

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@
SnapshotFields,
ConfigurationSettingFields,
SnapshotComposition,
FeatureFlag,
FeatureFlagFields,
Allocation,
UserAllocation,
PercentileAllocation,
GroupAllocation,
Conditions,
Variant,
Telemetry
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this should be exposed as "FeatureFlagTelemetry" or something more specific.

)
from ._version import VERSION
from ._azure_appconfiguration_error import ResourceReadOnlyError
Expand All @@ -41,6 +50,15 @@
"SnapshotFields",
"SnapshotComposition",
"LabelFields",
"FeatureFlag",
"FeatureFlagFields",
"Allocation",
"UserAllocation",
"PercentileAllocation",
"GroupAllocation",
"Conditions",
"Variant",
"Telemetry",
"ConfigurationSettingFields",
"ConfigurationSettingsFilter",
"ConfigurationSettingLabel",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
LabelFields,
ConfigurationSettingFields,
SnapshotUpdateParameters,
FeatureFlag,
FeatureFlagFields
)
from ._models import (
ConfigurationSetting,
Expand All @@ -34,6 +36,7 @@
)
from ._utils import (
get_key_filter,
get_name_filter,
get_label_filter,
parse_connection_string,
)
Expand Down Expand Up @@ -772,3 +775,287 @@ def __enter__(self) -> "AzureAppConfigurationClient":

def __exit__(self, *args: Any) -> None:
self._impl.__exit__(*args)

@overload
def list_feature_flags(
self,
*,
name_filter: Optional[str] = None,
label_filter: Optional[str] = None,
tags_filter: Optional[List[str]] = None,
accept_datetime: Optional[Union[datetime, str]] = None,
fields: Optional[List[Union[str, FeatureFlagFields]]] = None,
**kwargs: Any,
) -> ItemPaged[FeatureFlagFields]:
"""List the feature flags stored in the configuration service, optionally filtered by
key, label, tags and accept_datetime. For more information about supported filters, see
https://learn.microsoft.com/azure/azure-app-configuration/rest-api-key-value?pivots=v23-11#supported-filters.

:keyword name_filter: Filter results based on their names. '*' can be used as wildcard in the beginning or end
of the filter.
:paramtype name_filter: str or None
:keyword label_filter: Filter results based on their labels. '*' can be used as wildcard in the beginning or end
of the filter.
:paramtype label_filter: str or None
:keyword tags_filter: Filter results based on their tags.
:paramtype tags_filter: list[str] or None
:keyword accept_datetime: Retrieve FeatureFlag that existed at this datetime
:paramtype accept_datetime: ~datetime.datetime or str or None
:keyword fields: Specify which fields to include in the results. If not specified, will include all fields.
Available fields see :class:`~azure.appconfiguration.FeatureFlagFields`.
:paramtype fields: list[str] or list[~azure.appconfiguration.FeatureFlagFields] or None
:return: An iterator of :class:`~azure.appconfiguration.FeatureFlag`
:rtype: ~azure.core.paging.ItemPaged[~azure.appconfiguration.FeatureFlag]
:raises: :class:`~azure.core.exceptions.HttpResponseError`, \
:class:`~azure.core.exceptions.ClientAuthenticationError`

Example

.. code-block:: python

from datetime import datetime, timedelta

accept_datetime = datetime.utcnow() + timedelta(days=-1)

all_listed = client.list_feature_flags()
for item in all_listed:
pass # do something

filtered_listed = client.list_feature_flags(
label_filter="Labe*", name_filter="Na*", accept_datetime=str(accept_datetime)
)
for item in filtered_listed:
pass # do something
"""

@overload
def list_feature_flags(
self,
*,
label: Optional[str] = None,
tags: Optional[List[str]] = None,
select: Optional[List[Union[str, FeatureFlagFields]]] = None,
name: Optional[str] = None,
after: Optional[str] = None,
sync_token: Optional[str] = None,
accept_datetime: Optional[str] = None,
etag: Optional[str] = None,
match_condition: Optional[MatchConditions] = None,
**kwargs: Any
) -> ItemPaged[FeatureFlag]:
"""List feature flags.

:param label: Filter results based on their label.
:type label: Optional[str]
:param tags: Filter results based on their tags.
:type tags: Optional[List[str]]
:param select: Fields to include in the results.
:type select: Optional[List[Union[str, ~azure.appconfiguration.models.FeatureFlagFields]]]
:param name: Filter results based on their name.
:type name: Optional[str]
:param after: Identifier of the last item in previous results.
:type after: Optional[str]
:param sync_token: Used to guarantee real-time consistency between requests.
:type sync_token: Optional[str]
:param accept_datetime: Get values as they existed at specified time.
:type accept_datetime: Optional[str]
:param etag: For optimistic concurrency control.
:type etag: Optional[str]
:param match_condition: Match condition for etag.
:type match_condition: Optional[~azure.core.MatchConditions]
:return: An iterator of FeatureFlag objects.
:rtype: ~azure.core.paging.ItemPaged[~azure.appconfiguration.models.FeatureFlag]
:raises: ~azure.core.exceptions.HttpResponseError
"""

@distributed_trace
def list_feature_flags(self, *args: Optional[str], **kwargs: Any) -> ItemPaged[FeatureFlag]:
accept_datetime = kwargs.pop("accept_datetime", None)
if isinstance(accept_datetime, datetime):
accept_datetime = str(accept_datetime)
select = kwargs.pop("fields", None)
if select:
select = ["locked" if x == "read_only" else x for x in select]

tags = kwargs.pop("tags_filter", None)
name_filter, kwargs = get_name_filter(*args, **kwargs)
label_filter, kwargs = get_label_filter(*args, **kwargs)

return self._impl.get_feature_flags( # type: ignore[return-value]
name=name_filter,
label=label_filter,
tags=tags,
accept_datetime=accept_datetime,
select=select,
**kwargs)

@distributed_trace
def get_feature_flag(
self,
name: str,
*,
label: Optional[str] = None,
accept_datetime: Optional[str] = None,
etag: Optional[str] = None,
match_condition: Optional[MatchConditions] = None,
**kwargs: Any
) -> Union[None, FeatureFlag]:
"""Get a feature flag.

:param name: Name of the feature flag.
:type name: str
:param label: Feature flag's label.
:type label: Optional[str]
:param tags: Filter by tags.
:type tags: Optional[List[str]]
:param select: Fields to include in results.
:type select: Optional[List[Union[str, ~azure.appconfiguration.models.FeatureFlagFields]]]
:param sync_token: For consistency between requests.
:type sync_token: Optional[str]
:param accept_datetime: Get value as it existed at specified time.
:type accept_datetime: Optional[str]
:param etag: For optimistic concurrency control.
:type etag: Optional[str]
:param match_condition: Match condition for etag.
:type match_condition: Optional[~azure.core.MatchConditions]
:return: The requested feature flag.
:rtype: ~azure.appconfiguration.models.FeatureFlag
:raises: ~azure.core.exceptions.HttpResponseError
"""
if isinstance(accept_datetime, datetime):
accept_datetime = str(accept_datetime)
try:
return self._impl.get_feature_flag(
name=name,
label=label,
accept_datetime=accept_datetime,
etag=etag,
match_condition=match_condition,
**kwargs,
)
except ResourceNotModifiedError:
return None

@distributed_trace
def add_feature_flag(
self,
feature_flag: FeatureFlag,
**kwargs: Any
) -> FeatureFlag:
"""Create or update a feature flag.

:param name: Name of the feature flag.
:type name: str
:param resource: The feature flag to create or update.
:type resource: Union[~azure.appconfiguration.models.FeatureFlag, dict, IO[bytes]]
:param label: Feature flag's label.
:type label: Optional[str]
:param sync_token: For consistency between requests.
:type sync_token: Optional[str]
:param etag: For optimistic concurrency control.
:type etag: Optional[str]
:param match_condition: Match condition for etag.
:type match_condition: Optional[~azure.core.MatchConditions]
:return: The created or updated feature flag.
:rtype: ~azure.appconfiguration.models.FeatureFlag
:raises: ~azure.core.exceptions.HttpResponseError
"""
return self._impl.put_feature_flag(
name=feature_flag.name,
resource=feature_flag,
label=feature_flag.label,
match_condition=MatchConditions.IfMissing,
**kwargs,
)

@distributed_trace
def set_feature_flag(
self,
feature_flag: FeatureFlag,
match_condition: MatchConditions = MatchConditions.Unconditionally,
*,
etag: Optional[str] = None,
**kwargs: Any,
) -> FeatureFlag:
"""Add or update a FeatureFlag.
If the feature flag identified by name and label does not exist, this is a create.
Otherwise this is an update.

:param feature_flag: The FeatureFlag to be added (if not exists) \
or updated (if exists) to the service
:type feature_flag: ~azure.appconfiguration.FeatureFlag
:param match_condition: The match condition to use upon the etag
:type match_condition: ~azure.core.MatchConditions
:keyword etag: Check if the FeatureFlag is changed. \
Will use the value from param feature_flag if not set.
:paramtype etag: str or None
:return: The FeatureFlag returned from the service
:rtype: ~azure.appconfiguration.FeatureFlag
:raises: :class:`~azure.appconfiguration.ResourceReadOnlyError`, \
:class:`~azure.core.exceptions.HttpResponseError`, \
:class:`~azure.core.exceptions.ClientAuthenticationError`, \
:class:`~azure.core.exceptions.ResourceModifiedError`, \
:class:`~azure.core.exceptions.ResourceNotModifiedError`, \
:class:`~azure.core.exceptions.ResourceNotFoundError`, \
:class:`~azure.core.exceptions.ResourceExistsError`

Example

.. code-block:: python

feature_flag = FeatureFlag(
name="MyName",
label="MyLabel",
enabled=true,
tags={"my set tag": "my set tag value"}
)
returned_feature_flag = client.set_Feature_flag(feature_flag)
"""
error_map: Dict[int, Any] = {409: ResourceReadOnlyError}

return self._impl.put_feature_flag(
name=feature_flag.name,
resource=feature_flag,
label=feature_flag.label,
match_condition=match_condition,
error_map=error_map,
**kwargs,
)

@distributed_trace
def delete_feature_flag( # pylint:disable=delete-operation-wrong-return-type
self,
name: str,
label: Optional[str] = None,
*,
etag: Optional[str] = None,
match_condition: MatchConditions = MatchConditions.Unconditionally,
**kwargs: Any,
) -> Union[None, FeatureFlag]:
"""Delete a feature flag.

:param name: Name of the feature flag.
:type name: str
:param label: Feature flag's label.
:type label: Optional[str]
:param tags: Filter by tags.
:type tags: Optional[List[str]]
:param sync_token: For consistency between requests.
:type sync_token: Optional[str]
:param etag: For optimistic concurrency control.
:type etag: Optional[str]
:param match_condition: Match condition for etag.
:type match_condition: Optional[~azure.core.MatchConditions]
:return: The deleted feature flag or None if it didn't exist.
:rtype: Union[~azure.appconfiguration.models.FeatureFlag, None]
:raises: ~azure.core.exceptions.HttpResponseError
"""
error_map: Dict[int, Any] = {409: ResourceReadOnlyError}
return self._impl.delete_feature_flag(
name=name,
label=label,
etag=etag,
match_condition=match_condition,
error_map=error_map,
**kwargs,
)
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,18 @@ class AzureAppConfigurationClientConfiguration: # pylint: disable=too-many-inst

:param endpoint: Required.
:type endpoint: str
:param credential: Credential used to authenticate requests to the service. Is either a
AzureKeyCredential type or a TokenCredential type. Required.
:param credential: Credential used to authenticate requests to the service. Is either a key
credential type or a token credential type. Required.
:type credential: ~azure.core.credentials.AzureKeyCredential or
~azure.core.credentials.TokenCredential
:keyword api_version: The API version to use for this operation. Default value is "2023-11-01".
Note that overriding this default value may result in unsupported behavior.
:keyword api_version: The API version to use for this operation. Default value is
"2025-07-01-preview". Note that overriding this default value may result in unsupported
behavior.
:paramtype api_version: str
"""

def __init__(self, endpoint: str, credential: Union[AzureKeyCredential, "TokenCredential"], **kwargs: Any) -> None:
api_version: str = kwargs.pop("api_version", "2023-11-01")
api_version: str = kwargs.pop("api_version", "2025-07-01-preview")

if endpoint is None:
raise ValueError("Parameter 'endpoint' must not be None.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -894,6 +894,22 @@ def _deserialize(
return _deserialize_with_callable(deserializer, value)


def _failsafe_deserialize(
deserializer: typing.Any,
value: typing.Any,
module: typing.Optional[str] = None,
rf: typing.Optional["_RestField"] = None,
format: typing.Optional[str] = None,
) -> typing.Any:
try:
return _deserialize(deserializer, value, module, rf, format)
except DeserializationError:
_LOGGER.warning(
"Ran into a deserialization error. Ignoring since this is failsafe deserialization", exc_info=True
)
return None


class _RestField:
def __init__(
self,
Expand Down
Loading
Loading