@@ -50,7 +50,7 @@ def __init__(self, datafile, event_dispatcher=None, logger=None, error_handler=N
50
50
self .error_handler = error_handler or noop_error_handler
51
51
52
52
try :
53
- self ._validate_inputs (datafile , skip_json_validation )
53
+ self ._validate_instantiation_options (datafile , skip_json_validation )
54
54
except exceptions .InvalidInputException as error :
55
55
self .is_valid = False
56
56
self .logger = SimpleLogger ()
@@ -75,15 +75,15 @@ def __init__(self, datafile, event_dispatcher=None, logger=None, error_handler=N
75
75
self .bucketer = bucketer .Bucketer (self .config )
76
76
self .event_builder = event_builder .EventBuilder (self .config , self .bucketer )
77
77
78
- def _validate_inputs (self , datafile , skip_json_validation ):
79
- """ Helper method to validate all input parameters.
78
+ def _validate_instantiation_options (self , datafile , skip_json_validation ):
79
+ """ Helper method to validate all instantiation parameters.
80
80
81
81
Args:
82
82
datafile: JSON string representing the project.
83
83
skip_json_validation: Boolean representing whether JSON schema validation needs to be skipped or not.
84
84
85
85
Raises:
86
- Exception if provided input is invalid .
86
+ Exception if provided instantiation options are valid .
87
87
"""
88
88
89
89
if not skip_json_validation and not validator .is_datafile_valid (datafile ):
@@ -98,39 +98,74 @@ def _validate_inputs(self, datafile, skip_json_validation):
98
98
if not validator .is_error_handler_valid (self .error_handler ):
99
99
raise exceptions .InvalidInputException (enums .Errors .INVALID_INPUT_ERROR .format ('error_handler' ))
100
100
101
- def _validate_preconditions (self , experiment , user_id , attributes ):
101
+ def _validate_preconditions (self , experiment , attributes = None , event_tags = None ):
102
102
""" Helper method to validate all pre-conditions before we go ahead to bucket user.
103
103
104
104
Args:
105
105
experiment: Object representing the experiment.
106
- user_id: ID for user.
107
106
attributes: Dict representing user attributes.
108
107
109
108
Returns:
110
109
Boolean depending upon whether all conditions are met or not.
111
110
"""
112
-
113
- if attributes and not validator .are_attributes_valid (attributes ):
114
- self .logger .log (enums .LogLevels .ERROR , 'Provided attributes are in an invalid format.' )
115
- self .error_handler .handle_error (exceptions .InvalidAttributeException (enums .Errors .INVALID_ATTRIBUTE_FORMAT ))
111
+ if not self ._validate_user_inputs (attributes , event_tags ):
116
112
return False
117
113
118
114
if not experiment_helper .is_experiment_running (experiment ):
119
115
self .logger .log (enums .LogLevels .INFO , 'Experiment "%s" is not running.' % experiment .key )
120
116
return False
121
117
122
- if experiment_helper .is_user_in_forced_variation (experiment .forcedVariations , user_id ):
123
- return True
118
+ return True
124
119
125
- if not audience_helper .is_user_in_experiment (self .config , experiment , attributes ):
126
- self .logger .log (
127
- enums .LogLevels .INFO ,
128
- 'User "%s" does not meet conditions to be in experiment "%s".' % (user_id , experiment .key )
129
- )
120
+ def _validate_user_inputs (self , attributes = None , event_tags = None ):
121
+ """ Helper method to validate user inputs.
122
+
123
+ Args:
124
+ attributes: Dict representing user attributes.
125
+ event_tags: Dict representing metadata associated with an event.
126
+
127
+ Returns:
128
+ Boolean True if inputs are valid. False otherwise.
129
+
130
+ """
131
+
132
+ if attributes and not validator .are_attributes_valid (attributes ):
133
+ self .logger .log (enums .LogLevels .ERROR , 'Provided attributes are in an invalid format.' )
134
+ self .error_handler .handle_error (exceptions .InvalidAttributeException (enums .Errors .INVALID_ATTRIBUTE_FORMAT ))
135
+ return False
136
+
137
+ if event_tags and not validator .are_event_tags_valid (event_tags ):
138
+ self .logger .log (enums .LogLevels .ERROR , 'Provided event tags are in an invalid format.' )
139
+ self .error_handler .handle_error (exceptions .InvalidEventTagException (enums .Errors .INVALID_EVENT_TAG_FORMAT ))
130
140
return False
131
141
132
142
return True
133
143
144
+ def _get_valid_experiments_for_event (self , event , user_id , attributes ):
145
+ """ Helper method to determine which experiments we should track for the given event.
146
+
147
+ Args:
148
+ event: The event which needs to be recorded.
149
+ user_id: ID for user.
150
+ attributes: Dict representing user attributes.
151
+
152
+ Returns:
153
+ List of tuples representing valid experiment IDs and variation IDs into which the user is bucketed.
154
+ """
155
+ valid_experiments = []
156
+ for experiment_id in event .experimentIds :
157
+ experiment = self .config .get_experiment_from_id (experiment_id )
158
+ variation_key = self .get_variation (experiment .key , user_id , attributes )
159
+
160
+ if not variation_key :
161
+ self .logger .log (enums .LogLevels .INFO , 'Not tracking user "%s" for experiment "%s".' % (user_id , experiment .key ))
162
+ continue
163
+
164
+ variation = self .config .get_variation_from_key (experiment .key , variation_key )
165
+ valid_experiments .append ((experiment_id , variation .id ))
166
+
167
+ return valid_experiments
168
+
134
169
def activate (self , experiment_key , user_id , attributes = None ):
135
170
""" Buckets visitor and sends impression event to Optimizely.
136
171
@@ -148,22 +183,15 @@ def activate(self, experiment_key, user_id, attributes=None):
148
183
self .logger .log (enums .LogLevels .ERROR , enums .Errors .INVALID_DATAFILE .format ('activate' ))
149
184
return None
150
185
151
- experiment = self .config .get_experiment_from_key (experiment_key )
152
- if not experiment :
153
- self .logger .log (enums .LogLevels .INFO , 'Not activating user "%s".' % user_id )
154
- return None
155
-
156
- if not self ._validate_preconditions (experiment , user_id , attributes ):
157
- self .logger .log (enums .LogLevels .INFO , 'Not activating user "%s".' % user_id )
158
- return None
159
-
160
- variation = self .bucketer .bucket (experiment , user_id )
186
+ variation_key = self .get_variation (experiment_key , user_id , attributes )
161
187
162
- if not variation :
188
+ if not variation_key :
163
189
self .logger .log (enums .LogLevels .INFO , 'Not activating user "%s".' % user_id )
164
190
return None
165
191
166
192
# Create and dispatch impression event
193
+ experiment = self .config .get_experiment_from_key (experiment_key )
194
+ variation = self .config .get_variation_from_key (experiment_key , variation_key )
167
195
impression_event = self .event_builder .create_impression_event (experiment , variation .id , user_id , attributes )
168
196
self .logger .log (enums .LogLevels .INFO , 'Activating user "%s" in experiment "%s".' % (user_id , experiment .key ))
169
197
self .logger .log (enums .LogLevels .DEBUG ,
@@ -191,11 +219,6 @@ def track(self, event_key, user_id, attributes=None, event_tags=None):
191
219
self .logger .log (enums .LogLevels .ERROR , enums .Errors .INVALID_DATAFILE .format ('track' ))
192
220
return
193
221
194
- if attributes and not validator .are_attributes_valid (attributes ):
195
- self .logger .log (enums .LogLevels .ERROR , 'Provided attributes are in an invalid format.' )
196
- self .error_handler .handle_error (exceptions .InvalidAttributeException (enums .Errors .INVALID_ATTRIBUTE_FORMAT ))
197
- return
198
-
199
222
if event_tags :
200
223
if isinstance (event_tags , numbers .Number ):
201
224
event_tags = {
@@ -204,24 +227,16 @@ def track(self, event_key, user_id, attributes=None, event_tags=None):
204
227
self .logger .log (enums .LogLevels .WARNING ,
205
228
'Event value is deprecated in track call. Use event tags to pass in revenue value instead.' )
206
229
207
- if not validator .are_event_tags_valid (event_tags ):
208
- self .logger .log (enums .LogLevels .ERROR , 'Provided event tags are in an invalid format.' )
209
- self .error_handler .handle_error (exceptions .InvalidEventTagException (enums .Errors .INVALID_EVENT_TAG_FORMAT ))
210
- return
230
+ if not self ._validate_user_inputs (attributes , event_tags ):
231
+ return
211
232
212
233
event = self .config .get_event (event_key )
213
234
if not event :
214
235
self .logger .log (enums .LogLevels .INFO , 'Not tracking user "%s" for event "%s".' % (user_id , event_key ))
215
236
return
216
237
217
238
# Filter out experiments that are not running or that do not include the user in audience conditions
218
- valid_experiments = []
219
- for experiment_id in event .experimentIds :
220
- experiment = self .config .get_experiment_from_id (experiment_id )
221
- if not self ._validate_preconditions (experiment , user_id , attributes ):
222
- self .logger .log (enums .LogLevels .INFO , 'Not tracking user "%s" for experiment "%s".' % (user_id , experiment .key ))
223
- continue
224
- valid_experiments .append (experiment )
239
+ valid_experiments = self ._get_valid_experiments_for_event (event , user_id , attributes )
225
240
226
241
# Create and dispatch conversion event if there are valid experiments
227
242
if valid_experiments :
@@ -259,10 +274,23 @@ def get_variation(self, experiment_key, user_id, attributes=None):
259
274
260
275
experiment = self .config .get_experiment_from_key (experiment_key )
261
276
if not experiment :
277
+ self .logger .log (enums .LogLevels .INFO , 'Experiment key "%s" is invalid. Not activating user "%s".' % (experiment_key , user_id ))
262
278
return None
263
279
264
- if not self ._validate_preconditions (experiment , user_id , attributes ):
280
+ if not self ._validate_preconditions (experiment , attributes ):
265
281
return None
282
+
283
+ forcedVariation = self .bucketer .get_forced_variation (experiment , user_id )
284
+ if forcedVariation :
285
+ return forcedVariation .key
286
+
287
+ if not audience_helper .is_user_in_experiment (self .config , experiment , attributes ):
288
+ self .logger .log (
289
+ enums .LogLevels .INFO ,
290
+ 'User "%s" does not meet conditions to be in experiment "%s".' % (user_id , experiment .key )
291
+ )
292
+ return None
293
+
266
294
variation = self .bucketer .bucket (experiment , user_id )
267
295
268
296
if variation :
0 commit comments