Skip to content

Commit daebf5f

Browse files
briandchoivanklee86
authored andcommitted
Add ability to forward request kwargs (#48)
* For example, if trying to get feature toggles from on-prem Unleash server, being able to pass `verify=False` to requests would allow for bypassing self-signed certificate verification * Also, refactored unit test to reduce code duplication
1 parent 2e04857 commit daebf5f

File tree

13 files changed

+66
-90
lines changed

13 files changed

+66
-90
lines changed

UnleashClient/__init__.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ def __init__(self,
2828
disable_metrics: bool = False,
2929
disable_registration: bool = False,
3030
custom_headers: dict = {},
31+
custom_options: dict = {},
3132
custom_strategies: dict = {},
3233
cache_directory: str = None) -> None:
3334
"""
@@ -41,6 +42,7 @@ def __init__(self,
4142
:param metrics_interval: Metrics refresh interval in ms, optional & defaults to 60 seconds
4243
:param disable_metrics: Disables sending metrics to unleash server, optional & defaults to false.
4344
:param custom_headers: Default headers to send to unleash server, optional & defaults to empty.
45+
:param custom_options: Default requests parameters, optional & defaults to empty.
4446
:param custom_strategies: Dictionary of custom strategy names : custom strategy objects
4547
:param cache_directory: Location of the cache directory. When unset, FCache will determine the location
4648
"""
@@ -54,6 +56,7 @@ def __init__(self,
5456
self.unleash_disable_metrics = disable_metrics
5557
self.unleash_disable_registration = disable_registration
5658
self.unleash_custom_headers = custom_headers
59+
self.unleash_custom_options = custom_options
5760
self.unleash_static_context = {
5861
"appName": self.unleash_app_name,
5962
"environment": self.unleash_environment
@@ -101,6 +104,7 @@ def initialize_client(self) -> None:
101104
"app_name": self.unleash_app_name,
102105
"instance_id": self.unleash_instance_id,
103106
"custom_headers": self.unleash_custom_headers,
107+
"custom_options": self.unleash_custom_options,
104108
"cache": self.cache,
105109
"features": self.features,
106110
"strategy_mapping": self.strategy_mapping
@@ -111,14 +115,16 @@ def initialize_client(self) -> None:
111115
"app_name": self.unleash_app_name,
112116
"instance_id": self.unleash_instance_id,
113117
"custom_headers": self.unleash_custom_headers,
118+
"custom_options": self.unleash_custom_options,
114119
"features": self.features,
115120
"ondisk_cache": self.cache
116121
}
117122

118123
# Register app
119124
if not self.unleash_disable_registration:
120125
register_client(self.unleash_url, self.unleash_app_name, self.unleash_instance_id,
121-
self.unleash_metrics_interval, self.unleash_custom_headers, self.strategy_mapping)
126+
self.unleash_metrics_interval, self.unleash_custom_headers,
127+
self.unleash_custom_options, self.strategy_mapping)
122128

123129
fetch_and_load_features(**fl_args)
124130

UnleashClient/api/features.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
def get_feature_toggles(url: str,
88
app_name: str,
99
instance_id: str,
10-
custom_headers: dict) -> dict:
10+
custom_headers: dict,
11+
custom_options: dict) -> dict:
1112
"""
1213
Retrieves feature flags from unleash central server.
1314
@@ -19,6 +20,7 @@ def get_feature_toggles(url: str,
1920
:param app_name:
2021
:param instance_id:
2122
:param custom_headers:
23+
:param custom_options:
2224
:return: Feature flags if successful, empty dict if not.
2325
"""
2426
try:
@@ -31,7 +33,7 @@ def get_feature_toggles(url: str,
3133

3234
resp = requests.get(url + FEATURES_URL,
3335
headers={**custom_headers, **headers},
34-
timeout=REQUEST_TIMEOUT)
36+
timeout=REQUEST_TIMEOUT, **custom_options)
3537

