Skip to content

Commit f67a0cc

Browse files
authored
feat: fetch timeout made configurable (#411)
Fetch segment and send opd event timeout made configurable through sdk_settings option
1 parent f673a32 commit f67a0cc

10 files changed

+70
-16
lines changed

Diff for: optimizely/helpers/sdk_settings.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ def __init__(
3030
segments_cache_timeout_in_secs: int = enums.OdpSegmentsCacheConfig.DEFAULT_TIMEOUT_SECS,
3131
odp_segments_cache: Optional[OptimizelySegmentsCache] = None,
3232
odp_segment_manager: Optional[OdpSegmentManager] = None,
33-
odp_event_manager: Optional[OdpEventManager] = None
33+
odp_event_manager: Optional[OdpEventManager] = None,
34+
fetch_segments_timeout: Optional[int] = None,
35+
odp_event_timeout: Optional[int] = None
3436
) -> None:
3537
"""
3638
Args:
@@ -45,6 +47,8 @@ def __init__(
4547
`fetch_qualified_segments(user_key, user_value, options)`.
4648
odp_event_manager: A custom odp event manager. Required method is:
4749
`send_event(type:, action:, identifiers:, data:)`
50+
fetch_segments_timeout: A fetch segment timeout in seconds (optional).
51+
odp_event_timeout: A send odp event timeout in seconds (optional).
4852
"""
4953

5054
self.odp_disabled = odp_disabled
@@ -53,3 +57,5 @@ def __init__(
5357
self.segments_cache = odp_segments_cache
5458
self.odp_segment_manager = odp_segment_manager
5559
self.odp_event_manager = odp_event_manager
60+
self.fetch_segments_timeout = fetch_segments_timeout
61+
self.odp_event_timeout = odp_event_timeout

Diff for: optimizely/odp/odp_event_api_manager.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,14 @@
4040
class OdpEventApiManager:
4141
"""Provides an internal service for ODP event REST api access."""
4242

43-
def __init__(self, logger: Optional[optimizely_logger.Logger] = None):
43+
def __init__(self, logger: Optional[optimizely_logger.Logger] = None, timeout: Optional[int] = None):
4444
self.logger = logger or optimizely_logger.NoOpLogger()
45+
self.timeout = timeout or OdpEventApiConfig.REQUEST_TIMEOUT
4546

46-
def send_odp_events(self, api_key: str, api_host: str, events: list[OdpEvent]) -> bool:
47+
def send_odp_events(self,
48+
api_key: str,
49+
api_host: str,
50+
events: list[OdpEvent]) -> bool:
4751
"""
4852
Dispatch the event being represented by the OdpEvent object.
4953
@@ -69,7 +73,7 @@ def send_odp_events(self, api_key: str, api_host: str, events: list[OdpEvent]) -
6973
response = requests.post(url=url,
7074
headers=request_headers,
7175
data=payload_dict,
72-
timeout=OdpEventApiConfig.REQUEST_TIMEOUT)
76+
timeout=self.timeout)
7377

7478
response.raise_for_status()
7579

Diff for: optimizely/odp/odp_event_manager.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,18 @@ class OdpEventManager:
4545
def __init__(
4646
self,
4747
logger: Optional[_logging.Logger] = None,
48-
api_manager: Optional[OdpEventApiManager] = None
48+
api_manager: Optional[OdpEventApiManager] = None,
49+
timeout: Optional[int] = None
4950
):
5051
"""OdpEventManager init method to configure event batching.
5152
5253
Args:
5354
logger: Optional component which provides a log method to log messages. By default nothing would be logged.
5455
api_manager: Optional component which sends events to ODP.
56+
timeout: Optional event timeout in seconds.
5557
"""
5658
self.logger = logger or _logging.NoOpLogger()
57-
self.api_manager = api_manager or OdpEventApiManager(self.logger)
59+
self.api_manager = api_manager or OdpEventApiManager(self.logger, timeout)
5860

5961
self.odp_config: Optional[OdpConfig] = None
6062
self.api_key: Optional[str] = None
@@ -158,7 +160,9 @@ def _flush_batch(self) -> None:
158160

159161
for i in range(1 + self.retry_count):
160162
try:
161-
should_retry = self.api_manager.send_odp_events(self.api_key, self.api_host, self._current_batch)
163+
should_retry = self.api_manager.send_odp_events(self.api_key,
164+
self.api_host,
165+
self._current_batch)
162166
except Exception as error:
163167
should_retry = False
164168
self.logger.error(Errors.ODP_EVENT_FAILED.format(f'Error: {error} {self._current_batch}'))

Diff for: optimizely/odp/odp_manager.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ def __init__(
3333
segments_cache: Optional[OptimizelySegmentsCache] = None,
3434
segment_manager: Optional[OdpSegmentManager] = None,
3535
event_manager: Optional[OdpEventManager] = None,
36+
fetch_segments_timeout: Optional[int] = None,
37+
odp_event_timeout: Optional[int] = None,
3638
logger: Optional[optimizely_logger.Logger] = None
3739
) -> None:
3840

@@ -42,6 +44,7 @@ def __init__(
4244

4345
self.segment_manager = segment_manager
4446
self.event_manager = event_manager
47+
self.fetch_segments_timeout = fetch_segments_timeout
4548

4649
if not self.enabled:
4750
self.logger.info('ODP is disabled.')
@@ -53,9 +56,9 @@ def __init__(
5356
OdpSegmentsCacheConfig.DEFAULT_CAPACITY,
5457
OdpSegmentsCacheConfig.DEFAULT_TIMEOUT_SECS
5558
)
56-
self.segment_manager = OdpSegmentManager(segments_cache, logger=self.logger)
59+
self.segment_manager = OdpSegmentManager(segments_cache, logger=self.logger, timeout=fetch_segments_timeout)
5760

58-
self.event_manager = self.event_manager or OdpEventManager(self.logger)
61+
self.event_manager = self.event_manager or OdpEventManager(self.logger, timeout=odp_event_timeout)
5962
self.segment_manager.odp_config = self.odp_config
6063

6164
def fetch_qualified_segments(self, user_id: str, options: list[str]) -> Optional[list[str]]:

Diff for: optimizely/odp/odp_segment_api_manager.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,9 @@
108108
class OdpSegmentApiManager:
109109
"""Interface for manging the fetching of audience segments."""
110110

111-
def __init__(self, logger: Optional[optimizely_logger.Logger] = None):
111+
def __init__(self, logger: Optional[optimizely_logger.Logger] = None, timeout: Optional[int] = None):
112112
self.logger = logger or optimizely_logger.NoOpLogger()
113+
self.timeout = timeout or OdpSegmentApiConfig.REQUEST_TIMEOUT
113114

114115
def fetch_segments(self, api_key: str, api_host: str, user_key: str,
115116
user_value: str, segments_to_check: list[str]) -> Optional[list[str]]:
@@ -151,7 +152,7 @@ def fetch_segments(self, api_key: str, api_host: str, user_key: str,
151152
response = requests.post(url=url,
152153
headers=request_headers,
153154
data=payload_dict,
154-
timeout=OdpSegmentApiConfig.REQUEST_TIMEOUT)
155+
timeout=self.timeout)
155156

156157
response.raise_for_status()
157158
response_dict = response.json()

Diff for: optimizely/odp/odp_segment_manager.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,16 @@ def __init__(
3030
self,
3131
segments_cache: OptimizelySegmentsCache,
3232
api_manager: Optional[OdpSegmentApiManager] = None,
33-
logger: Optional[optimizely_logger.Logger] = None
33+
logger: Optional[optimizely_logger.Logger] = None,
34+
timeout: Optional[int] = None
3435
) -> None:
3536

3637
self.odp_config: Optional[OdpConfig] = None
3738
self.segments_cache = segments_cache
3839
self.logger = logger or optimizely_logger.NoOpLogger()
39-
self.api_manager = api_manager or OdpSegmentApiManager(self.logger)
40+
self.api_manager = api_manager or OdpSegmentApiManager(self.logger, timeout)
4041

41-
def fetch_qualified_segments(self, user_key: str, user_value: str, options: list[str]
42-
) -> Optional[list[str]]:
42+
def fetch_qualified_segments(self, user_key: str, user_value: str, options: list[str]) -> Optional[list[str]]:
4343
"""
4444
Args:
4545
user_key: The key for identifying the id type.

Diff for: optimizely/optimizely.py

+2
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ def __init__(
150150
self.sdk_settings.segments_cache,
151151
self.sdk_settings.odp_segment_manager,
152152
self.sdk_settings.odp_event_manager,
153+
self.sdk_settings.fetch_segments_timeout,
154+
self.sdk_settings.odp_event_timeout,
153155
self.logger
154156
)
155157

Diff for: tests/test_odp_event_api_manager.py

+13
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,19 @@ def test_send_odp_events__valid_request(self):
4545
data=json.dumps(self.events, cls=OdpEventEncoder),
4646
timeout=OdpEventApiConfig.REQUEST_TIMEOUT)
4747

48+
def test_send_odp_events__custom_timeout(self):
49+
with mock.patch('requests.post') as mock_request_post:
50+
api = OdpEventApiManager(timeout=14)
51+
api.send_odp_events(api_key=self.api_key,
52+
api_host=self.api_host,
53+
events=self.events)
54+
55+
request_headers = {'content-type': 'application/json', 'x-api-key': self.api_key}
56+
mock_request_post.assert_called_once_with(url=self.api_host + "/v3/events",
57+
headers=request_headers,
58+
data=json.dumps(self.events, cls=OdpEventEncoder),
59+
timeout=14)
60+
4861
def test_send_odp_ovents_success(self):
4962
with mock.patch('requests.post') as mock_request_post:
5063
# no need to mock url and content because we're not returning the response

Diff for: tests/test_odp_event_manager.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ class OdpEventManagerTest(BaseTest):
7171
"key-3": 3.0,
7272
"key-4": None,
7373
"key-5": True
74-
}
74+
},
7575
},
7676
{
7777
"type": "t2",

Diff for: tests/test_odp_segment_api_manager.py

+21
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,27 @@ def test_fetch_qualified_segments__valid_request(self):
4848
data=json.dumps(test_payload),
4949
timeout=OdpSegmentApiConfig.REQUEST_TIMEOUT)
5050

51+
def test_fetch_qualified_segments__custom_timeout(self):
52+
with mock.patch('requests.post') as mock_request_post:
53+
api = OdpSegmentApiManager(timeout=12)
54+
api.fetch_segments(api_key=self.api_key,
55+
api_host=self.api_host,
56+
user_key=self.user_key,
57+
user_value=self.user_value,
58+
segments_to_check=["a", "b", "c"])
59+
60+
test_payload = {
61+
'query': 'query($userId: String, $audiences: [String]) {'
62+
'customer(vuid: $userId) '
63+
'{audiences(subset: $audiences) {edges {node {name state}}}}}',
64+
'variables': {'userId': self.user_value, 'audiences': ["a", "b", "c"]}
65+
}
66+
request_headers = {'content-type': 'application/json', 'x-api-key': self.api_key}
67+
mock_request_post.assert_called_once_with(url=self.api_host + "/v3/graphql",
68+
headers=request_headers,
69+
data=json.dumps(test_payload),
70+
timeout=12)
71+
5172
def test_fetch_qualified_segments__success(self):
5273
with mock.patch('requests.post') as mock_request_post:
5374
mock_request_post.return_value = \

0 commit comments

Comments
 (0)