Skip to content

Commit

Permalink
feat: configurable timeout and retries (#292)
Browse files Browse the repository at this point in the history
  • Loading branch information
kwasniew authored Nov 24, 2023
1 parent 1181cf2 commit aa25e16
Show file tree
Hide file tree
Showing 13 changed files with 108 additions and 16 deletions.
18 changes: 17 additions & 1 deletion UnleashClient/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@
from apscheduler.triggers.interval import IntervalTrigger

from UnleashClient.api import register_client
from UnleashClient.constants import DISABLED_VARIATION, ETAG, METRIC_LAST_SENT_TIME
from UnleashClient.constants import (
DISABLED_VARIATION,
ETAG,
METRIC_LAST_SENT_TIME,
REQUEST_RETRIES,
REQUEST_TIMEOUT,
)
from UnleashClient.events import UnleashEvent, UnleashEventType
from UnleashClient.features import Feature
from UnleashClient.loader import load_features
Expand Down Expand Up @@ -49,6 +55,8 @@ class UnleashClient:
:param environment: Name of the environment using the unleash client, optional & defaults to "default".
:param instance_id: Unique identifier for unleash client instance, optional & defaults to "unleash-client-python"
:param refresh_interval: Provisioning refresh interval in seconds, optional & defaults to 15 seconds
:params request_timeout: Timeout for requests to unleash server in seconds, optional & defaults to 30 seconds
:params request_retries: Number of retries for requests to unleash server, optional & defaults to 3
:param refresh_jitter: Provisioning refresh interval jitter in seconds, optional & defaults to None
:param metrics_interval: Metrics refresh interval in seconds, optional & defaults to 60 seconds
:param metrics_jitter: Metrics refresh interval jitter in seconds, optional & defaults to None
Expand Down Expand Up @@ -80,6 +88,8 @@ def __init__(
disable_registration: bool = False,
custom_headers: Optional[dict] = None,
custom_options: Optional[dict] = None,
request_timeout: int = REQUEST_TIMEOUT,
request_retries: int = REQUEST_RETRIES,
custom_strategies: Optional[dict] = None,
cache_directory: Optional[str] = None,
project_name: Optional[str] = None,
Expand All @@ -100,6 +110,8 @@ def __init__(
self.unleash_environment = environment
self.unleash_instance_id = instance_id
self.unleash_refresh_interval = refresh_interval
self.unleash_request_timeout = request_timeout
self.unleash_request_retries = request_retries
self.unleash_refresh_jitter = (
int(refresh_jitter) if refresh_jitter is not None else None
)
Expand Down Expand Up @@ -224,6 +236,7 @@ def initialize_client(self, fetch_toggles: bool = True) -> None:
"custom_options": self.unleash_custom_options,
"features": self.features,
"cache": self.cache,
"request_timeout": self.unleash_request_timeout,
}

# Register app
Expand All @@ -236,6 +249,7 @@ def initialize_client(self, fetch_toggles: bool = True) -> None:
self.unleash_custom_headers,
self.unleash_custom_options,
self.strategy_mapping,
self.unleash_request_timeout,
)

if fetch_toggles:
Expand All @@ -248,6 +262,8 @@ def initialize_client(self, fetch_toggles: bool = True) -> None:
"cache": self.cache,
"features": self.features,
"strategy_mapping": self.strategy_mapping,
"request_timeout": self.unleash_request_timeout,
"request_retries": self.unleash_request_retries,
"project": self.unleash_project_name,
}
job_func: Callable = fetch_and_load_features
Expand Down
8 changes: 5 additions & 3 deletions UnleashClient/api/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from requests.adapters import HTTPAdapter
from urllib3 import Retry

from UnleashClient.constants import FEATURES_URL, REQUEST_RETRIES, REQUEST_TIMEOUT
from UnleashClient.constants import FEATURES_URL
from UnleashClient.utils import LOGGER, log_resp_info


