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