13
13
# limitations under the License.
14
14
#
15
15
16
+ import copy
16
17
import threading
17
18
19
+ from .helpers import enums
20
+
18
21
19
22
class OptimizelyUserContext (object ):
20
23
"""
21
24
Representation of an Optimizely User Context using which APIs are to be called.
22
25
"""
23
26
24
- def __init__ (self , optimizely_client , user_id , user_attributes = None ):
27
+ def __init__ (self , optimizely_client , logger , user_id , user_attributes = None ):
25
28
""" Create an instance of the Optimizely User Context.
26
29
27
30
Args:
28
31
optimizely_client: client used when calling decisions for this user context
32
+ logger: logger for logging
29
33
user_id: user id of this user context
30
34
user_attributes: user attributes to use for this user context
31
35
@@ -34,16 +38,48 @@ def __init__(self, optimizely_client, user_id, user_attributes=None):
34
38
"""
35
39
36
40
self .client = optimizely_client
41
+ self .logger = logger
37
42
self .user_id = user_id
38
43
39
44
if not isinstance (user_attributes , dict ):
40
45
user_attributes = {}
41
46
42
47
self ._user_attributes = user_attributes .copy () if user_attributes else {}
43
48
self .lock = threading .Lock ()
49
+ self .forced_decisions_map = {}
50
+
51
+ # decision context
52
+ class OptimizelyDecisionContext (object ):
53
+ """ Using class with attributes here instead of namedtuple because
54
+ class is extensible, it's easy to add another attribute if we wanted
55
+ to extend decision context.
56
+ """
57
+ def __init__ (self , flag_key , rule_key = None ):
58
+ self .flag_key = flag_key
59
+ self .rule_key = rule_key
60
+
61
+ def __hash__ (self ):
62
+ return hash ((self .flag_key , self .rule_key ))
63
+
64
+ def __eq__ (self , other ):
65
+ return (self .flag_key , self .rule_key ) == (other .flag_key , other .rule_key )
66
+
67
+ # forced decision
68
+ class OptimizelyForcedDecision (object ):
69
+ def __init__ (self , variation_key ):
70
+ self .variation_key = variation_key
44
71
45
72
def _clone (self ):
46
- return OptimizelyUserContext (self .client , self .user_id , self .get_user_attributes ())
73
+ if not self .client :
74
+ return None
75
+
76
+ user_context = OptimizelyUserContext (self .client , self .logger , self .user_id , self .get_user_attributes ())
77
+
78
+ with self .lock :
79
+ if self .forced_decisions_map :
80
+ user_context .forced_decisions_map = copy .deepcopy (self .forced_decisions_map )
81
+
82
+ return user_context
47
83
48
84
def get_user_attributes (self ):
49
85
with self .lock :
@@ -114,3 +150,136 @@ def as_json(self):
114
150
'user_id' : self .user_id ,
115
151
'attributes' : self .get_user_attributes (),
116
152
}
153
+
154
+ def set_forced_decision (self , decision_context , decision ):
155
+ """
156
+ Sets the forced decision for a given decision context.
157
+
158
+ Args:
159
+ decision_context: a decision context.
160
+ decision: a forced decision.
161
+
162
+ Returns:
163
+ True if the forced decision has been set successfully.
164
+ """
165
+ with self .lock :
166
+ self .forced_decisions_map [decision_context ] = decision
167
+
168
+ return True
169
+
170
+ def get_forced_decision (self , decision_context ):
171
+ """
172
+ Gets the forced decision (variation key) for a given decision context.
173
+
174
+ Args:
175
+ decision_context: a decision context.
176
+
177
+ Returns:
178
+ A forced_decision or None if forced decisions are not set for the parameters.
179
+ """
180
+ forced_decision = self .find_forced_decision (decision_context )
181
+ return forced_decision
182
+
183
+ def remove_forced_decision (self , decision_context ):
184
+ """
185
+ Removes the forced decision for a given decision context.
186
+
187
+ Args:
188
+ decision_context: a decision context.
189
+
190
+ Returns:
191
+ Returns: true if the forced decision has been removed successfully.
192
+ """
193
+ with self .lock :
194
+ if decision_context in self .forced_decisions_map :
195
+ del self .forced_decisions_map [decision_context ]
196
+ return True
197
+
198
+ return False
199
+
200
+ def remove_all_forced_decisions (self ):
201
+ """
202
+ Removes all forced decisions bound to this user context.
203
+
204
+ Returns:
205
+ True if forced decisions have been removed successfully.
206
+ """
207
+ with self .lock :
208
+ self .forced_decisions_map .clear ()
209
+
210
+ return True
211
+
212
+ def find_forced_decision (self , decision_context ):
213
+ """
214
+ Gets forced decision from forced decision map.
215
+
216
+ Args:
217
+ decision_context: a decision context.
218
+
219
+ Returns:
220
+ Forced decision.
221
+ """
222
+ with self .lock :
223
+ if not self .forced_decisions_map :
224
+ return None
225
+
226
+ # must allow None to be returned for the Flags only case
227
+ return self .forced_decisions_map .get (decision_context )
228
+
229
+ def find_validated_forced_decision (self , decision_context ):
230
+ """
231
+ Gets forced decisions based on flag key, rule key and variation.
232
+
233
+ Args:
234
+ decision context: a decision context
235
+
236
+ Returns:
237
+ Variation of the forced decision.
238
+ """
239
+ reasons = []
240
+
241
+ forced_decision = self .find_forced_decision (decision_context )
242
+
243
+ flag_key = decision_context .flag_key
244
+ rule_key = decision_context .rule_key
245
+
246
+ if forced_decision :
247
+ # we use config here so we can use get_flag_variation() function which is defined in project_config
248
+ # otherwise we would us self.client instead of config
249
+ config = self .client .config_manager .get_config () if self .client else None
250
+ if not config :
251
+ return None , reasons
252
+ variation = config .get_flag_variation (flag_key , 'key' , forced_decision .variation_key )
253
+ if variation :
254
+ if rule_key :
255
+ user_has_forced_decision = enums .ForcedDecisionLogs \
256
+ .USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED .format (forced_decision .variation_key ,
257
+ flag_key ,
258
+ rule_key ,
259
+ self .user_id )
260
+
261
+ else :
262
+ user_has_forced_decision = enums .ForcedDecisionLogs \
263
+ .USER_HAS_FORCED_DECISION_WITHOUT_RULE_SPECIFIED .format (forced_decision .variation_key ,
264
+ flag_key ,
265
+ self .user_id )
266
+
267
+ reasons .append (user_has_forced_decision )
268
+ self .logger .debug (user_has_forced_decision )
269
+
270
+ return variation , reasons
271
+
272
+ else :
273
+ if rule_key :
274
+ user_has_forced_decision_but_invalid = enums .ForcedDecisionLogs \
275
+ .USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID .format (flag_key ,
276
+ rule_key ,
277
+ self .user_id )
278
+ else :
279
+ user_has_forced_decision_but_invalid = enums .ForcedDecisionLogs \
280
+ .USER_HAS_FORCED_DECISION_WITHOUT_RULE_SPECIFIED_BUT_INVALID .format (flag_key , self .user_id )
281
+
282
+ reasons .append (user_has_forced_decision_but_invalid )
283
+ self .logger .debug (user_has_forced_decision_but_invalid )
284
+
285
+ return None , reasons
0 commit comments