Skip to content

Commit 17ad742

Browse files
committed
initial code
1 parent c5d9dae commit 17ad742

File tree

3 files changed

+203
-2
lines changed

3 files changed

+203
-2
lines changed

optimizely/optimizely.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1021,6 +1021,7 @@ def _decide(self, user_context, key, decide_options=None):
10211021
decision_event_dispatched = False
10221022
ignore_ups = OptimizelyDecideOption.IGNORE_USER_PROFILE_SERVICE in decide_options
10231023

1024+
10241025
decision, decision_reasons = self.decision_service.get_variation_for_feature(config, feature_flag, user_id,
10251026
attributes, ignore_ups)
10261027

@@ -1123,7 +1124,6 @@ def _decide_all(self, user_context, decide_options=None):
11231124

11241125
def _decide_for_keys(self, user_context, keys, decide_options=None):
11251126
"""
1126-
11271127
Args:
11281128
user_context: UserContent
11291129
keys: list of feature keys to run decide on.
@@ -1159,3 +1159,30 @@ def _decide_for_keys(self, user_context, keys, decide_options=None):
11591159
continue
11601160
decisions[key] = decision
11611161
return decisions
1162+
1163+
# TODO - NEW
1164+
def get_flag_variation_by_key(self, flag_key, variation_key):
1165+
config = self.config_manager.get_config()
1166+
variations = config.flag_variations_map[flag_key]
1167+
1168+
print('VARIATIONS ', variations)
1169+
1170+
if not config:
1171+
return None
1172+
1173+
if variations.key == variation_key:
1174+
return variations.key
1175+
1176+
1177+
1178+
1179+
1180+
1181+
1182+
1183+
1184+
1185+
1186+
1187+
1188+

optimizely/optimizely_user_context.py

Lines changed: 173 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@
1414
#
1515

1616
import threading
17+
import copy
1718

1819

1920
class OptimizelyUserContext(object):
2021
"""
2122
Representation of an Optimizely User Context using which APIs are to be called.
2223
"""
24+
forced_decisions = []
2325

2426
def __init__(self, optimizely_client, user_id, user_attributes=None):
2527
""" Create an instance of the Optimizely User Context.
@@ -42,8 +44,39 @@ def __init__(self, optimizely_client, user_id, user_attributes=None):
4244
self._user_attributes = user_attributes.copy() if user_attributes else {}
4345
self.lock = threading.Lock()
4446

47+
# TODO - ADD FORCED DECISION class
48+
"""
49+
struct ForcedDecision {
50+
let flagKey: String
51+
let ruleKey: String?
52+
var variationKey: String
53+
}
54+
var forcedDecisions = AtomicArray<ForcedDecision>()
55+
"""
56+
class ForcedDecision(object):
57+
def __init__(self, flag_key, rule_key, variation_key):
58+
self.flag_key = flag_key
59+
self.rule_key = rule_key
60+
self.variation_key = variation_key
61+
62+
63+
# TODO - NEW
4564
def _clone(self):
46-
return OptimizelyUserContext(self.client, self.user_id, self.get_user_attributes())
65+
if not self.client:
66+
return None
67+
68+
user_context = OptimizelyUserContext(self.client, self.user_id, self.get_user_attributes())
69+
70+
if len(self.forced_decisions) > 0:
71+
# Jae:
72+
# Make sure the assigning is to make a copy. Some langs use ref and other make a copy when assigning array/map.
73+
# Swift makes a copy automatically when assigning value type. Not sure about python.
74+
# So it needs to be pass by value. So the original object is not changed. Change is only in the new object. Here I’ll need to call copy.deepcopy()
75+
# The opposite. We won’t change the contents of the copied one. But the original can be changed any time later.
76+
user_context.forced_decisions = copy.deepcopy(self.forced_decisions) # TODO - IMPORTANT -> CHECK IF WE NEED DEEPCOPY OR NOT - SEE SLACK W JAE
77+
78+
return user_context
79+
4780

