11import json
2+ import random
23import threading
4+ import time
35from typing import Any , Dict
46
57import 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