@@ -948,6 +948,39 @@ def get_variations_for_feature_list(
948948 for feature in features :
949949 feature_reasons = decide_reasons .copy ()
950950 experiment_decision_found = False # Track if an experiment decision was made for the feature
951+ holdout_decision_found = False # Track if a holdout decision was made for the feature
952+ user_id = user_context .user_id
953+
954+ # Check holdouts first (they take precedence over experiments and rollouts)
955+ holdouts = project_config .get_holdouts_for_flag (feature .key )
956+ for holdout in holdouts :
957+ holdout_decision_result = self .get_variation_for_holdout (holdout , user_context , project_config )
958+ feature_reasons .extend (holdout_decision_result ['reasons' ])
959+
960+ decision = holdout_decision_result ['decision' ]
961+ # Check if user was bucketed into holdout (has a variation)
962+ if decision .variation is None :
963+ continue
964+
965+ message = (
966+ f"The user '{ user_id } ' is bucketed into holdout '{ holdout ['key' ]} ' "
967+ f"for feature flag '{ feature .key } '."
968+ )
969+ self .logger .info (message )
970+ feature_reasons .append (message )
971+
972+ decision_result : DecisionResult = {
973+ 'decision' : holdout_decision_result ['decision' ],
974+ 'error' : False ,
975+ 'reasons' : feature_reasons
976+ }
977+ decisions .append (decision_result )
978+ holdout_decision_found = True
979+ break
980+
981+ # If holdout decision found, skip experiment and rollout evaluation
982+ if holdout_decision_found :
983+ continue
951984
952985 # Check if the feature flag is under an experiment
953986 if feature .experimentIds :
@@ -978,7 +1011,7 @@ def get_variations_for_feature_list(
9781011
9791012 if error :
9801013 decision = Decision (experiment , None , enums .DecisionSources .FEATURE_TEST , cmab_uuid )
981- decision_result : DecisionResult = {
1014+ decision_result = {
9821015 'decision' : decision ,
9831016 'error' : True ,
9841017 'reasons' : feature_reasons
0 commit comments