Skip to content

Commit 11618d4

Browse files
committed
feat(client): add retry mechanism to synchronous Client
1 parent 6af48d5 commit 11618d4

1 file changed

Lines changed: 46 additions & 3 deletions

File tree

openapi_python_sdk/client.py

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

57
import httpx
@@ -15,10 +17,23 @@ class Client:
1517
Synchronous client for making authenticated requests to Openapi endpoints.
1618
"""
1719

18-
def __init__(self, token: str, client: Any = None, timeout: float = 30.0):
20+
def __init__(
21+
self,
22+
token: str,
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+
):
1929
self._client = client
2030
self._thread_local = threading.local()
2131
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+
)
2237
self.auth_header: str = f"Bearer {token}"
2338
self.headers: Dict[str, str] = {
2439
"Authorization": self.auth_header,
@@ -55,6 +70,32 @@ def close(self):
5570
"""Manually close the underlying HTTP client."""
5671
self.client.close()
5772

73+
def _request_with_retry(self, request_fn, *args, **kwargs) -> httpx.Response:
74+
attempts = 0
75+
while True:
76+
try:
77+
resp = request_fn(*args, **kwargs)
78+
if resp.status_code in self.retry_on_status and attempts < self.max_retries:
79+
attempts += 1
80+
sleep_time = self.backoff_factor * (2 ** attempts) + random.uniform(0, 0.5)
81+
if resp.status_code == 429:
82+
retry_after = resp.headers.get("Retry-After")
83+
if retry_after:
84+
try:
85+
sleep_time = float(retry_after)
86+
except ValueError:
87+
pass
88+
time.sleep(sleep_time)
89+
continue
90+
return resp
91+
except httpx.RequestError as exc:
92+
if attempts < self.max_retries:
93+
attempts += 1
94+
sleep_time = self.backoff_factor * (2 ** attempts) + random.uniform(0, 0.5)
95+
time.sleep(sleep_time)
96+
continue
97+
raise exc
98+
5899
def request(
59100
self,
60101
method: str = "GET",
@@ -75,13 +116,15 @@ def request(
75116
url = f"{url}&{query_string}" if "?" in url else f"{url}?{query_string}"
76117
params = None
77118

78-
data = self.client.request(
119+
resp = self._request_with_retry(
120+
self.client.request,
79121
method=method,
80122
url=url,
81123
headers=self.headers,
82124
json=payload,
83125
params=params,
84-
).json()
126+
)
127+
data = resp.json()
85128

86129
# Handle cases where the API might return a JSON-encoded string instead of an object
87130
if isinstance(data, str):

0 commit comments

Comments
 (0)