packages/python-packages/apiview-copilot/src/_retry.py (39 lines of code) (raw):
import json
def retry_with_backoff(
func,
*,
max_retries=5,
retry_exceptions=(json.JSONDecodeError, TimeoutError, ConnectionError),
non_retryable_exceptions=(AttributeError, TypeError, NameError, SyntaxError, PermissionError),
on_failure=None,
on_retry=None,
logger=None,
description="operation",
):
"""
Generic retry function with smart defaults.
Args:
func: The function to retry
max_retries: Maximum number of retries
retry_exceptions: Tuple of exceptions that should trigger a retry.
Default includes JSON parsing errors, timeouts, and connection issues.
non_retryable_exceptions: Tuple of exceptions that should never be retried.
Default includes programming errors that won't be fixed by retrying.
on_failure: Function to call on final failure (params: exception, attempt)
on_retry: Function to call on each retry (params: exception, attempt, max_retries)
logger: Logger object to use
description: Description of the operation for logging
Returns:
The result of the function call, or the result of on_failure if all retries fail
"""
for attempt in range(max_retries):
try:
result = func()
return result
except Exception as e:
# Check if this is a non-retryable exception
if isinstance(e, non_retryable_exceptions):
if logger:
logger.error(f"Non-retryable error in {description}: {str(e)}")
if on_failure:
return on_failure(e, attempt)
raise # Re-raise non-retryable exceptions immediately
# Check if this is a retryable exception
if not isinstance(e, retry_exceptions):
if logger:
logger.error(f"Unhandled error in {description}: {str(e)}")
if on_failure:
return on_failure(e, attempt)
raise # Re-raise exceptions that don't match retry_exceptions
# This is a retryable exception
error_msg = f"Error in {description}, attempt {attempt+1}/{max_retries}: {str(e)}"
if logger:
logger.error(error_msg)
# Call the on_retry callback if provided
if on_retry:
on_retry(e, attempt, max_retries)
# If this was the last attempt, call on_failure and return its result
if attempt == max_retries - 1:
if on_failure:
return on_failure(e, attempt)
raise # Re-raise the exception if no on_failure handler
# This shouldn't be reached, but just in case
raise RuntimeError(f"Failed after {max_retries} attempts")