Skip to content

Commit cc3a55b

Browse files
feat: implement rate limit handling for Vercel API requests (#718)
* feat: implement rate limit handling for Vercel API requests Added a new `vercel_request` function to manage API requests with automatic retries on rate limiting. Updated existing API calls to use this function for improved reliability in handling rate limits. * fix: improve handling of Vercel API rate limits with retries and error logging Signed-off-by: rohan <rohan.chaturvedi@protonmail.com> --------- Signed-off-by: rohan <rohan.chaturvedi@protonmail.com> Co-authored-by: rohan <rohan.chaturvedi@protonmail.com>
1 parent 608af31 commit cc3a55b

File tree

1 file changed

+45
-4
lines changed
  • backend/api/utils/syncing/vercel

1 file changed

+45
-4
lines changed

backend/api/utils/syncing/vercel/main.py

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import requests
22
import logging
3+
import time
34
from graphene import ObjectType, List, ID, String
45
from api.utils.syncing.auth import get_credentials
56

@@ -40,6 +41,46 @@ def get_vercel_headers(token):
4041
return {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
4142

4243

44+
def vercel_request(method, url, headers, json=None, max_retries=5):
45+
"""Make requests to the Vercel API and handle rate limits."""
46+
last_response = None
47+
max_retries = max(1, max_retries)
48+
for attempt in range(max_retries):
49+
try:
50+
response = requests.request(method, url, headers=headers, json=json)
51+
last_response = response
52+
53+
is_rate_limited = response.status_code == 429
54+
if not is_rate_limited:
55+
return response
56+
57+
reset_header = response.headers.get("X-RateLimit-Reset")
58+
wait_seconds = 5
59+
if reset_header and reset_header.isdigit():
60+
reset_ts = int(reset_header)
61+
wait_seconds = reset_ts - int(time.time())
62+
wait_seconds = max(min(wait_seconds, 300), 1)
63+
64+
logger.warning(
65+
f"Vercel {method} {url} rate limited (attempt {attempt + 1}/{max_retries}); "
66+
f"waiting {wait_seconds}s before retrying."
67+
)
68+
time.sleep(wait_seconds)
69+
except requests.exceptions.RequestException as e:
70+
logger.warning(
71+
f"Vercel {method} {url} request failed (attempt {attempt + 1}/{max_retries}): {e}; "
72+
f"retrying in 5s."
73+
)
74+
time.sleep(5)
75+
76+
if last_response is None:
77+
raise Exception(
78+
f"Failed to connect to Vercel API after {max_retries} attempts: {method} {url}"
79+
)
80+
81+
return last_response
82+
83+
4384
def test_vercel_creds(credential_id):
4485
"""Test if the Vercel credentials are valid."""
4586
try:
@@ -56,8 +97,8 @@ def delete_env_var(token, project_id, team_id, env_var_id):
5697
url = f"{VERCEL_API_BASE_URL}/v9/projects/{project_id}/env/{env_var_id}"
5798
if team_id is not None:
5899
url += f"?teamId={team_id}"
59-
response = requests.delete(url, headers=get_vercel_headers(token))
60100

101+
response = vercel_request("DELETE", url, headers=get_vercel_headers(token))
61102
if response.status_code != 200:
62103
raise Exception(f"Error deleting environment variable: {response.text}")
63104

@@ -237,7 +278,7 @@ def get_existing_env_vars(token, project_id, team_id, target_environment):
237278
if team_id is not None:
238279
url += f"?teamId={team_id}"
239280

240-
response = requests.get(url, headers=get_vercel_headers(token))
281+
response = vercel_request("GET", url, headers=get_vercel_headers(token))
241282
if response.status_code != 200:
242283
raise Exception(f"Error retrieving environment variables: {response.text}")
243284

@@ -415,8 +456,8 @@ def sync_vercel_secrets(
415456
if team_id is not None:
416457
url += f"&teamId={team_id}"
417458

418-
response = requests.post(
419-
url, headers=get_vercel_headers(token), json=payload
459+
response = vercel_request(
460+
"POST", url, headers=get_vercel_headers(token), json=payload
420461
)
421462

422463
if response.status_code not in [200, 201]:

0 commit comments

Comments
 (0)