Skip to content

Commit 3ce6411

Browse files
Moving forced variation map from ProjectConfig to DecisionService (#180)
1 parent 51fef17 commit 3ce6411

File tree

6 files changed

+377
-323
lines changed

6 files changed

+377
-323
lines changed

optimizely/decision_service.py

Lines changed: 116 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ def __init__(self, logger, user_profile_service):
3232
self.logger = logger
3333
self.user_profile_service = user_profile_service
3434

35+
# Map of user IDs to another map of experiments to variations.
36+
# This contains all the forced variations set by the user
37+
# by calling set_forced_variation (it is not the same as the
38+
# whitelisting forcedVariations data structure).
39+
self.forced_variation_map = {}
40+
3541
def _get_bucketing_id(self, user_id, attributes):
3642
""" Helper method to determine bucketing ID for the user.
3743
@@ -54,8 +60,114 @@ def _get_bucketing_id(self, user_id, attributes):
5460

5561
return user_id
5662

57-
def get_forced_variation(self, project_config, experiment, user_id):
58-
""" Determine if a user is forced into a variation for the given experiment and return that variation.
63+
def set_forced_variation(self, project_config, experiment_key, user_id, variation_key):
64+
""" Sets users to a map of experiments to forced variations.
65+
66+
Args:
67+
project_config: Instance of ProjectConfig.
68+
experiment_key: Key for experiment.
69+
user_id: The user ID.
70+
variation_key: Key for variation. If None, then clear the existing experiment-to-variation mapping.
71+
72+
Returns:
73+
A boolean value that indicates if the set completed successfully.
74+
"""
75+
experiment = project_config.get_experiment_from_key(experiment_key)
76+
if not experiment:
77+
# The invalid experiment key will be logged inside this call.
78+
return False
79+
80+
experiment_id = experiment.id
81+
if variation_key is None:
82+
if user_id in self.forced_variation_map:
83+
experiment_to_variation_map = self.forced_variation_map.get(user_id)
84+
if experiment_id in experiment_to_variation_map:
85+
del(self.forced_variation_map[user_id][experiment_id])
86+
self.logger.debug('Variation mapped to experiment "%s" has been removed for user "%s".' % (
87+
experiment_key,
88+
user_id
89+
))
90+
else:
91+
self.logger.debug('Nothing to remove. Variation mapped to experiment "%s" for user "%s" does not exist.' % (
92+
experiment_key,
93+
user_id
94+
))
95+
else:
96+
self.logger.debug('Nothing to remove. User "%s" does not exist in the forced variation map.' % user_id)
97+
return True
98+
99+
if not validator.is_non_empty_string(variation_key):
100+
self.logger.debug('Variation key is invalid.')
101+
return False
102+
103+
forced_variation = project_config.get_variation_from_key(experiment_key, variation_key)
104+
if not forced_variation:
105+
# The invalid variation key will be logged inside this call.
106+
return False
107+
108+
variation_id = forced_variation.id
109+
110+
if user_id not in self.forced_variation_map:
111+
self.forced_variation_map[user_id] = {experiment_id: variation_id}
112+
else:
113+
self.forced_variation_map[user_id][experiment_id] = variation_id
114+
115+
self.logger.debug('Set variation "%s" for experiment "%s" and user "%s" in the forced variation map.' % (
116+
variation_id,
117+
experiment_id,
118+
user_id
119+
))
120+
return True
121+
122+
def get_forced_variation(self, project_config, experiment_key, user_id):
123+
""" Gets the forced variation key for the given user and experiment.
124+
125+
Args:
126+
project_config: Instance of ProjectConfig.
127+
experiment_key: Key for experiment.
128+
user_id: The user ID.
129+
130+
Returns:
131+
The variation which the given user and experiment should be forced into.
132+
"""
133+
134+
if user_id not in self.forced_variation_map:
135+
self.logger.debug('User "%s" is not in the forced variation map.' % user_id)
136+
return None
137+
138+
experiment = project_config.get_experiment_from_key(experiment_key)
139+
if not experiment:
140+
# The invalid experiment key will be logged inside this call.
141+
return None
142+
143+
experiment_to_variation_map = self.forced_variation_map.get(user_id)
144+
145+
if not experiment_to_variation_map:
146+
self.logger.debug('No experiment "%s" mapped to user "%s" in the forced variation map.' % (
147+
experiment_key,
148+
user_id
149+
))
150+
return None
151+
152+
variation_id = experiment_to_variation_map.get(experiment.id)
153+
if variation_id is None:
154+
self.logger.debug(
155+
'No variation mapped to experiment "%s" in the forced variation map.' % experiment_key
156+
)
157+
return None
158+
159+
variation = project_config.get_variation_from_id(experiment_key, variation_id)
160+
161+
self.logger.debug('Variation "%s" is mapped to experiment "%s" and user "%s" in the forced variation map' % (
162+
variation.key,
163+
experiment_key,
164+
user_id
165+
))
166+
return variation
167+
168+
def get_whitelisted_variation(self, project_config, experiment, user_id):
169+
""" Determine if a user is forced into a variation (through whitelisting)
170+
for the given experiment and return that variation.
59171
60172
Args:
61173
project_config: Instance of ProjectConfig.
@@ -129,12 +241,12 @@ def get_variation(self, project_config, experiment, user_id, attributes, ignore_
129241
return None
130242

131243
# Check if the user is forced into a variation
132-
variation = project_config.get_forced_variation(experiment.key, user_id)
244+
variation = self.get_forced_variation(project_config, experiment.key, user_id)
133245
if variation:
134246
return variation
135247

136248
# Check to see if user is white-listed for a certain variation
137-
variation = self.get_forced_variation(project_config, experiment, user_id)
249+
variation = self.get_whitelisted_variation(project_config, experiment, user_id)
138250
if variation:
139251
return variation
140252

optimizely/optimizely.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -614,7 +614,7 @@ def set_forced_variation(self, experiment_key, user_id, variation_key):
614614
self.logger.error(enums.Errors.INVALID_INPUT_ERROR.format('user_id'))
615615
return False
616616

617-
return self.config.set_forced_variation(experiment_key, user_id, variation_key)
617+
return self.decision_service.set_forced_variation(self.config, experiment_key, user_id, variation_key)
618618

619619
def get_forced_variation(self, experiment_key, user_id):
620620
""" Gets the forced variation for a given user and experiment.
@@ -639,5 +639,5 @@ def get_forced_variation(self, experiment_key, user_id):
639639
self.logger.error(enums.Errors.INVALID_INPUT_ERROR.format('user_id'))
640640
return None
641641

642-
forced_variation = self.config.get_forced_variation(experiment_key, user_id)
642+
forced_variation = self.decision_service.get_forced_variation(self.config, experiment_key, user_id)
643643
return forced_variation.key if forced_variation else None

optimizely/project_config.py

Lines changed: 0 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616
from .helpers import condition as condition_helper
1717
from .helpers import enums
18-
from .helpers import validator
1918
from . import entities
2019
from . import exceptions
2120

@@ -124,12 +123,6 @@ def __init__(self, datafile, logger, error_handler):
124123
# Experiments in feature can only belong to one mutex group
125124
break
126125

127-
# Map of user IDs to another map of experiments to variations.
128-
# This contains all the forced variations set by the user
129-
# by calling set_forced_variation (it is not the same as the
130-
# whitelisting forcedVariations data structure).
131-
self.forced_variation_map = {}
132-
133126
@staticmethod
134127
def _generate_key_map(entity_list, key, entity_class):
135128
""" Helper method to generate map from key to entity object for given list of dicts.
@@ -496,109 +489,6 @@ def get_variable_for_feature(self, feature_key, variable_key):
496489

497490
return feature.variables.get(variable_key)
498491

499-
def set_forced_variation(self, experiment_key, user_id, variation_key):
500-
""" Sets users to a map of experiments to forced variations.
501-
502-
Args:
503-
experiment_key: Key for experiment.
504-
user_id: The user ID.
505-
variation_key: Key for variation. If None, then clear the existing experiment-to-variation mapping.
506-
507-
Returns:
508-
A boolean value that indicates if the set completed successfully.
509-
"""
510-
experiment = self.get_experiment_from_key(experiment_key)
511-
if not experiment:
512-
# The invalid experiment key will be logged inside this call.
513-
return False
514-
515-
experiment_id = experiment.id
516-
if variation_key is None:
517-
if user_id in self.forced_variation_map:
518-
experiment_to_variation_map = self.forced_variation_map.get(user_id)
519-
if experiment_id in experiment_to_variation_map:
520-
del(self.forced_variation_map[user_id][experiment_id])
521-
self.logger.debug('Variation mapped to experiment "%s" has been removed for user "%s".' % (
522-
experiment_key,
523-
user_id
524-
))
525-
else:
526-
self.logger.debug('Nothing to remove. Variation mapped to experiment "%s" for user "%s" does not exist.' % (
527-
experiment_key,
528-
user_id
529-
))
530-
else:
531-
self.logger.debug('Nothing to remove. User "%s" does not exist in the forced variation map.' % user_id)
532-
return True
533-
534-
if not validator.is_non_empty_string(variation_key):
535-
self.logger.debug('Variation key is invalid.')
536-
return False
537-
538-
forced_variation = self.get_variation_from_key(experiment_key, variation_key)
539-
if not forced_variation:
540-
# The invalid variation key will be logged inside this call.
541-
return False
542-
543-
variation_id = forced_variation.id
544-
545-
if user_id not in self.forced_variation_map:
546-
self.forced_variation_map[user_id] = {experiment_id: variation_id}
547-
else:
548-
self.forced_variation_map[user_id][experiment_id] = variation_id
549-
550-
self.logger.debug('Set variation "%s" for experiment "%s" and user "%s" in the forced variation map.' % (
551-
variation_id,
552-
experiment_id,
553-
user_id
554-
))
555-
return True
556-
557-
def get_forced_variation(self, experiment_key, user_id):
558-
""" Gets the forced variation key for the given user and experiment.
559-
560-
Args:
561-
experiment_key: Key for experiment.
562-
user_id: The user ID.
563-
564-
Returns:
565-
The variation which the given user and experiment should be forced into.
566-
"""
567-
568-
if user_id not in self.forced_variation_map:
569-
self.logger.debug('User "%s" is not in the forced variation map.' % user_id)
570-
return None
571-
572-
experiment = self.get_experiment_from_key(experiment_key)
573-
if not experiment:
574-
# The invalid experiment key will be logged inside this call.
575-
return None
576-
577-
experiment_to_variation_map = self.forced_variation_map.get(user_id)
578-
579-
if not experiment_to_variation_map:
580-
self.logger.debug('No experiment "%s" mapped to user "%s" in the forced variation map.' % (
581-
experiment_key,
582-
user_id
583-
))
584-
return None
585-
586-
variation_id = experiment_to_variation_map.get(experiment.id)
587-
if variation_id is None:
588-
self.logger.debug(
589-
'No variation mapped to experiment "%s" in the forced variation map.' % experiment_key
590-
)
591-
return None
592-
593-
variation = self.get_variation_from_id(experiment_key, variation_id)
594-
595-
self.logger.debug('Variation "%s" is mapped to experiment "%s" and user "%s" in the forced variation map' % (
596-
variation.key,
597-
experiment_key,
598-
user_id
599-
))
600-
return variation
601-
602492
def get_anonymize_ip_value(self):
603493
""" Gets the anonymize IP value.
604494

0 commit comments

Comments
 (0)