3636from flag_engine .utils .types import SupportsStr , get_casting_function
3737
3838
39- class FeatureContextWithSegmentName (TypedDict , typing .Generic [FeatureMetadataT ]):
39+ class SegmentOverride (TypedDict , typing .Generic [FeatureMetadataT ]):
4040 feature_context : FeatureContext [FeatureMetadataT ]
4141 segment_name : str
4242
4343
44+ SegmentOverrides = dict [str , SegmentOverride [FeatureMetadataT ]]
45+
4446# Type alias for EvaluationContext with any metadata types
4547# used in internal evaluation logic
4648_EvaluationContextAnyMeta = EvaluationContext [typing .Any , typing .Any ]
@@ -55,15 +57,52 @@ def get_evaluation_result(
5557 :param context: the evaluation context
5658 :return: EvaluationResult containing the context, flags, and segments
5759 """
58- segments : list [SegmentResult [SegmentMetadataT ]] = []
59- flags : dict [str , FlagResult [FeatureMetadataT ]] = {}
60+ context = get_enriched_context (context )
61+ segments , segment_overrides = evaluate_segments (context )
62+ flags = evaluate_features (context , segment_overrides )
63+
64+ return {
65+ "flags" : flags ,
66+ "segments" : segments ,
67+ }
68+
69+
70+ def get_enriched_context (
71+ context : EvaluationContext [SegmentMetadataT , FeatureMetadataT ],
72+ ) -> EvaluationContext [SegmentMetadataT , FeatureMetadataT ]:
73+ """
74+ Get an enriched version of the evaluation context by ensuring that:
75+ - `$.identity.key` is set
76+
77+ :param context: the evaluation context to enrich
78+ :return: the enriched evaluation context. If not modified, returns the original context.
79+ """
80+ if identity_context := context .get ("identity" ):
81+ if not identity_context .get ("key" ):
82+ context = context .copy ()
83+ context ["identity" ] = {
84+ ** identity_context ,
85+ "key" : (
86+ f"{ context ['environment' ]['key' ]} _{ identity_context ['identifier' ]} "
87+ ),
88+ }
89+
90+ return context
6091
61- segment_feature_contexts : dict [
62- SupportsStr ,
63- FeatureContextWithSegmentName [FeatureMetadataT ],
64- ] = {}
6592
66- for segment_context in (context .get ("segments" ) or {}).values ():
93+ def evaluate_segments (
94+ context : EvaluationContext [SegmentMetadataT , FeatureMetadataT ],
95+ ) -> typing .Tuple [
96+ list [SegmentResult [SegmentMetadataT ]],
97+ SegmentOverrides [FeatureMetadataT ],
98+ ]:
99+ if not (segment_contexts := context .get ("segments" )):
100+ return [], {}
101+
102+ segment_results : list [SegmentResult [SegmentMetadataT ]] = []
103+ segment_overrides : SegmentOverrides [FeatureMetadataT ] = {}
104+
105+ for segment_context in segment_contexts .values ():
67106 if not is_context_in_segment (context , segment_context ):
68107 continue
69108
@@ -72,69 +111,75 @@ def get_evaluation_result(
72111 }
73112 if segment_metadata := segment_context .get ("metadata" ):
74113 segment_result ["metadata" ] = segment_metadata
75- segments .append (segment_result )
114+ segment_results .append (segment_result )
76115
77116 if overrides := segment_context .get ("overrides" ):
78117 for override_feature_context in overrides :
79118 feature_name = override_feature_context ["name" ]
80119 if (
81- feature_name not in segment_feature_contexts
120+ feature_name not in segment_overrides
82121 or override_feature_context .get (
83122 "priority" ,
84123 constants .DEFAULT_PRIORITY ,
85124 )
86- < (segment_feature_contexts [feature_name ]["feature_context" ]).get (
125+ < (segment_overrides [feature_name ]["feature_context" ]).get (
87126 "priority" ,
88127 constants .DEFAULT_PRIORITY ,
89128 )
90129 ):
91- segment_feature_contexts [feature_name ] = (
92- FeatureContextWithSegmentName (
93- feature_context = override_feature_context ,
94- segment_name = segment_context ["name" ],
95- )
130+ segment_overrides [feature_name ] = SegmentOverride (
131+ feature_context = override_feature_context ,
132+ segment_name = segment_context ["name" ],
96133 )
97134
98- identity_key = _get_identity_key (context )
135+ return segment_results , segment_overrides
136+
137+
138+ def evaluate_features (
139+ context : EvaluationContext [typing .Any , FeatureMetadataT ],
140+ segment_overrides : SegmentOverrides [FeatureMetadataT ],
141+ ) -> dict [str , FlagResult [FeatureMetadataT ]]:
142+ flags : dict [str , FlagResult [FeatureMetadataT ]] = {}
143+
99144 for feature_context in (context .get ("features" ) or {}).values ():
100145 feature_name = feature_context ["name" ]
101- if feature_context_with_segment_name := segment_feature_contexts .get (
146+ if segment_override := segment_overrides .get (
102147 feature_context ["name" ],
103148 ):
104- feature_context = feature_context_with_segment_name ["feature_context" ]
149+ feature_context = segment_override ["feature_context" ]
105150 flag_result : FlagResult [FeatureMetadataT ]
106151 flags [feature_name ] = flag_result = {
107152 "enabled" : feature_context ["enabled" ],
108153 "name" : feature_context ["name" ],
109- "reason" : f"TARGETING_MATCH; segment={ feature_context_with_segment_name ['segment_name' ]} " ,
154+ "reason" : f"TARGETING_MATCH; segment={ segment_override ['segment_name' ]} " ,
110155 "value" : feature_context .get ("value" ),
111156 }
112157 if feature_metadata := feature_context .get ("metadata" ):
113158 flag_result ["metadata" ] = feature_metadata
114159 continue
115- flags [feature_name ] = get_flag_result_from_feature_context (
116- feature_context = feature_context ,
117- key = identity_key ,
160+ flags [feature_name ] = get_flag_result_from_context (
161+ context = context ,
162+ feature_name = feature_name ,
118163 )
119164
120- return {
121- "flags" : flags ,
122- "segments" : segments ,
123- }
165+ return flags
124166
125167
126- def get_flag_result_from_feature_context (
127- feature_context : FeatureContext [ FeatureMetadataT ],
128- key : typing . Optional [ SupportsStr ] ,
168+ def get_flag_result_from_context (
169+ context : EvaluationContext [ typing . Any , FeatureMetadataT ],
170+ feature_name : str ,
129171) -> FlagResult [FeatureMetadataT ]:
130172 """
131- Get a feature value from the feature context
132- for a given key .
173+ Get a feature value from the evaluation context
174+ for a given feature name .
133175
134- :param feature_context : the feature context
135- :param key : the key to get the value for
136- :return: the value for the key in the feature context
176+ :param context : the evaluation context
177+ :param feature_name : the feature name to get the value for
178+ :return: the value for the feature name in the evaluation context
137179 """
180+ feature_context = context ["features" ][feature_name ]
181+ key = _get_identity_key (context )
182+
138183 flag_result : typing .Optional [FlagResult [FeatureMetadataT ]] = None
139184
140185 if key is not None and (variants := feature_context .get ("variants" )):
@@ -253,8 +298,8 @@ def context_matches_condition(
253298 if condition ["operator" ] == constants .PERCENTAGE_SPLIT :
254299 if context_value is not None :
255300 object_ids = [segment_key , context_value ]
256- elif identity_context := context . get ( "identity" ):
257- object_ids = [segment_key , identity_context [ "key" ] ]
301+ elif identity_key := _get_identity_key ( context ):
302+ object_ids = [segment_key , identity_key ]
258303 else :
259304 return False
260305
@@ -376,10 +421,7 @@ def _get_identity_key(
376421 context : _EvaluationContextAnyMeta ,
377422) -> typing .Optional [SupportsStr ]:
378423 if identity_context := context .get ("identity" ):
379- return (
380- identity_context .get ("key" )
381- or f"{ context ['environment' ]['key' ]} _{ identity_context ['identifier' ]} "
382- )
424+ return identity_context .get ("key" )
383425 return None
384426
385427
0 commit comments