Skip to content

Commit 43363c7

Browse files
[Rest] Support exponential backoff and retry (#1339)
Uses the urllib3 builtin Retry mechanism. We only support retrying on HTTP return status, not on any of the other retries that that class defines (e.g. connection problems, redirects). The usual use case for this is to handle 429 responses from the Atlassian Cloud API which has request limits that are easily hit by scripts iterating over objects like Jira issues or Bitbucket pull requests. Based on earlier work by jp-harvey. Signed-off-by: Frank Lichtenheld <frank@lichtenheld.com> Co-authored-by: jp-harvey <jpharvey@cloudquarterback.com>
1 parent 7f81b04 commit 43363c7

File tree

1 file changed

+52
-0
lines changed

1 file changed

+52
-0
lines changed

atlassian/rest_client.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from json import dumps
44

55
import requests
6+
from requests.adapters import HTTPAdapter
67

78
try:
89
from oauthlib.oauth1.rfc5849 import SIGNATURE_RSA_SHA512 as SIGNATURE_RSA
@@ -11,6 +12,7 @@
1112
from requests import HTTPError
1213
from requests_oauthlib import OAuth1, OAuth2
1314
from six.moves.urllib.parse import urlencode
15+
from urllib3.util import Retry
1416

1517
from atlassian.request_utils import get_default_logger
1618

@@ -61,7 +63,44 @@ def __init__(
6163
proxies=None,
6264
token=None,
6365
cert=None,
66+
backoff_and_retry=False,
67+
retry_status_codes=[413, 429, 503],
68+
max_backoff_seconds=1800,
69+
max_backoff_retries=1000,
6470
):
71+
"""
72+
init function for the AtlassianRestAPI object.
73+
74+
:param url: The url to be used in the request.
75+
:param username: Username. Defaults to None.
76+
:param password: Password. Defaults to None.
77+
:param timeout: Request timeout. Defaults to 75.
78+
:param api_root: Root for the api requests. Defaults to "rest/api".
79+
:param api_version: Version of the API to use. Defaults to "latest".
80+
:param verify_ssl: Turn on / off SSL verification. Defaults to True.
81+
:param session: Pass an existing Python requests session object. Defaults to None.
82+
:param oauth: oauth. Defaults to None.
83+
:param oauth2: oauth2. Defaults to None.
84+
:param cookies: Cookies to send with the request. Defaults to None.
85+
:param advanced_mode: Return results in advanced mode. Defaults to None.
86+
:param kerberos: Kerberos. Defaults to None.
87+
:param cloud: Specify if using Atlassian Cloud. Defaults to False.
88+
:param proxies: Specify proxies to use. Defaults to None.
89+
:param token: Atlassian / Jira auth token. Defaults to None.
90+
:param cert: Client-side certificate to use. Defaults to None.
91+
:param backoff_and_retry: Enable exponential backoff and retry.
92+
This will retry the request if there is a predefined failure. Primarily
93+
designed for Atlassian Cloud where API limits are commonly hit if doing
94+
operations on many issues, and the limits require a cooling off period.
95+
The wait period before the next request increases exponentially with each
96+
failed retry. Defaults to False.
97+
:param retry_status_codes: Errors to match, passed as a list of HTTP
98+
response codes. Defaults to [413, 429, 503].
99+
:param max_backoff_seconds: Max backoff seconds. When backing off, requests won't
100+
wait any longer than this. Defaults to 1800.
101+
:param max_backoff_retries: Maximum number of retries to try before
102+
continuing. Defaults to 1000.
103+
"""
65104
self.url = url
66105
self.username = username
67106
self.password = password
@@ -78,6 +117,19 @@ def __init__(
78117
self._session = requests.Session()
79118
else:
80119
self._session = session
120+
if backoff_and_retry:
121+
# Note: we only retry on status and not on any of the
122+
# other supported reasons
123+
retries = Retry(
124+
total=None,
125+
status=max_backoff_retries,
126+
allowed_methods=None,
127+
status_forcelist=retry_status_codes,
128+
backoff_factor=1,
129+
backoff_jitter=1,
130+
backoff_max=max_backoff_seconds,
131+
)
132+
self._session.mount(self.url, HTTPAdapter(max_retries=retries))
81133
if username and password:
82134
self._create_basic_session(username, password)
83135
elif token is not None:

0 commit comments

Comments
 (0)