summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNejc Habjan <nejc.habjan@siemens.com>2022-12-05 21:48:07 +0100
committerNejc Habjan <hab.nejc@gmail.com>2022-12-19 22:58:03 +0100
commit3e1c625133074ccd2fb88c429ea151bfda96aebb (patch)
treea63af76909e9d322b9d2899a34cf68b4a514455f
parentdced76a9900c626c9f0b90b85a5e371101a24fb4 (diff)
downloadgitlab-3e1c625133074ccd2fb88c429ea151bfda96aebb.tar.gz
chore: add test, docs, and helper for 409 retries
-rw-r--r--docs/api-usage-advanced.rst11
-rw-r--r--gitlab/client.py21
-rw-r--r--tests/unit/test_gitlab_http_methods.py57
3 files changed, 79 insertions, 10 deletions
diff --git a/docs/api-usage-advanced.rst b/docs/api-usage-advanced.rst
index 256ae2d..dcc27ca 100644
--- a/docs/api-usage-advanced.rst
+++ b/docs/api-usage-advanced.rst
@@ -123,9 +123,14 @@ GitLab server can sometimes return a transient HTTP error.
python-gitlab can automatically retry in such case, when
``retry_transient_errors`` argument is set to ``True``. When enabled,
HTTP error codes 500 (Internal Server Error), 502 (502 Bad Gateway),
-503 (Service Unavailable), and 504 (Gateway Timeout) are retried.
-Additionally the HTTP error code 409 (Conflict) is retried if the text message
-mentions "Resource lock". It will retry until reaching the ``max_retries``
+503 (Service Unavailable), 504 (Gateway Timeout), and Cloudflare
+errors (520-530) are retried.
+
+Additionally, HTTP error code 409 (Conflict) is retried if the reason
+is a
+`Resource lock <https://gitlab.com/gitlab-org/gitlab/-/blob/443c12cf3b238385db728f03b2cdbb4f17c70292/lib/api/api.rb#L111>`__.
+
+It will retry until reaching the ``max_retries``
value. By default, ``retry_transient_errors`` is set to ``False`` and an
exception is raised for these errors.
diff --git a/gitlab/client.py b/gitlab/client.py
index f9462ba..ffdfadf 100644
--- a/gitlab/client.py
+++ b/gitlab/client.py
@@ -751,13 +751,20 @@ class Gitlab:
if 200 <= result.status_code < 300:
return result.response
- if (429 == result.status_code and obey_rate_limit) or (
- (
- result.status_code in gitlab.const.RETRYABLE_TRANSIENT_ERROR_CODES
- or (result.status_code == 409 and "Resource lock" in result.reason)
- )
- and retry_transient_errors
- ):
+ def should_retry() -> bool:
+ if result.status_code == 429 and obey_rate_limit:
+ return True
+
+ if not retry_transient_errors:
+ return False
+ if result.status_code in gitlab.const.RETRYABLE_TRANSIENT_ERROR_CODES:
+ return True
+ if result.status_code == 409 and "Resource lock" in result.reason:
+ return True
+
+ return False
+
+ if should_retry():
# Response headers documentation:
# https://docs.gitlab.com/ee/user/admin_area/settings/user_and_ip_rate_limits.html#response-headers
if max_retries == -1 or cur_retries < max_retries:
diff --git a/tests/unit/test_gitlab_http_methods.py b/tests/unit/test_gitlab_http_methods.py
index d4abe0b..dc1aef2 100644
--- a/tests/unit/test_gitlab_http_methods.py
+++ b/tests/unit/test_gitlab_http_methods.py
@@ -372,6 +372,63 @@ def test_http_request_302_put_raises_redirect_error(gl):
assert "http://example.com/api/v4/user/status" in error_message
+def test_http_request_on_409_resource_lock_retries(gl_retry):
+ url = "http://localhost/api/v4/user"
+ retried = False
+
+ def response_callback(
+ response: requests.models.Response,
+ ) -> requests.models.Response:
+ """We need a callback that adds a resource lock reason only on first call"""
+ nonlocal retried
+
+ if not retried:
+ response.reason = "Resource lock"
+
+ retried = True
+ return response
+
+ with responses.RequestsMock(response_callback=response_callback) as rsps:
+ rsps.add(
+ method=responses.GET,
+ url=url,
+ status=409,
+ match=helpers.MATCH_EMPTY_QUERY_PARAMS,
+ )
+ rsps.add(
+ method=responses.GET,
+ url=url,
+ status=200,
+ match=helpers.MATCH_EMPTY_QUERY_PARAMS,
+ )
+ response = gl_retry.http_request("get", "/user")
+
+ assert response.status_code == 200
+
+
+def test_http_request_on_409_resource_lock_without_retry_raises(gl):
+ url = "http://localhost/api/v4/user"
+
+ def response_callback(
+ response: requests.models.Response,
+ ) -> requests.models.Response:
+ """Without retry, this will fail on the first call"""
+ response.reason = "Resource lock"
+ return response
+
+ with responses.RequestsMock(response_callback=response_callback) as req_mock:
+ req_mock.add(
+ method=responses.GET,
+ url=url,
+ status=409,
+ match=helpers.MATCH_EMPTY_QUERY_PARAMS,
+ )
+ with pytest.raises(GitlabHttpError) as excinfo:
+ gl.http_request("get", "/user")
+
+ assert excinfo.value.response_code == 409
+
+
@responses.activate
def test_get_request(gl):
url = "http://localhost/api/v4/projects"