(*args: Any, **kwargs: Any)
| 97 | def decorator(func: Callable[..., Any]) -> Callable[..., Any]: |
| 98 | @functools.wraps(func) |
| 99 | async def wrapper(*args: Any, **kwargs: Any) -> Any: |
| 100 | last_exception: Exception | None = None |
| 101 | |
| 102 | for attempt in range(config.max_retries + 1): |
| 103 | try: |
| 104 | # Try to execute function |
| 105 | return await func(*args, **kwargs) |
| 106 | |
| 107 | except config.retryable_exceptions as e: |
| 108 | last_exception = e |
| 109 | |
| 110 | # If this is the last attempt, don't retry |
| 111 | if attempt >= config.max_retries: |
| 112 | logger.error(f"Function {func.__name__} retry failed, reached maximum retry count {config.max_retries}") |
| 113 | raise RetryExhaustedError(e, attempt + 1) |
| 114 | |
| 115 | # Calculate delay time |
| 116 | delay = config.calculate_delay(attempt) |
| 117 | |
| 118 | # Log |
| 119 | logger.warning( |
| 120 | f"Function {func.__name__} call {attempt + 1} failed: {str(e)}, " |
| 121 | f"retrying attempt {attempt + 2} after {delay:.2f} seconds" |
| 122 | ) |
| 123 | |
| 124 | # Call callback function |
| 125 | if on_retry: |
| 126 | on_retry(e, attempt + 1) |
| 127 | |
| 128 | # Wait before retry |
| 129 | await asyncio.sleep(delay) |
| 130 | |
| 131 | # Should not reach here in theory |
| 132 | if last_exception: |
| 133 | raise last_exception |
| 134 | raise Exception("Unknown error") |
| 135 | |
| 136 | return wrapper |
| 137 |
nothing calls this directly
no test coverage detected