FeatureManagement 2.0.0b1__tar.gz → 2.0.0b2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/CHANGELOG.md +5 -0
  2. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2/FeatureManagement.egg-info}/PKG-INFO +1 -1
  3. {featuremanagement-2.0.0b1/FeatureManagement.egg-info → featuremanagement-2.0.0b2}/PKG-INFO +1 -1
  4. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/featuremanagement/_featuremanagerbase.py +6 -3
  5. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/featuremanagement/_models/_allocation.py +5 -5
  6. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/featuremanagement/_models/_feature_flag.py +2 -4
  7. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/featuremanagement/_version.py +1 -1
  8. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/featuremanagement/azuremonitor/_send_telemetry.py +38 -4
  9. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/pyproject.toml +1 -1
  10. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/tests/test_feature_variants.py +40 -0
  11. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/CODE_OF_CONDUCT.md +0 -0
  12. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/FeatureManagement.egg-info/SOURCES.txt +0 -0
  13. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/FeatureManagement.egg-info/dependency_links.txt +0 -0
  14. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/FeatureManagement.egg-info/not-zip-safe +0 -0
  15. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/FeatureManagement.egg-info/requires.txt +0 -0
  16. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/FeatureManagement.egg-info/top_level.txt +0 -0
  17. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/LICENSE +0 -0
  18. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/MANIFEST.in +0 -0
  19. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/README.md +0 -0
  20. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/SECURITY.md +0 -0
  21. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/SUPPORT.md +0 -0
  22. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/featuremanagement/__init__.py +0 -0
  23. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/featuremanagement/_defaultfilters.py +0 -0
  24. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/featuremanagement/_featurefilters.py +0 -0
  25. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/featuremanagement/_featuremanager.py +0 -0
  26. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/featuremanagement/_models/__init__.py +0 -0
  27. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/featuremanagement/_models/_constants.py +0 -0
  28. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/featuremanagement/_models/_evaluation_event.py +0 -0
  29. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/featuremanagement/_models/_feature_conditions.py +0 -0
  30. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/featuremanagement/_models/_targeting_context.py +0 -0
  31. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/featuremanagement/_models/_telemetry.py +0 -0
  32. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/featuremanagement/_models/_variant.py +0 -0
  33. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/featuremanagement/_models/_variant_assignment_reason.py +0 -0
  34. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/featuremanagement/_models/_variant_reference.py +0 -0
  35. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/featuremanagement/aio/__init__.py +0 -0
  36. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/featuremanagement/aio/_defaultfilters.py +0 -0
  37. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/featuremanagement/aio/_featurefilters.py +0 -0
  38. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/featuremanagement/aio/_featuremanager.py +0 -0
  39. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/featuremanagement/azuremonitor/__init__.py +0 -0
  40. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/featuremanagement/py.typed +0 -0
  41. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/samples/feature_flag_sample.py +0 -0
  42. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/samples/feature_flag_with_azure_app_configuration_sample.py +0 -0
  43. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/samples/feature_variant_sample.py +0 -0
  44. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/samples/feature_variant_sample_with_telemetry.py +0 -0
  45. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/samples/formatted_feature_flags.json +0 -0
  46. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/samples/random_filter.py +0 -0
  47. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/setup.cfg +0 -0
  48. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/setup.py +0 -0
  49. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/tests/test_default_feature_flags.py +0 -0
  50. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/tests/test_default_feature_flags_async.py +0 -0
  51. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/tests/test_feature_manager.py +0 -0
  52. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/tests/test_feature_manager_async.py +0 -0
  53. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/tests/test_feature_manager_refresh.py +0 -0
  54. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/tests/test_feature_variants_async.py +0 -0
  55. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/tests/test_send_telemetry_appinsights.py +0 -0
  56. {featuremanagement-2.0.0b1 → featuremanagement-2.0.0b2}/tests/validation_tests/test_json_validations.py +0 -0
@@ -1,5 +1,10 @@
1
1
  # Release History
2
2
 
3
+ ## 2.0.0b2 (10/11/2024)
4
+
5
+ * Adds VariantAllocationPercentage, DefaultWhenEnabled, and AllocationId to telemetry.
6
+ * Allocation seed value is now None by default, and only defaults to `allocation\n<feature.id>` when assigning variants.
7
+
3
8
  ## 2.0.0b1 (09/10/2024)
4
9
 
5
10
  * Adds support for Feature Variants.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: FeatureManagement
3
- Version: 2.0.0b1
3
+ Version: 2.0.0b2
4
4
  Summary: A library for enabling/disabling features at runtime.
5
5
  Home-page: https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/appconfiguration/feature-management
6
6
  Author: Microsoft Corporation
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: FeatureManagement
3
- Version: 2.0.0b1
3
+ Version: 2.0.0b2
4
4
  Summary: A library for enabling/disabling features at runtime.
5
5
  Home-page: https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/appconfiguration/feature-management
6
6
  Author: Microsoft Corporation
@@ -168,14 +168,17 @@ class FeatureManagerBase(ABC):
168
168
  if targeting_context.user_id in user_allocation.users:
