Skip to content

Commit e9e84e1

Browse files
Copilotmrm9084
andcommitted
Optimize performance by caching reversed feature flags and simplify unique list generation
Co-authored-by: mrm9084 <1054559+mrm9084@users.noreply.github.com>
1 parent b964759 commit e9e84e1

File tree

1 file changed

+28
-15
lines changed

1 file changed

+28
-15
lines changed

featuremanagement/_featuremanagerbase.py

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,14 @@
2424
logger = logging.getLogger(__name__)
2525

2626

27-
def _get_feature_flag(configuration: Mapping[str, Any], feature_flag_name: str) -> Optional[FeatureFlag]:
27+
def _get_feature_flag(configuration: Mapping[str, Any], feature_flag_name: str, reversed_feature_flags: Optional[List] = None) -> Optional[FeatureFlag]:
2828
"""
2929
Gets the FeatureFlag json from the configuration, if it exists it gets converted to a FeatureFlag object.
3030
If multiple feature flags have the same id, the last one wins.
3131
3232
:param Mapping configuration: Configuration object.
3333
:param str feature_flag_name: Name of the feature flag.
34+
:param List reversed_feature_flags: Optional pre-reversed list of feature flags for performance.
3435
:return: FeatureFlag
3536
:rtype: FeatureFlag
3637
"""
@@ -41,8 +42,10 @@ def _get_feature_flag(configuration: Mapping[str, Any], feature_flag_name: str)
4142
if not feature_flags or not isinstance(feature_flags, list):
4243
return None
4344

44-
# Iterate backwards to find the last matching feature flag more efficiently
45-
for feature_flag in reversed(feature_flags):
45+
# Use pre-reversed list if available, otherwise reverse on demand
46+
flags_to_iterate = reversed_feature_flags if reversed_feature_flags is not None else reversed(feature_flags)
47+
48+
for feature_flag in flags_to_iterate:
4649
if feature_flag.get("id") == feature_flag_name:
4750
return FeatureFlag.convert_from_json(feature_flag)
4851

@@ -51,7 +54,8 @@ def _get_feature_flag(configuration: Mapping[str, Any], feature_flag_name: str)
5154

5255
def _list_feature_flag_names(configuration: Mapping[str, Any]) -> List[str]:
5356
"""
54-
List of all feature flag names. If there are duplicate names, only unique names are returned.
57+
List of all feature flag names. If there are duplicate names, only unique names are returned
58+
in order of first appearance.
5559
5660
:param Mapping configuration: Configuration object.
5761
:return: List of feature flag names.
@@ -63,16 +67,9 @@ def _list_feature_flag_names(configuration: Mapping[str, Any]) -> List[str]:
6367
if not feature_flags or not isinstance(feature_flags, list):
6468
return []
6569

66-
# Use a set to track unique names and a list to preserve order
67-
seen = set()
68-
unique_names = []
69-
for feature_flag in feature_flags:
70-
flag_id = feature_flag.get("id")
71-
if flag_id not in seen:
72-
seen.add(flag_id)
73-
unique_names.append(flag_id)
74-
75-
return unique_names
70+
# Use dict.fromkeys() to preserve order while ensuring uniqueness
71+
flag_ids = [feature_flag.get("id") for feature_flag in feature_flags]
72+
return list(dict.fromkeys(flag_ids))
7673

7774

7875
class FeatureManagerBase(ABC):
@@ -86,10 +83,25 @@ def __init__(self, configuration: Mapping[str, Any], **kwargs: Any):
8683
self._configuration = configuration
8784
self._cache: Dict[str, Optional[FeatureFlag]] = {}
8885
self._copy = configuration.get(FEATURE_MANAGEMENT_KEY)
86+
self._reversed_feature_flags: Optional[List] = None
8987
self._on_feature_evaluated = kwargs.pop("on_feature_evaluated", None)
9088
self._targeting_context_accessor: Optional[Callable[[], TargetingContext]] = kwargs.pop(
9189
"targeting_context_accessor", None
9290
)
91+
self._update_reversed_feature_flags()
92+
93+
def _update_reversed_feature_flags(self) -> None:
94+
"""Update the cached reversed feature flags list when configuration changes."""
95+
feature_management = self._configuration.get(FEATURE_MANAGEMENT_KEY)
96+
if not feature_management or not isinstance(feature_management, Mapping):
97+
self._reversed_feature_flags = None
98+
return
99+
feature_flags = feature_management.get(FEATURE_FLAG_KEY)
100+
if not feature_flags or not isinstance(feature_flags, list):
101+
self._reversed_feature_flags = None
102+
return
103+
104+
self._reversed_feature_flags = list(reversed(feature_flags))
93105

94106
@staticmethod
95107
def _assign_default_disabled_variant(evaluation_event: EvaluationEvent) -> None:
@@ -277,9 +289,10 @@ def _check_feature_base(self, feature_flag_id: str) -> Tuple[EvaluationEvent, bo
277289
if self._copy is not self._configuration.get(FEATURE_MANAGEMENT_KEY):
278290
self._cache = {}
279291
self._copy = self._configuration.get(FEATURE_MANAGEMENT_KEY)
292+
self._update_reversed_feature_flags()
280293

281294
if not self._cache.get(feature_flag_id):
282-
feature_flag = _get_feature_flag(self._configuration, feature_flag_id)
295+
feature_flag = _get_feature_flag(self._configuration, feature_flag_id, self._reversed_feature_flags)
283296
self._cache[feature_flag_id] = feature_flag
284297
else:
285298
feature_flag = self._cache.get(feature_flag_id)

0 commit comments

Comments
 (0)