27
27
class DecisionService (object ):
28
28
""" Class encapsulating all decision related capabilities. """
29
29
30
- def __init__ (self , config , user_profile_service ):
31
- self .bucketer = bucketer .Bucketer (config )
30
+ def __init__ (self , logger , user_profile_service ):
31
+ self .bucketer = bucketer .Bucketer ()
32
+ self .logger = logger
32
33
self .user_profile_service = user_profile_service
33
- self .config = config
34
- self .logger = config .logger
35
34
36
35
def _get_bucketing_id (self , user_id , attributes ):
37
36
""" Helper method to determine bucketing ID for the user.
@@ -55,10 +54,11 @@ def _get_bucketing_id(self, user_id, attributes):
55
54
56
55
return user_id
57
56
58
- def get_forced_variation (self , experiment , user_id ):
57
+ def get_forced_variation (self , project_config , experiment , user_id ):
59
58
""" Determine if a user is forced into a variation for the given experiment and return that variation.
60
59
61
60
Args:
61
+ project_config: Instance of ProjectConfig.
62
62
experiment: Object representing the experiment for which user is to be bucketed.
63
63
user_id: ID for the user.
64
64
@@ -69,17 +69,18 @@ def get_forced_variation(self, experiment, user_id):
69
69
forced_variations = experiment .forcedVariations
70
70
if forced_variations and user_id in forced_variations :
71
71
variation_key = forced_variations .get (user_id )
72
- variation = self . config .get_variation_from_key (experiment .key , variation_key )
72
+ variation = project_config .get_variation_from_key (experiment .key , variation_key )
73
73
if variation :
74
74
self .logger .info ('User "%s" is forced in variation "%s".' % (user_id , variation_key ))
75
75
return variation
76
76
77
77
return None
78
78
79
- def get_stored_variation (self , experiment , user_profile ):
79
+ def get_stored_variation (self , project_config , experiment , user_profile ):
80
80
""" Determine if the user has a stored variation available for the given experiment and return that.
81
81
82
82
Args:
83
+ project_config: Instance of ProjectConfig.
83
84
experiment: Object representing the experiment for which user is to be bucketed.
84
85
user_profile: UserProfile object representing the user's profile.
85
86
@@ -91,7 +92,7 @@ def get_stored_variation(self, experiment, user_profile):
91
92
variation_id = user_profile .get_variation_for_experiment (experiment .id )
92
93
93
94
if variation_id :
94
- variation = self . config .get_variation_from_id (experiment .key , variation_id )
95
+ variation = project_config .get_variation_from_id (experiment .key , variation_id )
95
96
if variation :
96
97
self .logger .info ('Found a stored decision. User "%s" is in variation "%s" of experiment "%s".' % (
97
98
user_id ,
@@ -102,7 +103,7 @@ def get_stored_variation(self, experiment, user_profile):
102
103
103
104
return None
104
105
105
- def get_variation (self , experiment , user_id , attributes , ignore_user_profile = False ):
106
+ def get_variation (self , project_config , experiment , user_id , attributes , ignore_user_profile = False ):
106
107
""" Top-level function to help determine variation user should be put in.
107
108
108
109
First, check if experiment is running.
@@ -112,6 +113,7 @@ def get_variation(self, experiment, user_id, attributes, ignore_user_profile=Fal
112
113
Fifth, bucket the user and return the variation.
113
114
114
115
Args:
116
+ project_config: Instance of ProjectConfig.
115
117
experiment: Experiment for which user variation needs to be determined.
116
118
user_id: ID for user.
117
119
attributes: Dict representing user attributes.
@@ -127,12 +129,12 @@ def get_variation(self, experiment, user_id, attributes, ignore_user_profile=Fal
127
129
return None
128
130
129
131
# Check if the user is forced into a variation
130
- variation = self . config .get_forced_variation (experiment .key , user_id )
132
+ variation = project_config .get_forced_variation (experiment .key , user_id )
131
133
if variation :
132
134
return variation
133
135
134
136
# Check to see if user is white-listed for a certain variation
135
- variation = self .get_forced_variation (experiment , user_id )
137
+ variation = self .get_forced_variation (project_config , experiment , user_id )
136
138
if variation :
137
139
return variation
138
140
@@ -147,14 +149,14 @@ def get_variation(self, experiment, user_id, attributes, ignore_user_profile=Fal
147
149
148
150
if validator .is_user_profile_valid (retrieved_profile ):
149
151
user_profile = UserProfile (** retrieved_profile )
150
- variation = self .get_stored_variation (experiment , user_profile )
152
+ variation = self .get_stored_variation (project_config , experiment , user_profile )
151
153
if variation :
152
154
return variation
153
155
else :
154
156
self .logger .warning ('User profile has invalid format.' )
155
157
156
158
# Bucket user and store the new decision
157
- if not audience_helper .is_user_in_experiment (self . config , experiment , attributes , self .logger ):
159
+ if not audience_helper .is_user_in_experiment (project_config , experiment , attributes , self .logger ):
158
160
self .logger .info ('User "%s" does not meet conditions to be in experiment "%s".' % (
159
161
user_id ,
160
162
experiment .key
@@ -163,7 +165,7 @@ def get_variation(self, experiment, user_id, attributes, ignore_user_profile=Fal
163
165
164
166
# Determine bucketing ID to be used
165
167
bucketing_id = self ._get_bucketing_id (user_id , attributes )
166
- variation = self .bucketer .bucket (experiment , user_id , bucketing_id )
168
+ variation = self .bucketer .bucket (project_config , experiment , user_id , bucketing_id )
167
169
168
170
if variation :
169
171
# Store this new decision and return the variation for the user
@@ -177,11 +179,12 @@ def get_variation(self, experiment, user_id, attributes, ignore_user_profile=Fal
177
179
178
180
return None
179
181
180
- def get_variation_for_rollout (self , rollout , user_id , attributes = None ):
182
+ def get_variation_for_rollout (self , project_config , rollout , user_id , attributes = None ):
181
183
""" Determine which experiment/variation the user is in for a given rollout.
182
184
Returns the variation of the first experiment the user qualifies for.
183
185
184
186
Args:
187
+ project_config: Instance of ProjectConfig.
185
188
rollout: Rollout for which we are getting the variation.
186
189
user_id: ID for user.
187
190
attributes: Dict representing user attributes.
@@ -193,10 +196,10 @@ def get_variation_for_rollout(self, rollout, user_id, attributes=None):
193
196
# Go through each experiment in order and try to get the variation for the user
194
197
if rollout and len (rollout .experiments ) > 0 :
195
198
for idx in range (len (rollout .experiments ) - 1 ):
196
- experiment = self . config .get_experiment_from_key (rollout .experiments [idx ].get ('key' ))
199
+ experiment = project_config .get_experiment_from_key (rollout .experiments [idx ].get ('key' ))
197
200
198
201
# Check if user meets audience conditions for targeting rule
199
- if not audience_helper .is_user_in_experiment (self . config , experiment , attributes , self .logger ):
202
+ if not audience_helper .is_user_in_experiment (project_config , experiment , attributes , self .logger ):
200
203
self .logger .debug ('User "%s" does not meet conditions for targeting rule %s.' % (
201
204
user_id ,
202
205
idx + 1
@@ -206,7 +209,7 @@ def get_variation_for_rollout(self, rollout, user_id, attributes=None):
206
209
self .logger .debug ('User "%s" meets conditions for targeting rule %s.' % (user_id , idx + 1 ))
207
210
# Determine bucketing ID to be used
208
211
bucketing_id = self ._get_bucketing_id (user_id , attributes )
209
- variation = self .bucketer .bucket (experiment , user_id , bucketing_id )
212
+ variation = self .bucketer .bucket (project_config , experiment , user_id , bucketing_id )
210
213
if variation :
211
214
self .logger .debug ('User "%s" is in variation %s of experiment %s.' % (
212
215
user_id ,
@@ -221,34 +224,36 @@ def get_variation_for_rollout(self, rollout, user_id, attributes=None):
221
224
break
222
225
223
226
# Evaluate last rule i.e. "Everyone Else" rule
224
- everyone_else_experiment = self .config .get_experiment_from_key (rollout .experiments [- 1 ].get ('key' ))
225
- if audience_helper .is_user_in_experiment (self .config ,
226
- self .config .get_experiment_from_key (rollout .experiments [- 1 ].get ('key' )),
227
- attributes ,
228
- self .logger ):
227
+ everyone_else_experiment = project_config .get_experiment_from_key (rollout .experiments [- 1 ].get ('key' ))
228
+ if audience_helper .is_user_in_experiment (
229
+ project_config ,
230
+ project_config .get_experiment_from_key (rollout .experiments [- 1 ].get ('key' )),
231
+ attributes ,
232
+ self .logger ):
229
233
# Determine bucketing ID to be used
230
234
bucketing_id = self ._get_bucketing_id (user_id , attributes )
231
- variation = self .bucketer .bucket (everyone_else_experiment , user_id , bucketing_id )
235
+ variation = self .bucketer .bucket (project_config , everyone_else_experiment , user_id , bucketing_id )
232
236
if variation :
233
237
self .logger .debug ('User "%s" meets conditions for targeting rule "Everyone Else".' % user_id )
234
238
return Decision (everyone_else_experiment , variation , enums .DecisionSources .ROLLOUT )
235
239
236
240
return Decision (None , None , enums .DecisionSources .ROLLOUT )
237
241
238
- def get_experiment_in_group (self , group , bucketing_id ):
242
+ def get_experiment_in_group (self , project_config , group , bucketing_id ):
239
243
""" Determine which experiment in the group the user is bucketed into.
240
244
241
245
Args:
246
+ project_config: Instance of ProjectConfig.
242
247
group: The group to bucket the user into.
243
248
bucketing_id: ID to be used for bucketing the user.
244
249
245
250
Returns:
246
251
Experiment if the user is bucketed into an experiment in the specified group. None otherwise.
247
252
"""
248
253
249
- experiment_id = self .bucketer .find_bucket (bucketing_id , group .id , group .trafficAllocation )
254
+ experiment_id = self .bucketer .find_bucket (project_config , bucketing_id , group .id , group .trafficAllocation )
250
255
if experiment_id :
251
- experiment = self . config .get_experiment_from_id (experiment_id )
256
+ experiment = project_config .get_experiment_from_id (experiment_id )
252
257
if experiment :
253
258
self .logger .info ('User with bucketing ID "%s" is in experiment %s of group %s.' % (
254
259
bucketing_id ,
@@ -264,10 +269,11 @@ def get_experiment_in_group(self, group, bucketing_id):
264
269
265
270
return None
266
271
267
- def get_variation_for_feature (self , feature , user_id , attributes = None ):
272
+ def get_variation_for_feature (self , project_config , feature , user_id , attributes = None ):
268
273
""" Returns the experiment/variation the user is bucketed in for the given feature.
269
274
270
275
Args:
276
+ project_config: Instance of ProjectConfig.
271
277
feature: Feature for which we are determining if it is enabled or not for the given user.
272
278
user_id: ID for user.
273
279
attributes: Dict representing user attributes.
@@ -276,17 +282,15 @@ def get_variation_for_feature(self, feature, user_id, attributes=None):
276
282
Decision namedtuple consisting of experiment and variation for the user.
277
283
"""
278
284
279
- experiment = None
280
- variation = None
281
285
bucketing_id = self ._get_bucketing_id (user_id , attributes )
282
286
283
287
# First check if the feature is in a mutex group
284
288
if feature .groupId :
285
- group = self . config .get_group (feature .groupId )
289
+ group = project_config .get_group (feature .groupId )
286
290
if group :
287
- experiment = self .get_experiment_in_group (group , bucketing_id )
291
+ experiment = self .get_experiment_in_group (project_config , group , bucketing_id )
288
292
if experiment and experiment .id in feature .experimentIds :
289
- variation = self .get_variation (experiment , user_id , attributes )
293
+ variation = self .get_variation (project_config , experiment , user_id , attributes )
290
294
291
295
if variation :
292
296
self .logger .debug ('User "%s" is in variation %s of experiment %s.' % (
@@ -301,9 +305,9 @@ def get_variation_for_feature(self, feature, user_id, attributes=None):
301
305
# Next check if the feature is being experimented on
302
306
elif feature .experimentIds :
303
307
# If an experiment is not in a group, then the feature can only be associated with one experiment
304
- experiment = self . config .get_experiment_from_id (feature .experimentIds [0 ])
308
+ experiment = project_config .get_experiment_from_id (feature .experimentIds [0 ])
305
309
if experiment :
306
- variation = self .get_variation (experiment , user_id , attributes )
310
+ variation = self .get_variation (project_config , experiment , user_id , attributes )
307
311
308
312
if variation :
309
313
self .logger .debug ('User "%s" is in variation %s of experiment %s.' % (
@@ -315,7 +319,7 @@ def get_variation_for_feature(self, feature, user_id, attributes=None):
315
319
316
320
# Next check if user is part of a rollout
317
321
if feature .rolloutId :
318
- rollout = self . config .get_rollout_from_id (feature .rolloutId )
319
- return self .get_variation_for_rollout (rollout , user_id , attributes )
322
+ rollout = project_config .get_rollout_from_id (feature .rolloutId )
323
+ return self .get_variation_for_rollout (project_config , rollout , user_id , attributes )
320
324
else :
321
325
return Decision (None , None , enums .DecisionSources .ROLLOUT )
0 commit comments