1+ import json
12import typing
23from collections import defaultdict
34
910)
1011from flag_engine .environments .models import EnvironmentModel
1112from flag_engine .features .models import (
13+ FeatureModel ,
1214 FeatureStateModel ,
1315 MultivariateFeatureStateValueModel ,
1416)
1517from flag_engine .identities .models import IdentityModel
1618from flag_engine .identities .traits .models import TraitModel
19+ from flag_engine .result .types import FlagResult
1720from flag_engine .segments .models import SegmentRuleModel
1821
1922OverrideKey = typing .Tuple [
@@ -38,28 +41,61 @@ def map_environment_identity_to_context(
3841 :param override_traits: A list of TraitModel objects, to be used in place of `identity.identity_traits` if provided.
3942 :return: An EvaluationContext containing the environment and identity.
4043 """
41- features = map_feature_states_to_feature_contexts (environment .feature_states )
44+ features = _map_feature_states_to_feature_contexts (environment .feature_states )
4245 segments : typing .Dict [str , SegmentContext ] = {}
4346 for segment in environment .project .segments :
4447 segment_ctx_data : SegmentContext = {
4548 "key" : str (segment .id ),
4649 "name" : segment .name ,
47- "rules" : map_segment_rules_to_segment_context_rules (segment .rules ),
50+ "rules" : _map_segment_rules_to_segment_context_rules (segment .rules ),
4851 }
4952 if segment_feature_states := segment .feature_states :
5053 segment_ctx_data ["overrides" ] = list (
51- map_feature_states_to_feature_contexts (segment_feature_states ).values ()
54+ _map_feature_states_to_feature_contexts (segment_feature_states ).values ()
5255 )
53- segments [segment .name ] = segment_ctx_data
54- # Concatenate feature states overriden for identities
55- # to segment contexts
56+ segments [str (segment .id )] = segment_ctx_data
57+ identity_overrides = environment .identity_overrides + [identity ] if identity else []
58+ segments .update (_map_identity_overrides_to_segment_contexts (identity_overrides ))
59+ return {
60+ "environment" : {
61+ "key" : environment .api_key ,
62+ "name" : environment .name or "" ,
63+ },
64+ "identity" : (
65+ {
66+ "identifier" : identity .identifier ,
67+ "key" : str (identity .django_id or identity .composite_key ),
68+ "traits" : {
69+ trait .trait_key : trait .trait_value
70+ for trait in (
71+ override_traits
72+ if override_traits is not None
73+ else identity .identity_traits
74+ )
75+ },
76+ }
77+ if identity
78+ else None
79+ ),
80+ "features" : features ,
81+ "segments" : segments ,
82+ }
83+
84+
85+ def _map_identity_overrides_to_segment_contexts (
86+ identity_overrides : typing .List [IdentityModel ],
87+ ) -> typing .Dict [str , SegmentContext ]:
88+ """
89+ Map identity overrides to segment contexts.
90+
91+ :param identity_overrides: A list of IdentityModel objects.
92+ :return: A dictionary mapping segment ids to SegmentContext objects.
93+ """
5694 features_to_identifiers : typing .Dict [
5795 OverridesKey ,
5896 typing .List [str ],
5997 ] = defaultdict (list )
60- for identity_override in (* environment .identity_overrides , identity ):
61- if identity_override is None :
62- continue
98+ for identity_override in identity_overrides :
6399 identity_features : typing .List [FeatureStateModel ] = (
64100 identity_override .identity_features
65101 )
@@ -75,24 +111,19 @@ def map_environment_identity_to_context(
75111 for feature_state in sorted (identity_features , key = _get_name )
76112 )
77113 features_to_identifiers [overrides_key ].append (identity_override .identifier )
114+ segment_contexts : typing .Dict [str , SegmentContext ] = {}
78115 for overrides_key , identifiers in features_to_identifiers .items ():
79- segment_name = f"overrides_{ abs (hash (overrides_key ))} "
80- segments [segment_name ] = SegmentContext (
116+ segment_contexts [str (hash (overrides_key ))] = SegmentContext (
81117 key = "" , # Identity override segments never use % Split operator
82- name = segment_name ,
118+ name = "identity_overrides" ,
83119 rules = [
84120 {
85121 "type" : "ALL" ,
86- "rules " : [
122+ "conditions " : [
87123 {
88- "type" : "ALL" ,
89- "conditions" : [
90- {
91- "property" : "$.identity.identifier" ,
92- "operator" : "IN" ,
93- "value" : "," .join (identifiers ),
94- }
95- ],
124+ "property" : "$.identity.identifier" ,
125+ "operator" : "IN" ,
126+ "value" : json .dumps (identifiers ),
96127 }
97128 ],
98129 }
@@ -109,31 +140,10 @@ def map_environment_identity_to_context(
109140 for feature_key , feature_name , feature_enabled , feature_value in overrides_key
110141 ],
111142 )
112- return {
113- "environment" : {
114- "key" : environment .api_key ,
115- "name" : environment .name or "" ,
116- },
117- "identity" : {
118- "identifier" : identity .identifier ,
119- "key" : str (identity .django_id or identity .composite_key ),
120- "traits" : {
121- trait .trait_key : trait .trait_value
122- for trait in (
123- override_traits
124- if override_traits is not None
125- else identity .identity_traits
126- )
127- },
128- }
129- if identity
130- else None ,
131- "features" : features ,
132- "segments" : segments ,
133- }
143+ return segment_contexts
134144
135145
136- def map_feature_states_to_feature_contexts (
146+ def _map_feature_states_to_feature_contexts (
137147 feature_states : typing .List [FeatureStateModel ],
138148) -> typing .Dict [str , FeatureContext ]:
139149 """
@@ -144,7 +154,7 @@ def map_feature_states_to_feature_contexts(
144154 """
145155 features : typing .Dict [str , FeatureContext ] = {}
146156 for feature_state in feature_states :
147- feature_ctx_data : FeatureContext = {
157+ feature_context : FeatureContext = {
148158 "key" : str (feature_state .django_id or feature_state .featurestate_uuid ),
149159 "feature_key" : str (feature_state .feature .id ),
150160 "name" : feature_state .feature .name ,
@@ -155,9 +165,10 @@ def map_feature_states_to_feature_contexts(
155165 MultivariateFeatureStateValueModel
156166 ]
157167 if (
158- multivariate_feature_state_values := feature_state .multivariate_feature_state_values
168+ multivariate_feature_state_values
169+ := feature_state .multivariate_feature_state_values
159170 ):
160- feature_ctx_data ["variants" ] = [
171+ feature_context ["variants" ] = [
161172 {
162173 "value" : multivariate_feature_state_value .multivariate_feature_option .value ,
163174 "weight" : multivariate_feature_state_value .percentage_allocation ,
@@ -169,21 +180,12 @@ def map_feature_states_to_feature_contexts(
169180 ]
170181 if feature_segment := feature_state .feature_segment :
171182 if (priority := feature_segment .priority ) is not None :
172- feature_ctx_data ["priority" ] = priority
173- features [feature_state .feature .name ] = feature_ctx_data
183+ feature_context ["priority" ] = priority
184+ features [feature_state .feature .name ] = feature_context
174185 return features
175186
176187
177- def _get_multivariate_feature_state_value_id (
178- multivariate_feature_state_value : MultivariateFeatureStateValueModel ,
179- ) -> int :
180- return (
181- multivariate_feature_state_value .id
182- or multivariate_feature_state_value .mv_fs_value_uuid .int
183- )
184-
185-
186- def map_segment_rules_to_segment_context_rules (
188+ def _map_segment_rules_to_segment_context_rules (
187189 rules : typing .List [SegmentRuleModel ],
188190) -> typing .List [SegmentRule ]:
189191 """
@@ -203,11 +205,47 @@ def map_segment_rules_to_segment_context_rules(
203205 }
204206 for condition in rule .conditions
205207 ],
206- "rules" : map_segment_rules_to_segment_context_rules (rule .rules ),
208+ "rules" : _map_segment_rules_to_segment_context_rules (rule .rules ),
207209 }
208210 for rule in rules
209211 ]
210212
211213
214+ def map_flag_results_to_feature_states (
215+ flag_results : typing .List [FlagResult ],
216+ ) -> typing .List [FeatureStateModel ]:
217+ """
218+ Map flag results to feature states.
219+
220+ :param flag_results: A list of FlagResult objects.
221+ :return: A list of FeatureStateModel objects.
222+ """
223+ return [
224+ FeatureStateModel (
225+ feature = FeatureModel (
226+ id = flag_result ["feature_key" ],
227+ name = flag_result ["name" ],
228+ type = (
229+ "MULTIVARIATE"
230+ if flag_result ["reason" ].startswith ("SPLIT" )
231+ else "STANDARD"
232+ ),
233+ ),
234+ enabled = flag_result ["enabled" ],
235+ feature_state_value = flag_result ["value" ],
236+ )
237+ for flag_result in flag_results
238+ ]
239+
240+
241+ def _get_multivariate_feature_state_value_id (
242+ multivariate_feature_state_value : MultivariateFeatureStateValueModel ,
243+ ) -> int :
244+ return (
245+ multivariate_feature_state_value .id
246+ or multivariate_feature_state_value .mv_fs_value_uuid .int
247+ )
248+
249+
212250def _get_name (feature_state : FeatureStateModel ) -> str :
213251 return feature_state .feature .name
0 commit comments