2424logger = 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
5255def _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
7875class 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