Expand All @@ -15,10 +15,10 @@ def get_feature_toggles(
instance_id: str,
custom_headers: dict,
custom_options: dict,
request_timeout: int,
request_retries: int,
project: Optional[str] = None,
cached_etag: str = "",
request_timeout: int = REQUEST_TIMEOUT,
request_retries: int = REQUEST_RETRIES,
) -> Tuple[dict, str]:
"""
Retrieves feature flags from unleash central server.
Expand All @@ -32,6 +32,8 @@ def get_feature_toggles(
:param instance_id:
:param custom_headers:
:param custom_options:
:param request_timeout:
:param request_retries:
:param project:
:param cached_etag:
:return: (Feature flags, etag) if successful, ({},'') if not
Expand Down
5 changes: 3 additions & 2 deletions UnleashClient/api/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import requests

from UnleashClient.constants import APPLICATION_HEADERS, METRICS_URL, REQUEST_TIMEOUT
from UnleashClient.constants import APPLICATION_HEADERS, METRICS_URL
from UnleashClient.utils import LOGGER, log_resp_info


Expand All @@ -12,7 +12,7 @@ def send_metrics(
request_body: dict,
custom_headers: dict,
custom_options: dict,
request_timeout: int = REQUEST_TIMEOUT,
request_timeout: int,
) -> bool:
"""
Attempts to send metrics to Unleash server
Expand All @@ -24,6 +24,7 @@ def send_metrics(
:param request_body:
:param custom_headers:
:param custom_options:
:param request_timeout:
:return: true if registration successful, false if registration unsuccessful or exception.
"""
try:
Expand Down
4 changes: 2 additions & 2 deletions UnleashClient/api/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from UnleashClient.constants import (
APPLICATION_HEADERS,
REGISTER_URL,
REQUEST_TIMEOUT,
SDK_NAME,
SDK_VERSION,
)
Expand All @@ -23,7 +22,7 @@ def register_client(
custom_headers: dict,
custom_options: dict,
supported_strategies: dict,
request_timeout=REQUEST_TIMEOUT,
request_timeout: int,
) -> bool:
"""
Attempts to register client with unleash server.
Expand All @@ -39,6 +38,7 @@ def register_client(
:param custom_headers:
:param custom_options:
:param supported_strategies:
:param request_timeout:
:return: true if registration successful, false if registration unsuccessful or exception.
"""
registation_request = {
Expand Down
4 changes: 4 additions & 0 deletions UnleashClient/periodic_tasks/fetch_and_load.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ def fetch_and_load_features(
cache: BaseCache,
features: dict,
strategy_mapping: dict,
request_timeout: int,
request_retries: int,
project: Optional[str] = None,
) -> None:
(feature_provisioning, etag) = get_feature_toggles(
Expand All @@ -24,6 +26,8 @@ def fetch_and_load_features(
instance_id,
custom_headers,
custom_options,
request_timeout,
request_retries,
project,
cache.get(ETAG),
)
Expand Down
5 changes: 4 additions & 1 deletion UnleashClient/periodic_tasks/send_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def aggregate_and_send_metrics(
custom_options: dict,
features: dict,
cache: BaseCache,
request_timeout: int,
) -> None:
feature_stats_list = []

Expand Down Expand Up @@ -44,7 +45,9 @@ def aggregate_and_send_metrics(
}

if feature_stats_list:
send_metrics(url, metrics_request, custom_headers, custom_options)
send_metrics(
url, metrics_request, custom_headers, custom_options, request_timeout
)
cache.set(METRIC_LAST_SENT_TIME, datetime.now(timezone.utc))
else:
LOGGER.debug("No feature flags with metrics, skipping metrics submission.")
32 changes: 29 additions & 3 deletions tests/unit_tests/api/test_feature.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
INSTANCE_ID,
PROJECT_NAME,
PROJECT_URL,
REQUEST_RETRIES,
REQUEST_TIMEOUT,
URL,
)
from UnleashClient.api import get_feature_toggles
Expand Down Expand Up @@ -46,7 +48,13 @@ def test_get_feature_toggle(response, status, calls, expected):
)

(result, etag) = get_feature_toggles(
URL, APP_NAME, INSTANCE_ID, CUSTOM_HEADERS, CUSTOM_OPTIONS
URL,
APP_NAME,
INSTANCE_ID,
CUSTOM_HEADERS,
CUSTOM_OPTIONS,
REQUEST_TIMEOUT,
REQUEST_RETRIES,
)

assert len(responses.calls) == calls
Expand All @@ -64,7 +72,14 @@ def test_get_feature_toggle_project():
)

(result, etag) = get_feature_toggles(
URL, APP_NAME, INSTANCE_ID, CUSTOM_HEADERS, CUSTOM_OPTIONS, PROJECT_NAME
URL,
APP_NAME,
INSTANCE_ID,
CUSTOM_HEADERS,
CUSTOM_OPTIONS,
REQUEST_TIMEOUT,
REQUEST_RETRIES,
PROJECT_NAME,
)

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

(result, etag) = get_feature_toggles(
URL, APP_NAME, INSTANCE_ID, CUSTOM_HEADERS, CUSTOM_OPTIONS, PROJECT_NAME
URL,
APP_NAME,
INSTANCE_ID,
CUSTOM_HEADERS,
CUSTOM_OPTIONS,
REQUEST_TIMEOUT,
REQUEST_RETRIES,
PROJECT_NAME,
)

assert len(responses.calls) == 4
Expand All @@ -96,6 +118,8 @@ def test_get_feature_toggle_etag_present():
INSTANCE_ID,
CUSTOM_HEADERS,
CUSTOM_OPTIONS,
REQUEST_TIMEOUT,
REQUEST_RETRIES,
PROJECT_NAME,
ETAG_VALUE,
)
Expand Down Expand Up @@ -123,6 +147,8 @@ def test_get_feature_toggle_retries():
INSTANCE_ID,
CUSTOM_HEADERS,
CUSTOM_OPTIONS,
REQUEST_TIMEOUT,
REQUEST_RETRIES,
PROJECT_NAME,
ETAG_VALUE,
)
Expand Down
11 changes: 9 additions & 2 deletions tests/unit_tests/api/test_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
from requests import ConnectionError

from tests.utilities.mocks.mock_metrics import MOCK_METRICS_REQUEST
from tests.utilities.testing_constants import CUSTOM_HEADERS, CUSTOM_OPTIONS, URL
from tests.utilities.testing_constants import (
CUSTOM_HEADERS,
CUSTOM_OPTIONS,
REQUEST_TIMEOUT,
URL,
)
from UnleashClient.api import send_metrics
from UnleashClient.constants import METRICS_URL

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

result = send_metrics(URL, MOCK_METRICS_REQUEST, CUSTOM_HEADERS, CUSTOM_OPTIONS)
result = send_metrics(
URL, MOCK_METRICS_REQUEST, CUSTOM_HEADERS, CUSTOM_OPTIONS, REQUEST_TIMEOUT
)

assert len(responses.calls) == 1
assert expected(result)
2 changes: 2 additions & 0 deletions tests/unit_tests/api/test_register.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
DEFAULT_STRATEGY_MAPPING,
INSTANCE_ID,
METRICS_INTERVAL,
REQUEST_TIMEOUT,
URL,
)
from UnleashClient.api import register_client
Expand Down Expand Up @@ -42,6 +43,7 @@ def test_register_client(payload, status, expected):
CUSTOM_HEADERS,
CUSTOM_OPTIONS,
DEFAULT_STRATEGY_MAPPING,
REQUEST_TIMEOUT,
)

assert len(responses.calls) == 1
Expand Down
19 changes: 17 additions & 2 deletions tests/unit_tests/periodic/test_aggregate_and_send_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
CUSTOM_OPTIONS,
INSTANCE_ID,
IP_LIST,
REQUEST_TIMEOUT,
URL,
)
from UnleashClient.cache import FileCache
Expand Down Expand Up @@ -55,7 +56,14 @@ def test_aggregate_and_send_metrics():
features = {"My Feature1": my_feature1, "My Feature 2": my_feature2}

aggregate_and_send_metrics(
URL, APP_NAME, INSTANCE_ID, CUSTOM_HEADERS, CUSTOM_OPTIONS, features, cache
URL,
APP_NAME,
INSTANCE_ID,
CUSTOM_HEADERS,
CUSTOM_OPTIONS,
features,
cache,
REQUEST_TIMEOUT,
)

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

aggregate_and_send_metrics(
URL, APP_NAME, INSTANCE_ID, CUSTOM_HEADERS, CUSTOM_OPTIONS, features, cache
URL,
APP_NAME,
INSTANCE_ID,
CUSTOM_HEADERS,
CUSTOM_OPTIONS,
features,
cache,
REQUEST_TIMEOUT,
)

assert len(responses.calls) == 0
10 changes: 10 additions & 0 deletions tests/unit_tests/periodic/test_fetch_and_load.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
INSTANCE_ID,
PROJECT_NAME,
PROJECT_URL,
REQUEST_RETRIES,
REQUEST_TIMEOUT,
URL,
)
from UnleashClient.constants import ETAG, FEATURES_URL
Expand Down Expand Up @@ -44,6 +46,8 @@ def test_fetch_and_load(cache_empty): # noqa: F811
temp_cache,
in_memory_features,
DEFAULT_STRATEGY_MAPPING,
REQUEST_TIMEOUT,
REQUEST_RETRIES,
)

assert isinstance(in_memory_features["testFlag"], Feature)
Expand All @@ -68,6 +72,8 @@ def test_fetch_and_load_project(cache_empty): # noqa: F811
temp_cache,
in_memory_features,
DEFAULT_STRATEGY_MAPPING,
REQUEST_TIMEOUT,
REQUEST_RETRIES,
PROJECT_NAME,
)

Expand All @@ -93,6 +99,8 @@ def test_fetch_and_load_failure(cache_empty): # noqa: F811
temp_cache,
in_memory_features,
DEFAULT_STRATEGY_MAPPING,
REQUEST_TIMEOUT,
REQUEST_RETRIES,
)

# Fail next request
Expand All @@ -108,6 +116,8 @@ def test_fetch_and_load_failure(cache_empty): # noqa: F811
temp_cache,
in_memory_features,
DEFAULT_STRATEGY_MAPPING,
REQUEST_TIMEOUT,
REQUEST_RETRIES,
)

assert isinstance(in_memory_features["testFlag"], Feature)
Loading

0 comments on commit aa25e16

Please sign in to comment.