Skip to content

Commit f77841e

Browse files
committed
feat: retry network errors
1 parent 807da32 commit f77841e

1 file changed

Lines changed: 60 additions & 1 deletion

File tree

infisical_sdk/infisical_requests.py

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,26 @@
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
23
import requests
4+
import functools
35
from dataclasses import dataclass
6+
import time
47

58
T = 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+
724
def 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

53108
class 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

Comments
 (0)