Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"permissions": {
"allow": [
"Bash(find /Users/muzahidul.islam/opti/python-sdk -name \"*.py\" -type f -exec grep -l \"oldout\" {} ;)",
"Bash(python -m py_compile optimizely/project_config.py)",
"Bash(python -m py_compile optimizely/decision_service.py)",
"Bash(python -m py_compile optimizely/bucketer.py)",
"Bash(python -c \"from optimizely import entities; print(''entities.py imports successfully'')\")",
"Bash(python -c \"from optimizely import project_config; print(''project_config.py imports successfully'')\")",
"Bash(pip install -r requirements/core.txt)",
"Bash(python3 -c \"from optimizely import project_config; print(''project_config.py imports successfully'')\")",
"Bash(python3 -m pip install jsonschema requests --quiet)",
"Bash(python3 -c \"import jsonschema; print(''jsonschema installed'')\")",
"Bash(python3 -c \"from optimizely import decision_service; print(''decision_service.py imports successfully'')\")",
"Bash(python3 -c \"from optimizely import bucketer; print(''bucketer.py imports successfully'')\")",
"Bash(python3 -m mypy optimizely/entities.py --no-error-summary)",
"Bash(python3 -m unittest tests.test_decision_service_holdout)",
"Bash(python3 -m unittest discover tests -p \"test_*.py\" -v)",
"Bash(git add -A)",
"Bash(git commit -m \"Fix config tests for holdout entity changes\n\n- Add required fields (variations, trafficAllocation, audienceIds) to holdout test configs\n- Fix BaseEntity __eq__ to handle comparisons with non-entity types\n- Fix test assertion to check for holdout IDs in global_holdouts list\n- All 86 config tests passing\n- All 12 holdout config tests passing\")",
"Bash(python3 -m unittest tests.test_bucketing -v)"
]
}
}
51 changes: 21 additions & 30 deletions optimizely/bucketer.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,14 +125,10 @@ def bucket(
project_config.logger.debug(message)
return None, []

if isinstance(experiment, dict):
# This is a holdout dictionary
experiment_key = experiment.get('key', '')
experiment_id = experiment.get('id', '')
else:
# This is an Experiment object
experiment_key = experiment.key
experiment_id = experiment.id
# Handle both Experiment and Holdout entities (aligned with Swift SDK)
# Both have key and id attributes
experiment_key = experiment.key
experiment_id = experiment.id

if not experiment_key or not experiment_key.strip():
message = 'Invalid entity key provided for bucketing. Returning nil.'
Expand All @@ -141,13 +137,10 @@ def bucket(

variation_id, decide_reasons = self.bucket_to_entity_id(project_config, experiment, user_id, bucketing_id)
if variation_id:
if isinstance(experiment, dict):
# For holdouts, find the variation in the holdout's variations array
variations = experiment.get('variations', [])
variation = next((v for v in variations if v.get('id') == variation_id), None)
else:
# For experiments, use the existing method
variation = project_config.get_variation_from_id_by_experiment_id(experiment_id, variation_id)
# Use the same variation lookup method for both experiments and holdouts
# This works because we now convert holdout variations to Variation entities
# in project_config.py (matching Swift SDK approach)
variation = project_config.get_variation_from_id_by_experiment_id(experiment_id, variation_id)
return variation, decide_reasons

# No variation found - log message for empty traffic range
Expand Down Expand Up @@ -176,21 +169,19 @@ def bucket_to_entity_id(
if not experiment:
return None, decide_reasons

# Handle both Experiment objects and holdout dictionaries
if isinstance(experiment, dict):
# This is a holdout dictionary - holdouts don't have groups
experiment_key = experiment.get('key', '')
experiment_id = experiment.get('id', '')
traffic_allocations = experiment.get('trafficAllocation', [])
has_cmab = False
group_policy = None
else:
# This is an Experiment object
experiment_key = experiment.key
experiment_id = experiment.id
traffic_allocations = experiment.trafficAllocation
has_cmab = bool(experiment.cmab)
group_policy = getattr(experiment, 'groupPolicy', None)
# Handle both Experiment and Holdout entities (aligned with Swift SDK)
# Both entities have key, id, and trafficAllocation attributes
from . import entities

experiment_key = experiment.key
experiment_id = experiment.id
traffic_allocations = experiment.trafficAllocation

# Check if this is a Holdout entity
# Holdouts don't have groups or CMAB
is_holdout = isinstance(experiment, entities.Holdout)
has_cmab = False if is_holdout else bool(getattr(experiment, 'cmab', None))
group_policy = None if is_holdout else getattr(experiment, 'groupPolicy', None)

# Determine if experiment is in a mutually exclusive group.
# This will not affect evaluation of rollout rules or holdouts.
Expand Down
Loading
Loading