11import typing
22
3- from flag_engine .context .types import EvaluationContext
3+ from flag_engine .context .types import (
4+ EvaluationContext ,
5+ FeatureContext ,
6+ SegmentContext ,
7+ SegmentRule ,
8+ )
49from flag_engine .environments .models import EnvironmentModel
10+ from flag_engine .features .models import (
11+ FeatureModel ,
12+ FeatureStateModel ,
13+ MultivariateFeatureStateValueModel ,
14+ )
515from flag_engine .identities .models import IdentityModel
616from flag_engine .identities .traits .models import TraitModel
17+ from flag_engine .result .types import FlagResult
18+ from flag_engine .segments .models import SegmentRuleModel
719
820
921def map_environment_identity_to_context (
@@ -19,6 +31,76 @@ def map_environment_identity_to_context(
1931 :param override_traits: A list of TraitModel objects, to be used in place of `identity.identity_traits` if provided.
2032 :return: An EvaluationContext containing the environment and identity.
2133 """
34+ features = map_feature_states_to_feature_contexts (environment .feature_states )
35+ segments : typing .Dict [str , SegmentContext ] = {}
36+ for segment in environment .project .segments :
37+ segment_ctx_data : SegmentContext = {
38+ "key" : str (segment .id ),
39+ "name" : segment .name ,
40+ "rules" : map_segment_rules_to_segment_context_rules (segment .rules ),
41+ }
42+ if segment_feature_states := segment .feature_states :
43+ segment_ctx_data ["overrides" ] = list (
44+ map_feature_states_to_feature_contexts (segment_feature_states ).values ()
45+ )
46+ segments [segment .name ] = segment_ctx_data
47+ # Concatenate feature states overriden for identities
48+ # to segment contexts
49+ features_to_identifiers : typing .Dict [
50+ tuple [tuple [str , str , bool , typing .Any ], ...], list [str ]
51+ ] = {}
52+ for identity_override in (* environment .identity_overrides , identity ):
53+ identity_features : typing .List [FeatureStateModel ] = (
54+ identity_override .identity_features
55+ )
56+ if not identity_features :
57+ continue
58+ overrides_key = tuple (
59+ (
60+ str (feature_state .feature .id ),
61+ feature_state .feature .name ,
62+ feature_state .enabled ,
63+ feature_state .feature_state_value ,
64+ )
65+ for feature_state in sorted (identity_features , key = _get_name )
66+ )
67+ features_to_identifiers .setdefault (overrides_key , []).append (
68+ identity_override .identifier
69+ )
70+ for overrides_key , identifiers in features_to_identifiers .items ():
71+ segment_name = f"overrides_{ abs (hash (overrides_key ))} "
72+ segments [segment_name ] = SegmentContext (
73+ key = "" , # Identity override segments never use % Split operator
74+ name = segment_name ,
75+ rules = [
76+ {
77+ "type" : "ALL" ,
78+ "rules" : [
79+ {
80+ "type" : "ALL" ,
81+ "conditions" : [
82+ {
83+ "property" : "$.identity.identifier" ,
84+ "operator" : "IN" ,
85+ "value" : "," .join (identifiers ),
86+ }
87+ ],
88+ }
89+ ],
90+ }
91+ ],
92+ overrides = [
93+ {
94+ "key" : "" , # Identity overrides never carry multivariate options
95+ "feature_key" : feature_key ,
96+ "name" : feature_name ,
97+ "enabled" : feature_enabled ,
98+ "value" : feature_value ,
99+ "priority" : float ("-inf" ), # Highest possible priority
100+ }
101+ for feature_key , feature_name , feature_enabled , feature_value in overrides_key
102+ ],
103+ )
22104 return {
23105 "environment" : {
24106 "key" : environment .api_key ,
@@ -36,4 +118,95 @@ def map_environment_identity_to_context(
36118 )
37119 },
38120 },
121+ "features" : features ,
122+ "segments" : segments ,
39123 }
124+
125+
126+ def map_feature_states_to_feature_contexts (
127+ feature_states : typing .List [FeatureStateModel ],
128+ * ,
129+ priority : int | float | None = None ,
130+ ) -> typing .Dict [str , FeatureContext ]:
131+ features : typing .Dict [str , FeatureContext ] = {}
132+ for feature_state in feature_states :
133+ feature_ctx_data : FeatureContext = {
134+ "key" : str (feature_state .django_id or feature_state .featurestate_uuid ),
135+ "feature_key" : str (feature_state .feature .id ),
136+ "name" : feature_state .feature .name ,
137+ "enabled" : feature_state .enabled ,
138+ "value" : feature_state .feature_state_value ,
139+ }
140+ multivariate_feature_state_values : typing .List [
141+ MultivariateFeatureStateValueModel
142+ ]
143+ if (
144+ multivariate_feature_state_values
145+ := feature_state .multivariate_feature_state_values
146+ ):
147+ feature_ctx_data ["variants" ] = [
148+ {
149+ "value" : multivariate_feature_state_value .multivariate_feature_option .value ,
150+ "weight" : multivariate_feature_state_value .percentage_allocation ,
151+ }
152+ for multivariate_feature_state_value in sorted (
153+ multivariate_feature_state_values ,
154+ key = _get_multivariate_feature_state_value_id ,
155+ )
156+ ]
157+ if feature_segment := feature_state .feature_segment :
158+ priority = feature_segment .priority
159+ if priority is not None :
160+ feature_ctx_data ["priority" ] = priority
161+ features [feature_state .feature .name ] = feature_ctx_data
162+ return features
163+
164+
165+ def map_segment_rules_to_segment_context_rules (
166+ rules : typing .List [SegmentRuleModel ],
167+ ) -> typing .List [SegmentRule ]:
168+ return [
169+ {
170+ "type" : rule .type ,
171+ "conditions" : [
172+ {
173+ "property" : condition .property_ or "" ,
174+ "operator" : condition .operator ,
175+ "value" : condition .value or "" ,
176+ }
177+ for condition in rule .conditions
178+ ],
179+ "rules" : map_segment_rules_to_segment_context_rules (rule .rules ),
180+ }
181+ for rule in rules
182+ ]
183+
184+
185+ def map_flag_results_to_feature_states (
186+ flag_results : typing .List [FlagResult ],
187+ ) -> typing .List [FeatureStateModel ]:
188+ return [
189+ FeatureStateModel (
190+ feature = FeatureModel (
191+ id = flag_result ["feature_key" ],
192+ name = flag_result ["name" ],
193+ type = "STANDARD" ,
194+ ),
195+ enabled = flag_result ["enabled" ],
196+ feature_state_value = flag_result ["value" ],
197+ )
198+ for flag_result in flag_results
199+ ]
200+
201+
202+ def _get_multivariate_feature_state_value_id (
203+ multivariate_feature_state_value : MultivariateFeatureStateValueModel ,
204+ ) -> int :
205+ return (
206+ multivariate_feature_state_value .id
207+ or multivariate_feature_state_value .mv_fs_value_uuid .int
208+ )
209+
210+
211+ def _get_name (feature_state : FeatureStateModel ) -> str :
212+ return feature_state .feature .name
0 commit comments