add retry decorator (#1798)
This commit is contained in:
65
skyvern/forge/sdk/core/retry.py
Normal file
65
skyvern/forge/sdk/core/retry.py
Normal file
@@ -0,0 +1,65 @@
|
||||
import asyncio
|
||||
import time
|
||||
from functools import wraps
|
||||
from typing import Any, Callable, Type, TypeVar, cast
|
||||
|
||||
import structlog
|
||||
|
||||
LOG = structlog.get_logger()
|
||||
|
||||
# Type for any callable (sync or async)
|
||||
F = TypeVar("F", bound=Callable[..., Any])
|
||||
|
||||
|
||||
def retry(
|
||||
exceptions: Type[Exception] | tuple[Type[Exception], ...] | None = None,
|
||||
tries: int = 3,
|
||||
delay: int = 3,
|
||||
backoff: int = 2,
|
||||
) -> Callable[[F], F]:
|
||||
"""
|
||||
Decorator to retry a function a specified number of times with a delay between attempts.
|
||||
Works with both async and sync functions.
|
||||
|
||||
Args:
|
||||
exceptions: A tuple of exceptions to catch and retry on.
|
||||
tries: The total number attempts to make.
|
||||
delay: The initial delay between attempts.
|
||||
backoff: The factor by which the delay increases after each attempt.
|
||||
"""
|
||||
if exceptions is None:
|
||||
exceptions = Exception
|
||||
|
||||
def retry_decorator(func: F) -> F:
|
||||
@wraps(func)
|
||||
async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
|
||||
remaining_tries, current_delay = tries, delay
|
||||
while remaining_tries > 1:
|
||||
try:
|
||||
return await func(*args, **kwargs)
|
||||
except exceptions as e:
|
||||
LOG.warning(f"Retrying {func.__name__} in {current_delay} seconds... {e}")
|
||||
await asyncio.sleep(current_delay)
|
||||
remaining_tries -= 1
|
||||
current_delay *= backoff
|
||||
return await func(*args, **kwargs)
|
||||
|
||||
@wraps(func)
|
||||
def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
|
||||
remaining_tries, current_delay = tries, delay
|
||||
while remaining_tries > 1:
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except exceptions as e:
|
||||
LOG.warning(f"Retrying {func.__name__} in {current_delay} seconds... {e}")
|
||||
time.sleep(current_delay)
|
||||
remaining_tries -= 1
|
||||
current_delay *= backoff
|
||||
return func(*args, **kwargs)
|
||||
|
||||
# Return async wrapper if function is async, otherwise sync wrapper
|
||||
if asyncio.iscoroutinefunction(func):
|
||||
return cast(F, async_wrapper)
|
||||
return cast(F, sync_wrapper)
|
||||
|
||||
return retry_decorator
|
||||
Reference in New Issue
Block a user