Skip to content

Commit 3754fcd

Browse files
committed
[FSSDK-12760] Clarify holdout scope docs (spec §5 cleanup)
Update stale docstrings/comments that described holdout scope as being determined by the includedRules field. After FSSDK-12760, scope is determined by which top-level datafile section the entity is parsed from ('holdouts' = global, 'localHoldouts' = local). The entity-level is_global property continues to compute from included_rules for code holding direct entity references. No behavior change.
1 parent d6c9a5e commit 3754fcd

2 files changed

Lines changed: 22 additions & 4 deletions

File tree

optimizely/entities.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,11 @@ def __init__(
233233
self.trafficAllocation = trafficAllocation
234234
self.audienceIds = audienceIds
235235
self.audienceConditions = audienceConditions
236-
# None = global holdout (applies to all rules); list of rule IDs = local holdout
236+
# Per-rule targeting list for local holdouts. Scope is NOT determined by this
237+
# field — it is determined by which datafile section the entity was parsed
238+
# from ('holdouts' = global, 'localHoldouts' = local). ProjectConfig strips
239+
# this field on entries parsed from the 'holdouts' section so that entities
240+
# built from that section always satisfy is_global. See FSSDK-12760.
237241
self.included_rules: Optional[list[str]] = includedRules
238242

239243
def get_audience_conditions_or_ids(self) -> Sequence[str | list[str]]:
@@ -248,8 +252,17 @@ def get_audience_conditions_or_ids(self) -> Sequence[str | list[str]]:
248252
def is_global(self) -> bool:
249253
"""Check if this is a global holdout (applies to all rules across all flags).
250254
251-
A holdout is global when includedRules is None (absent from datafile).
252-
An empty list [] is a local holdout that targets no rules (different from global).
255+
Authoritative scope is the datafile section the entity came from:
256+
the top-level 'holdouts' section is global, 'localHoldouts' is local.
257+
ProjectConfig enforces this by stripping 'includedRules' from entries
258+
parsed out of the 'holdouts' section before constructing the entity,
259+
so for entities built via ProjectConfig this property is consistent
260+
with section membership.
261+
262+
At the entity level the property is still computed from
263+
``included_rules`` for code holding entity references directly:
264+
None → global, [] or non-empty list → local. An empty list is a
265+
local holdout that targets no rules (distinct from None/global).
253266
254267
Returns:
255268
True if included_rules is None (global), False otherwise (local).

optimizely/helpers/types.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,4 +130,9 @@ class HoldoutDict(ExperimentDict):
130130
Extends ExperimentDict with holdout-specific properties.
131131
"""
132132
holdoutStatus: HoldoutStatus
133-
includedRules: Optional[list[str]] # None = global holdout; list of rule IDs = local holdout
133+
# Per-rule targeting list for local holdouts. Holdout scope is determined by which
134+
# top-level datafile section the entry appears in ('holdouts' vs 'localHoldouts'),
135+
# NOT by this field. On 'holdouts'-section entries this field is ignored and
136+
# stripped at parse time (see ProjectConfig). On 'localHoldouts'-section entries
137+
# the field is required; missing or None is an error and the entry is excluded.
138+
includedRules: Optional[list[str]]

0 commit comments

Comments
 (0)