Skip to content

Commit 81f5be9

Browse files
[FSSDK-11175] Update: Implement Decision Service methods to handle CMAB (#457)
* update: integrate CMAB components into OptimizelyFactory * update: add cmab_service parameter to Optimizely constructor for CMAB support * update: add docstring to DefaultCmabService class for improved documentation * update: implement CMAB support in bucketer and decision service, revert OptimizelyFactory * linting fix * update: add cmab_uuid handling in DecisionService and related tests * - updated function bucket_to_entity_id - test_optimizely.py fixed to expect new Decision objects * update: add None parameter to Decision constructor in user context tests * update: enhance CMAB decision handling and add related tests * update: fix logger message formatting in CMAB experiment tests * mypy fix * update: refine traffic allocation type hints and key naming in bucketer and decision service * update: remove unused import of cast in bucketer.py * update: fix return type for numeric_metric_value in get_numeric_value and ensure key is of bytes type in hash128 * update: specify type hint for numeric_metric_value in get_numeric_value function * update: fix logger reference in DefaultCmabClient initialization and add __init__.py for cmab module * update: enhance error logging for CMAB fetch failures with detailed messages and add a test for handling 500 errors * update: enhance decision result handling by introducing VariationResult and updating get_variation return type to include detailed error information * update: refactor get_variation return structure and change tests accordingly * -Error propagated to optimizely.py -test cases changed to handle return type dicts of DecisionResult and VariationResult * update: modify get_variation to return VariationResult and adjust related logic for improved variation handling * update: unit test fixes * Revert "update: unit test fixes" This reverts commit d2fc631. * Revert "update: modify get_variation to return VariationResult and adjust related logic for improved variation handling" This reverts commit b901c5f. * update: enhance decision service to handle error states and improve bucketing logic * update: remove debug print statement from Optimizely class * update: enhance bucketing logic to support CMAB traffic allocations * update: improve error logging for CMAB decision fetch failures * update: improve logging and error handling in bucketer and decision service
1 parent 82ec019 commit 81f5be9

13 files changed

+1255
-433
lines changed

optimizely/bucketer.py

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,34 @@ def bucket(
119119
and array of log messages representing decision making.
120120
*/.
121121
"""
122+
variation_id, decide_reasons = self.bucket_to_entity_id(project_config, experiment, user_id, bucketing_id)
123+
if variation_id:
124+
variation = project_config.get_variation_from_id_by_experiment_id(experiment.id, variation_id)
125+
return variation, decide_reasons
126+
127+
else:
128+
message = 'Bucketed into an empty traffic range. Returning nil.'
129+
project_config.logger.info(message)
130+
decide_reasons.append(message)
131+
132+
return None, decide_reasons
133+
134+
def bucket_to_entity_id(
135+
self, project_config: ProjectConfig,
136+
experiment: Experiment, user_id: str, bucketing_id: str
137+
) -> tuple[Optional[str], list[str]]:
138+
"""
139+
For a given experiment and bucketing ID determines variation ID to be shown to user.
140+
141+
Args:
142+
project_config: Instance of ProjectConfig.
143+
experiment: The experiment object (used for group/groupPolicy logic if needed).
144+
user_id: The user ID string.
145+
bucketing_id: The bucketing ID string for the user.
146+
147+
Returns:
148+
Tuple of (entity_id or None, list of decide reasons).
149+
"""
122150
decide_reasons: list[str] = []
123151
if not experiment:
124152
return None, decide_reasons
@@ -151,16 +179,16 @@ def bucket(
151179
project_config.logger.info(message)
152180
decide_reasons.append(message)
153181

182+
traffic_allocations: list[TrafficAllocation] = experiment.trafficAllocation
183+
if experiment.cmab:
184+
traffic_allocations = [
185+
{
186+
"entityId": "$",
187+
"endOfRange": experiment.cmab['trafficAllocation']
188+
}
189+
]
154190
# Bucket user if not in white-list and in group (if any)
155191
variation_id = self.find_bucket(project_config, bucketing_id,
156-
experiment.id, experiment.trafficAllocation)
157-
if variation_id:
158-
variation = project_config.get_variation_from_id_by_experiment_id(experiment.id, variation_id)
159-
return variation, decide_reasons
192+
experiment.id, traffic_allocations)
160193

161-
else:
162-
message = 'Bucketed into an empty traffic range. Returning nil.'
163-
project_config.logger.info(message)
164-
decide_reasons.append(message)
165-
166-
return None, decide_reasons
194+
return variation_id, decide_reasons

optimizely/cmab/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Copyright 2025, Optimizely
2+
# Licensed under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License.
4+
# You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.

optimizely/cmab/cmab_service.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,18 @@ class CmabCacheValue(TypedDict):
3535

3636

3737
class DefaultCmabService:
38+
"""
39+
DefaultCmabService handles decisioning for Contextual Multi-Armed Bandit (CMAB) experiments,
40+
including caching and filtering user attributes for efficient decision retrieval.
41+
42+
Attributes:
43+
cmab_cache: LRUCache for user CMAB decisions.
44+
cmab_client: Client to fetch decisions from the CMAB backend.
45+
logger: Optional logger.
46+
47+
Methods:
48+
get_decision: Retrieves a CMAB decision with caching and attribute filtering.
49+
"""
3850
def __init__(self, cmab_cache: LRUCache[str, CmabCacheValue],
3951
cmab_client: DefaultCmabClient, logger: Optional[_logging.Logger] = None):
4052
self.cmab_cache = cmab_cache

optimizely/decision/optimizely_decision.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,23 @@ def as_json(self) -> dict[str, Any]:
4848
'user_context': self.user_context.as_json() if self.user_context else None,
4949
'reasons': self.reasons
5050
}
51+
52+
@classmethod
53+
def new_error_decision(cls, key: str, user: OptimizelyUserContext, reasons: list[str]) -> OptimizelyDecision:
54+
"""Create a new OptimizelyDecision representing an error state.
55+
Args:
56+
key: The flag key
57+
user: The user context
58+
reasons: List of reasons explaining the error
59+
Returns:
60+
OptimizelyDecision with error state values
61+
"""
62+
return cls(
63+
variation_key=None,
64+
enabled=False,
65+
variables={},
66+
rule_key=None,
67+
flag_key=key,
68+
user_context=user,
69+
reasons=reasons if reasons else []
70+
)

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy