Skip to content

Commit eaf1f82

Browse files
authored
Merge pull request #34 from Infisical/daniel/retry-on-error
feat: retry network errors
2 parents 807da32 + b276b41 commit eaf1f82

File tree

1 file changed

+59
-1
lines changed

1 file changed

+59
-1
lines changed

infisical_sdk/infisical_requests.py

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,27 @@
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
7+
import random
48

59
T = 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+
725
def 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

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

Comments
 (0)