deploy-ready-copilot / error_handler.py
HIMANSHUKUMARJHA's picture
Add production-ready enhancements: progress tracking, error handling, exports
92f3410
"""Comprehensive error handling with retries and fallbacks."""
from __future__ import annotations
import asyncio
import logging
import time
from functools import wraps
from typing import Any, Callable, Dict, Optional, TypeVar, Union
logger = logging.getLogger(__name__)
T = TypeVar('T')
class RetryConfig:
"""Configuration for retry logic."""
def __init__(
self,
max_retries: int = 3,
initial_delay: float = 1.0,
backoff_factor: float = 2.0,
max_delay: float = 60.0,
retryable_exceptions: tuple = (Exception,)
):
self.max_retries = max_retries
self.initial_delay = initial_delay
self.backoff_factor = backoff_factor
self.max_delay = max_delay
self.retryable_exceptions = retryable_exceptions
def retry_with_backoff(config: Optional[RetryConfig] = None):
"""Decorator for retrying functions with exponential backoff."""
if config is None:
config = RetryConfig()
def decorator(func: Callable[..., T]) -> Callable[..., T]:
@wraps(func)
def sync_wrapper(*args, **kwargs) -> T:
last_exception = None
delay = config.initial_delay
for attempt in range(config.max_retries + 1):
try:
return func(*args, **kwargs)
except config.retryable_exceptions as e:
last_exception = e
if attempt < config.max_retries:
logger.warning(
f"{func.__name__} failed (attempt {attempt + 1}/{config.max_retries + 1}): {e}. "
f"Retrying in {delay:.1f}s..."
)
time.sleep(delay)
delay = min(delay * config.backoff_factor, config.max_delay)
else:
logger.error(f"{func.__name__} failed after {config.max_retries + 1} attempts: {e}")
raise last_exception
@wraps(func)
async def async_wrapper(*args, **kwargs) -> T:
last_exception = None
delay = config.initial_delay
for attempt in range(config.max_retries + 1):
try:
return await func(*args, **kwargs)
except config.retryable_exceptions as e:
last_exception = e
if attempt < config.max_retries:
logger.warning(
f"{func.__name__} failed (attempt {attempt + 1}/{config.max_retries + 1}): {e}. "
f"Retrying in {delay:.1f}s..."
)
await asyncio.sleep(delay)
delay = min(delay * config.backoff_factor, config.max_delay)
else:
logger.error(f"{func.__name__} failed after {config.max_retries + 1} attempts: {e}")
raise last_exception
if asyncio.iscoroutinefunction(func):
return async_wrapper
return sync_wrapper
return decorator
def safe_execute(
func: Callable[..., T],
*args,
default: Optional[T] = None,
error_message: str = "Operation failed",
**kwargs
) -> Union[T, Dict[str, Any]]:
"""Safely execute a function with fallback."""
try:
return func(*args, **kwargs)
except Exception as e:
logger.error(f"{error_message}: {e}")
if default is not None:
return default
return {
"success": False,
"error": str(e),
"error_type": type(e).__name__
}
async def safe_execute_async(
func: Callable[..., T],
*args,
default: Optional[T] = None,
error_message: str = "Operation failed",
**kwargs
) -> Union[T, Dict[str, Any]]:
"""Safely execute an async function with fallback."""
try:
return await func(*args, **kwargs)
except Exception as e:
logger.error(f"{error_message}: {e}")
if default is not None:
return default
return {
"success": False,
"error": str(e),
"error_type": type(e).__name__
}
class PartialResult:
"""Container for partial results when some operations fail."""
def __init__(self):
self.results: Dict[str, Any] = {}
self.errors: Dict[str, str] = {}
self.success_count: int = 0
self.failure_count: int = 0
def add_result(self, key: str, value: Any):
"""Add a successful result."""
self.results[key] = value
self.success_count += 1
def add_error(self, key: str, error: str):
"""Add an error."""
self.errors[key] = error
self.failure_count += 1
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary."""
return {
"results": self.results,
"errors": self.errors,
"success_count": self.success_count,
"failure_count": self.failure_count,
"has_errors": self.failure_count > 0,
"is_partial": self.failure_count > 0 and self.success_count > 0
}