Skip to content

Commit c6b2cde

Browse files
committed
Merge branch 'master' of github.com:jaeopt/python-sdk
2 parents 494e18b + 88b0644 commit c6b2cde

5 files changed

Lines changed: 6 additions & 140 deletions

File tree

optimizely/decision_service.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -515,11 +515,6 @@ def get_variation(
515515
'reasons': decide_reasons,
516516
'variation': None
517517
}
518-
ignore_user_profile = True
519-
self.logger.debug(
520-
f'Skipping user profile service for CMAB experiment "{experiment.key}". '
521-
f'CMAB decisions are dynamic and not stored for sticky bucketing.'
522-
)
523518
variation_id = cmab_decision['variation_id'] if cmab_decision else None
524519
cmab_uuid = cmab_decision['cmab_uuid'] if cmab_decision else None
525520
variation = project_config.get_variation_from_id(experiment_key=experiment.key,

optimizely/event_dispatcher.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def dispatch_event(event: event_builder.Event) -> None:
4949
session = requests.Session()
5050

5151
retries = Retry(total=EventDispatchConfig.RETRIES,
52-
backoff_factor=0.2,
52+
backoff_factor=0.1,
5353
status_forcelist=[500, 502, 503, 504])
5454
adapter = HTTPAdapter(max_retries=retries)
5555

optimizely/odp/odp_event_manager.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,6 @@ def _flush_batch(self) -> None:
163163

164164
self.logger.debug(f'ODP event queue: flushing batch size {batch_len}.')
165165
should_retry = False
166-
initial_retry_interval = 0.2 # 200ms
167-
max_retry_interval = 1.0 # 1 second
168166

169167
for i in range(1 + self.retry_count):
170168
try:
@@ -178,12 +176,7 @@ def _flush_batch(self) -> None:
178176
if not should_retry:
179177
break
180178
if i < self.retry_count:
181-
# Exponential backoff: 200ms, 400ms, 800ms, ... capped at 1s
182-
delay = initial_retry_interval * (2 ** i)
183-
if delay > max_retry_interval:
184-
delay = max_retry_interval
185-
self.logger.debug(f'Error dispatching ODP events, retrying after {delay}s.')
186-
time.sleep(delay)
179+
self.logger.debug('Error dispatching ODP events, scheduled to retry.')
187180

188181
if should_retry:
189182
self.logger.error(Errors.ODP_EVENT_FAILED.format(f'Failed after {i} retries: {self._current_batch}'))

tests/test_decision_service.py

Lines changed: 0 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,124 +1074,6 @@ def test_get_variation_cmab_experiment_with_whitelisted_variation(self):
10741074
mock_bucket.assert_not_called()
10751075
mock_cmab_decision.assert_not_called()
10761076

1077-
def test_get_variation_cmab_experiment_does_not_save_user_profile(self):
1078-
"""Test that CMAB experiments do not save bucketing decisions to user profile."""
1079-
1080-
# Create a user context
1081-
user = optimizely_user_context.OptimizelyUserContext(
1082-
optimizely_client=None,
1083-
logger=None,
1084-
user_id="test_user",
1085-
user_attributes={}
1086-
)
1087-
1088-
# Create a user profile service and tracker
1089-
user_profile_service = user_profile.UserProfileService()
1090-
user_profile_tracker = user_profile.UserProfileTracker(user.user_id, user_profile_service)
1091-
1092-
# Create a CMAB experiment
1093-
cmab_experiment = entities.Experiment(
1094-
'111150',
1095-
'cmab_experiment',
1096-
'Running',
1097-
'111150',
1098-
[], # No audience IDs
1099-
{},
1100-
[
1101-
entities.Variation('111151', 'variation_1'),
1102-
entities.Variation('111152', 'variation_2')
1103-
],
1104-
[
1105-
{'entityId': '111151', 'endOfRange': 5000},
1106-
{'entityId': '111152', 'endOfRange': 10000}
1107-
],
1108-
cmab={'trafficAllocation': 5000}
1109-
)
1110-
1111-
with mock.patch('optimizely.helpers.experiment.is_experiment_running', return_value=True), \
1112-
mock.patch('optimizely.helpers.audience.does_user_meet_audience_conditions', return_value=[True, []]), \
1113-
mock.patch.object(self.decision_service.bucketer, 'bucket_to_entity_id',
1114-
return_value=['$', []]), \
1115-
mock.patch.object(self.decision_service, 'cmab_service') as mock_cmab_service, \
1116-
mock.patch.object(self.project_config, 'get_variation_from_id',
1117-
return_value=entities.Variation('111151', 'variation_1')), \
1118-
mock.patch.object(user_profile_tracker, 'update_user_profile') as mock_update_profile, \
1119-
mock.patch.object(self.decision_service, 'logger') as mock_logger:
1120-
1121-
# Configure CMAB service to return a decision
1122-
mock_cmab_service.get_decision.return_value = (
1123-
{
1124-
'variation_id': '111151',
1125-
'cmab_uuid': 'test-cmab-uuid-123'
1126-
},
1127-
[] # reasons list
1128-
)
1129-
1130-
# Call get_variation with the CMAB experiment and user profile tracker
1131-
variation_result = self.decision_service.get_variation(
1132-
self.project_config,
1133-
cmab_experiment,
1134-
user,
1135-
user_profile_tracker
1136-
)
1137-
variation = variation_result['variation']
1138-
cmab_uuid = variation_result['cmab_uuid']
1139-
1140-
# Verify the variation and cmab_uuid are returned
1141-
self.assertEqual(entities.Variation('111151', 'variation_1'), variation)
1142-
self.assertEqual('test-cmab-uuid-123', cmab_uuid)
1143-
1144-
# Verify user profile was NOT updated for CMAB experiment
1145-
mock_update_profile.assert_not_called()
1146-
1147-
# Verify debug log was called to explain CMAB exclusion
1148-
mock_logger.debug.assert_any_call(
1149-
'Skipping user profile service for CMAB experiment "cmab_experiment". '
1150-
'CMAB decisions are dynamic and not stored for sticky bucketing.'
1151-
)
1152-
1153-
def test_get_variation_standard_experiment_saves_user_profile(self):
1154-
"""Test that standard (non-CMAB) experiments DO save bucketing decisions to user profile."""
1155-
1156-
user = optimizely_user_context.OptimizelyUserContext(
1157-
optimizely_client=None,
1158-
logger=None,
1159-
user_id="test_user",
1160-
user_attributes={}
1161-
)
1162-
1163-
# Create a user profile service and tracker
1164-
user_profile_service = user_profile.UserProfileService()
1165-
user_profile_tracker = user_profile.UserProfileTracker(user.user_id, user_profile_service)
1166-
1167-
# Get a standard (non-CMAB) experiment
1168-
experiment = self.project_config.get_experiment_from_key("test_experiment")
1169-
1170-
with mock.patch('optimizely.decision_service.DecisionService.get_whitelisted_variation',
1171-
return_value=[None, []]), \
1172-
mock.patch('optimizely.decision_service.DecisionService.get_stored_variation',
1173-
return_value=None), \
1174-
mock.patch('optimizely.helpers.audience.does_user_meet_audience_conditions',
1175-
return_value=[True, []]), \
1176-
mock.patch('optimizely.bucketer.Bucketer.bucket',
1177-
return_value=[entities.Variation("111129", "variation"), []]), \
1178-
mock.patch.object(user_profile_tracker, 'update_user_profile') as mock_update_profile:
1179-
1180-
# Call get_variation with standard experiment and user profile tracker
1181-
variation_result = self.decision_service.get_variation(
1182-
self.project_config,
1183-
experiment,
1184-
user,
1185-
user_profile_tracker
1186-
)
1187-
variation = variation_result['variation']
1188-
1189-
# Verify variation was returned
1190-
self.assertEqual(entities.Variation("111129", "variation"), variation)
1191-
1192-
# Verify user profile WAS updated for standard experiment
1193-
mock_update_profile.assert_called_once_with(experiment, variation)
1194-
11951077

11961078
class FeatureFlagDecisionTests(base.BaseTest):
11971079
def setUp(self):

tests/test_odp_event_manager.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ def test_odp_event_manager_retry_failure(self, *args):
265265

266266
with mock.patch.object(
267267
event_manager.api_manager, 'send_odp_events', new_callable=CopyingMock, return_value=True
268-
) as mock_send, mock.patch('time.sleep') as mock_sleep:
268+
) as mock_send:
269269
event_manager.send_event(**self.events[0])
270270
event_manager.send_event(**self.events[1])
271271
event_manager.flush()
@@ -275,9 +275,7 @@ def test_odp_event_manager_retry_failure(self, *args):
275275
[mock.call(self.api_key, self.api_host, self.processed_events)] * number_of_tries
276276
)
277277
self.assertEqual(len(event_manager._current_batch), 0)
278-
# Verify exponential backoff delays: 0.2s, 0.4s, 0.8s
279-
mock_sleep.assert_has_calls([mock.call(0.2), mock.call(0.4), mock.call(0.8)])
280-
mock_logger.debug.assert_any_call('Error dispatching ODP events, retrying after 0.2s.')
278+
mock_logger.debug.assert_any_call('Error dispatching ODP events, scheduled to retry.')
281279
mock_logger.error.assert_called_once_with(
282280
f'ODP event send failed (Failed after 3 retries: {self.processed_events}).'
283281
)
@@ -290,17 +288,15 @@ def test_odp_event_manager_retry_success(self, *args):
290288

291289
with mock.patch.object(
292290
event_manager.api_manager, 'send_odp_events', new_callable=CopyingMock, side_effect=[True, True, False]
293-
) as mock_send, mock.patch('time.sleep') as mock_sleep:
291+
) as mock_send:
294292
event_manager.send_event(**self.events[0])
295293
event_manager.send_event(**self.events[1])
296294
event_manager.flush()
297295
event_manager.event_queue.join()
298296

299297
mock_send.assert_has_calls([mock.call(self.api_key, self.api_host, self.processed_events)] * 3)
300298
self.assertEqual(len(event_manager._current_batch), 0)
301-
# Verify exponential backoff delays: 0.2s, 0.4s (only 2 delays for 3 attempts)
302-
mock_sleep.assert_has_calls([mock.call(0.2), mock.call(0.4)])
303-
mock_logger.debug.assert_any_call('Error dispatching ODP events, retrying after 0.2s.')
299+
mock_logger.debug.assert_any_call('Error dispatching ODP events, scheduled to retry.')
304300
mock_logger.error.assert_not_called()
305301
self.assertStrictTrue(event_manager.is_running)
306302
event_manager.stop()

0 commit comments

Comments
 (0)