1- from typing import Any , Dict , Generic , Optional , TypeVar , Type
1+ from typing import Any , Dict , Generic , Optional , TypeVar , Type , Callable , List
2+ import socket
23import requests
4+ import functools
35from dataclasses import dataclass
6+ import time
47
58T = TypeVar ("T" )
69
10+ # List of network-related exceptions that should trigger retries
11+ NETWORK_ERRORS = [
12+ requests .exceptions .ConnectionError ,
13+ requests .exceptions .ChunkedEncodingError ,
14+ requests .exceptions .ReadTimeout ,
15+ requests .exceptions .ConnectTimeout ,
16+ socket .gaierror ,
17+ socket .timeout ,
18+ ConnectionResetError ,
19+ ConnectionRefusedError ,
20+ ConnectionError ,
21+ ConnectionAbortedError ,
22+ ]
23+
724def join_url (base : str , path : str ) -> str :
825 """
926 Join base URL and path properly, handling slashes appropriately.
@@ -49,6 +66,44 @@ def from_dict(cls, data: Dict) -> 'APIResponse[T]':
4966 headers = data ['headers' ]
5067 )
5168
69+ def with_retry (
70+ max_retries : int = 3 ,
71+ base_delay : float = 1.0 ,
72+ network_errors : Optional [List [Type [Exception ]]] = None
73+ ) -> Callable :
74+ """
75+ Decorator to add retry logic with exponential backoff to requests methods.
76+ """
77+ if network_errors is None :
78+ network_errors = NETWORK_ERRORS
79+
80+ def decorator (func : Callable ) -> Callable :
81+ @functools .wraps (func )
82+ def wrapper (* args , ** kwargs ):
83+ retry_count = 0
84+
85+ while True :
86+ try :
87+ return func (* args , ** kwargs )
88+ except tuple (network_errors ) as error :
89+ retry_count += 1
90+ if retry_count > max_retries :
91+ print (f"Max retries ({ max_retries } ) exceeded. Giving up." )
92+ raise
93+
94+ delay = base_delay * (2 ** (retry_count - 1 ))
95+
96+ print (
97+ f"Network error { error .__class__ .__name__ } : { str (error )} . "
98+ f"Retrying in { delay :.2f} s (attempt { retry_count } /{ max_retries } )"
99+ )
100+
101+ time .sleep (delay )
102+
103+ return wrapper
104+
105+ return decorator
106+
52107
53108class InfisicalRequests :
54109 def __init__ (self , host : str , token : Optional [str ] = None ):
@@ -93,6 +148,7 @@ def _handle_response(self, response: requests.Response) -> Dict[str, Any]:
93148 except ValueError :
94149 raise InfisicalError ("Invalid JSON response" )
95150
151+ @with_retry (max_retries = 4 , base_delay = 1.0 )
96152 def get (
97153 self ,
98154 path : str ,
@@ -119,6 +175,7 @@ def get(
119175 headers = dict (response .headers )
120176 )
121177
178+ @with_retry (max_retries = 4 , base_delay = 1.0 )
122179 def post (
123180 self ,
124181 path : str ,
@@ -143,6 +200,7 @@ def post(
143200 headers = dict (response .headers )
144201 )
145202
203+ @with_retry (max_retries = 4 , base_delay = 1.0 )
146204 def patch (
147205 self ,
148206 path : str ,
@@ -167,6 +225,7 @@ def patch(
167225 headers = dict (response .headers )
168226 )
169227
228+ @with_retry (max_retries = 4 , base_delay = 1.0 )
170229 def delete (
171230 self ,
172231 path : str ,
0 commit comments