Files
xingrin/tools/seed-api/error_handler.py

163 lines
5.2 KiB
Python
Raw Normal View History

"""
Error Handler Module
Handles API errors, retry logic, and error logging.
"""
import time
from typing import Optional, Callable, Any
class ErrorHandler:
"""Handles errors and retry logic for API calls."""
def __init__(self, max_retries: int = 3, retry_delay: float = 1.0):
"""
Initialize error handler.
Args:
max_retries: Maximum number of retries for failed requests
retry_delay: Delay in seconds between retries
"""
self.max_retries = max_retries
self.retry_delay = retry_delay
def should_retry(self, status_code: int) -> bool:
"""
Determine if a request should be retried based on status code.
Args:
status_code: HTTP status code
Returns:
True if should retry, False otherwise
"""
# Retry on 5xx server errors
if 500 <= status_code < 600:
return True
# Retry on 429 rate limit
if status_code == 429:
return True
# Don't retry on 4xx client errors (except 429)
return False
def handle_error(self, error: Exception, context: dict) -> bool:
"""
Handle error and determine if operation should continue.
Args:
error: The exception that occurred
context: Context information (endpoint, data, etc.)
Returns:
True if should continue, False if should stop
"""
from api_client import APIError
# Handle API errors
if isinstance(error, APIError):
if error.status_code and self.should_retry(error.status_code):
return True # Continue (will be retried)
else:
# Log and skip this record
self.log_error(str(error), context.get("request_data"), context.get("response_data"))
return True # Continue with next record
# Handle network errors (timeout, connection error)
if isinstance(error, Exception) and "Network error" in str(error):
return True # Continue (will be retried)
# Unknown error - log and continue
self.log_error(str(error), context.get("request_data"))
return True
def retry_with_backoff(self, func: Callable, *args, **kwargs) -> Any:
"""
Execute function with retry and exponential backoff.
Args:
func: Function to execute
*args: Positional arguments for function
**kwargs: Keyword arguments for function
Returns:
Function result
Raises:
Exception: If all retries fail
"""
from api_client import APIError
last_error = None
for attempt in range(self.max_retries + 1):
try:
return func(*args, **kwargs)
except APIError as e:
last_error = e
# Don't retry if not a retryable error
if e.status_code and not self.should_retry(e.status_code):
raise
# Don't retry on last attempt
if attempt >= self.max_retries:
raise
# Calculate delay (longer for rate limit)
delay = 5.0 if e.status_code == 429 else self.retry_delay
print(f" ⚠ Retry {attempt + 1}/{self.max_retries} after {delay}s (Status: {e.status_code})")
time.sleep(delay)
except Exception as e:
last_error = e
# Check if it's a network error
if "Network error" not in str(e):
raise
# Don't retry on last attempt
if attempt >= self.max_retries:
raise
print(f" ⚠ Retry {attempt + 1}/{self.max_retries} after {self.retry_delay}s (Network error)")
time.sleep(self.retry_delay)
# Should not reach here, but just in case
if last_error:
raise last_error
def log_error(self, error: str, request_data: Optional[dict] = None, response_data: Optional[dict] = None):
"""
Log error details to file.
Args:
error: Error message
request_data: Request data (if available)
response_data: Response data (if available)
"""
import json
from datetime import datetime
log_entry = {
"timestamp": datetime.now().isoformat(),
"error": error,
}
if request_data:
log_entry["request"] = request_data
if response_data:
log_entry["response"] = response_data
# Append to log file
try:
with open("seed_errors.log", "a", encoding="utf-8") as f:
f.write(json.dumps(log_entry, ensure_ascii=False) + "\n")
except Exception as e:
print(f" ⚠ Failed to write error log: {e}")