3638
if resp.status_code != 200:
3739
LOGGER.warning("unleash feature fetch failed!")

UnleashClient/api/metrics.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
# pylint: disable=broad-except
88
def send_metrics(url: str,
99
request_body: dict,
10-
custom_headers: dict) -> bool:
10+
custom_headers: dict,
11+
custom_options: dict) -> bool:
1112
"""
1213
Attempts to send metrics to Unleash server
1314
@@ -19,6 +20,7 @@ def send_metrics(url: str,
1920
:param instance_id:
2021
:param metrics_interval:
2122
:param custom_headers:
23+
:param custom_options:
2224
:return: true if registration successful, false if registration unsuccessful or exception.
2325
"""
2426
try:
@@ -28,7 +30,7 @@ def send_metrics(url: str,
2830
resp = requests.post(url + METRICS_URL,
2931
data=json.dumps(request_body),
3032
headers={**custom_headers, **APPLICATION_HEADERS},
31-
timeout=REQUEST_TIMEOUT)
33+
timeout=REQUEST_TIMEOUT, **custom_options)
3234

3335
if resp.status_code != 202:
3436
LOGGER.warning("unleash metrics submission failed.")

UnleashClient/api/register.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ def register_client(url: str,
1111
instance_id: str,
1212
metrics_interval: int,
1313
custom_headers: dict,
14+
custom_options: dict,
1415
supported_strategies: dict) -> bool:
1516
"""
1617
Attempts to register client with unleash server.
@@ -24,6 +25,7 @@ def register_client(url: str,
2425
:param instance_id:
2526
:param metrics_interval:
2627
:param custom_headers:
28+
:param custom_options:
2729
:param supported_strategies:
2830
:return: true if registration successful, false if registration unsuccessful or exception.
2931
"""
@@ -43,7 +45,7 @@ def register_client(url: str,
4345
resp = requests.post(url + REGISTER_URL,
4446
data=json.dumps(registation_request),
4547
headers={**custom_headers, **APPLICATION_HEADERS},
46-
timeout=REQUEST_TIMEOUT)
48+
timeout=REQUEST_TIMEOUT, **custom_options)
4749

4850
if resp.status_code != 202:
4951
LOGGER.warning("unleash client registration failed.")

UnleashClient/periodic_tasks/fetch_and_load.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ def fetch_and_load_features(url: str,
99
app_name: str,
1010
instance_id: str,
1111
custom_headers: dict,
12+
custom_options: dict,
1213
cache: FileCache,
1314
features: dict,
1415
strategy_mapping: dict) -> None:
15-
feature_provisioning = get_feature_toggles(url, app_name, instance_id, custom_headers)
16+
feature_provisioning = get_feature_toggles(url, app_name, instance_id, custom_headers, custom_options)
1617

1718
if feature_provisioning:
1819
cache[FEATURES_URL] = feature_provisioning

UnleashClient/periodic_tasks/send_metrics.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ def aggregate_and_send_metrics(url: str,
99
app_name: str,
1010
instance_id: str,
1111
custom_headers: dict,
12+
custom_options: dict,
1213
features: dict,
1314
ondisk_cache: fcache.cache
1415
) -> None:
@@ -35,6 +36,6 @@ def aggregate_and_send_metrics(url: str,
3536
}
3637
}
3738

38-
send_metrics(url, metrics_request, custom_headers)
39+
send_metrics(url, metrics_request, custom_headers, custom_options)
3940
ondisk_cache[METRIC_LAST_SENT_TIME] = datetime.now(timezone.utc)
4041
ondisk_cache.sync()
Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import responses
2+
from pytest import mark, param
23
from tests.utilities.mocks.mock_features import MOCK_FEATURE_RESPONSE
3-
from tests.utilities.testing_constants import URL, APP_NAME, INSTANCE_ID, CUSTOM_HEADERS
4+
from tests.utilities.testing_constants import URL, APP_NAME, INSTANCE_ID, CUSTOM_HEADERS, CUSTOM_OPTIONS
45
from UnleashClient.constants import FEATURES_URL
56
from UnleashClient.api import get_feature_toggles
67

