Skip to content

Commit 9517442

Browse files
oakbanijaeopt
andauthored
refact: Decide API (#314)
* WIP * WIP * fix: Passes All FSC * fix: unit tests and cleanup * refact: rename decide classes * fix: merge default decide options in decide for keys * prefix decide methods with _ * fix: decide option import * mutex locks * Apply suggestions from code review Co-authored-by: Jae Kim <[email protected]> * tests: user context tests * tests: WIP * feat: reasons work * tests: refact * tests: Add unit tests * remove reasons from find_bucket * address comments * tests: decide * fix: import * tests: Add reasons tests Co-authored-by: Jae Kim <[email protected]>
1 parent 2c1f8b0 commit 9517442

18 files changed

+2085
-958
lines changed

.travis.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,6 @@ python:
88
# - "3.8" is handled in 'Test' job using xenial as Python 3.8 is not available for trusty.
99
- "pypy"
1010
- "pypy3"
11-
before_install:
12-
- sudo apt-get --auto-remove --yes remove python-openssl
13-
- sudo pip install pyOpenSSL
1411
install: "pip install -r requirements/core.txt;pip install -r requirements/test.txt"
1512
script: "pytest --cov=optimizely"
1613
after_success:

optimizely/bucketer.py

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2016-2017, 2019-2020 Optimizely
1+
# Copyright 2016-2017, 2019-2021 Optimizely
22
# Licensed under the Apache License, Version 2.0 (the "License");
33
# you may not use this file except in compliance with the License.
44
# You may obtain a copy of the License at
@@ -71,13 +71,13 @@ def find_bucket(self, project_config, bucketing_id, parent_id, traffic_allocatio
7171
traffic_allocations: Traffic allocations representing traffic allotted to experiments or variations.
7272
7373
Returns:
74-
Entity ID which may represent experiment or variation.
74+
Entity ID which may represent experiment or variation and
7575
"""
76-
7776
bucketing_key = BUCKETING_ID_TEMPLATE.format(bucketing_id=bucketing_id, parent_id=parent_id)
7877
bucketing_number = self._generate_bucket_value(bucketing_key)
78+
message = 'Assigned bucket %s to user with bucketing ID "%s".' % (bucketing_number, bucketing_id)
7979
project_config.logger.debug(
80-
'Assigned bucket %s to user with bucketing ID "%s".' % (bucketing_number, bucketing_id)
80+
message
8181
)
8282

8383
for traffic_allocation in traffic_allocations:
@@ -97,41 +97,57 @@ def bucket(self, project_config, experiment, user_id, bucketing_id):
9797
bucketing_id: ID to be used for bucketing the user.
9898
9999
Returns:
100-
Variation in which user with ID user_id will be put in. None if no variation.
100+
Variation in which user with ID user_id will be put in. None if no variation
101+
and array of log messages representing decision making.
102+
*/.
101103
"""
102-
104+
decide_reasons = []
103105
if not experiment:
104-
return None
106+
return None, decide_reasons
105107

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

111113
if not group:
112-
return None
114+
return None, decide_reasons
113115

114116
user_experiment_id = self.find_bucket(
115117
project_config, bucketing_id, experiment.groupId, group.trafficAllocation,
116118
)
119+
117120
if not user_experiment_id:
118-
project_config.logger.info('User "%s" is in no experiment.' % user_id)
119-
return None
121+
message = 'User "%s" is in no experiment.' % user_id
122+
project_config.logger.info(message)
123+
decide_reasons.append(message)
124+
return None, decide_reasons
120125

121126
if user_experiment_id != experiment.id:
127+
message = 'User "%s" is not in experiment "%s" of group %s.' \
128+
% (user_id, experiment.key, experiment.groupId)
122129
project_config.logger.info(
123-
'User "%s" is not in experiment "%s" of group %s.' % (user_id, experiment.key, experiment.groupId)
130+
message
124131
)
125-
return None
132+
decide_reasons.append(message)
133+
return None, decide_reasons
126134

135+
message = 'User "%s" is in experiment %s of group %s.' % (user_id, experiment.key, experiment.groupId)
127136
project_config.logger.info(
128-
'User "%s" is in experiment %s of group %s.' % (user_id, experiment.key, experiment.groupId)
137+
message
129138
)
139+
decide_reasons.append(message)
130140

131141
# Bucket user if not in white-list and in group (if any)
132-
variation_id = self.find_bucket(project_config, bucketing_id, experiment.id, experiment.trafficAllocation)
142+
variation_id = self.find_bucket(project_config, bucketing_id,
143+
experiment.id, experiment.trafficAllocation)
133144
if variation_id:
134145
variation = project_config.get_variation_from_id(experiment.key, variation_id)
135-
return variation
146+
return variation, decide_reasons
136147

137-
return None
148+
else:
149+
message = 'Bucketed into an empty traffic range. Returning nil.'
150+
project_config.logger.info(message)
151+
decide_reasons.append(message)
152+
153+
return None, decide_reasons

optimizely/decision/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2020, Optimizely
1+
# Copyright 2021, Optimizely
22
# Licensed under the Apache License, Version 2.0 (the "License");
33
# you may not use this file except in compliance with the License.
44
# You may obtain a copy of the License at

optimizely/decision/decide_option.py renamed to optimizely/decision/optimizely_decide_option.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2020, Optimizely
1+
# Copyright 2021, Optimizely
22
# Licensed under the Apache License, Version 2.0 (the "License");
33
# you may not use this file except in compliance with the License.
44
# You may obtain a copy of the License at
@@ -12,7 +12,7 @@
1212
# limitations under the License.
1313

1414

15-
class DecideOption(object):
15+
class OptimizelyDecideOption(object):
1616
DISABLE_DECISION_EVENT = 'DISABLE_DECISION_EVENT'
1717
ENABLED_FLAGS_ONLY = 'ENABLED_FLAGS_ONLY'
1818
IGNORE_USER_PROFILE_SERVICE = 'IGNORE_USER_PROFILE_SERVICE'

optimizely/decision/decision.py renamed to optimizely/decision/optimizely_decision.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2020, Optimizely
1+
# Copyright 2021, Optimizely
22
# Licensed under the Apache License, Version 2.0 (the "License");
33
# you may not use this file except in compliance with the License.
44
# You may obtain a copy of the License at
@@ -12,7 +12,7 @@
1212
# limitations under the License.
1313

1414

15-
class Decision(object):
15+
class OptimizelyDecision(object):
1616
def __init__(self, variation_key=None, enabled=None,
1717
variables=None, rule_key=None, flag_key=None, user_context=None, reasons=None):
1818
self.variation_key = variation_key
@@ -22,3 +22,14 @@ def __init__(self, variation_key=None, enabled=None,
2222
self.flag_key = flag_key
2323
self.user_context = user_context
2424
self.reasons = reasons or []
25+
26+
def as_json(self):
27+
return {
28+
'variation_key': self.variation_key,
29+
'enabled': self.enabled,
30+
'variables': self.variables,
31+
'rule_key': self.rule_key,
32+
'flag_key': self.flag_key,
33+
'user_context': self.user_context.as_json(),
34+
'reasons': self.reasons
35+
}

optimizely/decision/decision_message.py renamed to optimizely/decision/optimizely_decision_message.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2020, Optimizely
1+
# Copyright 2021, Optimizely
22
# Licensed under the Apache License, Version 2.0 (the "License");
33
# you may not use this file except in compliance with the License.
44
# You may obtain a copy of the License at
@@ -12,7 +12,7 @@
1212
# limitations under the License.
1313

1414

15-
class DecisionMessage(object):
15+
class OptimizelyDecisionMessage(object):
1616
SDK_NOT_READY = 'Optimizely SDK not configured properly yet.'
17-
FLAG_KEY_INVALID = 'No flag was found for key "%s".'
18-
VARIABLE_VALUE_INVALID = 'Variable value for key "%s" is invalid or wrong type.'
17+
FLAG_KEY_INVALID = 'No flag was found for key "{}".'
18+
VARIABLE_VALUE_INVALID = 'Variable value for key "{}" is invalid or wrong type.'

0 commit comments

Comments
 (0)