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
7+ import random
48
59T = TypeVar ("T" )
610
11+ # List of network-related exceptions that should trigger retries
12+ NETWORK_ERRORS = [
13+ requests .exceptions .ConnectionError ,
14+ requests .exceptions .ChunkedEncodingError ,
15+ requests .exceptions .ReadTimeout ,
16+ requests .exceptions .ConnectTimeout ,
17+ socket .gaierror ,
18+ socket .timeout ,
19+ ConnectionResetError ,
20+ ConnectionRefusedError ,
21+ ConnectionError ,
22+ ConnectionAbortedError ,
23+ ]
24+
725def join_url (base : str , path : str ) -> str :
826 """
927 Join base URL and path properly, handling slashes appropriately.
@@ -49,6 +67,42 @@ def from_dict(cls, data: Dict) -> 'APIResponse[T]':
4967 headers = data ['headers' ]
5068 )
5169
70+ def with_retry (
71+ max_retries : int = 3 ,
72+ base_delay : float = 1.0 ,
73+ network_errors : Optional [List [Type [Exception ]]] = None
74+ ) -> Callable :
75+ """
76+ Decorator to add retry logic with exponential backoff to requests methods.
77+ """
78+ if network_errors is None :
79+ network_errors = NETWORK_ERRORS
80+
81+ def decorator (func : Callable ) -> Callable :
82+ @functools .wraps (func )
83+ def wrapper (* args , ** kwargs ):
84+ retry_count = 0
85+
86+ while True :
87+ try :
88+ return func (* args , ** kwargs )
89+ except tuple (network_errors ) as error :
90+ retry_count += 1
91+ if retry_count > max_retries :
92+ raise
93+
94+ base_delay_with_backoff = base_delay * (2 ** (retry_count - 1 ))
95+
96+ # +/-20% jitter
97+ jitter = random .uniform (- 0.2 , 0.2 ) * base_delay_with_backoff
98+ delay = base_delay_with_backoff + jitter
99+
100+ time .sleep (delay )
101+
102+ return wrapper
103+
104+ return decorator
105+
52106
53107class InfisicalRequests :
54108 def __init__ (self , host : str , token : Optional [str ] = None ):
@@ -93,6 +147,7 @@ def _handle_response(self, response: requests.Response) -> Dict[str, Any]:
93147 except ValueError :
94148 raise InfisicalError ("Invalid JSON response" )
95149
150+ @with_retry (max_retries = 4 , base_delay = 1.0 )
96151 def get (
97152 self ,
98153 path : str ,
@@ -119,6 +174,7 @@ def get(
119174 headers = dict (response .headers )
120175 )
121176
177+ @with_retry (max_retries = 4 , base_delay = 1.0 )
122178 def post (
123179 self ,
124180 path : str ,
@@ -143,6 +199,7 @@ def post(
143199 headers = dict (response .headers )
144200 )
145201
202+ @with_retry (max_retries = 4 , base_delay = 1.0 )
146203 def patch (
147204 self ,
148205 path : str ,
@@ -167,6 +224,7 @@ def patch(
167224 headers = dict (response .headers )
168225 )
169226
227+ @with_retry (max_retries = 4 , base_delay = 1.0 )
170228 def delete (
171229 self ,
172230 path : str ,
0 commit comments