Skip to content

Commit 7f0d3d2

Browse files
author
Clayton Walker
committed
fix: add 52x range to retry transient failures and tests
1 parent 3c1fcec commit 7f0d3d2

File tree

2 files changed

+103
-4
lines changed

2 files changed

+103
-4
lines changed

gitlab/client.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
"{source!r} to {target!r}"
3636
)
3737

38+
RETRYABLE_TRANSIENT_ERROR_CODES = [500, 502, 503, 504] + list(range(520, 531))
39+
3840

3941
class Gitlab:
4042
"""Represents a GitLab server connection.
@@ -694,9 +696,9 @@ def http_request(
694696
)
695697
except requests.ConnectionError:
696698
if retry_transient_errors and (
697-
max_retries == -1 or cur_retries < max_retries
699+
max_retries == -1 or cur_retries < max_retries
698700
):
699-
wait_time = 2 ** cur_retries * 0.1
701+
wait_time = 2**cur_retries * 0.1
700702
cur_retries += 1
701703
time.sleep(wait_time)
702704
continue
@@ -712,7 +714,8 @@ def http_request(
712714
"retry_transient_errors", self.retry_transient_errors
713715
)
714716
if (429 == result.status_code and obey_rate_limit) or (
715-
result.status_code in [500, 502, 503, 504] and retry_transient_errors
717+
result.status_code in RETRYABLE_TRANSIENT_ERROR_CODES
718+
and retry_transient_errors
716719
):
717720
# Response headers documentation:
718721
# https://docs.gitlab.com/ee/user/admin_area/settings/user_and_ip_rate_limits.html#response-headers

tests/unit/test_gitlab_http_methods.py

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import responses
44

55
from gitlab import GitlabHttpError, GitlabList, GitlabParsingError, RedirectError
6+
from gitlab.client import RETRYABLE_TRANSIENT_ERROR_CODES
67
from tests.unit import helpers
78

89
MATCH_EMPTY_QUERY_PARAMS = [responses.matchers.query_param_matcher({})]
@@ -51,7 +52,7 @@ def test_http_request_404(gl):
5152

5253

5354
@responses.activate
54-
@pytest.mark.parametrize("status_code", [500, 502, 503, 504])
55+
@pytest.mark.parametrize("status_code", RETRYABLE_TRANSIENT_ERROR_CODES)
5556
def test_http_request_with_only_failures(gl, status_code):
5657
url = "http://localhost/api/v4/projects"
5758
responses.add(
@@ -97,6 +98,37 @@ def request_callback(request):
9798
assert len(responses.calls) == calls_before_success
9899

99100

101+
@responses.activate
102+
def test_http_request_with_retry_on_method_for_transient_network_failures(gl):
103+
call_count = 0
104+
calls_before_success = 3
105+
106+
url = "http://localhost/api/v4/projects"
107+
108+
def request_callback(request):
109+
nonlocal call_count
110+
call_count += 1
111+
status_code = 200
112+
headers = {}
113+
body = "[]"
114+
115+
if call_count >= calls_before_success:
116+
return (status_code, headers, body)
117+
raise requests.ConnectionError("Connection aborted.")
118+
119+
responses.add_callback(
120+
method=responses.GET,
121+
url=url,
122+
callback=request_callback,
123+
content_type="application/json",
124+
)
125+
126+
http_r = gl.http_request("get", "/projects", retry_transient_errors=True)
127+
128+
assert http_r.status_code == 200
129+
assert len(responses.calls) == calls_before_success
130+
131+
100132
@responses.activate
101133
def test_http_request_with_retry_on_class_for_transient_failures(gl_retry):
102134
call_count = 0
@@ -126,6 +158,37 @@ def request_callback(request: requests.models.PreparedRequest):
126158
assert len(responses.calls) == calls_before_success
127159

128160

161+
@responses.activate
162+
def test_http_request_with_retry_on_class_for_transient_network_failures(gl_retry):
163+
call_count = 0
164+
calls_before_success = 3
165+
166+
url = "http://localhost/api/v4/projects"
167+
168+
def request_callback(request: requests.models.PreparedRequest):
169+
nonlocal call_count
170+
call_count += 1
171+
status_code = 200
172+
headers = {}
173+
body = "[]"
174+
175+
if call_count >= calls_before_success:
176+
return (status_code, headers, body)
177+
raise requests.ConnectionError("Connection aborted.")
178+
179+
responses.add_callback(
180+
method=responses.GET,
181+
url=url,
182+
callback=request_callback,
183+
content_type="application/json",
184+
)
185+
186+
http_r = gl_retry.http_request("get", "/projects", retry_transient_errors=True)
187+
188+
assert http_r.status_code == 200
189+
assert len(responses.calls) == calls_before_success
190+
191+
129192
@responses.activate
130193
def test_http_request_with_retry_on_class_and_method_for_transient_failures(gl_retry):
131194
call_count = 0
@@ -155,6 +218,39 @@ def request_callback(request):
155218
assert len(responses.calls) == 1
156219

157220

221+
@responses.activate
222+
def test_http_request_with_retry_on_class_and_method_for_transient_network_failures(
223+
gl_retry,
224+
):
225+
call_count = 0
226+
calls_before_success = 3
227+
228+
url = "http://localhost/api/v4/projects"
229+
230+
def request_callback(request):
231+
nonlocal call_count
232+
call_count += 1
233+
status_code = 200
234+
headers = {}
235+
body = "[]"
236+
237+
if call_count >= calls_before_success:
238+
return (status_code, headers, body)
239+
raise requests.ConnectionError("Connection aborted.")
240+
241+
responses.add_callback(
242+
method=responses.GET,
243+
url=url,
244+
callback=request_callback,
245+
content_type="application/json",
246+
)
247+
248+
with pytest.raises(requests.ConnectionError):
249+
gl_retry.http_request("get", "/projects", retry_transient_errors=False)
250+
251+
assert len(responses.calls) == 1
252+
253+
158254
def create_redirect_response(
159255
*, response: requests.models.Response, http_method: str, api_path: str
160256
) -> requests.models.Response:

0 commit comments

Comments
 (0)