Skip to content

Commit d37cb9f

Browse files
Changing to use objects (#21)
1 parent 65c1940 commit d37cb9f

13 files changed

+431
-517
lines changed

optimizely/bucketer.py

Lines changed: 18 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -77,76 +77,60 @@ def _find_bucket(self, user_id, parent_id, traffic_allocations):
7777

7878
return None
7979

80-
def bucket(self, experiment_key, user_id):
81-
""" For a given experiment key and bucketing ID determines ID of variation to be shown to visitor.
80+
def bucket(self, experiment, user_id):
81+
""" For a given experiment and bucketing ID determines ID of variation to be shown to user.
8282
8383
Args:
84-
experiment_key: Key representing experiment for which visitor is to be bucketed.
84+
experiment: Object representing the experiment for which user is to be bucketed.
8585
user_id: ID for user.
8686
8787
Returns:
8888
Variation ID for variation in which the visitor with ID user_id will be put in. None if no variation.
8989
"""
9090

91+
if not experiment:
92+
return None
93+
9194
# Check if user is white-listed for a variation
92-
forced_variations = self.config.get_experiment_forced_variations(experiment_key)
95+
forced_variations = experiment.forcedVariations
9396
if forced_variations and user_id in forced_variations:
9497
variation_key = forced_variations.get(user_id)
95-
variation_id = self.config.get_variation_id(experiment_key, variation_key)
98+
variation_id = self.config.get_variation_id(experiment.key, variation_key)
9699
if variation_id:
97100
self.config.logger.log(enums.LogLevels.INFO,
98101
'User "%s" is forced in variation "%s".' % (user_id, variation_key))
99102
return variation_id
100103

101-
# Determine experiment ID
102-
experiment_id = self.config.get_experiment_id(experiment_key)
103-
if not experiment_id:
104-
return None
105-
106104
# Determine if experiment is in a mutually exclusive group
107-
group_policy = self.config.get_experiment_group_policy(experiment_key)
108-
if group_policy in GROUP_POLICIES:
109-
group_id = self.config.get_experiment_group_id(experiment_key)
110-
111-
if not group_id:
112-
return None
113-
114-
group_traffic_allocations = self.config.get_traffic_allocation(self.config.group_id_map, group_id)
105+
if experiment.groupPolicy in GROUP_POLICIES:
106+
group_traffic_allocations = self.config.get_traffic_allocation(self.config.group_id_map, experiment.groupId)
115107

116108
if not group_traffic_allocations:
117-
self.config.logger.log(enums.LogLevels.ERROR, 'Group ID "%s" is not in datafile.' % group_id)
109+
self.config.logger.log(enums.LogLevels.ERROR, 'Group ID "%s" is not in datafile.' % experiment.groupId)
118110
self.config.error_handler.handle_error(
119111
exceptions.InvalidExperimentException(enums.Errors.INVALID_GROUP_ID_ERROR)
120112
)
121113
return None
122114

123-
user_experiment_id = self._find_bucket(user_id, group_id, group_traffic_allocations)
115+
user_experiment_id = self._find_bucket(user_id, experiment.groupId, group_traffic_allocations)
124116
if not user_experiment_id:
125117
self.config.logger.log(enums.LogLevels.INFO, 'User "%s" is in no experiment.' % user_id)
126118
return None
127119

128-
if user_experiment_id != experiment_id:
120+
if user_experiment_id != experiment.id:
129121
self.config.logger.log(enums.LogLevels.INFO, 'User "%s" is not in experiment "%s" of group %s.' %
130-
(user_id, experiment_key, group_id))
122+
(user_id, experiment.key, experiment.groupId))
131123
return None
132124

133125
self.config.logger.log(enums.LogLevels.INFO, 'User "%s" is in experiment %s of group %s.' %
134-
(user_id, experiment_key, group_id))
126+
(user_id, experiment.key, experiment.groupId))
135127

136128
# Bucket user if not in white-list and in group (if any)
137-
experiment_traffic_allocations = self.config.get_traffic_allocation(self.config.experiment_key_map, experiment_key)
138-
if not experiment_traffic_allocations:
139-
self.config.logger.log(enums.LogLevels.ERROR, 'Experiment key "%s" is not in datafile.' % experiment_key)
140-
self.config.error_handler.handle_error(
141-
exceptions.InvalidExperimentException(enums.Errors.INVALID_EXPERIMENT_KEY_ERROR)
142-
)
143-
return None
144-
145-
variation_id = self._find_bucket(user_id, experiment_id, experiment_traffic_allocations)
129+
variation_id = self._find_bucket(user_id, experiment.id, experiment.trafficAllocation)
146130
if variation_id:
147-
variation_key = self.config.get_variation_key_from_id(experiment_key, variation_id)
131+
variation_key = self.config.get_variation_key_from_id(experiment.key, variation_id)
148132
self.config.logger.log(enums.LogLevels.INFO, 'User "%s" is in variation "%s" of experiment %s.' %
149-
(user_id, variation_key, experiment_key))
133+
(user_id, variation_key, experiment.key))
150134
return variation_id
151135

152136
self.config.logger.log(enums.LogLevels.INFO, 'User "%s" is in no variation.' % user_id)

optimizely/entities.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
class BaseEntity(object):
2+
3+
def __eq__(self, other):
4+
return self.__dict__ == other.__dict__
5+
6+
7+
class Attribute(BaseEntity):
8+
9+
def __init__(self, id, key, segmentId=None):
10+
self.id = id
11+
self.key = key
12+
self.segmentId = segmentId
13+
14+
15+
class Event(BaseEntity):
16+
17+
def __init__(self, id, key, experimentIds):
18+
self.id = id
19+
self.key = key
20+
self.experimentIds = experimentIds
21+
22+
23+
class Experiment(BaseEntity):
24+
25+
def __init__(self, id, key, status, audienceIds, variations, forcedVariations,
26+
trafficAllocation, layerId=None, groupId=None, groupPolicy=None):
27+
self.id = id
28+
self.key = key
29+
self.status = status
30+
self.audienceIds = audienceIds
31+
self.variations = variations
32+
self.forcedVariations = forcedVariations
33+
self.trafficAllocation = trafficAllocation
34+
self.layerId = layerId
35+
self.groupId = groupId
36+
self.groupPolicy = groupPolicy

optimizely/event_builder.py

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -132,28 +132,27 @@ def _add_time(self):
132132

133133
self.params[self.EventParams.TIME] = int(time.time())
134134

135-
def _add_impression_goal(self, experiment_key):
135+
def _add_impression_goal(self, experiment):
136136
""" Add impression goal information to the event.
137137
138138
Args:
139-
experiment_key: Experiment which is being activated.
139+
experiment: Object representing experiment being activated.
140140
"""
141141

142142
# For tracking impressions, goal ID is set equal to experiment ID of experiment being activated
143-
self.params[self.EventParams.GOAL_ID] = self.config.get_experiment_id(experiment_key)
143+
self.params[self.EventParams.GOAL_ID] = experiment.id
144144
self.params[self.EventParams.GOAL_NAME] = 'visitor-event'
145145

146-
def _add_experiment(self, experiment_key, variation_id):
146+
def _add_experiment(self, experiment, variation_id):
147147
""" Add experiment to variation mapping to the impression event.
148148
149149
Args:
150-
experiment_key: Experiment which is being activated.
150+
experiment: Object representing experiment being activated.
151151
variation_id: ID for variation which would be presented to user.
152152
"""
153153

154-
experiment_id = self.config.get_experiment_id(experiment_key)
155154
self.params[self.EXPERIMENT_PARAM_FORMAT.format(experiment_prefix=self.EventParams.EXPERIMENT_PREFIX,
156-
experiment_id=experiment_id)] = variation_id
155+
experiment_id=experiment.id)] = variation_id
157156

158157
def _add_experiment_variation_params(self, user_id, valid_experiments):
159158
""" Maps experiment and corresponding variation as parameters to be used in the event tracking call.
@@ -164,10 +163,10 @@ def _add_experiment_variation_params(self, user_id, valid_experiments):
164163
"""
165164

166165
for experiment in valid_experiments:
167-
variation_id = self.bucketer.bucket(experiment[1], user_id)
166+
variation_id = self.bucketer.bucket(experiment, user_id)
168167
if variation_id:
169168
self.params[self.EXPERIMENT_PARAM_FORMAT.format(experiment_prefix=self.EventParams.EXPERIMENT_PREFIX,
170-
experiment_id=experiment[0])] = variation_id
169+
experiment_id=experiment.id)] = variation_id
171170

172171
def _add_conversion_goal(self, event_key, event_value):
173172
""" Add conversion goal information to the event.
@@ -178,6 +177,10 @@ def _add_conversion_goal(self, event_key, event_value):
178177
"""
179178

180179
event = self.config.get_event(event_key)
180+
181+
if not event:
182+
return
183+
181184
event_ids = event.id
182185

183186
if event_value:
@@ -188,11 +191,11 @@ def _add_conversion_goal(self, event_key, event_value):
188191
self.params[self.EventParams.GOAL_ID] = event_ids
189192
self.params[self.EventParams.GOAL_NAME] = event_key
190193

191-
def create_impression_event(self, experiment_key, variation_id, user_id, attributes):
194+
def create_impression_event(self, experiment, variation_id, user_id, attributes):
192195
""" Create impression Event to be sent to the logging endpoint.
193196
194197
Args:
195-
experiment_key: Experiment for which impression needs to be recorded.
198+
experiment: Object representing experiment for which impression needs to be recorded.
196199
variation_id: ID for variation which would be presented to user.
197200
user_id: ID for user.
198201
attributes: Dict representing user attributes and values which need to be recorded.
@@ -203,8 +206,8 @@ def create_impression_event(self, experiment_key, variation_id, user_id, attribu
203206

204207
self.params = {}
205208
self._add_common_params(user_id, attributes)
206-
self._add_impression_goal(experiment_key)
207-
self._add_experiment(experiment_key, variation_id)
209+
self._add_impression_goal(experiment)
210+
self._add_experiment(experiment, variation_id)
208211
return Event(self.OFFLINE_API_PATH.format(project_id=self.params[self.EventParams.PROJECT_ID]),
209212
self.params)
210213

@@ -296,18 +299,18 @@ def _add_time(self):
296299

297300
self.params[self.EventParams.TIME] = int(round(time.time() * 1000))
298301

299-
def _add_required_params_for_impression(self, experiment_key, variation_id):
302+
def _add_required_params_for_impression(self, experiment, variation_id):
300303
""" Add parameters that are required for the impression event to register.
301304
302305
Args:
303-
experiment_key: Experiment for which impression needs to be recorded.
306+
experiment: Experiment for which impression needs to be recorded.
304307
variation_id: ID for variation which would be presented to user.
305308
"""
306309

307310
self.params[self.EventParams.IS_GLOBAL_HOLDBACK] = False
308-
self.params[self.EventParams.LAYER_ID] = self.config.get_layer_id_for_experiment(experiment_key)
311+
self.params[self.EventParams.LAYER_ID] = experiment.layerId
309312
self.params[self.EventParams.DECISION] = {
310-
self.EventParams.EXPERIMENT_ID: self.config.get_experiment_id(experiment_key),
313+
self.EventParams.EXPERIMENT_ID: experiment.id,
311314
self.EventParams.VARIATION_ID: variation_id,
312315
self.EventParams.IS_LAYER_HOLDBACK: False
313316
}
@@ -334,13 +337,13 @@ def _add_required_params_for_conversion(self, event_key, user_id, event_value, v
334337

335338
self.params[self.EventParams.LAYER_STATES] = []
336339
for experiment in valid_experiments:
337-
variation_id = self.bucketer.bucket(experiment[1], user_id)
340+
variation_id = self.bucketer.bucket(experiment, user_id)
338341
if variation_id:
339342
self.params[self.EventParams.LAYER_STATES].append({
340-
self.EventParams.LAYER_ID: self.config.get_layer_id_for_experiment(experiment[1]),
343+
self.EventParams.LAYER_ID: experiment.layerId,
341344
self.EventParams.ACTION_TRIGGERED: True,
342345
self.EventParams.DECISION: {
343-
self.EventParams.EXPERIMENT_ID: experiment[0],
346+
self.EventParams.EXPERIMENT_ID: experiment.id,
344347
self.EventParams.VARIATION_ID: variation_id,
345348
self.EventParams.IS_LAYER_HOLDBACK: False
346349
}
@@ -349,11 +352,11 @@ def _add_required_params_for_conversion(self, event_key, user_id, event_value, v
349352
self.params[self.EventParams.EVENT_ID] = self.config.get_event(event_key).id
350353
self.params[self.EventParams.EVENT_NAME] = event_key
351354

352-
def create_impression_event(self, experiment_key, variation_id, user_id, attributes):
355+
def create_impression_event(self, experiment, variation_id, user_id, attributes):
353356
""" Create impression Event to be sent to the logging endpoint.
354357
355358
Args:
356-
experiment_key: Experiment for which impression needs to be recorded.
359+
experiment: Experiment for which impression needs to be recorded.
357360
variation_id: ID for variation which would be presented to user.
358361
user_id: ID for user.
359362
attributes: Dict representing user attributes and values which need to be recorded.
@@ -364,7 +367,7 @@ def create_impression_event(self, experiment_key, variation_id, user_id, attribu
364367

365368
self.params = {}
366369
self._add_common_params(user_id, attributes)
367-
self._add_required_params_for_impression(experiment_key, variation_id)
370+
self._add_required_params_for_impression(experiment, variation_id)
368371
return Event(self.IMPRESSION_ENDPOINT,
369372
self.params,
370373
http_verb=self.HTTP_VERB,

optimizely/helpers/audience.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,30 +15,28 @@ def is_match(audience, attributes):
1515
return condition_evaluator.evaluate(audience.get('conditionStructure'))
1616

1717

18-
def is_user_in_experiment(config, experiment_key, attributes):
18+
def is_user_in_experiment(config, experiment, attributes):
1919
""" Determine for given experiment if user satisfies the audiences for the experiment.
2020
2121
Args:
2222
config: project_config.ProjectConfig object representing the project.
23-
experiment_key: Key representing experiment for which user is to be bucketed.
23+
experiment: Object representing the experiment.
2424
attributes: Dict representing user attributes which will be used in determining if the audience conditions are met.
2525
2626
Returns:
2727
Boolean representing if user satisfies audience conditions for any of the audiences or not.
2828
"""
2929

30-
audience_ids = config.get_audience_ids_for_experiment(experiment_key)
31-
3230
# Return True in case there are no audiences
33-
if not audience_ids:
31+
if not experiment.audienceIds:
3432
return True
3533

3634
# Return False if there are audiences, but no attributes
3735
if not attributes:
3836
return False
3937

4038
# Return True if conditions for any one audience are met
41-
for audience_id in audience_ids:
39+
for audience_id in experiment.audienceIds:
4240
audience = config.get_audience_object_from_id(audience_id)
4341

4442
if is_match(audience, attributes):

optimizely/helpers/experiment.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
ALLOWED_EXPERIMENT_STATUS = ['Running']
22

33

4-
def is_experiment_running(config, experiment_key):
4+
def is_experiment_running(experiment):
55
""" Determine for given experiment if experiment is running.
66
77
Args:
8-
config: project_config.ProjectConfig object representing the project.
9-
experiment_key: Key representing experiment for which user is to be bucketed.
8+
experiment: Object representing the experiment.
109
1110
Returns:
1211
Boolean representing if experiment is running or not.
1312
"""
1413

15-
return config.get_experiment_status(experiment_key) in ALLOWED_EXPERIMENT_STATUS
14+
return experiment.status in ALLOWED_EXPERIMENT_STATUS

0 commit comments

Comments
 (0)