Skip to content
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

feat: add decide api #309

Merged
merged 24 commits into from
Feb 1, 2021
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
35a6eac
add user context
thomaszurkan-optimizely Nov 9, 2020
509ff50
cleanup lint
thomaszurkan-optimizely Nov 9, 2020
0ef34ee
update comments
thomaszurkan-optimizely Nov 9, 2020
61fe356
fixes from ali's comments
thomaszurkan-optimizely Nov 25, 2020
c8cabbd
add all decide objects
thomaszurkan-optimizely Nov 25, 2020
dc226c1
add decide apis
thomaszurkan-optimizely Nov 26, 2020
366030e
Merge branch 'master' into decideApi
thomaszurkan-optimizely Nov 26, 2020
fd99ec1
fix lint errors
thomaszurkan-optimizely Nov 26, 2020
36b0328
Merge branch 'decideApi' of https://github.com/optimizely/python-sdk …
thomaszurkan-optimizely Nov 26, 2020
19b7fae
added unit tests and cleanup decide
thomaszurkan-optimizely Dec 3, 2020
ffa40d6
cleanup lint errors
thomaszurkan-optimizely Dec 3, 2020
13ec677
fix lint
thomaszurkan-optimizely Dec 3, 2020
5feca2c
remove reduce to work with python 3.x
thomaszurkan-optimizely Dec 3, 2020
4c4b4a4
add more unit tests for user context
thomaszurkan-optimizely Dec 3, 2020
460c904
another test
thomaszurkan-optimizely Dec 3, 2020
0d4ec50
try and fix travis pypy tests
thomaszurkan-optimizely Dec 3, 2020
8b5d56d
attempt to fix pypy on travis
thomaszurkan-optimizely Dec 3, 2020
a1cbc68
fix travis pypy
thomaszurkan-optimizely Dec 3, 2020
67fc4f2
add more tests
thomaszurkan-optimizely Dec 8, 2020
f3e3854
finish python with reasons
thomaszurkan-optimizely Dec 9, 2020
c38dbfc
make sure we are at the lowest level DEBUG
thomaszurkan-optimizely Dec 9, 2020
2c1f8b0
only capture info messages and up
thomaszurkan-optimizely Dec 10, 2020
9517442
refact: Decide API (#314)
oakbani Feb 1, 2021
4f00cb5
Merge branch 'master' into decideApi
jaeopt Feb 1, 2021
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
48 changes: 32 additions & 16 deletions optimizely/bucketer.py
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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.'
Loading