""" Custom exceptions for NFOGuard Provides structured error handling and better error reporting """ from typing import Optional, Dict, Any class NFOGuardException(Exception): """Base exception for all NFOGuard errors""" def __init__(self, message: str, details: Optional[Dict[str, Any]] = None): super().__init__(message) self.message = message self.details = details or {} def to_dict(self) -> Dict[str, Any]: """Convert exception to dictionary for structured logging""" return { "error_type": self.__class__.__name__, "message": self.message, "details": self.details } class MediaPathNotFoundError(NFOGuardException): """Raised when media directory cannot be found""" def __init__(self, media_type: str, title: str, imdb_id: Optional[str] = None, search_paths: Optional[list] = None): details = { "media_type": media_type, "title": title, "imdb_id": imdb_id, "search_paths": [str(p) for p in (search_paths or [])] } message = f"{media_type.title()} directory not found: {title}" if imdb_id: message += f" (IMDb: {imdb_id})" super().__init__(message, details) class IMDbIDNotFoundError(NFOGuardException): """Raised when IMDb ID cannot be extracted from path or files""" def __init__(self, path: str, media_type: str = "media"): details = { "path": path, "media_type": media_type } message = f"No IMDb ID found for {media_type}: {path}" super().__init__(message, details) class WebhookProcessingError(NFOGuardException): """Raised when webhook processing fails""" def __init__(self, webhook_type: str, reason: str, payload: Optional[Dict] = None): details = { "webhook_type": webhook_type, "reason": reason, "payload": payload } message = f"{webhook_type} webhook processing failed: {reason}" super().__init__(message, details) class ExternalAPIError(NFOGuardException): """Raised when external API calls fail""" def __init__(self, api_name: str, operation: str, status_code: Optional[int] = None, response: Optional[str] = None): details = { "api_name": api_name, "operation": operation, "status_code": status_code, "response": response } message = f"{api_name} API error during {operation}" if status_code: message += f" (HTTP {status_code})" super().__init__(message, details) class DatabaseError(NFOGuardException): """Raised when database operations fail""" def __init__(self, operation: str, table: Optional[str] = None, original_error: Optional[Exception] = None): details = { "operation": operation, "table": table, "original_error": str(original_error) if original_error else None } message = f"Database error during {operation}" if table: message += f" on table {table}" super().__init__(message, details) class NFOCreationError(NFOGuardException): """Raised when NFO file creation fails""" def __init__(self, nfo_path: str, reason: str, media_type: str = "media"): details = { "nfo_path": nfo_path, "reason": reason, "media_type": media_type } message = f"Failed to create {media_type} NFO file: {reason}" super().__init__(message, details) class ConfigurationError(NFOGuardException): """Raised when configuration is invalid or missing""" def __init__(self, setting: str, reason: str, current_value: Optional[Any] = None): details = { "setting": setting, "reason": reason, "current_value": current_value } message = f"Configuration error for {setting}: {reason}" super().__init__(message, details) class FileOperationError(NFOGuardException): """Raised when file operations fail""" def __init__(self, operation: str, file_path: str, reason: str): details = { "operation": operation, "file_path": file_path, "reason": reason } message = f"File {operation} failed for {file_path}: {reason}" super().__init__(message, details) class DateProcessingError(NFOGuardException): """Raised when date processing or parsing fails""" def __init__(self, date_value: str, operation: str, media_type: str = "media"): details = { "date_value": date_value, "operation": operation, "media_type": media_type } message = f"Date processing error during {operation} for {media_type}: {date_value}" super().__init__(message, details) class RetryableError(NFOGuardException): """Base class for errors that can be retried""" def __init__(self, message: str, details: Optional[Dict[str, Any]] = None, retry_after: Optional[int] = None): super().__init__(message, details) self.retry_after = retry_after # Seconds to wait before retry class NetworkRetryableError(RetryableError): """Network errors that can be retried""" def __init__(self, url: str, reason: str, retry_after: Optional[int] = 30): details = {"url": url, "reason": reason} message = f"Network error for {url}: {reason}" super().__init__(message, details, retry_after) class TemporaryFileError(RetryableError): """Temporary file system errors that can be retried""" def __init__(self, file_path: str, operation: str, reason: str, retry_after: Optional[int] = 5): details = {"file_path": file_path, "operation": operation, "reason": reason} message = f"Temporary file error during {operation} for {file_path}: {reason}" super().__init__(message, details, retry_after)