Skip to content

Commit a332b7e

Browse files
authored
Merge pull request #18 from cloudblue/retries_for_502
Add a max_retries parameter to the ConnectClient constructor to retry…
2 parents 841a09e + 98fd4fb commit a332b7e

2 files changed

Lines changed: 80 additions & 75 deletions

File tree

cnct/client/fluent.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import threading
2+
import time
23

34
from json.decoder import JSONDecodeError
45

@@ -26,6 +27,7 @@ def __init__(
2627
validate_using_specs=True,
2728
default_headers=None,
2829
default_limit=100,
30+
max_retries=0,
2931
):
3032
"""
3133
Create a new instance of the ConnectClient.
@@ -46,6 +48,7 @@ def __init__(
4648
self.api_key = api_key
4749
self.default_headers = default_headers or {}
4850
self.default_limit = default_limit
51+
self.max_retries = max_retries
4952
self._use_specs = use_specs
5053
self._validate_using_specs = validate_using_specs
5154
self.specs_location = specs_location or CONNECT_SPECS_URL
@@ -175,7 +178,14 @@ def help(self):
175178
return self
176179

177180
def _execute_http_call(self, method, url, kwargs):
178-
self.response = requests.request(method, url, **kwargs)
181+
retry_count = 0
182+
while True:
183+
self.response = requests.request(method, url, **kwargs)
184+
if self.response.status_code == 502 and retry_count < self.max_retries:
185+
retry_count += 1
186+
time.sleep(1)
187+
continue
188+
break
179189
if self.response.status_code >= 400:
180190
self.response.raise_for_status()
181191

tests/client/test_fluent.py

Lines changed: 69 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -38,72 +38,7 @@ def test_default_limit():
3838
assert rs._limit == 10
3939

4040

41-
# def test_getattr(mocker):
42-
# c = ConnectClient('Api Key', use_specs=False)
43-
44-
# with pytest.raises(AttributeError) as cv:
45-
# c.resources
46-
47-
# assert str(cv.value) == (
48-
# 'No specs available. Use `ns` '
49-
# 'or `collection` methods instead.'
50-
# )
51-
52-
53-
# def test_getattr_with_specs_dash(mocker, apiinfo_factory, nsinfo_factory, colinfo_factory):
54-
# specs = apiinfo_factory(
55-
# collections=[colinfo_factory(name='my-resources')],
56-
# namespaces=[nsinfo_factory(name='name-space')],
57-
# )
58-
# mocker.patch(
59-
# 'cnct.client.fluent.parse',
60-
# return_value=specs,
61-
# )
62-
63-
# c = ConnectClient('Api Key')
64-
65-
# assert isinstance(c.my_resources, Collection)
66-
# assert isinstance(c.name_space, NS)
67-
68-
# specs = apiinfo_factory(
69-
# collections=[colinfo_factory('resources')],
70-
# namespaces=[nsinfo_factory('namespace')],
71-
# )
72-
# mocker.patch(
73-
# 'cnct.client.fluent.parse',
74-
# return_value=specs,
75-
# )
76-
77-
# c = ConnectClient('Api Key')
78-
79-
# assert isinstance(c.resources, Collection)
80-
# assert isinstance(c.namespace, NS)
81-
82-
83-
# def test_getattr_with_specs_unresolved(mocker, apiinfo_factory, nsinfo_factory, colinfo_factory):
84-
# specs = apiinfo_factory(
85-
# collections=[colinfo_factory(name='resources')],
86-
# namespaces=[nsinfo_factory(name='namespace')],
87-
# )
88-
# mocker.patch(
89-
# 'cnct.client.fluent.parse',
90-
# return_value=specs,
91-
# )
92-
93-
# c = ConnectClient('Api Key')
94-
95-
# with pytest.raises(AttributeError) as cv:
96-
# c.others
97-
98-
# assert str(cv.value) == 'Unable to resolve others.'
99-
100-
10141
def test_ns(mocker):
102-
# mocker.patch(
103-
# 'cnct.client.fluent.parse',
104-
# return_value=None,
105-
# )
106-
10742
c = ConnectClient('Api Key', use_specs=False)
10843

10944
assert isinstance(c.ns('namespace'), NS)
@@ -233,6 +168,75 @@ def test_execute(mocked_responses):
233168
assert results == expected
234169

235170

171+
def test_execute_retries(mocked_responses):
172+
expected = [{'id': i} for i in range(10)]
173+
mocked_responses.add(
174+
responses.GET,
175+
'https://localhost/resources',
176+
status=502,
177+
)
178+
179+
mocked_responses.add(
180+
responses.GET,
181+
'https://localhost/resources',
182+
status=502,
183+
)
184+
185+
mocked_responses.add(
186+
responses.GET,
187+
'https://localhost/resources',
188+
status=200,
189+
json=expected,
190+
)
191+
192+
c = ConnectClient(
193+
'API_KEY',
194+
endpoint='https://localhost',
195+
use_specs=False,
196+
max_retries=2,
197+
)
198+
199+
results = c.execute('get', 'resources')
200+
201+
assert mocked_responses.calls[0].request.method == 'GET'
202+
headers = mocked_responses.calls[0].request.headers
203+
204+
assert 'Authorization' in headers and headers['Authorization'] == 'API_KEY'
205+
assert 'User-Agent' in headers and headers['User-Agent'].startswith('connect-fluent')
206+
207+
assert results == expected
208+
209+
210+
def test_execute_max_retries_exceeded(mocked_responses):
211+
mocked_responses.add(
212+
responses.GET,
213+
'https://localhost/resources',
214+
status=502,
215+
)
216+
217+
mocked_responses.add(
218+
responses.GET,
219+
'https://localhost/resources',
220+
status=502,
221+
)
222+
223+
mocked_responses.add(
224+
responses.GET,
225+
'https://localhost/resources',
226+
status=502,
227+
)
228+
229+
c = ConnectClient(
230+
'API_KEY',
231+
endpoint='https://localhost',
232+
use_specs=False,
233+
max_retries=2,
234+
)
235+
236+
with pytest.raises(ClientError):
237+
c.execute('get', 'resources')
238+
239+
236240
def test_execute_default_headers(mocked_responses):
237241
mocked_responses.add(
238242
responses.GET,
@@ -350,12 +354,3 @@ def test_execute_delete(mocked_responses):
350354
results = c.execute('delete', 'resources')
351355

352356
assert results is None
353-
354-
355-
# def test_help(mocker, col_factory):
356-
# print_help = mocker.patch.object(DefaultFormatter, 'print_help')
357-
# c = ConnectClient('API_KEY', use_specs=False)
358-
# c1 = c.help()
359-
360-
# assert print_help.called_once_with(None)
361-
# assert c == c1

0 commit comments

Comments
 (0)