@@ -9,26 +10,18 @@
910

1011

1112
@responses.activate
12-
def test_get_feature_toggle_success():
13-
responses.add(responses.GET, FULL_FEATURE_URL, json=MOCK_FEATURE_RESPONSE, status=200)
13+
@mark.parametrize("response,status,expected", (
14+
param(MOCK_FEATURE_RESPONSE, 200, lambda result: result["version"] == 1, id="success"),
15+
param({}, 500, lambda result: not result, id="failure"),
16+
))
17+
def test_get_feature_toggle(response, status, expected):
18+
responses.add(responses.GET, FULL_FEATURE_URL, json=response, status=status)
1419

1520
result = get_feature_toggles(URL,
1621
APP_NAME,
1722
INSTANCE_ID,
18-
CUSTOM_HEADERS)
23+
CUSTOM_HEADERS,
24+
CUSTOM_OPTIONS)
1925

2026
assert len(responses.calls) == 1
21-
assert result["version"] == 1
22-
23-
24-
@responses.activate
25-
def test_get_feature_toggle_failure():
26-
responses.add(responses.GET, FULL_FEATURE_URL, json={}, status=500)
27-
28-
result = get_feature_toggles(URL,
29-
APP_NAME,
30-
INSTANCE_ID,
31-
CUSTOM_HEADERS)
32-
33-
assert len(responses.calls) == 1
34-
assert not result
27+
assert expected(result)
Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import responses
2+
from pytest import mark, param
23
from requests import ConnectionError
3-
from tests.utilities.testing_constants import URL, CUSTOM_HEADERS
4+
from tests.utilities.testing_constants import URL, CUSTOM_HEADERS, CUSTOM_OPTIONS
45
from tests.utilities.mocks.mock_metrics import MOCK_METRICS_REQUEST
56
from UnleashClient.constants import METRICS_URL
67
from UnleashClient.api import send_metrics
@@ -10,30 +11,15 @@
1011

1112

1213
@responses.activate
13-
def test_send_metrics_success():
14-
responses.add(responses.POST, FULL_METRICS_URL, json={}, status=202)
14+
@mark.parametrize("payload,status,expected", (
15+
param({"json": {}}, 202, lambda result: result, id="success"),
16+
param({"json": {}}, 500, lambda result: not result, id="failure"),
17+
param({"body": ConnectionError("Test connection error.")}, 200, lambda result: not result, id="exception"),
18+
))
19+
def test_send_metrics(payload, status, expected):
20+
responses.add(responses.POST, FULL_METRICS_URL, **payload, status=status)
1521

16-
result = send_metrics(URL, MOCK_METRICS_REQUEST, CUSTOM_HEADERS)
22+
result = send_metrics(URL, MOCK_METRICS_REQUEST, CUSTOM_HEADERS, CUSTOM_OPTIONS)
1723

1824
assert len(responses.calls) == 1
19-
assert result
20-
21-
22-
@responses.activate
23-
def test_send_metrics_failure():
24-
responses.add(responses.POST, FULL_METRICS_URL, json={}, status=500)
25-
26-
result = send_metrics(URL, MOCK_METRICS_REQUEST, CUSTOM_HEADERS)
27-
28-
assert len(responses.calls) == 1
29-
assert not result
30-
31-
32-
@responses.activate
33-
def test_register_client_exception():
34-
responses.add(responses.POST, FULL_METRICS_URL, body=ConnectionError("Test connection error."), status=200)
35-
36-
result = send_metrics(URL, MOCK_METRICS_REQUEST, CUSTOM_HEADERS)
37-
38-
assert len(responses.calls) == 1
39-
assert not result
25+
assert expected(result)
Lines changed: 11 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,30 @@
11
import responses
2+
from pytest import mark, param
23
from requests import ConnectionError
34
from UnleashClient.constants import REGISTER_URL
45
from UnleashClient.api import register_client
5-
from tests.utilities.testing_constants import URL, APP_NAME, INSTANCE_ID, METRICS_INTERVAL, CUSTOM_HEADERS, DEFAULT_STRATEGY_MAPPING
6+
from tests.utilities.testing_constants import URL, APP_NAME, INSTANCE_ID, METRICS_INTERVAL, CUSTOM_HEADERS, CUSTOM_OPTIONS, DEFAULT_STRATEGY_MAPPING
67

