Skip to content

Commit aa25e16

Browse files
authored
feat: configurable timeout and retries (#292)
1 parent 1181cf2 commit aa25e16

File tree

13 files changed

+108
-16
lines changed

13 files changed

+108
-16
lines changed

UnleashClient/__init__.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,13 @@
1313
from apscheduler.triggers.interval import IntervalTrigger
1414

1515
from UnleashClient.api import register_client
16-
from UnleashClient.constants import DISABLED_VARIATION, ETAG, METRIC_LAST_SENT_TIME
16+
from UnleashClient.constants import (
17+
DISABLED_VARIATION,
18+
ETAG,
19+
METRIC_LAST_SENT_TIME,
20+
REQUEST_RETRIES,
21+
REQUEST_TIMEOUT,
22+
)
1723
from UnleashClient.events import UnleashEvent, UnleashEventType
1824
from UnleashClient.features import Feature
1925
from UnleashClient.loader import load_features
@@ -49,6 +55,8 @@ class UnleashClient:
4955
:param environment: Name of the environment using the unleash client, optional & defaults to "default".
5056
:param instance_id: Unique identifier for unleash client instance, optional & defaults to "unleash-client-python"
5157
:param refresh_interval: Provisioning refresh interval in seconds, optional & defaults to 15 seconds
58+
:params request_timeout: Timeout for requests to unleash server in seconds, optional & defaults to 30 seconds
59+
:params request_retries: Number of retries for requests to unleash server, optional & defaults to 3
5260
:param refresh_jitter: Provisioning refresh interval jitter in seconds, optional & defaults to None
5361
:param metrics_interval: Metrics refresh interval in seconds, optional & defaults to 60 seconds
5462
:param metrics_jitter: Metrics refresh interval jitter in seconds, optional & defaults to None
@@ -80,6 +88,8 @@ def __init__(
8088
disable_registration: bool = False,
8189
custom_headers: Optional[dict] = None,
8290
custom_options: Optional[dict] = None,
91+
request_timeout: int = REQUEST_TIMEOUT,
92+
request_retries: int = REQUEST_RETRIES,
8393
custom_strategies: Optional[dict] = None,
8494
cache_directory: Optional[str] = None,
8595
project_name: Optional[str] = None,
@@ -100,6 +110,8 @@ def __init__(
100110
self.unleash_environment = environment
101111
self.unleash_instance_id = instance_id
102112
self.unleash_refresh_interval = refresh_interval
113+
self.unleash_request_timeout = request_timeout
114+
self.unleash_request_retries = request_retries
103115
self.unleash_refresh_jitter = (
104116
int(refresh_jitter) if refresh_jitter is not None else None
105117
)
@@ -224,6 +236,7 @@ def initialize_client(self, fetch_toggles: bool = True) -> None:
224236
"custom_options": self.unleash_custom_options,
225237
"features": self.features,
226238
"cache": self.cache,
239+
"request_timeout": self.unleash_request_timeout,
227240
}
228241

229242
# Register app
@@ -236,6 +249,7 @@ def initialize_client(self, fetch_toggles: bool = True) -> None:
236249
self.unleash_custom_headers,
237250
self.unleash_custom_options,
238251
self.strategy_mapping,
252+
self.unleash_request_timeout,
239253
)
240254

241255
if fetch_toggles:
@@ -248,6 +262,8 @@ def initialize_client(self, fetch_toggles: bool = True) -> None:
248262
"cache": self.cache,
249263
"features": self.features,
250264
"strategy_mapping": self.strategy_mapping,
265+
"request_timeout": self.unleash_request_timeout,
266+
"request_retries": self.unleash_request_retries,
251267
"project": self.unleash_project_name,
252268
}
253269
job_func: Callable = fetch_and_load_features

UnleashClient/api/features.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from requests.adapters import HTTPAdapter
55
from urllib3 import Retry
66

7-
from UnleashClient.constants import FEATURES_URL, REQUEST_RETRIES, REQUEST_TIMEOUT
7+
from UnleashClient.constants import FEATURES_URL
88
from UnleashClient.utils import LOGGER, log_resp_info
99

1010

