Skip to content

Commit 91d53b6

Browse files
update: add cmab_uuid handling in DecisionService and related tests
1 parent 6d1f73d commit 91d53b6

File tree

3 files changed

+61
-50
lines changed

3 files changed

+61
-50
lines changed

optimizely/decision_service.py

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class Decision(NamedTuple):
4444
experiment: Optional[entities.Experiment]
4545
variation: Optional[entities.Variation]
4646
source: Optional[str]
47-
# cmab_uuid: Optional[str]
47+
cmab_uuid: Optional[str]
4848

4949

5050
class DecisionService:
@@ -58,6 +58,7 @@ def __init__(self,
5858
self.logger = logger
5959
self.user_profile_service = user_profile_service
6060
self.cmab_service = cmab_service
61+
self.cmab_uuid = None
6162

6263
# Map of user IDs to another map of experiments to variations.
6364
# This contains all the forced variations set by the user
@@ -305,7 +306,7 @@ def get_variation(
305306
user_profile_tracker: Optional[UserProfileTracker],
306307
reasons: list[str] = [],
307308
options: Optional[Sequence[str]] = None
308-
) -> tuple[Optional[entities.Variation], list[str]]:
309+
) -> tuple[Optional[entities.Variation], list[str], Optional[str]]:
309310
""" Top-level function to help determine variation user should be put in.
310311
311312
First, check if experiment is running.
@@ -323,11 +324,12 @@ def get_variation(
323324
options: Decide options.
324325
325326
Returns:
326-
Variation user should see. None if user is not in experiment or experiment is not running
327-
And an array of log messages representing decision making.
327+
Variation user should see. None if user is not in experiment or experiment is not running,
328+
an array of log messages representing decision making
329+
and a cmab_uuid if experiment is cmab-experiment
328330
"""
329331
user_id = user_context.user_id
330-
332+
cmab_uuid = None
331333
if options:
332334
ignore_user_profile = OptimizelyDecideOption.IGNORE_USER_PROFILE_SERVICE in options
333335
else:
@@ -341,20 +343,20 @@ def get_variation(
341343
message = f'Experiment "{experiment.key}" is not running.'
342344
self.logger.info(message)
343345
decide_reasons.append(message)
344-
return None, decide_reasons
346+
return None, decide_reasons, cmab_uuid
345347

346348
# Check if the user is forced into a variation
347349
variation: Optional[entities.Variation]
348350
variation, reasons_received = self.get_forced_variation(project_config, experiment.key, user_id)
349351
decide_reasons += reasons_received
350352
if variation:
351-
return variation, decide_reasons
353+
return variation, decide_reasons, cmab_uuid
352354

353355
# Check to see if user is white-listed for a certain variation
354356
variation, reasons_received = self.get_whitelisted_variation(project_config, experiment, user_id)
355357
decide_reasons += reasons_received
356358
if variation:
357-
return variation, decide_reasons
359+
return variation, decide_reasons, cmab_uuid
358360

359361
# Check to see if user has a decision available for the given experiment
360362
if user_profile_tracker is not None and not ignore_user_profile:
@@ -364,7 +366,7 @@ def get_variation(
364366
f'"{experiment}" for user "{user_id}" from user profile.'
365367
self.logger.info(message)
366368
decide_reasons.append(message)
367-
return variation, decide_reasons
369+
return variation, decide_reasons, cmab_uuid
368370
else:
369371
self.logger.warning('User profile has invalid format.')
370372

@@ -380,7 +382,7 @@ def get_variation(
380382
message = f'User "{user_id}" does not meet conditions to be in experiment "{experiment.key}".'
381383
self.logger.info(message)
382384
decide_reasons.append(message)
383-
return None, decide_reasons
385+
return None, decide_reasons, cmab_uuid
384386

385387
# Determine bucketing ID to be used
386388
bucketing_id, bucketing_id_reasons = self._get_bucketing_id(user_id, user_context.get_user_attributes())
@@ -403,7 +405,7 @@ def get_variation(
403405
message = f'User "{user_id}" not in CMAB experiment "{experiment.key}" due to traffic allocation.'
404406
self.logger.info(message)
405407
decide_reasons.append(message)
406-
return None, decide_reasons
408+
return None, decide_reasons, cmab_uuid
407409

408410
# User is in CMAB allocation, proceed to CMAB decision
409411
decision_variation_value = self._get_decision_for_cmab_experiment(project_config,
@@ -413,8 +415,9 @@ def get_variation(
413415
decide_reasons += decision_variation_value.get('reasons', [])
414416
cmab_decision = decision_variation_value.get('result')
415417
if not cmab_decision:
416-
return None, decide_reasons
418+
return None, decide_reasons, cmab_uuid
417419
variation_id = cmab_decision['variation_id']
420+
cmab_uuid = cmab_decision['cmab_uuid']
418421
variation = project_config.get_variation_from_id(experiment_key=experiment.key, variation_id=variation_id)
419422
else:
420423
# Bucket the user
@@ -431,11 +434,11 @@ def get_variation(
431434
user_profile_tracker.update_user_profile(experiment, variation)
432435
except:
433436
self.logger.exception(f'Unable to save user profile for user "{user_id}".')
434-
return variation, decide_reasons
437+
return variation, decide_reasons, cmab_uuid
435438
message = f'User "{user_id}" is in no variation.'
436439
self.logger.info(message)
437440
decide_reasons.append(message)
438-
return None, decide_reasons
441+
return None, decide_reasons, cmab_uuid
439442

440443
def get_variation_for_rollout(
441444
self, project_config: ProjectConfig, feature: entities.FeatureFlag, user_context: OptimizelyUserContext
@@ -459,23 +462,23 @@ def get_variation_for_rollout(
459462
attributes = user_context.get_user_attributes()
460463

461464
if not feature or not feature.rolloutId:
462-
return Decision(None, None, enums.DecisionSources.ROLLOUT), decide_reasons
465+
return Decision(None, None, enums.DecisionSources.ROLLOUT, None), decide_reasons
463466

464467
rollout = project_config.get_rollout_from_id(feature.rolloutId)
465468

466469
if not rollout:
467470
message = f'There is no rollout of feature {feature.key}.'
468471
self.logger.debug(message)
469472
decide_reasons.append(message)
470-
return Decision(None, None, enums.DecisionSources.ROLLOUT), decide_reasons
473+
return Decision(None, None, enums.DecisionSources.ROLLOUT, None), decide_reasons
471474

472475
rollout_rules = project_config.get_rollout_experiments(rollout)
473476

474477
if not rollout_rules:
475478
message = f'Rollout {rollout.id} has no experiments.'
476479
self.logger.debug(message)
477480
decide_reasons.append(message)
478-
return Decision(None, None, enums.DecisionSources.ROLLOUT), decide_reasons
481+
return Decision(None, None, enums.DecisionSources.ROLLOUT, None), decide_reasons
479482

480483
index = 0
481484
while index < len(rollout_rules):
@@ -490,7 +493,7 @@ def get_variation_for_rollout(
490493

491494
if forced_decision_variation:
492495
return Decision(experiment=rule, variation=forced_decision_variation,
493-
source=enums.DecisionSources.ROLLOUT), decide_reasons
496+
source=enums.DecisionSources.ROLLOUT, cmab_uuid=None), decide_reasons
494497

495498
bucketing_id, bucket_reasons = self._get_bucketing_id(user_id, attributes)
496499
decide_reasons += bucket_reasons
@@ -524,7 +527,7 @@ def get_variation_for_rollout(
524527
self.logger.debug(message)
525528
decide_reasons.append(message)
526529
return Decision(experiment=rule, variation=bucketed_variation,
527-
source=enums.DecisionSources.ROLLOUT), decide_reasons
530+
source=enums.DecisionSources.ROLLOUT, cmab_uuid=None), decide_reasons
528531

529532
elif not everyone_else:
530533
# skip this logging for EveryoneElse since this has a message not for everyone_else
@@ -544,7 +547,7 @@ def get_variation_for_rollout(
544547
# the last rule is special for "Everyone Else"
545548
index = len(rollout_rules) - 1 if skip_to_everyone_else else index + 1
546549

547-
return Decision(None, None, enums.DecisionSources.ROLLOUT), decide_reasons
550+
return Decision(None, None, enums.DecisionSources.ROLLOUT, None), decide_reasons
548551

549552
def get_variation_for_feature(
550553
self,
@@ -680,8 +683,9 @@ def get_variations_for_feature_list(
680683

681684
if forced_decision_variation:
682685
decision_variation = forced_decision_variation
686+
cmab_uuid = None
683687
else:
684-
decision_variation, variation_reasons = self.get_variation(
688+
decision_variation, variation_reasons, cmab_uuid = self.get_variation(
685689
project_config, experiment, user_context, user_profile_tracker, feature_reasons, options
686690
)
687691
feature_reasons.extend(variation_reasons)
@@ -691,7 +695,8 @@ def get_variations_for_feature_list(
691695
f'User "{user_context.user_id}" '
692696
f'bucketed into experiment "{experiment.key}" of feature "{feature.key}".'
693697
)
694-
decision = Decision(experiment, decision_variation, enums.DecisionSources.FEATURE_TEST)
698+
decision = Decision(experiment, decision_variation,
699+
enums.DecisionSources.FEATURE_TEST, cmab_uuid)
695700
decisions.append((decision, feature_reasons))
696701
experiment_decision_found = True # Mark that a decision was found
697702
break # Stop after the first successful experiment decision

optimizely/optimizely.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -652,10 +652,8 @@ def get_variation(
652652
user_context = OptimizelyUserContext(self, self.logger, user_id, attributes, False)
653653
user_profile_tracker = user_profile.UserProfileTracker(user_id, self.user_profile_service, self.logger)
654654
user_profile_tracker.load_user_profile()
655-
variation, _ = self.decision_service.get_variation(project_config,
656-
experiment,
657-
user_context,
658-
user_profile_tracker)
655+
variation, _, _ = self.decision_service.get_variation(project_config, experiment,
656+
user_context, user_profile_tracker)
659657
user_profile_tracker.save_user_profile()
660658
if variation:
661659
variation_key = variation.key
@@ -1356,7 +1354,7 @@ def _decide_for_keys(
13561354
decision_reasons_dict[key] += decision_reasons
13571355

13581356
if variation:
1359-
decision = Decision(None, variation, enums.DecisionSources.FEATURE_TEST)
1357+
decision = Decision(None, variation, enums.DecisionSources.FEATURE_TEST, None)
13601358
flag_decisions[key] = decision
13611359
else:
13621360
flags_without_forced_decision.append(feature_flag)

0 commit comments

Comments
 (0)