Skip to content

Commit 9d18379

Browse files
Fix user profile tracking in get_variation_for_feature
CRITICAL FIX: Restore user profile service integration that was broken after holdout refactor. The get_variation_for_feature method was calling get_decision_for_flag without creating or managing user_profile_tracker, which broke sticky bucketing for all single-feature decision methods. Changes: - Create user_profile_tracker when user_profile_service is available - Load user profile before calling get_decision_for_flag - Pass user_profile_tracker to get_decision_for_flag (instead of None) - Save user profile after decision is made - Matches pattern used in get_variations_for_feature_list Impact: - Restores sticky bucketing for is_feature_enabled, get_feature_variable, etc. - Fixes user profile e2e test failures - All 734 tests passing Aligned with Swift SDK behavior while maintaining backward compatibility.
1 parent 8b301a8 commit 9d18379

File tree

1 file changed

+20
-2
lines changed

1 file changed

+20
-2
lines changed

optimizely/decision_service.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -674,11 +674,29 @@ def get_variation_for_feature(
674674
- 'error': Boolean indicating if an error occurred during the decision process.
675675
- 'reasons': List of log messages representing decision making for the feature.
676676
"""
677+
# Check if user profile service should be ignored
678+
if options:
679+
ignore_ups = OptimizelyDecideOption.IGNORE_USER_PROFILE_SERVICE in options
680+
else:
681+
ignore_ups = False
682+
683+
# Create user profile tracker for sticky bucketing (same pattern as get_variations_for_feature_list)
684+
user_profile_tracker: Optional[UserProfileTracker] = None
685+
if self.user_profile_service is not None and not ignore_ups:
686+
user_profile_tracker = UserProfileTracker(user_context.user_id, self.user_profile_service, self.logger)
687+
# Load user profile once before processing
688+
user_profile_tracker.load_user_profile([], None)
689+
677690
# CRITICAL FIX: Always call get_decision_for_flag (matching Swift SDK behavior)
678691
# Swift always goes through getDecisionForFlag which checks holdouts first,
679692
# then experiments, then rollouts - regardless of whether holdouts exist.
680-
# Previously Python had conditional logic that created two different code paths.
681-
return self.get_decision_for_flag(feature, user_context, project_config, options)
693+
result = self.get_decision_for_flag(feature, user_context, project_config, options, user_profile_tracker)
694+
695+
# Save user profile after decision (same pattern as get_variations_for_feature_list)
696+
if user_profile_tracker is not None and not ignore_ups:
697+
user_profile_tracker.save_user_profile()
698+
699+
return result
682700

683701
def get_decision_for_flag(
684702
self,

0 commit comments

Comments
 (0)