Skip to content

Commit

Permalink
Moving forced variation map from ProjectConfig to DecisionService (#180)
Browse files Browse the repository at this point in the history
  • Loading branch information
aliabbasrizvi committed Jun 11, 2019
1 parent 51fef17 commit 3ce6411
Show file tree
Hide file tree
Showing 6 changed files with 377 additions and 323 deletions.
120 changes: 116 additions & 4 deletions optimizely/decision_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ def __init__(self, logger, user_profile_service):
self.logger = logger
self.user_profile_service = user_profile_service

# Map of user IDs to another map of experiments to variations.
# This contains all the forced variations set by the user
# by calling set_forced_variation (it is not the same as the
# whitelisting forcedVariations data structure).
self.forced_variation_map = {}

def _get_bucketing_id(self, user_id, attributes):
""" Helper method to determine bucketing ID for the user.
Expand All @@ -54,8 +60,114 @@ def _get_bucketing_id(self, user_id, attributes):

return user_id

def get_forced_variation(self, project_config, experiment, user_id):
""" Determine if a user is forced into a variation for the given experiment and return that variation.
def set_forced_variation(self, project_config, experiment_key, user_id, variation_key):
""" Sets users to a map of experiments to forced variations.
Args:
project_config: Instance of ProjectConfig.
experiment_key: Key for experiment.
user_id: The user ID.
variation_key: Key for variation. If None, then clear the existing experiment-to-variation mapping.
Returns:
A boolean value that indicates if the set completed successfully.
"""
experiment = project_config.get_experiment_from_key(experiment_key)
if not experiment:
# The invalid experiment key will be logged inside this call.
return False

experiment_id = experiment.id
if variation_key is None:
if user_id in self.forced_variation_map:
experiment_to_variation_map = self.forced_variation_map.get(user_id)
if experiment_id in experiment_to_variation_map:
del(self.forced_variation_map[user_id][experiment_id])
self.logger.debug('Variation mapped to experiment "%s" has been removed for user "%s".' % (
experiment_key,
user_id
))
else:
self.logger.debug('Nothing to remove. Variation mapped to experiment "%s" for user "%s" does not exist.' % (
experiment_key,
user_id
))
else:
self.logger.debug('Nothing to remove. User "%s" does not exist in the forced variation map.' % user_id)
return True

if not validator.is_non_empty_string(variation_key):
self.logger.debug('Variation key is invalid.')
return False

forced_variation = project_config.get_variation_from_key(experiment_key, variation_key)
if not forced_variation:
# The invalid variation key will be logged inside this call.
return False

variation_id = forced_variation.id

if user_id not in self.forced_variation_map:
self.forced_variation_map[user_id] = {experiment_id: variation_id}
else:
self.forced_variation_map[user_id][experiment_id] = variation_id

self.logger.debug('Set variation "%s" for experiment "%s" and user "%s" in the forced variation map.' % (
variation_id,
experiment_id,
user_id
))
return True

def get_forced_variation(self, project_config, experiment_key, user_id):
""" Gets the forced variation key for the given user and experiment.
Args:
project_config: Instance of ProjectConfig.
experiment_key: Key for experiment.
user_id: The user ID.
Returns:
The variation which the given user and experiment should be forced into.
"""

if user_id not in self.forced_variation_map:
self.logger.debug('User "%s" is not in the forced variation map.' % user_id)
return None

experiment = project_config.get_experiment_from_key(experiment_key)
if not experiment:
# The invalid experiment key will be logged inside this call.
return None

experiment_to_variation_map = self.forced_variation_map.get(user_id)

if not experiment_to_variation_map:
self.logger.debug('No experiment "%s" mapped to user "%s" in the forced variation map.' % (
experiment_key,
user_id
))
return None

variation_id = experiment_to_variation_map.get(experiment.id)
if variation_id is None:
self.logger.debug(
'No variation mapped to experiment "%s" in the forced variation map.' % experiment_key
)
return None

variation = project_config.get_variation_from_id(experiment_key, variation_id)

self.logger.debug('Variation "%s" is mapped to experiment "%s" and user "%s" in the forced variation map' % (
variation.key,
experiment_key,
user_id
))
return variation

def get_whitelisted_variation(self, project_config, experiment, user_id):
""" Determine if a user is forced into a variation (through whitelisting)
for the given experiment and return that variation.
Args:
project_config: Instance of ProjectConfig.
Expand Down Expand Up @@ -129,12 +241,12 @@ def get_variation(self, project_config, experiment, user_id, attributes, ignore_
return None

# Check if the user is forced into a variation
variation = project_config.get_forced_variation(experiment.key, user_id)
variation = self.get_forced_variation(project_config, experiment.key, user_id)
if variation:
return variation

# Check to see if user is white-listed for a certain variation
variation = self.get_forced_variation(project_config, experiment, user_id)
variation = self.get_whitelisted_variation(project_config, experiment, user_id)
if variation:
return variation

Expand Down
4 changes: 2 additions & 2 deletions optimizely/optimizely.py
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,7 @@ def set_forced_variation(self, experiment_key, user_id, variation_key):
self.logger.error(enums.Errors.INVALID_INPUT_ERROR.format('user_id'))
return False

return self.config.set_forced_variation(experiment_key, user_id, variation_key)
return self.decision_service.set_forced_variation(self.config, experiment_key, user_id, variation_key)

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

forced_variation = self.config.get_forced_variation(experiment_key, user_id)
forced_variation = self.decision_service.get_forced_variation(self.config, experiment_key, user_id)
return forced_variation.key if forced_variation else None
110 changes: 0 additions & 110 deletions optimizely/project_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

from .helpers import condition as condition_helper
from .helpers import enums
from .helpers import validator
from . import entities
from . import exceptions

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

# Map of user IDs to another map of experiments to variations.
# This contains all the forced variations set by the user
# by calling set_forced_variation (it is not the same as the
# whitelisting forcedVariations data structure).
self.forced_variation_map = {}

@staticmethod
def _generate_key_map(entity_list, key, entity_class):
""" Helper method to generate map from key to entity object for given list of dicts.
Expand Down Expand Up @@ -496,109 +489,6 @@ def get_variable_for_feature(self, feature_key, variable_key):

return feature.variables.get(variable_key)

def set_forced_variation(self, experiment_key, user_id, variation_key):
""" Sets users to a map of experiments to forced variations.
Args:
experiment_key: Key for experiment.
user_id: The user ID.
variation_key: Key for variation. If None, then clear the existing experiment-to-variation mapping.
Returns:
A boolean value that indicates if the set completed successfully.
"""
experiment = self.get_experiment_from_key(experiment_key)
if not experiment:
# The invalid experiment key will be logged inside this call.
return False

experiment_id = experiment.id
if variation_key is None:
if user_id in self.forced_variation_map:
experiment_to_variation_map = self.forced_variation_map.get(user_id)
if experiment_id in experiment_to_variation_map:
del(self.forced_variation_map[user_id][experiment_id])
self.logger.debug('Variation mapped to experiment "%s" has been removed for user "%s".' % (
experiment_key,
user_id
))
else:
self.logger.debug('Nothing to remove. Variation mapped to experiment "%s" for user "%s" does not exist.' % (
experiment_key,
user_id
))
else:
self.logger.debug('Nothing to remove. User "%s" does not exist in the forced variation map.' % user_id)
return True

if not validator.is_non_empty_string(variation_key):
self.logger.debug('Variation key is invalid.')
return False

forced_variation = self.get_variation_from_key(experiment_key, variation_key)
if not forced_variation:
# The invalid variation key will be logged inside this call.
return False

variation_id = forced_variation.id

if user_id not in self.forced_variation_map:
self.forced_variation_map[user_id] = {experiment_id: variation_id}
else:
self.forced_variation_map[user_id][experiment_id] = variation_id

self.logger.debug('Set variation "%s" for experiment "%s" and user "%s" in the forced variation map.' % (
variation_id,
experiment_id,
user_id
))
return True

def get_forced_variation(self, experiment_key, user_id):
""" Gets the forced variation key for the given user and experiment.
Args:
experiment_key: Key for experiment.
user_id: The user ID.
Returns:
The variation which the given user and experiment should be forced into.
"""

if user_id not in self.forced_variation_map:
self.logger.debug('User "%s" is not in the forced variation map.' % user_id)
return None

experiment = self.get_experiment_from_key(experiment_key)
if not experiment:
# The invalid experiment key will be logged inside this call.
return None

experiment_to_variation_map = self.forced_variation_map.get(user_id)

if not experiment_to_variation_map:
self.logger.debug('No experiment "%s" mapped to user "%s" in the forced variation map.' % (
experiment_key,
user_id
))
return None

variation_id = experiment_to_variation_map.get(experiment.id)
if variation_id is None:
self.logger.debug(
'No variation mapped to experiment "%s" in the forced variation map.' % experiment_key
)
return None

variation = self.get_variation_from_id(experiment_key, variation_id)

self.logger.debug('Variation "%s" is mapped to experiment "%s" and user "%s" in the forced variation map' % (
variation.key,
experiment_key,
user_id
))
return variation

def get_anonymize_ip_value(self):
""" Gets the anonymize IP value.
Expand Down
Loading

0 comments on commit 3ce6411

Please sign in to comment.