Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Retries implementation of rest.py missing for POST/PATCH/PUT/DELETE #513

Closed
5 tasks done
tlotter opened this issue Aug 17, 2023 · 1 comment · Fixed by #518
Closed
5 tasks done

Retries implementation of rest.py missing for POST/PATCH/PUT/DELETE #513

tlotter opened this issue Aug 17, 2023 · 1 comment · Fixed by #518
Labels
bug This points to a verified bug in the code

Comments

@tlotter
Copy link

tlotter commented Aug 17, 2023

Checklist

  • I have looked into the Readme and Examples, and have not found a suitable solution or answer.
  • I have looked into the API documentation and have not found a suitable solution or answer.
  • I have searched the issues and have not found a suitable solution or answer.
  • I have searched the Auth0 Community forums and have not found a suitable solution or answer.
  • I agree to the terms within the Auth0 Code of Conduct.

Description

If you make a lot of API calls after each other, you will run into Error Code 429 of GlobalRateLimit.

Only GET implements retries, as you can see here:

auth0-python/auth0/rest.py

Lines 139 to 178 in 8e1bf4a

def get(
self,
url: str,
params: dict[str, Any] | None = None,
headers: dict[str, str] | None = None,
) -> Any:
request_headers = self.base_headers.copy()
request_headers.update(headers or {})
# Track the API request attempt number
attempt = 0
# Reset the metrics tracker
self._metrics = {"retries": 0, "backoff": []}
while True:
# Increment attempt number
attempt += 1
# Issue the request
response = requests.get(
url,
params=params,
headers=request_headers,
timeout=self.options.timeout,
)
# If the response did not have a 429 header, or the attempt number is greater than the configured retries, break
if response.status_code != 429 or attempt > self._retries:
break
wait = self._calculate_wait(attempt)
# Skip calling sleep() when running unit tests
if self._skip_sleep is False:
# sleep() functions in seconds, so convert the milliseconds formula above accordingly
sleep(wait / 1000)
# Return the final Response
return self._process_response(response)

The retry implementation for POST/FILE_POST/PATCH/PUT/DELETE is missing, as you can see here:

auth0-python/auth0/rest.py

Lines 180 to 239 in 8e1bf4a

def post(
self,
url: str,
data: RequestData | None = None,
headers: dict[str, str] | None = None,
) -> Any:
request_headers = self.base_headers.copy()
request_headers.update(headers or {})
response = requests.post(
url, json=data, headers=request_headers, timeout=self.options.timeout
)
return self._process_response(response)
def file_post(
self,
url: str,
data: RequestData | None = None,
files: dict[str, Any] | None = None,
) -> Any:
headers = self.base_headers.copy()
headers.pop("Content-Type", None)
response = requests.post(
url, data=data, files=files, headers=headers, timeout=self.options.timeout
)
return self._process_response(response)
def patch(self, url: str, data: RequestData | None = None) -> Any:
headers = self.base_headers.copy()
response = requests.patch(
url, json=data, headers=headers, timeout=self.options.timeout
)
return self._process_response(response)
def put(self, url: str, data: RequestData | None = None) -> Any:
headers = self.base_headers.copy()
response = requests.put(
url, json=data, headers=headers, timeout=self.options.timeout
)
return self._process_response(response)
def delete(
self,
url: str,
params: dict[str, Any] | None = None,
data: RequestData | None = None,
) -> Any:
headers = self.base_headers.copy()
response = requests.delete(
url,
headers=headers,
params=params or {},
json=data,
timeout=self.options.timeout,
)
return self._process_response(response)

Reproduction

Try the following script to do a lot of POST call. (I used adding a member to an Organization)

# =========================================================
# Step 1: Install Auth0 Phyton SDK
# =========================================================
"""
pip install auth0-python
"""

# =========================================================
# Step 2: Create M2M Application
# =========================================================
"""
1. Go to "Applications" -> "Applications" and click on "+ Create Application".
2. Enter as "Name": "SaaS Management M2M", select "Machine to Machine Applications" and click on "Create".
3. Select "Auth0 Management API" and select "All" Permissions (for production, select only the scopes that are required).
4. Click on "Authorize"
5. Switch to the tab "Settings" and copy the values of "Domain", "Client ID" and "Client Secret".
"""

# =========================================================
# Step 3: Run Deployment
# =========================================================
from auth0.authentication import GetToken
from auth0.management import Auth0
from auth0.rest import RestClientOptions
from auth0.rest import RateLimitError
import time

# Step 1: Get Access Token for Management API
auth0_domain = "domain.eu.auth0.com"
auth0_client_id = "XXXX"
auth0_client_secret = "XXXX"

get_token = GetToken(auth0_domain, auth0_client_id, client_secret=auth0_client_secret)
token = get_token.client_credentials('https://{}/api/v2/'.format(auth0_domain))
mgmt_api_token = token['access_token']

# Step 2: Create Auth0
rest_client_option = RestClientOptions(retries=10) # set max retries to maximum of 10
auth0 =  Auth0(auth0_domain, mgmt_api_token, rest_client_option)

# Step 3: Loop on any API that is doing a put/post/delete call for about 30 tries,
# for me - it usually happens on try 16 or 17
for i in range(1, 30):
    start_time = time.perf_counter()
    print("===== Iteration "+ str(i) + " =====")
    try:
        organization_id = "org_V6RnNQ9x3vKcGcB9"
        user_id = "auth0|64db3cf0cf71fa8ca0ae9226"
        auth0.organizations.create_organization_members(organization_id, {"members":[user_id]})
    except RateLimitError as e:
        print("!!!!!!!!!!!! RateLimitError !!!!!!!!!!!!")
        print("Time per API call: " + str(time.perf_counter() - start_time))
        raise e
    
    print("Time per API call: " + str(time.perf_counter() - start_time))

Additional context

No response

auth0-python version

4.4.0

Python version

3.11.4

@tlotter tlotter added the bug This points to a verified bug in the code label Aug 17, 2023
@adamjmcgrath
Copy link
Contributor

Thanks for raising this @tlotter - will take a look

github-merge-queue bot pushed a commit that referenced this issue Oct 24, 2023
### Changes

Currently the SDK only retries on 429s in a GET, add retry to the other
methods

### References

fixes #513
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug This points to a verified bug in the code
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants