devcycle-python-server-sdk 3.5.1__py3-none-any.whl → 3.6.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- devcycle_python_sdk/VERSION.txt +1 -1
- devcycle_python_sdk/api/config_client.py +18 -6
- devcycle_python_sdk/managers/config_manager.py +7 -2
- {devcycle_python_server_sdk-3.5.1.dist-info → devcycle_python_server_sdk-3.6.1.dist-info}/METADATA +2 -2
- {devcycle_python_server_sdk-3.5.1.dist-info → devcycle_python_server_sdk-3.6.1.dist-info}/RECORD +10 -10
- test/api/test_config_client.py +24 -6
- test/managers/test_config_manager.py +32 -10
- {devcycle_python_server_sdk-3.5.1.dist-info → devcycle_python_server_sdk-3.6.1.dist-info}/LICENSE +0 -0
- {devcycle_python_server_sdk-3.5.1.dist-info → devcycle_python_server_sdk-3.6.1.dist-info}/WHEEL +0 -0
- {devcycle_python_server_sdk-3.5.1.dist-info → devcycle_python_server_sdk-3.6.1.dist-info}/top_level.txt +0 -0
devcycle_python_sdk/VERSION.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.6.1
|
|
@@ -2,7 +2,7 @@ import logging
|
|
|
2
2
|
import time
|
|
3
3
|
from http import HTTPStatus
|
|
4
4
|
from typing import Optional, Tuple
|
|
5
|
-
|
|
5
|
+
import email.utils
|
|
6
6
|
import requests
|
|
7
7
|
|
|
8
8
|
from devcycle_python_sdk.api.backoff import exponential_backoff
|
|
@@ -36,15 +36,15 @@ class ConfigAPIClient:
|
|
|
36
36
|
)
|
|
37
37
|
|
|
38
38
|
def get_config(
|
|
39
|
-
self, config_etag: Optional[str] = None
|
|
40
|
-
) -> Tuple[Optional[dict], Optional[str]]:
|
|
39
|
+
self, config_etag: Optional[str] = None, last_modified: Optional[str] = None
|
|
40
|
+
) -> Tuple[Optional[dict], Optional[str], Optional[str]]:
|
|
41
41
|
"""
|
|
42
42
|
Get the config from the server. If the config_etag is provided, the server will only return the config if it
|
|
43
43
|
has changed since the last request. If the config hasn't changed, the server will return a 304 Not Modified
|
|
44
44
|
response.
|
|
45
45
|
|
|
46
46
|
:param config_etag: The etag of the last config request
|
|
47
|
-
|
|
47
|
+
:param last_modified: Last modified RFC1123 Timestamp of the stored config
|
|
48
48
|
:return: A tuple containing the config and the etag of the config. If the config hasn't changed since the last
|
|
49
49
|
request, the config will be None and the etag will be the same as the last request.
|
|
50
50
|
"""
|
|
@@ -56,6 +56,8 @@ class ConfigAPIClient:
|
|
|
56
56
|
headers = {}
|
|
57
57
|
if config_etag:
|
|
58
58
|
headers["If-None-Match"] = config_etag
|
|
59
|
+
if last_modified:
|
|
60
|
+
headers["If-Modified-Since"] = last_modified
|
|
59
61
|
|
|
60
62
|
attempts = 1
|
|
61
63
|
while retries_remaining > 0:
|
|
@@ -74,7 +76,7 @@ class ConfigAPIClient:
|
|
|
74
76
|
elif res.status_code == HTTPStatus.NOT_MODIFIED:
|
|
75
77
|
# the config hasn't changed since the last request
|
|
76
78
|
# don't return anything
|
|
77
|
-
return None, config_etag
|
|
79
|
+
return None, config_etag, last_modified
|
|
78
80
|
elif res.status_code == HTTPStatus.NOT_FOUND:
|
|
79
81
|
# Not a retryable error
|
|
80
82
|
raise NotFoundError(url)
|
|
@@ -113,6 +115,16 @@ class ConfigAPIClient:
|
|
|
113
115
|
raise APIClientError(message="Retries exceeded", cause=request_error)
|
|
114
116
|
|
|
115
117
|
new_etag = res.headers.get("ETag", None)
|
|
118
|
+
new_lastmodified = res.headers.get("Last-Modified", None)
|
|
119
|
+
|
|
120
|
+
if last_modified and new_lastmodified:
|
|
121
|
+
stored_lm = email.utils.parsedate_to_datetime(last_modified)
|
|
122
|
+
response_lm = email.utils.parsedate_to_datetime(new_lastmodified)
|
|
123
|
+
if stored_lm > response_lm:
|
|
124
|
+
logger.warning(
|
|
125
|
+
"Request returned a last modified header older than the current stored timestamp. not saving config"
|
|
126
|
+
)
|
|
127
|
+
return None, None, None
|
|
116
128
|
|
|
117
129
|
data: dict = res.json()
|
|
118
|
-
return data, new_etag
|
|
130
|
+
return data, new_etag, new_lastmodified
|
|
@@ -30,6 +30,7 @@ class EnvironmentConfigManager(threading.Thread):
|
|
|
30
30
|
|
|
31
31
|
self._config: Optional[dict] = None
|
|
32
32
|
self._config_etag: Optional[str] = None
|
|
33
|
+
self._config_lastmodified: Optional[str] = None
|
|
33
34
|
|
|
34
35
|
self._config_api_client = ConfigAPIClient(self._sdk_key, self._options)
|
|
35
36
|
|
|
@@ -42,10 +43,13 @@ class EnvironmentConfigManager(threading.Thread):
|
|
|
42
43
|
|
|
43
44
|
def _get_config(self):
|
|
44
45
|
try:
|
|
45
|
-
new_config, new_etag = self._config_api_client.get_config(
|
|
46
|
-
config_etag=self._config_etag
|
|
46
|
+
new_config, new_etag, new_lastmodified = self._config_api_client.get_config(
|
|
47
|
+
config_etag=self._config_etag, last_modified=self._config_lastmodified
|
|
47
48
|
)
|
|
48
49
|
|
|
50
|
+
# Abort early if the last modified is before the sent one.
|
|
51
|
+
if new_config is None and new_etag is None and new_lastmodified is None:
|
|
52
|
+
return
|
|
49
53
|
if new_config is None and new_etag == self._config_etag:
|
|
50
54
|
# api not returning data and the etag is the same
|
|
51
55
|
# no change to the config since last request
|
|
@@ -60,6 +64,7 @@ class EnvironmentConfigManager(threading.Thread):
|
|
|
60
64
|
|
|
61
65
|
self._config = new_config
|
|
62
66
|
self._config_etag = new_etag
|
|
67
|
+
self._config_lastmodified = new_lastmodified
|
|
63
68
|
|
|
64
69
|
json_config = json.dumps(self._config)
|
|
65
70
|
self._local_bucketing.store_config(json_config)
|
{devcycle_python_server_sdk-3.5.1.dist-info → devcycle_python_server_sdk-3.6.1.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: devcycle-python-server-sdk
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.6.1
|
|
4
4
|
Summary: DevCycle Python SDK
|
|
5
5
|
Home-page: https://github.com/devcycleHQ/python-server-sdk
|
|
6
6
|
Author: UNKNOWN
|
|
@@ -11,7 +11,7 @@ Platform: UNKNOWN
|
|
|
11
11
|
Requires-Dist: setuptools >=21.0.0
|
|
12
12
|
Requires-Dist: urllib3 >=1.15.1
|
|
13
13
|
Requires-Dist: requests ~=2.31
|
|
14
|
-
Requires-Dist: wasmtime ==
|
|
14
|
+
Requires-Dist: wasmtime ==23.0.0
|
|
15
15
|
Requires-Dist: protobuf >=4.23.3
|
|
16
16
|
Requires-Dist: openfeature-sdk >=0.7.0
|
|
17
17
|
|
{devcycle_python_server_sdk-3.5.1.dist-info → devcycle_python_server_sdk-3.6.1.dist-info}/RECORD
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
devcycle_python_sdk/VERSION.txt,sha256=
|
|
1
|
+
devcycle_python_sdk/VERSION.txt,sha256=3ljYQb1wmqhaZuzFAN-7SOfCB-hMOZ4w51HozK1ujJs,6
|
|
2
2
|
devcycle_python_sdk/__init__.py,sha256=30TcYb9PbvVi97gwk16omCGG1UoUI9lmmU7xpvxNMvc,336
|
|
3
3
|
devcycle_python_sdk/bucketing-lib.release.wasm,sha256=yrNDrAxZS1h9VlTzsdOrNLP4ngnM04PmKxcWop1yrt4,202623
|
|
4
4
|
devcycle_python_sdk/cloud_client.py,sha256=UMfXvXwO4v3pZW9ZA52ivxPH1Lp4Goy5gpV_s0qCpxQ,7352
|
|
@@ -10,11 +10,11 @@ devcycle_python_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,
|
|
|
10
10
|
devcycle_python_sdk/api/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
11
11
|
devcycle_python_sdk/api/backoff.py,sha256=F6WYCloaZD9CjNu6mKBMq7K20S8-i2WVU6X05ZRoawE,303
|
|
12
12
|
devcycle_python_sdk/api/bucketing_client.py,sha256=-S0y9uAffippCTfjYgxI5j4j7YNfMxQJYUba1219nps,5111
|
|
13
|
-
devcycle_python_sdk/api/config_client.py,sha256=
|
|
13
|
+
devcycle_python_sdk/api/config_client.py,sha256=NnqHmPcwK__LNqPzmKo5EBqULAQEteH1YmzRE_eIXJs,5077
|
|
14
14
|
devcycle_python_sdk/api/event_client.py,sha256=WxkuV6geEC9MgG4FN36HeEQQa22ilq2mkOVMZkpiEk4,3356
|
|
15
15
|
devcycle_python_sdk/api/local_bucketing.py,sha256=-TfG3Jb0syVF3sqzzlFyKxHqLQ6a37C4mW0oj53J-yQ,16332
|
|
16
16
|
devcycle_python_sdk/managers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
-
devcycle_python_sdk/managers/config_manager.py,sha256=
|
|
17
|
+
devcycle_python_sdk/managers/config_manager.py,sha256=YfgGKW4mtMhqdwWZiZ_Y67AYLC9VTpTjYhpDgfGFA4g,3599
|
|
18
18
|
devcycle_python_sdk/managers/event_queue_manager.py,sha256=06TcdlRoB89C7vGH1UTnnoDFsSnT71tPwf0gn1NK-W0,8071
|
|
19
19
|
devcycle_python_sdk/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
20
|
devcycle_python_sdk/models/bucketed_config.py,sha256=NsdT-6RbEuDy5QuK5qlr6UqNKQQnwjoXVYN-uQp0P-E,3999
|
|
@@ -41,13 +41,13 @@ test/test_cloud_client.py,sha256=zjk302m7qKMCQryolkc1WKLFmbGWUxA0VtHlZk0rY-0,114
|
|
|
41
41
|
test/test_local_client.py,sha256=Ds6VIfLcaefboyvGrhQTGsdJj_DGB4IVnR2_16xDpSY,13022
|
|
42
42
|
test/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
43
43
|
test/api/test_bucketing_client.py,sha256=M2mjACowLvbsIjXdP-w8-fwY02xqv1X1r_h9v-pXa7A,7739
|
|
44
|
-
test/api/test_config_client.py,sha256=
|
|
44
|
+
test/api/test_config_client.py,sha256=jT48_llov5QQs0jNDbHANRqySVT1HamXm82uPZXKSrE,5129
|
|
45
45
|
test/api/test_event_client.py,sha256=v4Xoum-PdywfczrfMV1llM5vrIJoqHybzxPLVokh09g,3310
|
|
46
46
|
test/api/test_local_bucketing.py,sha256=lPL3ThZcGG-8-67jFxRvGrwL8NB6-sM5qGJb0p2O__Q,15254
|
|
47
47
|
test/fixture/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
48
48
|
test/fixture/data.py,sha256=6ReLxWPMhCDbJ5Ex24_iR0PJNjKzPFV1I9j_v0FVbBU,1462
|
|
49
49
|
test/managers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
50
|
-
test/managers/test_config_manager.py,sha256=
|
|
50
|
+
test/managers/test_config_manager.py,sha256=X0DxzWdjkvkUmarJSgzTDvO2Ma0TU6RNwL0WUwX2uA8,5963
|
|
51
51
|
test/managers/test_event_queue_manager.py,sha256=KquHBuYiDdoVStm5Ga9mIB-wvmJnChiFRBNC8sMKaxQ,11053
|
|
52
52
|
test/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
53
53
|
test/models/test_bucketed_config.py,sha256=8wCEyxaHxY21nl7ZbiFzp-IzZQBZIPIVPPzeREjumQk,858
|
|
@@ -59,8 +59,8 @@ test/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
59
59
|
test/util/test_strings.py,sha256=T5H5U0af7_ld1yJC8IdDmay_Mx-iJQ5Fi5fbvKcAc7A,862
|
|
60
60
|
test/util/test_utils.py,sha256=tVBJm_qnqg-4PssLpXIxqlk0JehuiAIPs4zpi2oNVnY,6671
|
|
61
61
|
test/util/test_version.py,sha256=3Xg0_84m0bdHXz6pxd2Z85Oz9DoUC1T0Ol_WqEH_jac,335
|
|
62
|
-
devcycle_python_server_sdk-3.
|
|
63
|
-
devcycle_python_server_sdk-3.
|
|
64
|
-
devcycle_python_server_sdk-3.
|
|
65
|
-
devcycle_python_server_sdk-3.
|
|
66
|
-
devcycle_python_server_sdk-3.
|
|
62
|
+
devcycle_python_server_sdk-3.6.1.dist-info/LICENSE,sha256=tHQMqu-Jic2UpMGqb-vNFbGygysl8IG4l1clnnh0OFo,1071
|
|
63
|
+
devcycle_python_server_sdk-3.6.1.dist-info/METADATA,sha256=Kg7NXWkaxSb1dVCWXdFGcYbvX3hNeoBy63uTZNOQ2UE,509
|
|
64
|
+
devcycle_python_server_sdk-3.6.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
65
|
+
devcycle_python_server_sdk-3.6.1.dist-info/top_level.txt,sha256=34f8zDzihAHoH7siAFlmscyO_QLNWWq9HApFlwtYL4k,33
|
|
66
|
+
devcycle_python_server_sdk-3.6.1.dist-info/RECORD,,
|
test/api/test_config_client.py
CHANGED
|
@@ -2,6 +2,9 @@ import logging
|
|
|
2
2
|
import unittest
|
|
3
3
|
import uuid
|
|
4
4
|
from http import HTTPStatus
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from email.utils import formatdate
|
|
7
|
+
from time import mktime
|
|
5
8
|
|
|
6
9
|
import requests
|
|
7
10
|
import responses
|
|
@@ -36,6 +39,9 @@ class ConfigAPIClientTest(unittest.TestCase):
|
|
|
36
39
|
|
|
37
40
|
options = DevCycleLocalOptions(config_retry_delay_ms=0)
|
|
38
41
|
self.test_client = ConfigAPIClient(self.sdk_key, options)
|
|
42
|
+
now = datetime.now()
|
|
43
|
+
stamp = mktime(now.timetuple())
|
|
44
|
+
self.test_lastmodified = formatdate(timeval=stamp, localtime=False, usegmt=True)
|
|
39
45
|
self.test_etag = str(uuid.uuid4())
|
|
40
46
|
self.test_config_json: dict = small_config_json()
|
|
41
47
|
|
|
@@ -45,14 +51,20 @@ class ConfigAPIClientTest(unittest.TestCase):
|
|
|
45
51
|
@responses.activate
|
|
46
52
|
def test_get_config(self):
|
|
47
53
|
new_etag = str(uuid.uuid4())
|
|
54
|
+
now = datetime.now()
|
|
55
|
+
stamp = mktime(now.timetuple())
|
|
56
|
+
new_lastmodified = formatdate(timeval=stamp, localtime=False, usegmt=True)
|
|
48
57
|
responses.add(
|
|
49
58
|
responses.GET,
|
|
50
59
|
self.config_url,
|
|
51
|
-
headers={"ETag": new_etag},
|
|
60
|
+
headers={"ETag": new_etag, "Last-Modified": new_lastmodified},
|
|
52
61
|
json=self.test_config_json,
|
|
53
62
|
)
|
|
54
|
-
result, etag = self.test_client.get_config(
|
|
63
|
+
result, etag, lastmodified = self.test_client.get_config(
|
|
64
|
+
config_etag=self.test_etag, last_modified=self.test_lastmodified
|
|
65
|
+
)
|
|
55
66
|
self.assertDictEqual(result, self.test_config_json)
|
|
67
|
+
self.assertEqual(lastmodified, new_lastmodified)
|
|
56
68
|
self.assertEqual(etag, new_etag)
|
|
57
69
|
|
|
58
70
|
@responses.activate(registry=OrderedRegistry)
|
|
@@ -68,7 +80,9 @@ class ConfigAPIClientTest(unittest.TestCase):
|
|
|
68
80
|
headers={"ETag": self.test_etag},
|
|
69
81
|
json=self.test_config_json,
|
|
70
82
|
)
|
|
71
|
-
result, etag = self.test_client.get_config(
|
|
83
|
+
result, etag, lastmodified = self.test_client.get_config(
|
|
84
|
+
config_etag=self.test_etag
|
|
85
|
+
)
|
|
72
86
|
|
|
73
87
|
self.assertDictEqual(result, self.test_config_json)
|
|
74
88
|
self.assertEqual(etag, self.test_etag)
|
|
@@ -83,10 +97,12 @@ class ConfigAPIClientTest(unittest.TestCase):
|
|
|
83
97
|
responses.add(
|
|
84
98
|
responses.GET,
|
|
85
99
|
self.config_url,
|
|
86
|
-
headers={"ETag": self.test_etag},
|
|
100
|
+
headers={"ETag": self.test_etag, "Last-Modified": self.test_lastmodified},
|
|
87
101
|
json=self.test_config_json,
|
|
88
102
|
)
|
|
89
|
-
result, etag = self.test_client.get_config(
|
|
103
|
+
result, etag, last_modified = self.test_client.get_config(
|
|
104
|
+
config_etag=self.test_etag
|
|
105
|
+
)
|
|
90
106
|
self.assertDictEqual(result, self.test_config_json)
|
|
91
107
|
self.assertEqual(etag, self.test_etag)
|
|
92
108
|
|
|
@@ -131,6 +147,8 @@ class ConfigAPIClientTest(unittest.TestCase):
|
|
|
131
147
|
status=HTTPStatus.NOT_MODIFIED,
|
|
132
148
|
)
|
|
133
149
|
|
|
134
|
-
new_config, new_etag = self.test_client.get_config(
|
|
150
|
+
new_config, new_etag, last_modified = self.test_client.get_config(
|
|
151
|
+
config_etag=self.test_etag
|
|
152
|
+
)
|
|
135
153
|
self.assertIsNone(new_config)
|
|
136
154
|
self.assertEqual(new_etag, self.test_etag)
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import logging
|
|
2
1
|
import json
|
|
2
|
+
import logging
|
|
3
3
|
import time
|
|
4
|
-
import uuid
|
|
5
4
|
import unittest
|
|
5
|
+
import uuid
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from email.utils import formatdate
|
|
8
|
+
from time import mktime
|
|
6
9
|
from unittest.mock import patch, MagicMock
|
|
7
10
|
|
|
8
11
|
from devcycle_python_sdk import DevCycleLocalOptions
|
|
@@ -18,20 +21,27 @@ class EnvironmentConfigManagerTest(unittest.TestCase):
|
|
|
18
21
|
self.test_local_bucketing = MagicMock()
|
|
19
22
|
self.test_options = DevCycleLocalOptions(config_polling_interval_ms=500)
|
|
20
23
|
|
|
24
|
+
now = datetime.now()
|
|
25
|
+
stamp = mktime(now.timetuple())
|
|
26
|
+
self.test_lastmodified = formatdate(timeval=stamp, localtime=False, usegmt=True)
|
|
21
27
|
self.test_etag = str(uuid.uuid4())
|
|
22
28
|
self.test_config_json = small_config_json()
|
|
23
29
|
self.test_config_string = json.dumps(self.test_config_json)
|
|
24
30
|
|
|
25
31
|
@patch("devcycle_python_sdk.api.config_client.ConfigAPIClient.get_config")
|
|
26
32
|
def test_init(self, mock_get_config):
|
|
27
|
-
mock_get_config.return_value = (
|
|
33
|
+
mock_get_config.return_value = (
|
|
34
|
+
self.test_config_json,
|
|
35
|
+
self.test_etag,
|
|
36
|
+
self.test_lastmodified,
|
|
37
|
+
)
|
|
28
38
|
config_manager = EnvironmentConfigManager(
|
|
29
39
|
self.sdk_key, self.test_options, self.test_local_bucketing
|
|
30
40
|
)
|
|
31
41
|
|
|
32
42
|
# sleep to allow polling thread to load the config
|
|
33
43
|
time.sleep(0.1)
|
|
34
|
-
mock_get_config.assert_called_once_with(config_etag=None)
|
|
44
|
+
mock_get_config.assert_called_once_with(config_etag=None, last_modified=None)
|
|
35
45
|
|
|
36
46
|
self.assertTrue(config_manager._polling_enabled)
|
|
37
47
|
self.assertTrue(config_manager.is_alive())
|
|
@@ -45,7 +55,11 @@ class EnvironmentConfigManagerTest(unittest.TestCase):
|
|
|
45
55
|
|
|
46
56
|
@patch("devcycle_python_sdk.api.config_client.ConfigAPIClient.get_config")
|
|
47
57
|
def test_init_with_client_callback(self, mock_get_config):
|
|
48
|
-
mock_get_config.return_value = (
|
|
58
|
+
mock_get_config.return_value = (
|
|
59
|
+
self.test_config_json,
|
|
60
|
+
self.test_etag,
|
|
61
|
+
self.test_lastmodified,
|
|
62
|
+
)
|
|
49
63
|
|
|
50
64
|
mock_callback = MagicMock()
|
|
51
65
|
|
|
@@ -55,7 +69,7 @@ class EnvironmentConfigManagerTest(unittest.TestCase):
|
|
|
55
69
|
self.sdk_key, self.test_options, self.test_local_bucketing
|
|
56
70
|
)
|
|
57
71
|
time.sleep(0.1)
|
|
58
|
-
mock_get_config.assert_called_once_with(config_etag=None)
|
|
72
|
+
mock_get_config.assert_called_once_with(config_etag=None, last_modified=None)
|
|
59
73
|
self.assertEqual(config_manager._config_etag, self.test_etag)
|
|
60
74
|
self.assertDictEqual(config_manager._config, self.test_config_json)
|
|
61
75
|
self.test_local_bucketing.store_config.assert_called_once_with(
|
|
@@ -66,7 +80,11 @@ class EnvironmentConfigManagerTest(unittest.TestCase):
|
|
|
66
80
|
|
|
67
81
|
@patch("devcycle_python_sdk.api.config_client.ConfigAPIClient.get_config")
|
|
68
82
|
def test_init_with_client_callback_with_error(self, mock_get_config):
|
|
69
|
-
mock_get_config.return_value = (
|
|
83
|
+
mock_get_config.return_value = (
|
|
84
|
+
self.test_config_json,
|
|
85
|
+
self.test_etag,
|
|
86
|
+
self.test_lastmodified,
|
|
87
|
+
)
|
|
70
88
|
mock_callback = MagicMock()
|
|
71
89
|
mock_callback.side_effect = Exception(
|
|
72
90
|
"Badly written callback generates an exception"
|
|
@@ -79,7 +97,7 @@ class EnvironmentConfigManagerTest(unittest.TestCase):
|
|
|
79
97
|
)
|
|
80
98
|
# the callback error should not negatively impact initialization of the config manager
|
|
81
99
|
time.sleep(0.1)
|
|
82
|
-
mock_get_config.assert_called_once_with(config_etag=None)
|
|
100
|
+
mock_get_config.assert_called_once_with(config_etag=None, last_modified=None)
|
|
83
101
|
self.assertEqual(config_manager._config_etag, self.test_etag)
|
|
84
102
|
self.assertDictEqual(config_manager._config, self.test_config_json)
|
|
85
103
|
self.test_local_bucketing.store_config.assert_called_once_with(
|
|
@@ -105,7 +123,11 @@ class EnvironmentConfigManagerTest(unittest.TestCase):
|
|
|
105
123
|
|
|
106
124
|
@patch("devcycle_python_sdk.api.config_client.ConfigAPIClient.get_config")
|
|
107
125
|
def test_get_config_unchanged(self, mock_get_config):
|
|
108
|
-
mock_get_config.return_value = (
|
|
126
|
+
mock_get_config.return_value = (
|
|
127
|
+
self.test_config_json,
|
|
128
|
+
self.test_etag,
|
|
129
|
+
self.test_lastmodified,
|
|
130
|
+
)
|
|
109
131
|
|
|
110
132
|
self.test_options.config_polling_interval_ms = 200
|
|
111
133
|
config_manager = EnvironmentConfigManager(
|
|
@@ -117,7 +139,7 @@ class EnvironmentConfigManagerTest(unittest.TestCase):
|
|
|
117
139
|
config_manager.close()
|
|
118
140
|
|
|
119
141
|
self.test_local_bucketing.store_config.reset_mock()
|
|
120
|
-
mock_get_config.return_value = (None, config_manager._config_etag)
|
|
142
|
+
mock_get_config.return_value = (None, config_manager._config_etag, None)
|
|
121
143
|
|
|
122
144
|
# trigger refresh of the config directly
|
|
123
145
|
config_manager._get_config()
|
{devcycle_python_server_sdk-3.5.1.dist-info → devcycle_python_server_sdk-3.6.1.dist-info}/LICENSE
RENAMED
|
File without changes
|
{devcycle_python_server_sdk-3.5.1.dist-info → devcycle_python_server_sdk-3.6.1.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|