4881
def get_user_attributes(self):
4982
with self.lock:
@@ -114,3 +147,142 @@ def as_json(self):
114147
'user_id': self.user_id,
115148
'attributes': self.get_user_attributes(),
116149
}
150+
151+
152+
# TODO - NEW
153+
def set_forced_decision(self, flag_key, rule_key, variation_key):
154+
"""
155+
Sets the forced decision (variation key) for a given flag and an optional rule.
156+
157+
Args:
158+
flag_key: A flag key.
159+
rule_key: An experiment or delivery rule key (optional).
160+
variation_key: A variation key.
161+
162+
Returns:
163+
True if the forced decision has been set successfully.
164+
"""
165+
config = self.client.get_optimizely_config()
166+
167+
if self.client is None or config is None: # TODO - check if to use "is not" or "not =="
168+
# TODO log error sdk key not ready - whichlogger, to show in console, logger for optimizely_client,loggger for what? where do we want it to log?
169+
170+
return False
171+
172+
if rule_key:
173+
print('xxx1 ', self.forced_decisions)
174+
for decision in self.forced_decisions:
175+
if decision.flag_key == flag_key and decision.rule_key == rule_key:
176+
decision.variation_key = variation_key # TODO check if .variation_key needs to be a dict key instead of dot notation object
177+
178+
self.forced_decisions.append(self.ForcedDecision(flag_key, rule_key, variation_key))
179+
print('xxx2 ', self.forced_decisions[0].variation_key)
180+
181+
return True
182+
183+
184+
# TODO - NEW
185+
def get_forced_decision(self, flag_key, rule_key):
186+
"""
187+
Sets the forced decision (variation key) for a given flag and an optional rule.
188+
189+
Args:
190+
flag_key: A flag key.
191+
rule_key: An experiment or delivery rule key (optional).
192+
193+
Returns:
194+
A variation key or None if forced decisions are not set for the parameters.
195+
"""
196+
config = self.client.get_optimizely_config()
197+
198+
if self.client is None or config is None: # TODO - check if to use "is not" or "not =="
199+
# TODO log error sdk key not ready - whichlogger, to sho win console, logger for optimizely_client,loggger for what? where do we want it to log?
200+
201+
return False
202+
203+
return self.find_forced_decision(flag_key, rule_key)
204+
205+
206+
# TODO - NEW
207+
def remove_forced_decision(self, flag_key, *arg): # making rule_key here optional arg - WHAT ABOUT IF RULE_KEY IS KEYWORD ARG????? <--- CHECK THIS!
208+
"""
209+
Removes the forced decision for a given flag and an optional rule.
210+
211+
Args:
212+
flag_key: A flag key.
213+
rule_key: An experiment or delivery rule key (optional).
214+
215+
Returns:
216+
Returns: true if the forced decision has been removed successfully.
217+
"""
218+
config = self.client.get_optimizely_config()
219+
220+
if self.client is None or config is None: # TODO - check if to use "is not" or "not =="
221+
# TODO log error sdk key not ready - whichlogger, to sho win console, logger for optimizely_client,loggger for what? where do we want it to log?
222+
223+
return False
224+
225+
# remove() built-in function by default removes the first occurrence of the element that meets the condition
226+
for decision in self.forced_decisions:
227+
if decision.flag_key == flag_key and decision.rule_key == arg:
228+
self.forced_decisions.remove(decision) #TODO - check if it needs to only remove the first occurrence and no other!!! Test separately if rmoe removes all occurences!
229+
230+
return False
231+
232+
# TODO - NEW
233+
def remove_all_forced_decisions(self):
234+
"""
235+
Removes all forced decisions bound to this user context.
236+
237+
Returns:
238+
True if forced decisions have been removed successfully.
239+
"""
240+
config = self.client.get_optimizely_config()
241+
242+
if self.client is None or config is None: # TODO - check if to use "is not" or "not =="
243+
# TODO log error sdk key not ready - whichlogger, to sho win console, logger for optimizely_client,loggger for what? where do we want it to log?
244+
245+
return False
246+
247+
self.forced_decisions.clear()
248+
249+
return True
250+
251+
# TODO - NEW
252+
def find_forced_decision(self, flag_key, rule_key):
253+
if len(self.forced_decisions) == 0:
254+
return None
255+
256+
for decision in self.forced_decisions:
257+
if decision.flag_key == flag_key and decision.rule_key == rule_key:
258+
return decision.variation_key
259+
260+
261+
262+
# TODO - For dding logger see this: https://github.com/optimizely/javascript-sdk/compare/pnguen/forced-decisions#diff-2bb39c11f271344df01b662f4313312870714813ceb8508ce7bdb851f09b5666R182-R192
263+
# TODO - NEW
264+
def find_validated_forced_decision(self, flag_key, rule_key, options):
265+
reasons = [] # TODO - what to do with reasons?? Jae has reasons. Do we need them?
266+
variation_key = self.find_forced_decision(flag_key, rule_key)
267+
if variation_key:
268+
self.client.get_flag_variation_by_key(flag_key, variation_key)
269+
270+
271+
272+
273+
274+
275+
276+
277+
278+
279+
280+
281+
282+
283+
284+
285+
286+
287+
288+

optimizely/project_config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ def __init__(self, datafile, logger, error_handler):
118118
variation.variables, 'id', entities.Variation.VariableUsage
119119
)
120120

121+
# TODO - NEW
121122
self.feature_key_map = self._generate_key_map(self.feature_flags, 'key', entities.FeatureFlag)
122123

123124
# As we cannot create json variables in datafile directly, here we convert
@@ -138,6 +139,7 @@ def __init__(self, datafile, logger, error_handler):
138139
# Add this experiment in experiment-feature map.
139140
self.experiment_feature_map[exp_id] = [feature.id]
140141

142+
# TODO - NEW
141143
# TODO - make sure to add a test for multiple flags. My test datafile only has a single flag. Because for loop needs to work across all flags.
142144
# all rules(experiment rules and delivery rules) for each flag
143145
self.flag_rules_map = {}

0 commit comments

Comments
 (0)