169
169
  evaluation_event.reason = VariantAssignmentReason.USER
170
170
  variant_name = user_allocation.variant
171
- elif feature.allocation.group and len(targeting_context.groups) > 0:
171
+ if not variant_name and feature.allocation.group and len(targeting_context.groups) > 0:
172
172
  for group_allocation in feature.allocation.group:
173
173
  for group in targeting_context.groups:
174
174
  if group in group_allocation.groups:
175
175
  evaluation_event.reason = VariantAssignmentReason.GROUP
176
176
  variant_name = group_allocation.variant
177
- elif feature.allocation.percentile:
178
- context_id = targeting_context.user_id + "\n" + feature.allocation.seed
177
+ if not variant_name and feature.allocation.percentile:
178
+ seed = feature.allocation.seed
179
+ if not seed:
180
+ seed = "allocation\n" + feature.name
181
+ context_id = targeting_context.user_id + "\n" + seed
179
182
  box: float = self._is_targeted(context_id)
180
183
  for percentile_allocation in feature.allocation.percentile:
181
184
  if box == 100 and percentile_allocation.percentile_to == 100:
@@ -103,16 +103,16 @@ class Allocation:
103
103
  Represents an allocation configuration for a feature flag.
104
104
  """
105
105
 
106
- def __init__(self, feature_name: str) -> None:
106
+ def __init__(self) -> None:
107
107
  self._default_when_enabled = None
108
108
  self._default_when_disabled = None
109
109
  self._user: List[UserAllocation] = []
110
110
  self._group: List[GroupAllocation] = []
111
111
  self._percentile: List[PercentileAllocation] = []
112
- self._seed = "allocation\n" + feature_name
112
+ self._seed = None
113
113
 
114
114
  @classmethod
115
- def convert_from_json(cls, json: Dict[str, Any], feature_name: str) -> Optional["Allocation"]:
115
+ def convert_from_json(cls, json: Dict[str, Any]) -> Optional["Allocation"]:
116
116
  """
117
117
  Convert a JSON object to Allocation.
118
118
 
@@ -123,7 +123,7 @@ class Allocation:
123
123
  """
124
124
  if not json:
125
125
  return None
126
- allocation = cls(feature_name)
126
+ allocation = cls()
127
127
  allocation._default_when_enabled = json.get(DEFAULT_WHEN_ENABLED)
128
128
  allocation._default_when_disabled = json.get(DEFAULT_WHEN_DISABLED)
129
129
  allocation._user = []
@@ -197,7 +197,7 @@ class Allocation:
197
197
  return self._percentile
198
198
 
199
199
  @property
200
- def seed(self) -> str:
200
+ def seed(self) -> Optional[str]:
201
201
  """
202
202
  Get the seed for the allocation.
203
203
 
@@ -51,9 +51,7 @@ class FeatureFlag:
51
51
  )
52
52
  else:
53
53
  feature_flag._conditions = FeatureConditions()
54
- feature_flag._allocation = Allocation.convert_from_json(
55
- json_value.get(FEATURE_FLAG_ALLOCATION, None), feature_flag._id
56
- )
54
+ feature_flag._allocation = Allocation.convert_from_json(json_value.get(FEATURE_FLAG_ALLOCATION, None))
57
55
  if FEATURE_FLAG_VARIANTS in json_value:
58
56
  variants: List[Mapping[str, Any]] = json_value.get(FEATURE_FLAG_VARIANTS, [])
59
57
  feature_flag._variants = []
@@ -66,7 +64,7 @@ class FeatureFlag:
66
64
  return feature_flag
67
65
 
68
66
  @property
69
- def name(self) -> Optional[str]:
67
+ def name(self) -> str:
70
68
  """
71
69
  Get the name of the feature flag.
72
70
 
@@ -4,4 +4,4 @@
4
4
  # license information.
5
5
  # -------------------------------------------------------------------------
6
6
 
7
- VERSION = "2.0.0b1"
7
+ VERSION = "2.0.0b2"
@@ -25,6 +25,8 @@ REASON = "VariantAssignmentReason"
25
25
 
26
26
  EVENT_NAME = "FeatureEvaluation"
27
27
 
28
+ EVALUATION_EVENT_VERSION = "1.0.0"
29
+
28
30
 
29
31
  def track_event(event_name: str, user: str, event_properties: Optional[Dict[str, Optional[str]]] = None) -> None:
30
32
  """
