Skip to content

Commit 73f5f0e

Browse files
committed
Implement suggested comments
1 parent ee376d2 commit 73f5f0e

File tree

3 files changed

+29
-15
lines changed

3 files changed

+29
-15
lines changed

optimizely/decision_service.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
# limitations under the License.
1313

1414
from __future__ import annotations
15-
from typing import TYPE_CHECKING, NamedTuple, Optional, Sequence, List, TypedDict, Dict
15+
from typing import TYPE_CHECKING, NamedTuple, Optional, Sequence, List, TypedDict
16+
17+
from optimizely.helpers.types import HoldoutDict
1618

1719
from . import bucketer
1820
from . import entities
@@ -83,7 +85,6 @@ class Decision(NamedTuple):
8385
variation: Optional[entities.Variation]
8486
source: Optional[str]
8587
cmab_uuid: Optional[str]
86-
holdout: Optional[Dict[str, str]] = None
8788

8889

8990
class DecisionService:
@@ -713,7 +714,8 @@ def get_decision_for_flag(
713714
reasons.extend(holdout_decision['reasons'])
714715

715716
decision = holdout_decision['decision']
716-
if (decision.experiment is None and decision.variation is None and decision.holdout is None):
717+
# Check if user was bucketed into holdout (has a variation)
718+
if decision.variation is None:
717719
continue
718720

719721
message = (
@@ -746,15 +748,15 @@ def get_decision_for_flag(
746748

747749
def get_variation_for_holdout(
748750
self,
749-
holdout: Dict[str, str],
751+
holdout: HoldoutDict,
750752
user_context: OptimizelyUserContext,
751753
project_config: ProjectConfig
752754
) -> DecisionResult:
753755
"""
754756
Get the variation for holdout.
755757
756758
Args:
757-
holdout: The holdout configuration.
759+
holdout: The holdout configuration (HoldoutDict).
758760
user_context: The user context.
759761
project_config: The project config.
760762
@@ -773,7 +775,7 @@ def get_variation_for_holdout(
773775
self.logger.info(message)
774776
decide_reasons.append(message)
775777
return {
776-
'decision': Decision(None, None, None, None, None),
778+
'decision': Decision(None, None, enums.DecisionSources.HOLDOUT, None),
777779
'error': False,
778780
'reasons': decide_reasons
779781
}
@@ -801,7 +803,7 @@ def get_variation_for_holdout(
801803
self.logger.debug(message)
802804
decide_reasons.append(message)
803805
return {
804-
'decision': Decision(None, None, None, None, None),
806+
'decision': Decision(None, None, enums.DecisionSources.HOLDOUT, None),
805807
'error': False,
806808
'reasons': decide_reasons
807809
}
@@ -822,13 +824,12 @@ def get_variation_for_holdout(
822824
self.logger.info(message)
823825
decide_reasons.append(message)
824826

825-
# Create Decision with holdout - experiment is None, holdout field contains the holdout dict
827+
# Create Decision for holdout - experiment is None, source is HOLDOUT
826828
holdout_decision: Decision = Decision(
827829
experiment=None,
828-
variation=None,
830+
variation=variation,
829831
source=enums.DecisionSources.HOLDOUT,
830-
cmab_uuid=None,
831-
holdout=holdout
832+
cmab_uuid=None
832833
)
833834
return {
834835
'decision': holdout_decision,
@@ -840,7 +841,7 @@ def get_variation_for_holdout(
840841
self.logger.info(message)
841842
decide_reasons.append(message)
842843
return {
843-
'decision': Decision(None, None, None, None, None),
844+
'decision': Decision(None, None, enums.DecisionSources.HOLDOUT, None),
844845
'error': False,
845846
'reasons': decide_reasons
846847
}

optimizely/helpers/types.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
# limitations under the License.
1313
from __future__ import annotations
1414

15-
from typing import Optional, Any
15+
from typing import Literal, Optional, Any
1616
from sys import version_info
1717

1818

@@ -115,3 +115,16 @@ class CmabDict(BaseEntity):
115115
"""Cmab dict from parsed datafile json."""
116116
attributeIds: list[str]
117117
trafficAllocation: int
118+
119+
120+
HoldoutStatus = Literal['Draft', 'Running', 'Concluded', 'Archived']
121+
122+
123+
class HoldoutDict(ExperimentDict):
124+
"""Holdout dict from parsed datafile json.
125+
126+
Extends ExperimentDict with holdout-specific properties.
127+
"""
128+
holdoutStatus: HoldoutStatus
129+
includedFlags: list[str]
130+
excludedFlags: list[str]

tests/test_decision_service_holdout.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,8 +290,8 @@ def test_user_bucketed_into_holdout_returns_before_experiments(self):
290290
if decision_result.get('decision'):
291291
decision = decision_result['decision']
292292
self.assertEqual(decision.source, enums.DecisionSources.HOLDOUT)
293-
self.assertIsNotNone(decision.holdout)
294-
self.assertEqual(decision.holdout.get('key'), 'test_holdout')
293+
self.assertIsNotNone(decision.variation)
294+
self.assertIsNone(decision.experiment)
295295

296296
def test_no_holdout_decision_falls_through_to_experiment_and_rollout(self):
297297
"""When holdout returns no decision, should fall through to experiment and rollout evaluation."""

0 commit comments

Comments
 (0)