78

89
FULL_REGISTER_URL = URL + REGISTER_URL
910

1011

1112
@responses.activate
12-
def test_register_client_success():
13-
responses.add(responses.POST, FULL_REGISTER_URL, json={}, status=202)
13+
@mark.parametrize("payload,status,expected", (
14+
param({"json": {}}, 202, True, id="success"),
15+
param({"json": {}}, 500, False, id="failure"),
16+
param({"body": ConnectionError("Test connection error")}, 200, False, id="exception"),
17+
))
18+
def test_register_client(payload, status, expected):
19+
responses.add(responses.POST, FULL_REGISTER_URL, **payload, status=status)
1420

1521
result = register_client(URL,
1622
APP_NAME,
1723
INSTANCE_ID,
1824
METRICS_INTERVAL,
1925
CUSTOM_HEADERS,
26+
CUSTOM_OPTIONS,
2027
DEFAULT_STRATEGY_MAPPING)
2128

2229
assert len(responses.calls) == 1
23-
assert result
24-
25-
26-
@responses.activate
27-
def test_register_client_failure():
28-
responses.add(responses.POST, FULL_REGISTER_URL, json={}, status=500)
29-
30-
result = register_client(URL,
31-
APP_NAME,
32-
INSTANCE_ID,
33-
METRICS_INTERVAL,
34-
CUSTOM_HEADERS,
35-
DEFAULT_STRATEGY_MAPPING)
36-
37-
assert len(responses.calls) == 1
38-
assert not result
39-
40-
41-
@responses.activate
42-
def test_register_client_exception():
43-
responses.add(responses.POST, FULL_REGISTER_URL, body=ConnectionError("Test connection error."), status=200)
44-
45-
result = register_client(URL,
46-
APP_NAME,
47-
INSTANCE_ID,
48-
METRICS_INTERVAL,
49-
CUSTOM_HEADERS,
50-
DEFAULT_STRATEGY_MAPPING)
51-
52-
assert len(responses.calls) == 1
53-
assert not result
30+
assert result is expected

tests/unit_tests/periodic/test_aggregate_and_send_metrics.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from datetime import datetime, timezone, timedelta
33
import responses
44
from fcache.cache import FileCache
5-
from tests.utilities.testing_constants import URL, APP_NAME, INSTANCE_ID, CUSTOM_HEADERS, IP_LIST
5+
from tests.utilities.testing_constants import URL, APP_NAME, INSTANCE_ID, CUSTOM_HEADERS, CUSTOM_OPTIONS, IP_LIST
66
from UnleashClient.constants import METRICS_URL, METRIC_LAST_SENT_TIME
77
from UnleashClient.periodic_tasks import aggregate_and_send_metrics
88
from UnleashClient.features import Feature
@@ -31,7 +31,7 @@ def test_aggregate_and_send_metrics():
3131

3232
features = {"My Feature1": my_feature1, "My Feature 2": my_feature2}
3333

34-
aggregate_and_send_metrics(URL, APP_NAME, INSTANCE_ID, CUSTOM_HEADERS, features, cache)
34+
aggregate_and_send_metrics(URL, APP_NAME, INSTANCE_ID, CUSTOM_HEADERS, CUSTOM_OPTIONS, features, cache)
3535

3636
assert len(responses.calls) == 1
3737
request = json.loads(responses.calls[0].request.body)

0 commit comments

Comments
 (0)