Files
xingrin/tools/seed-api/error_handler.py
yyhuni 069527a7f1 feat(backend): implement vulnerability and screenshot snapshot APIs with directories tab reorganization
- Add vulnerability snapshot DTO, handler, repository, and service layer with comprehensive test coverage
- Add screenshot snapshot DTO, handler, repository, and service layer for snapshot management
- Reorganize directories tab from secondary assets navigation to primary navigation in scan history and target layouts
- Update frontend navigation to include FolderSearch icon for directories tab with badge count display
- Add i18n translations for directories tab in English and Chinese messages
- Implement seed data generation tools with Python API client for testing and data population
- Add data generator, error handler, and progress tracking utilities for seed API
- Update target validator to support new snapshot-related validations
- Refactor organization and vulnerability handlers to support snapshot operations
- Add integration tests and property-based tests for vulnerability snapshot functionality
- Update Go module dependencies to support new snapshot features
2026-01-15 10:25:34 +08:00

163 lines
5.2 KiB
Python

"""
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}")