Skip to content

Commit d9aaa1e

Browse files
Stefano Borierostefanoboriero
Stefano Boriero
authored andcommitted
Handle rate limit error
* add status code 429 to the list of retriable errors FIXES pycontribs#925
1 parent e00c1f7 commit d9aaa1e

File tree

2 files changed

+50
-5
lines changed

2 files changed

+50
-5
lines changed

jira/resilientsession.py

+8-5
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def raise_on_error(r: Optional[Response], verb="???", **kwargs):
8888
class ResilientSession(Session):
8989
"""This class is supposed to retry requests that do return temporary errors.
9090
91-
At this moment it supports: 502, 503, 504
91+
At this moment it supports: 429
9292
"""
9393

9494
def __init__(self, timeout=None):
@@ -113,11 +113,14 @@ def __recoverable(
113113
f"Got ConnectionError [{response}] errno:{response.errno} on {request} {url}\n{vars(response)}\n{response.__dict__}"
114114
)
115115
if isinstance(response, Response):
116-
if response.status_code in [502, 503, 504, 401]:
117-
# 401 UNAUTHORIZED still randomly returned by Atlassian Cloud as of 2017-01-16
116+
if response.status_code in [429]:
117+
rate_limit_remaining = response.headers["X-RateLimit-Remaining"]
118118
msg = f"{response.status_code} {response.reason}"
119-
# 2019-07-25: Disabled recovery for codes above^
120-
return False
119+
logging.warning(
120+
f"""Request rate limited by Jira: number of tokens remaining {rate_limit_remaining}. Consider adding exemption for the user as explained in
121+
https://confluence.atlassian.com/adminjiraserver/improving-instance-stability-with-rate-limiting-983794911.html
122+
"""
123+
)
121124
elif not (
122125
response.status_code == 200
123126
and len(response.content) == 0

tests/test_resilientsession.py

+42
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import logging
2+
from unittest.mock import Mock, patch
3+
4+
import pytest
5+
from requests import Response
26

37
import jira.resilientsession
8+
from jira.exceptions import JIRAError
49
from tests.conftest import JiraTestCase
510

611

@@ -53,3 +58,40 @@ def test_logging_with_connection_error(self):
5358
def tearDown(self):
5459
jira.resilientsession.logging.getLogger().removeHandler(self.loggingHandler)
5560
del self.loggingHandler
61+
62+
63+
status_codes_retries_test_data = [
64+
(429, 4, 3),
65+
(401, 1, 0),
66+
(403, 1, 0),
67+
(404, 1, 0),
68+
(502, 1, 0),
69+
(503, 1, 0),
70+
(504, 1, 0),
71+
]
72+
73+
74+
@patch("requests.Session.get")
75+
@patch("time.sleep")
76+
@pytest.mark.parametrize(
77+
"status_code,expected_number_of_retries,expected_number_of_sleep_invocations",
78+
status_codes_retries_test_data,
79+
)
80+
def test_status_codes_retries(
81+
mocked_sleep_method: Mock,
82+
mocked_get_method: Mock,
83+
status_code: int,
84+
expected_number_of_retries: int,
85+
expected_number_of_sleep_invocations: int,
86+
):
87+
mocked_response: Response = Response()
88+
mocked_response.status_code = status_code
89+
mocked_response.headers["X-RateLimit-Remaining"] = "1"
90+
mocked_get_method.return_value = mocked_response
91+
session: jira.resilientsession.ResilientSession = (
92+
jira.resilientsession.ResilientSession()
93+
)
94+
with pytest.raises(JIRAError):
95+
session.get("mocked_url")
96+
assert mocked_get_method.call_count == expected_number_of_retries
97+
assert mocked_sleep_method.call_count == expected_number_of_sleep_invocations

0 commit comments

Comments
 (0)