Skip to content

Commit 237e03d

Browse files
committed
chore: updated-mappers
1 parent bec9c31 commit 237e03d

File tree

1 file changed

+98
-60
lines changed

1 file changed

+98
-60
lines changed

flagsmith/mappers.py

Lines changed: 98 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import json
12
import typing
23
from collections import defaultdict
34

@@ -9,11 +10,13 @@
910
)
1011
from flag_engine.environments.models import EnvironmentModel
1112
from flag_engine.features.models import (
13+
FeatureModel,
1214
FeatureStateModel,
1315
MultivariateFeatureStateValueModel,
1416
)
1517
from flag_engine.identities.models import IdentityModel
1618
from flag_engine.identities.traits.models import TraitModel
19+
from flag_engine.result.types import FlagResult
1720
from flag_engine.segments.models import SegmentRuleModel
1821

1922
OverrideKey = 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+
212250
def _get_name(feature_state: FeatureStateModel) -> str:
213251
return feature_state.feature.name

0 commit comments

Comments
 (0)