1- from typing import Any , AsyncIterable , Callable , Dict , List , Optional
1+ from typing import Any , AsyncIterable , Dict , List , Optional
22
3- import asyncio
43import json
4+ import logging
55import os
6- import time
7- from functools import wraps
86from importlib import metadata as importlib_metadata
97
108import httpx
119from dotenv import load_dotenv
10+ from tenacity import retry , stop_after_attempt , wait_exponential
1211
1312load_dotenv ()
1413
15- MAX_RETRIES = 8
16- BACKOFF_FACTOR = 0.5
17-
18-
19- def retry_on_502 (func : Callable [..., Any ]) -> Callable [..., Any ]:
20- """
21- A decorator to retry a function or coroutine on encountering a 502 error.
22- Parameters:
23- - func: The function or coroutine to be decorated.
24- Returns:
25- - A wrapper function that incorporates retry logic.
26- """
27-
28- @wraps (func )
29- async def async_wrapper (* args , ** kwargs ):
30- for retry in range (MAX_RETRIES ):
31- try :
32- return await func (* args , ** kwargs )
33- except httpx .HTTPError as e :
34- if not _should_retry (e , retry ):
35- raise
36- await asyncio .sleep (BACKOFF_FACTOR * (2 ** retry ))
37-
38- @wraps (func )
39- def sync_wrapper (* args , ** kwargs ):
40- for retry in range (MAX_RETRIES ):
41- try :
42- return func (* args , ** kwargs )
43- except httpx .HTTPError as e :
44- if not _should_retry (e , retry ):
45- raise
46- time .sleep (BACKOFF_FACTOR * (2 ** retry ))
47-
48- def _should_retry (error , current_retry ):
49- """Determines if the function should retry on error."""
50- is_502_error = isinstance (error , httpx .HTTPStatusError ) and error .response .status_code == 502
51- is_last_retry = current_retry == MAX_RETRIES - 1
52- return not is_last_retry and (isinstance (error , (httpx .ConnectError , httpx .ReadError , httpx .RemoteProtocolError )) or is_502_error )
53-
54- if asyncio .iscoroutinefunction (func ):
55- return async_wrapper
56- else :
57- return sync_wrapper
14+ logger = logging .getLogger ()
5815
5916
6017class HTTPClient :
@@ -87,7 +44,7 @@ def _get_headers(self, api_key: Optional[str] = None) -> Dict[str, str]:
8744 headers ["x-sdk-integrations" ] = "," .join (self .integrations )
8845 return headers
8946
90- @retry_on_502
47+ @retry ( stop = stop_after_attempt ( 3 ), wait = wait_exponential ( min = 4 , max = 10 ))
9148 def request (
9249 self ,
9350 method : str ,
@@ -108,9 +65,25 @@ def request(
10865 if e .response .status_code == 422 :
10966 # update the error message to include the validation errors
11067 e .args = (f"{ e .args [0 ]} : { e .response .json ()} " ,)
68+ logger .error (
69+ f"HTTP error { e .response .status_code } for { e .request .method } with: { e .args } " ,
70+ extra = {"request_data" : data , "request_params" : params },
71+ )
72+ raise
73+ except httpx .TimeoutException as e :
74+ logger .error (
75+ f"Timeout error for { e .request .method } { e .request .url } " ,
76+ extra = {"request_data" : data , "request_params" : params },
77+ )
78+ raise
79+ except httpx .RequestError as e :
80+ logger .error (
81+ f"Request error for { e .request .method } { e .request .url } : { str (e )} " ,
82+ extra = {"request_data" : data , "request_params" : params },
83+ )
11184 raise
11285
113- @retry_on_502
86+ @retry ( stop = stop_after_attempt ( 3 ), wait = wait_exponential ( min = 4 , max = 10 ))
11487 async def request_async (
11588 self ,
11689 method : str ,
@@ -128,10 +101,28 @@ async def request_async(
128101 response .raise_for_status ()
129102 return response
130103 except httpx .HTTPStatusError as e :
131- print (f"HTTP Error { e .response .status_code } for { e .request .url } : { e .response .text } " )
104+ if e .response .status_code == 422 :
105+ # update the error message to include the validation errors
106+ e .args = (f"{ e .args [0 ]} : { e .response .json ()} " ,)
107+ logger .error (
108+ f"HTTP error { e .response .status_code } for { e .request .method } with: { e .args } " ,
109+ extra = {"request_data" : data , "request_params" : params },
110+ )
111+ raise
112+ except httpx .TimeoutException as e :
113+ logger .error (
114+ f"Timeout error for { e .request .method } { e .request .url } " ,
115+ extra = {"request_data" : data , "request_params" : params },
116+ )
117+ raise
118+ except httpx .RequestError as e :
119+ logger .error (
120+ f"Request error for { e .request .method } { e .request .url } : { str (e )} " ,
121+ extra = {"request_data" : data , "request_params" : params },
122+ )
132123 raise
133124
134- @retry_on_502
125+ @retry ( stop = stop_after_attempt ( 3 ), wait = wait_exponential ( min = 4 , max = 10 ))
135126 def stream_request (
136127 self ,
137128 method : str ,
@@ -151,10 +142,28 @@ def stream_request(
151142 for chunk in response .iter_bytes (chunk_size ):
152143 yield parse_event_data (chunk )
153144 except httpx .HTTPStatusError as e :
154- print (f"HTTP Error { e .response .status_code } for { e .request .url } : { e .response .text } " )
145+ if e .response .status_code == 422 :
146+ # update the error message to include the validation errors
147+ e .args = (f"{ e .args [0 ]} : { e .response .json ()} " ,)
148+ logger .error (
149+ f"HTTP error { e .response .status_code } for { e .request .method } with: { e .args } " ,
150+ extra = {"request_data" : data , "request_params" : params },
151+ )
152+ raise
153+ except httpx .TimeoutException as e :
154+ logger .error (
155+ f"Timeout error for { e .request .method } { e .request .url } " ,
156+ extra = {"request_data" : data , "request_params" : params },
157+ )
158+ raise
159+ except httpx .RequestError as e :
160+ logger .error (
161+ f"Request error for { e .request .method } { e .request .url } : { str (e )} " ,
162+ extra = {"request_data" : data , "request_params" : params },
163+ )
155164 raise
156165
157- @retry_on_502
166+ @retry ( stop = stop_after_attempt ( 3 ), wait = wait_exponential ( min = 4 , max = 10 ))
158167 async def stream_request_async (
159168 self ,
160169 method : str ,
@@ -174,7 +183,25 @@ async def stream_request_async(
174183 async for chunk in response .aiter_bytes (chunk_size ):
175184 yield parse_event_data (chunk )
176185 except httpx .HTTPStatusError as e :
177- print (f"HTTP Error { e .response .status_code } for { e .request .url } : { e .response .text } " )
186+ if e .response .status_code == 422 :
187+ # update the error message to include the validation errors
188+ e .args = (f"{ e .args [0 ]} : { e .response .json ()} " ,)
189+ logger .error (
190+ f"HTTP error { e .response .status_code } for { e .request .method } with: { e .args } " ,
191+ extra = {"request_data" : data , "request_params" : params },
192+ )
193+ raise
194+ except httpx .TimeoutException as e :
195+ logger .error (
196+ f"Timeout error for { e .request .method } { e .request .url } " ,
197+ extra = {"request_data" : data , "request_params" : params },
198+ )
199+ raise
200+ except httpx .RequestError as e :
201+ logger .error (
202+ f"Request error for { e .request .method } { e .request .url } : { str (e )} " ,
203+ extra = {"request_data" : data , "request_params" : params },
204+ )
178205 raise
179206
180207 def close (self ):
0 commit comments