@@ -15,10 +15,10 @@ def get_feature_toggles(
1515
instance_id: str,
1616
custom_headers: dict,
1717
custom_options: dict,
18+
request_timeout: int,
19+
request_retries: int,
1820
project: Optional[str] = None,
1921
cached_etag: str = "",
20-
request_timeout: int = REQUEST_TIMEOUT,
21-
request_retries: int = REQUEST_RETRIES,
2222
) -> Tuple[dict, str]:
2323
"""
2424
Retrieves feature flags from unleash central server.
@@ -32,6 +32,8 @@ def get_feature_toggles(
3232
:param instance_id:
3333
:param custom_headers:
3434
:param custom_options:
35+
:param request_timeout:
36+
:param request_retries:
3537
:param project:
3638
:param cached_etag:
3739
:return: (Feature flags, etag) if successful, ({},'') if not

UnleashClient/api/metrics.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import requests
44

5-
from UnleashClient.constants import APPLICATION_HEADERS, METRICS_URL, REQUEST_TIMEOUT
5+
from UnleashClient.constants import APPLICATION_HEADERS, METRICS_URL
66
from UnleashClient.utils import LOGGER, log_resp_info
77

88

@@ -12,7 +12,7 @@ def send_metrics(
1212
request_body: dict,
1313
custom_headers: dict,
1414
custom_options: dict,
15-
request_timeout: int = REQUEST_TIMEOUT,
15+
request_timeout: int,
1616
) -> bool:
1717
"""
1818
Attempts to send metrics to Unleash server
@@ -24,6 +24,7 @@ def send_metrics(
2424
:param request_body:
2525
:param custom_headers:
2626
:param custom_options:
27+
:param request_timeout:
2728
:return: true if registration successful, false if registration unsuccessful or exception.
2829
"""
2930
try:

UnleashClient/api/register.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from UnleashClient.constants import (
88
APPLICATION_HEADERS,
99
REGISTER_URL,
10-
REQUEST_TIMEOUT,
1110
SDK_NAME,
1211
SDK_VERSION,
1312
)
@@ -23,7 +22,7 @@ def register_client(
2322
custom_headers: dict,
2423
custom_options: dict,
2524
supported_strategies: dict,
26-
request_timeout=REQUEST_TIMEOUT,
25+
request_timeout: int,
2726
) -> bool:
2827
"""
2928
Attempts to register client with unleash server.
@@ -39,6 +38,7 @@ def register_client(
3938
:param custom_headers:
4039
:param custom_options:
4140
:param supported_strategies:
41+
:param request_timeout:
4242
:return: true if registration successful, false if registration unsuccessful or exception.
4343
"""
4444
registation_request = {

UnleashClient/periodic_tasks/fetch_and_load.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ def fetch_and_load_features(
1616
cache: BaseCache,
1717
features: dict,
1818
strategy_mapping: dict,
19+
request_timeout: int,
20+
request_retries: int,
1921
project: Optional[str] = None,
2022
) -> None:
2123
(feature_provisioning, etag) = get_feature_toggles(
@@ -24,6 +26,8 @@ def fetch_and_load_features(
2426
instance_id,
2527
custom_headers,
2628
custom_options,
29+
request_timeout,
30+
request_retries,
2731
project,
2832
cache.get(ETAG),
2933
)

UnleashClient/periodic_tasks/send_metrics.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ def aggregate_and_send_metrics(
1515
custom_options: dict,
1616
features: dict,
1717
cache: BaseCache,
18+
request_timeout: int,
1819
) -> None:
1920
feature_stats_list = []
2021

@@ -44,7 +45,9 @@ def aggregate_and_send_metrics(
4445
}
4546

4647
if feature_stats_list:
47-
send_metrics(url, metrics_request, custom_headers, custom_options)
48+
send_metrics(
49+
url, metrics_request, custom_headers, custom_options, request_timeout
50+
)
4851
cache.set(METRIC_LAST_SENT_TIME, datetime.now(timezone.utc))
4952
else:
5053
LOGGER.debug("No feature flags with metrics, skipping metrics submission.")

tests/unit_tests/api/test_feature.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
INSTANCE_ID,
1414
PROJECT_NAME,
1515
PROJECT_URL,
16+
REQUEST_RETRIES,
17+
REQUEST_TIMEOUT,
1618
URL,
1719
)
1820
from UnleashClient.api import get_feature_toggles
@@ -46,7 +48,13 @@ def test_get_feature_toggle(response, status, calls, expected):
4648
)
4749

4850
(result, etag) = get_feature_toggles(
49-
URL, APP_NAME, INSTANCE_ID, CUSTOM_HEADERS, CUSTOM_OPTIONS
51+
URL,
52+
APP_NAME,
53+
INSTANCE_ID,
54+
CUSTOM_HEADERS,
55+
CUSTOM_OPTIONS,
56+
REQUEST_TIMEOUT,
57+
REQUEST_RETRIES,
5058
)
5159

5260
assert len(responses.calls) == calls
@@ -64,7 +72,14 @@ def test_get_feature_toggle_project():
6472
)
6573

6674
(result, etag) = get_feature_toggles(
67-
URL, APP_NAME, INSTANCE_ID, CUSTOM_HEADERS, CUSTOM_OPTIONS, PROJECT_NAME
75+
URL,
76+
APP_NAME,
77+
INSTANCE_ID,
78+
CUSTOM_HEADERS,
79+
CUSTOM_OPTIONS,
80+
REQUEST_TIMEOUT,
81+
REQUEST_RETRIES,
82+
PROJECT_NAME,
6883
)
6984

7085
assert len(responses.calls) == 1
@@ -79,7 +94,14 @@ def test_get_feature_toggle_failed_etag():
7994
)
8095

8196
(result, etag) = get_feature_toggles(
82-
URL, APP_NAME, INSTANCE_ID, CUSTOM_HEADERS, CUSTOM_OPTIONS, PROJECT_NAME
97+
URL,
98+
APP_NAME,
99+
INSTANCE_ID,
100+
CUSTOM_HEADERS,
101+
CUSTOM_OPTIONS,
102+
REQUEST_TIMEOUT,
103+
REQUEST_RETRIES,
104+
PROJECT_NAME,
83105
)
84106

85107
assert len(responses.calls) == 4
@@ -96,6 +118,8 @@ def test_get_feature_toggle_etag_present():
96118
INSTANCE_ID,
97119
CUSTOM_HEADERS,
98120
CUSTOM_OPTIONS,
121+
REQUEST_TIMEOUT,
122+
REQUEST_RETRIES,
99123
PROJECT_NAME,
100124
ETAG_VALUE,
101125
)
@@ -123,6 +147,8 @@ def test_get_feature_toggle_retries():
123147
INSTANCE_ID,
124148
CUSTOM_HEADERS,
125149
CUSTOM_OPTIONS,
150+
REQUEST_TIMEOUT,
151+
REQUEST_RETRIES,
126152
PROJECT_NAME,
127153
ETAG_VALUE,
128154
)

tests/unit_tests/api/test_metrics.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@
33
from requests import ConnectionError
44

55
from tests.utilities.mocks.mock_metrics import MOCK_METRICS_REQUEST
6-
from tests.utilities.testing_constants import CUSTOM_HEADERS, CUSTOM_OPTIONS, URL
6+
from tests.utilities.testing_constants import (
7+
CUSTOM_HEADERS,
8+
CUSTOM_OPTIONS,
9+
REQUEST_TIMEOUT,
10+
URL,
11+
)
712
from UnleashClient.api import send_metrics
813
from UnleashClient.constants import METRICS_URL
914

@@ -27,7 +32,9 @@
2732
def test_send_metrics(payload, status, expected):
2833
responses.add(responses.POST, FULL_METRICS_URL, **payload, status=status)
2934

30-
result = send_metrics(URL, MOCK_METRICS_REQUEST, CUSTOM_HEADERS, CUSTOM_OPTIONS)
35+
result = send_metrics(
36+
URL, MOCK_METRICS_REQUEST, CUSTOM_HEADERS, CUSTOM_OPTIONS, REQUEST_TIMEOUT
37+
)
3138

3239
assert len(responses.calls) == 1
3340
assert expected(result)

tests/unit_tests/api/test_register.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
DEFAULT_STRATEGY_MAPPING,
1010
INSTANCE_ID,
1111
METRICS_INTERVAL,
12+
REQUEST_TIMEOUT,
1213
URL,
1314
)
1415
from UnleashClient.api import register_client
@@ -42,6 +43,7 @@ def test_register_client(payload, status, expected):
4243
CUSTOM_HEADERS,
4344
CUSTOM_OPTIONS,
4445
DEFAULT_STRATEGY_MAPPING,
46+
REQUEST_TIMEOUT,
4547
)
4648

4749
assert len(responses.calls) == 1

tests/unit_tests/periodic/test_aggregate_and_send_metrics.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
CUSTOM_OPTIONS,
1111
INSTANCE_ID,
1212
IP_LIST,
13+
REQUEST_TIMEOUT,
1314
URL,
1415
)
1516
from UnleashClient.cache import FileCache
@@ -55,7 +56,14 @@ def test_aggregate_and_send_metrics():
5556
features = {"My Feature1": my_feature1, "My Feature 2": my_feature2}
5657

5758
aggregate_and_send_metrics(
58-
URL, APP_NAME, INSTANCE_ID, CUSTOM_HEADERS, CUSTOM_OPTIONS, features, cache
59+
URL,
60+
APP_NAME,
61+
INSTANCE_ID,
62+
CUSTOM_HEADERS,
63+
CUSTOM_OPTIONS,
64+
features,
65+
cache,
66+
REQUEST_TIMEOUT,
5967
)
6068

6169
assert len(responses.calls) == 1
@@ -88,7 +96,14 @@ def test_no_metrics():
8896
features = {"My Feature1": my_feature1}
8997

9098
aggregate_and_send_metrics(
91-
URL, APP_NAME, INSTANCE_ID, CUSTOM_HEADERS, CUSTOM_OPTIONS, features, cache
99+
URL,
100+
APP_NAME,
101+
INSTANCE_ID,
102+
CUSTOM_HEADERS,
103+
CUSTOM_OPTIONS,
104+
features,
105+
cache,
106+
REQUEST_TIMEOUT,
92107
)
93108

94109
assert len(responses.calls) == 0

0 commit comments

Comments
 (0)