Skip to content

Commit 22005cc

Browse files
committed
Moving more shared code to base
1 parent 9afcf33 commit 22005cc

File tree

5 files changed

+78
-66
lines changed

5 files changed

+78
-66
lines changed

featuremanagement/_featuremanager.py

Lines changed: 5 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,13 @@
33
# Licensed under the MIT License. See License.txt in the project root for
44
# license information.
55
# -------------------------------------------------------------------------
6-
import logging
76
from typing import cast, overload, Any, Optional, Dict, Mapping, List
87
from ._defaultfilters import TimeWindowFilter, TargetingFilter
98
from ._featurefilters import FeatureFilter
109
from ._models import EvaluationEvent, Variant, TargetingContext
1110
from ._featuremanagerbase import (
12-
_get_feature_flag,
1311
FeatureManagerBase,
1412
PROVIDED_FEATURE_FILTERS,
15-
FEATURE_MANAGEMENT_KEY,
1613
REQUIREMENT_TYPE_ALL,
1714
FEATURE_FILTER_NAME,
1815
)
@@ -143,35 +140,13 @@ def _check_feature(
143140
Determine if the feature flag is enabled for the given context.
144141
145142
:param str feature_flag_id: Name of the feature flag.
146-
:return: True if the feature flag is enabled for the given context.
147-
:rtype: bool
143+
:param TargetingContext targeting_context: Targeting context.
144+
:return: EvaluationEvent for the given context.
145+
:rtype: EvaluationEvent
148146
"""
149-
if self._copy is not self._configuration.get(FEATURE_MANAGEMENT_KEY):
150-
self._cache = {}
151-
self._copy = self._configuration.get(FEATURE_MANAGEMENT_KEY)
152-
153-
if not self._cache.get(feature_flag_id):
154-
feature_flag = _get_feature_flag(self._configuration, feature_flag_id)
155-
self._cache[feature_flag_id] = feature_flag
156-
else:
157-
feature_flag = self._cache.get(feature_flag_id)
158-
159-
evaluation_event = EvaluationEvent(feature_flag)
160-
if not feature_flag:
161-
logging.warning("Feature flag %s not found", feature_flag_id)
162-
# Unknown feature flags are disabled by default
163-
return evaluation_event
164-
165-
if not feature_flag.enabled:
166-
# Feature flags that are disabled are always disabled
167-
FeatureManager._check_default_disabled_variant(evaluation_event)
168-
if feature_flag.allocation:
169-
variant_name = feature_flag.allocation.default_when_disabled
170-
evaluation_event.variant = self._variant_name_to_variant(feature_flag, variant_name)
171-
evaluation_event.feature = feature_flag
147+
evaluation_event, done = FeatureManager._check_feature_base(self, feature_flag_id)
172148

173-
# If a feature flag is disabled and override can't enable it
174-
evaluation_event.enabled = False
149+
if done:
175150
return evaluation_event
176151

177152
self._check_feature_filters(evaluation_event, targeting_context, **kwargs)

featuremanagement/_featuremanagerbase.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# license information.
55
# -------------------------------------------------------------------------
66
import hashlib
7+
import logging
78
from abc import ABC
89
from typing import List, Optional, Dict, Tuple, Any, Mapping
910
from ._models import FeatureFlag, Variant, VariantAssignmentReason, TargetingContext, EvaluationEvent, VariantReference
@@ -261,6 +262,43 @@ def _assign_allocation(self, evaluation_event: EvaluationEvent, targeting_contex
261262

262263
self._assign_variant(feature_flag, targeting_context, evaluation_event)
263264

265+
def _check_feature_base(self, feature_flag_id: str) -> Tuple[EvaluationEvent, bool]:
266+
"""
267+
Determine if the feature flag is enabled for the given context.
268+
269+
:param str feature_flag_id: Name of the feature flag.
270+
:return: The evaluation event and if the feature filters need to be checked.
271+
:rtype: evaluation_event, bool
272+
"""
273+
if self._copy is not self._configuration.get(FEATURE_MANAGEMENT_KEY):
274+
self._cache = {}
275+
self._copy = self._configuration.get(FEATURE_MANAGEMENT_KEY)
276+
277+
if not self._cache.get(feature_flag_id):
278+
feature_flag = _get_feature_flag(self._configuration, feature_flag_id)
279+
self._cache[feature_flag_id] = feature_flag
280+
else:
281+
feature_flag = self._cache.get(feature_flag_id)
282+
283+
evaluation_event = EvaluationEvent(feature_flag)
284+
if not feature_flag:
285+
logging.warning("Feature flag %s not found", feature_flag_id)
286+
# Unknown feature flags are disabled by default
287+
return evaluation_event, True
288+
289+
if not feature_flag.enabled:
290+
# Feature flags that are disabled are always disabled
291+
self._check_default_disabled_variant(evaluation_event)
292+
if feature_flag.allocation:
293+
variant_name = feature_flag.allocation.default_when_disabled
294+
evaluation_event.variant = self._variant_name_to_variant(feature_flag, variant_name)
295+
evaluation_event.feature = feature_flag
296+
297+
# If a feature flag is disabled and override can't enable it
298+
evaluation_event.enabled = False
299+
return evaluation_event, True
300+
return evaluation_event, False
301+
264302
def list_feature_flag_names(self) -> List[str]:
265303
"""
266304
List of all feature flag names.

featuremanagement/aio/_featuremanager.py

Lines changed: 18 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,13 @@
44
# license information.
55
# -------------------------------------------------------------------------
66
import inspect
7-
import logging
8-
from typing import cast, overload, Mapping, Dict, Any, Optional, List
7+
from typing import cast, overload, Any, Optional, Dict, Mapping, List
98
from ._defaultfilters import TimeWindowFilter, TargetingFilter
109
from ._featurefilters import FeatureFilter
11-
from .._models import EvaluationEvent, TargetingContext, Variant
10+
from .._models import EvaluationEvent, Variant, TargetingContext
1211
from .._featuremanagerbase import (
13-
_get_feature_flag,
1412
FeatureManagerBase,
1513
PROVIDED_FEATURE_FILTERS,
16-
FEATURE_MANAGEMENT_KEY,
1714
REQUIREMENT_TYPE_ALL,
1815
FEATURE_FILTER_NAME,
1916
)
@@ -63,7 +60,12 @@ async def is_enabled(self, feature_flag_id: str, *args: Any, **kwargs: Any) -> b
6360
targeting_context = self._build_targeting_context(args)
6461

6562
result = await self._check_feature(feature_flag_id, targeting_context, **kwargs)
66-
if self._on_feature_evaluated and result.feature and result.feature.telemetry.enabled:
63+
if (
64+
self._on_feature_evaluated
65+
and result.feature
66+
and result.feature.telemetry.enabled
67+
and callable(self._on_feature_evaluated)
68+
):
6769
result.user = targeting_context.user_id
6870
if inspect.iscoroutinefunction(self._on_feature_evaluated):
6971
await self._on_feature_evaluated(result)
@@ -94,7 +96,12 @@ async def get_variant(self, feature_flag_id: str, *args: Any, **kwargs: Any) ->
9496
targeting_context = self._build_targeting_context(args)
9597

9698
result = await self._check_feature(feature_flag_id, targeting_context, **kwargs)
97-
if self._on_feature_evaluated and result.feature and result.feature.telemetry.enabled:
99+
if (
100+
self._on_feature_evaluated
101+
and result.feature
102+
and result.feature.telemetry.enabled
103+
and callable(self._on_feature_evaluated)
104+
):
98105
result.user = targeting_context.user_id
99106
if inspect.iscoroutinefunction(self._on_feature_evaluated):
100107
await self._on_feature_evaluated(result)
@@ -141,35 +148,12 @@ async def _check_feature(
141148
142149
:param str feature_flag_id: Name of the feature flag.
143150
:param TargetingContext targeting_context: Targeting context.
144-
:return: True if the feature flag is enabled for the given context.
145-
:rtype: bool
151+
:return: EvaluationEvent for the given context.
152+
:rtype: EvaluationEvent
146153
"""
147-
if self._copy is not self._configuration.get(FEATURE_MANAGEMENT_KEY):
148-
self._cache = {}
149-
self._copy = self._configuration.get(FEATURE_MANAGEMENT_KEY)
150-
151-
if not self._cache.get(feature_flag_id):
152-
feature_flag = _get_feature_flag(self._configuration, feature_flag_id)
153-
self._cache[feature_flag_id] = feature_flag
154-
else:
155-
feature_flag = self._cache.get(feature_flag_id)
156-
157-
evaluation_event = EvaluationEvent(feature_flag)
158-
if not feature_flag:
159-
logging.warning("Feature flag %s not found", feature_flag_id)
160-
# Unknown feature flags are disabled by default
161-
return evaluation_event
162-
163-
if not feature_flag.enabled:
164-
# Feature flags that are disabled are always disabled
165-
FeatureManager._check_default_disabled_variant(evaluation_event)
166-
if feature_flag.allocation:
167-
variant_name = feature_flag.allocation.default_when_disabled
168-
evaluation_event.variant = self._variant_name_to_variant(feature_flag, variant_name)
169-
evaluation_event.feature = feature_flag
154+
evaluation_event, done = FeatureManager._check_feature_base(self, feature_flag_id)
170155

171-
# If a feature flag is disabled and override can't enable it
172-
evaluation_event.enabled = False
156+
if done:
173157
return evaluation_event
174158

175159
await self._check_feature_filters(evaluation_event, targeting_context, **kwargs)

tests/test_feature_manager.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
# license information.
55
# --------------------------------------------------------------------------
66
import pytest
7+
import unittest
78
from featuremanagement import FeatureManager, FeatureFilter
89

910

10-
class TestFeatureManager:
11+
class TestFeatureManager(unittest.TestCase):
1112
# method: feature_manager_creation
1213
def test_empty_feature_manager_creation(self):
1314
feature_manager = FeatureManager({})
@@ -29,6 +30,12 @@ def test_basic_feature_manager_creation(self):
2930
assert feature_manager.is_enabled("Alpha")
3031
assert not feature_manager.is_enabled("Beta")
3132

33+
# method: feature_manager_creation
34+
def test_feature_manager_creation_invalid_feature_filter(self):
35+
feature_flags = {"feature_management": {"feature_flags": []}}
36+
with self.assertRaises(ValueError):
37+
FeatureManager(feature_flags, feature_filters=["invalid_filter"])
38+
3239
# method: feature_manager_creation
3340
def test_feature_manager_creation_with_filters(self):
3441
feature_flags = {

tests/test_feature_manager_async.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
# license information.
55
# --------------------------------------------------------------------------
66
import pytest
7+
import unittest
78
from featuremanagement.aio import FeatureManager, FeatureFilter
89

910

10-
class TestFeatureManager:
11+
class TestFeatureManager(unittest.IsolatedAsyncioTestCase):
1112
# method: feature_manager_creation
1213
@pytest.mark.asyncio
1314
async def test_empty_feature_manager_creation(self):
@@ -32,6 +33,13 @@ async def test_basic_feature_manager_creation(self):
3233
assert await feature_manager.is_enabled("Alpha")
3334
assert not await feature_manager.is_enabled("Beta")
3435

36+
# method: feature_manager_creation
37+
@pytest.mark.asyncio
38+
def test_feature_manager_creation_invalid_feature_filter(self):
39+
feature_flags = {"feature_management": {"feature_flags": []}}
40+
with self.assertRaises(ValueError):
41+
FeatureManager(feature_flags, feature_filters=["invalid_filter"])
42+
3543
# method: feature_manager_creation
3644
@pytest.mark.asyncio
3745
async def test_feature_manager_creation_with_filters(self):

0 commit comments

Comments
 (0)