Skip to content

Commit

Permalink
Merge 4f00cb5 into be63510
Browse files Browse the repository at this point in the history
  • Loading branch information
thomaszurkan-optimizely committed Feb 1, 2021
2 parents be63510 + 4f00cb5 commit 0a1a406
Show file tree
Hide file tree
Showing 17 changed files with 2,564 additions and 614 deletions.
48 changes: 32 additions & 16 deletions optimizely/bucketer.py
@@ -1,4 +1,4 @@
# Copyright 2016-2017, 2019-2020 Optimizely
# Copyright 2016-2017, 2019-2021 Optimizely
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
Expand Down Expand Up @@ -71,13 +71,13 @@ def find_bucket(self, project_config, bucketing_id, parent_id, traffic_allocatio
traffic_allocations: Traffic allocations representing traffic allotted to experiments or variations.
Returns:
Entity ID which may represent experiment or variation.
Entity ID which may represent experiment or variation and
"""

bucketing_key = BUCKETING_ID_TEMPLATE.format(bucketing_id=bucketing_id, parent_id=parent_id)
bucketing_number = self._generate_bucket_value(bucketing_key)
message = 'Assigned bucket %s to user with bucketing ID "%s".' % (bucketing_number, bucketing_id)
project_config.logger.debug(
'Assigned bucket %s to user with bucketing ID "%s".' % (bucketing_number, bucketing_id)
message
)

for traffic_allocation in traffic_allocations:
Expand All @@ -97,41 +97,57 @@ def bucket(self, project_config, experiment, user_id, bucketing_id):
bucketing_id: ID to be used for bucketing the user.
Returns:
Variation in which user with ID user_id will be put in. None if no variation.
Variation in which user with ID user_id will be put in. None if no variation
and array of log messages representing decision making.
*/.
"""

decide_reasons = []
if not experiment:
return None
return None, decide_reasons

# Determine if experiment is in a mutually exclusive group.
# This will not affect evaluation of rollout rules.
if experiment.groupPolicy in GROUP_POLICIES:
group = project_config.get_group(experiment.groupId)

if not group:
return None
return None, decide_reasons

user_experiment_id = self.find_bucket(
project_config, bucketing_id, experiment.groupId, group.trafficAllocation,
)

if not user_experiment_id:
project_config.logger.info('User "%s" is in no experiment.' % user_id)
return None
message = 'User "%s" is in no experiment.' % user_id
project_config.logger.info(message)
decide_reasons.append(message)
return None, decide_reasons

if user_experiment_id != experiment.id:
message = 'User "%s" is not in experiment "%s" of group %s.' \
% (user_id, experiment.key, experiment.groupId)
project_config.logger.info(
'User "%s" is not in experiment "%s" of group %s.' % (user_id, experiment.key, experiment.groupId)
message
)
return None
decide_reasons.append(message)
return None, decide_reasons

message = 'User "%s" is in experiment %s of group %s.' % (user_id, experiment.key, experiment.groupId)
project_config.logger.info(
'User "%s" is in experiment %s of group %s.' % (user_id, experiment.key, experiment.groupId)
message
)
decide_reasons.append(message)

# Bucket user if not in white-list and in group (if any)
variation_id = self.find_bucket(project_config, bucketing_id, experiment.id, experiment.trafficAllocation)
variation_id = self.find_bucket(project_config, bucketing_id,
experiment.id, experiment.trafficAllocation)
if variation_id:
variation = project_config.get_variation_from_id(experiment.key, variation_id)
return variation
return variation, decide_reasons

return None
else:
message = 'Bucketed into an empty traffic range. Returning nil.'
project_config.logger.info(message)
decide_reasons.append(message)

return None, decide_reasons
12 changes: 12 additions & 0 deletions optimizely/decision/__init__.py
@@ -0,0 +1,12 @@
# Copyright 2021, Optimizely
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
20 changes: 20 additions & 0 deletions optimizely/decision/optimizely_decide_option.py
@@ -0,0 +1,20 @@
# Copyright 2021, Optimizely
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


class OptimizelyDecideOption(object):
DISABLE_DECISION_EVENT = 'DISABLE_DECISION_EVENT'
ENABLED_FLAGS_ONLY = 'ENABLED_FLAGS_ONLY'
IGNORE_USER_PROFILE_SERVICE = 'IGNORE_USER_PROFILE_SERVICE'
INCLUDE_REASONS = 'INCLUDE_REASONS'
EXCLUDE_VARIABLES = 'EXCLUDE_VARIABLES'
35 changes: 35 additions & 0 deletions optimizely/decision/optimizely_decision.py
@@ -0,0 +1,35 @@
# Copyright 2021, Optimizely
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


class OptimizelyDecision(object):
def __init__(self, variation_key=None, enabled=None,
variables=None, rule_key=None, flag_key=None, user_context=None, reasons=None):
self.variation_key = variation_key
self.enabled = enabled or False
self.variables = variables or {}
self.rule_key = rule_key
self.flag_key = flag_key
self.user_context = user_context
self.reasons = reasons or []

def as_json(self):
return {
'variation_key': self.variation_key,
'enabled': self.enabled,
'variables': self.variables,
'rule_key': self.rule_key,
'flag_key': self.flag_key,
'user_context': self.user_context.as_json(),
'reasons': self.reasons
}
18 changes: 18 additions & 0 deletions optimizely/decision/optimizely_decision_message.py
@@ -0,0 +1,18 @@
# Copyright 2021, Optimizely
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


class OptimizelyDecisionMessage(object):
SDK_NOT_READY = 'Optimizely SDK not configured properly yet.'
FLAG_KEY_INVALID = 'No flag was found for key "{}".'
VARIABLE_VALUE_INVALID = 'Variable value for key "{}" is invalid or wrong type.'

0 comments on commit 0a1a406

Please sign in to comment.