Skip to content

Commit 69eecb5

Browse files
committed
feat(oauth-client): add retry mechanism to OauthClient
1 parent 44b08a1 commit 69eecb5

1 file changed

Lines changed: 54 additions & 6 deletions

File tree

openapi_python_sdk/oauth_client.py

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import base64
2+
import random
23
import threading
4+
import time
35
from typing import Any, Dict, List
46

57
import httpx
@@ -13,10 +15,25 @@ class OauthClient:
1315
Synchronous client for handling Openapi authentication and token management.
1416
"""
1517

16-
def __init__(self, username: str, apikey: str, test: bool = False, client: Any = None, timeout: float = 30.0):
18+
def __init__(
19+
self,
20+
username: str,
21+
apikey: str,
22+
test: bool = False,
23+
client: Any = None,
24+
timeout: float = 30.0,
25+
max_retries: int = 0,
26+
backoff_factor: float = 1.0,
27+
retry_on_status: List[int] = None,
28+
):
1729
self._client = client
1830
self._thread_local = threading.local()
1931
self.timeout = timeout
32+
self.max_retries = max_retries
33+
self.backoff_factor = backoff_factor
34+
self.retry_on_status = (
35+
retry_on_status if retry_on_status is not None else [429, 502, 503, 504]
36+
)
2037
self.url: str = TEST_OAUTH_BASE_URL if test else OAUTH_BASE_URL
2138
self.auth_header: str = (
2239
"Basic " + base64.b64encode(f"{username}:{apikey}".encode("utf-8")).decode()
@@ -55,30 +72,61 @@ def close(self):
5572
"""Manually close the underlying HTTP client."""
5673
self.client.close()
5774

75+
def _request_with_retry(self, request_fn, *args, **kwargs) -> httpx.Response:
76+
attempts = 0
77+
while True:
78+
try:
79+
resp = request_fn(*args, **kwargs)
80+
if resp.status_code in self.retry_on_status and attempts < self.max_retries:
81+
attempts += 1
82+
sleep_time = self.backoff_factor * (2 ** attempts) + random.uniform(0, 0.5)
83+
if resp.status_code == 429:
84+
retry_after = resp.headers.get("Retry-After")
85+
if retry_after:
86+
try:
87+
sleep_time = float(retry_after)
88+
except ValueError:
89+
pass
90+
time.sleep(sleep_time)
91+
continue
92+
return resp
93+
except httpx.RequestError as exc:
94+
if attempts < self.max_retries:
95+
attempts += 1
96+
sleep_time = self.backoff_factor * (2 ** attempts) + random.uniform(0, 0.5)
97+
time.sleep(sleep_time)
98+
continue
99+
raise exc
100+
58101
def get_scopes(self, limit: bool = False) -> Dict[str, Any]:
59102
"""Retrieve available scopes for the current user."""
60103
params = {"limit": int(limit)}
61104
url = f"{self.url}/scopes"
62-
return self.client.get(url=url, headers=self.headers, params=params).json()
105+
resp = self._request_with_retry(self.client.get, url=url, headers=self.headers, params=params)
106+
return resp.json()
63107

64108
def create_token(self, scopes: List[str] = [], ttl: int = 0) -> Dict[str, Any]:
65109
"""Create a new bearer token with specified scopes and TTL."""
66110
payload = {"scopes": scopes, "ttl": ttl}
67111
url = f"{self.url}/token"
68-
return self.client.post(url=url, headers=self.headers, json=payload).json()
112+
resp = self._request_with_retry(self.client.post, url=url, headers=self.headers, json=payload)
113+
return resp.json()
69114

70115
def get_token(self, scope: str = None) -> Dict[str, Any]:
71116
"""Retrieve an existing token, optionally filtered by scope."""
72117
params = {"scope": scope or ""}
73118
url = f"{self.url}/token"
74-
return self.client.get(url=url, headers=self.headers, params=params).json()
119+
resp = self._request_with_retry(self.client.get, url=url, headers=self.headers, params=params)
120+
return resp.json()
75121

76122
def delete_token(self, id: str) -> Dict[str, Any]:
77123
"""Revoke/Delete a specific token by ID."""
78124
url = f"{self.url}/token/{id}"
79-
return self.client.delete(url=url, headers=self.headers).json()
125+
resp = self._request_with_retry(self.client.delete, url=url, headers=self.headers)
126+
return resp.json()
80127

81128
def get_counters(self, period: str, date: str) -> Dict[str, Any]:
82129
"""Retrieve usage counters for a specific period and date."""
83130
url = f"{self.url}/counters/{period}/{date}"
84-
return self.client.get(url=url, headers=self.headers).json()
131+
resp = self._request_with_retry(self.client.get, url=url, headers=self.headers)
132+
return resp.json()

0 commit comments

Comments
 (0)