@@ -51,17 +53,49 @@ def publish_telemetry(evaluation_event: EvaluationEvent) -> None:
51
53
  """
52
54
  if not HAS_AZURE_MONITOR_EVENTS_EXTENSION:
53
55
  return
54
- event = {}
55
- if evaluation_event.feature:
56
- event[FEATURE_NAME] = evaluation_event.feature.name
56
+ event: Dict[str, Optional[str]] = {}
57
+ if not evaluation_event.feature:
58
+ return
59
+ event[FEATURE_NAME] = evaluation_event.feature.name
57
60
  event[ENABLED] = str(evaluation_event.enabled)
61
+ event["Version"] = EVALUATION_EVENT_VERSION
58
62
 
63
+ # VariantAllocationPercentage
59
64
  if evaluation_event.reason and evaluation_event.reason != VariantAssignmentReason.NONE:
60
65
  if evaluation_event.variant:
61
66
  event[VARIANT] = evaluation_event.variant.name
62
67
  event[REASON] = evaluation_event.reason.value
63
68
 
64
- if evaluation_event.feature and evaluation_event.feature.telemetry:
69
+ if evaluation_event.reason == VariantAssignmentReason.DEFAULT_WHEN_ENABLED:
70
+ allocation_percentage = 0
71
+
72
+ if evaluation_event.feature.allocation and evaluation_event.feature.allocation.percentile:
73
+ for allocation in evaluation_event.feature.allocation.percentile:
74
+ if (
75
+ evaluation_event.variant
76
+ and allocation.variant == evaluation_event.variant.name
77
+ and allocation.percentile_to
78
+ ):
79
+ allocation_percentage += allocation.percentile_to - allocation.percentile_from
80
+
81
+ event["VariantAssignmentPercentage"] = str(100 - allocation_percentage)
82
+ elif evaluation_event.reason == VariantAssignmentReason.PERCENTILE:
83
+ if evaluation_event.feature.allocation and evaluation_event.feature.allocation.percentile:
84
+ allocation_percentage = 0
85
+ for allocation in evaluation_event.feature.allocation.percentile:
86
+ if (
87
+ evaluation_event.variant
88
+ and allocation.variant == evaluation_event.variant.name
89
+ and allocation.percentile_to
90
+ ):
91
+ allocation_percentage += allocation.percentile_to - allocation.percentile_from
92
+ event["VariantAssignmentPercentage"] = str(allocation_percentage)
93
+
94
+ # DefaultWhenEnabled
95
+ if evaluation_event.feature.allocation and evaluation_event.feature.allocation.default_when_enabled:
96
+ event["DefaultWhenEnabled"] = evaluation_event.feature.allocation.default_when_enabled
97
+
98
+ if evaluation_event.feature.telemetry:
65
99
  for metadata_key, metadata_value in evaluation_event.feature.telemetry.metadata.items():
66
100
  if metadata_key not in event:
67
101
  event[metadata_key] = metadata_value
@@ -19,7 +19,7 @@ build-backend = "setuptools.build_meta"
19
19
 
20
20
  [project]
21
21
  name = "FeatureManagement"
22
- version = "2.0.0b1"
22
+ version = "2.0.0b2"
23
23
  authors = [
24
24
  { name="Microsoft Corporation", email="appconfig@microsoft.com" },
25
25
  ]
@@ -112,6 +112,46 @@ class TestFeatureVariants:
112
112
  assert feature_manager.is_enabled("Alpha", "Charlie")
113
113
  assert feature_manager.get_variant("Alpha", "Charlie") is None
114
114
 
115
+ def test_user_and_group_allocation(self):
116
+ feature_flags = {
117
+ "feature_management": {
118
+ "feature_flags": [
119
+ {
120
+ "id": "Alpha",
121
+ "enabled": True,
122
+ "variants": [
123
+ {"name": "Off", "status_override": "Enabled"},
124
+ {"name": "On", "status_override": "Disabled"},
125
+ ],
126
+ "allocation": {
127
+ "user": [{"variant": "On", "users": ["Adam"]}, {"variant": "Off", "users": ["Brittney"]}],
128
+ "group": [
129
+ {"variant": "On", "groups": ["Group1"]},
130
+ {"variant": "Off", "groups": ["Group2"]},
131
+ ],
132
+ },
133
+ "conditions": {
134
+ "client_filters": [
135
+ {
136
+ "name": "AlwaysOnFilter",
137
+ "parameters": {},
138
+ }
139
+ ]
140
+ },
141
+ }
142
+ ]
143
+ }
144
+ }
145
+ feature_manager = FeatureManager(feature_flags, feature_filters=[AlwaysOnFilter()])
146
+ assert feature_manager.is_enabled("Alpha")
147
+ assert feature_manager.get_variant("Alpha") is None
148
+ assert not feature_manager.is_enabled("Alpha", TargetingContext(user_id="NotAdam", groups=["Group1"]))
149
+ assert feature_manager.get_variant("Alpha", TargetingContext(user_id="NotAdam", groups=["Group1"])).name == "On"
150
+ assert not feature_manager.is_enabled("Alpha", TargetingContext(user_id="NotAdam", groups=["Group1"]))
151
+ assert feature_manager.get_variant("Alpha", TargetingContext(groups=["Group2"])).name == "Off"
152
+ assert feature_manager.is_enabled("Alpha", TargetingContext(user_id="NotCharlie", groups=["Group3"]))
153
+ assert feature_manager.get_variant("Alpha", TargetingContext(user_id="NotCharlie", groups=["Group3"])) is None
154
+
115
155
  # method: is_enabled
116
156
  def test_basic_feature_variant_allocation_groups(self):
117
157
  feature_flags = {