Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 7f810a7

Browse files
committed
feat: Implement rate-limit retries in RestClient
1 parent 6341d84 commit 7f810a7

File tree

1 file changed

+67
-4
lines changed

1 file changed

+67
-4
lines changed

auth0/v3/management/rest.py

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22
import json
33
import platform
44
import sys
5-
65
import requests
76

87
from ..exceptions import Auth0Error, RateLimitError
8+
from time import sleep
9+
from random import randint
910

1011
UNKNOWN_ERROR = 'a0.sdk.internal.unknown'
1112

12-
1313
class RestClient(object):
1414
"""Provides simple methods for handling all RESTful api endpoints.
1515
@@ -22,14 +22,18 @@ class RestClient(object):
2222
(defaults to 5.0 for both)
2323
"""
2424

25-
def __init__(self, jwt, telemetry=True, timeout=5.0):
25+
def __init__(self, jwt, telemetry=True, timeout=5.0, retries=3):
2626
self.jwt = jwt
2727
self.timeout = timeout
28+
self.retries = retries
29+
self._metrics = {'retries': 0, 'backoff': []}
30+
self._skip_sleep = False
2831

2932
self.base_headers = {
3033
'Authorization': 'Bearer {}'.format(self.jwt),
3134
'Content-Type': 'application/json',
3235
}
36+
3337
if telemetry:
3438
py_version = platform.python_version()
3539
version = sys.modules['auth0'].__version__
@@ -47,10 +51,69 @@ def __init__(self, jwt, telemetry=True, timeout=5.0):
4751
'Auth0-Client': base64.b64encode(auth0_client),
4852
})
4953

54+
# Returns a hard cap for the maximum number of retries allowed (10)
55+
def MAX_REQUEST_RETRIES(self):
56+
return 10
57+
58+
# Returns the maximum amount of jitter to introduce in milliseconds (100ms)
59+
def MAX_REQUEST_RETRY_JITTER(self):
60+
return 100
61+
62+
# Returns the maximum delay window allowed (1000ms)
63+
def MAX_REQUEST_RETRY_DELAY(self):
64+
return 1000
65+
66+
# Returns the minimum delay window allowed (100ms)
67+
def MIN_REQUEST_RETRY_DELAY(self):
68+
return 100
69+
5070
def get(self, url, params=None):
5171
headers = self.base_headers.copy()
5272

53-
response = requests.get(url, params=params, headers=headers, timeout=self.timeout)
73+
# Track the API request attempt number
74+
attempt = 0
75+
76+
# Reset the metrics tracker
77+
self._metrics = {'retries': 0, 'backoff': []}
78+
79+
# Cap the maximum number of retries to 10 or fewer. Floor the retries at 0.
80+
retries = min(self.MAX_REQUEST_RETRIES(), max(0, self.retries))
81+
82+
while True:
83+
# Increment attempt number
84+
attempt += 1
85+
86+
# Issue the request
87+
response = requests.get(url, params=params, headers=headers, timeout=self.timeout);
88+
89+
# If the response did not have a 429 header, or the retries were configured at 0, or the attempt number is equal to or greater than the configured retries, break
90+
if response.status_code != 429 or retries <= 0 or attempt > retries:
91+
break
92+
93+
# Retry the request. Apply a exponential backoff for subsequent attempts, using this formula:
94+
# max(MIN_REQUEST_RETRY_DELAY, min(MAX_REQUEST_RETRY_DELAY, (100ms * (2 ** attempt - 1)) + random_between(1, MAX_REQUEST_RETRY_JITTER)))
95+
96+
# ✔ Increases base delay by (100ms * (2 ** attempt - 1))
97+
wait = 100 * 2 ** (attempt - 1)
98+
99+
# ✔ Introduces jitter to the base delay; increases delay between 1ms to MAX_REQUEST_RETRY_JITTER (100ms)
100+
wait += randint(1, self.MAX_REQUEST_RETRY_JITTER())
101+
102+
# ✔ Is never more than MAX_REQUEST_RETRY_DELAY (1s)
103+
wait = min(self.MAX_REQUEST_RETRY_DELAY(), wait)
104+
105+
# ✔ Is never less than MIN_REQUEST_RETRY_DELAY (100ms)
106+
wait = max(self.MIN_REQUEST_RETRY_DELAY(), wait)
107+
108+
self._metrics['retries'] = attempt
109+
self._metrics['backoff'].append(wait)
110+
111+
# Skip calling sleep() when running unit tests
112+
if self._skip_sleep is False:
113+
# sleep() functions in seconds, so convert the milliseconds formula above accordingly
114+
sleep(wait / 1000)
115+
116+
# Return the final Response
54117
return self._process_response(response)
55118

56119
def post(self, url, data=None):

0 commit comments

Comments
 (0)