Skip to content

Moving forced variation map from ProjectConfig to DecisionService #180

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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