207 lines
5.1 KiB
Python
207 lines
5.1 KiB
Python
"""
|
|
Error handling and formatting utilities for consistent error messages across the application.
|
|
"""
|
|
|
|
import subprocess
|
|
from pathlib import Path
|
|
from typing import Any, Dict, Optional
|
|
|
|
|
|
class DownloadError(Exception):
|
|
"""Base exception for download-related errors."""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str,
|
|
error_type: str = "download_error",
|
|
details: Optional[str] = None,
|
|
):
|
|
self.message = message
|
|
self.error_type = error_type
|
|
self.details = details
|
|
super().__init__(self.message)
|
|
|
|
|
|
class YtDlpError(DownloadError):
|
|
"""Exception for yt-dlp specific errors."""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str,
|
|
exit_code: Optional[int] = None,
|
|
stderr: Optional[str] = None,
|
|
):
|
|
self.exit_code = exit_code
|
|
self.stderr = stderr
|
|
super().__init__(
|
|
message, "yt_dlp_error", f"Exit code: {exit_code}, Stderr: {stderr}"
|
|
)
|
|
|
|
|
|
class FileValidationError(DownloadError):
|
|
"""Exception for file validation errors."""
|
|
|
|
def __init__(self, message: str, file_path: Optional[Path] = None):
|
|
self.file_path = file_path
|
|
super().__init__(message, "file_validation_error", f"File: {file_path}")
|
|
|
|
|
|
def format_error_message(
|
|
error_type: str,
|
|
artist: str,
|
|
title: str,
|
|
video_id: Optional[str] = None,
|
|
channel_name: Optional[str] = None,
|
|
details: Optional[str] = None,
|
|
) -> str:
|
|
"""
|
|
Format a consistent error message for tracking and logging.
|
|
|
|
Args:
|
|
error_type: Type of error (e.g., "yt-dlp failed", "file verification failed")
|
|
artist: Artist name
|
|
title: Song title
|
|
video_id: YouTube video ID (optional)
|
|
channel_name: Channel name (optional)
|
|
details: Additional error details (optional)
|
|
|
|
Returns:
|
|
Formatted error message
|
|
"""
|
|
base_msg = f"{error_type}: {artist} - {title}"
|
|
|
|
if video_id:
|
|
base_msg += f" (Video ID: {video_id})"
|
|
|
|
if channel_name:
|
|
base_msg += f" (Channel: {channel_name})"
|
|
|
|
if details:
|
|
base_msg += f" - {details}"
|
|
|
|
return base_msg
|
|
|
|
|
|
def handle_yt_dlp_error(
|
|
exception: subprocess.CalledProcessError,
|
|
artist: str,
|
|
title: str,
|
|
video_id: Optional[str] = None,
|
|
channel_name: Optional[str] = None,
|
|
) -> YtDlpError:
|
|
"""
|
|
Handle yt-dlp subprocess errors and create a standardized exception.
|
|
|
|
Args:
|
|
exception: The CalledProcessError from subprocess.run
|
|
artist: Artist name
|
|
title: Song title
|
|
video_id: YouTube video ID (optional)
|
|
channel_name: Channel name (optional)
|
|
|
|
Returns:
|
|
YtDlpError with formatted message
|
|
"""
|
|
error_msg = format_error_message(
|
|
"yt-dlp failed",
|
|
artist,
|
|
title,
|
|
video_id,
|
|
channel_name,
|
|
f"exit code {exception.returncode}: {exception.stderr}",
|
|
)
|
|
|
|
return YtDlpError(
|
|
error_msg, exit_code=exception.returncode, stderr=exception.stderr
|
|
)
|
|
|
|
|
|
def handle_file_validation_error(
|
|
message: str,
|
|
file_path: Path,
|
|
artist: str,
|
|
title: str,
|
|
video_id: Optional[str] = None,
|
|
channel_name: Optional[str] = None,
|
|
) -> FileValidationError:
|
|
"""
|
|
Handle file validation errors and create a standardized exception.
|
|
|
|
Args:
|
|
message: Error message
|
|
file_path: Path to the file that failed validation
|
|
artist: Artist name
|
|
title: Song title
|
|
video_id: YouTube video ID (optional)
|
|
channel_name: Channel name (optional)
|
|
|
|
Returns:
|
|
FileValidationError with formatted message
|
|
"""
|
|
error_msg = format_error_message(
|
|
"file validation failed",
|
|
artist,
|
|
title,
|
|
video_id,
|
|
channel_name,
|
|
f"{message} - File: {file_path}",
|
|
)
|
|
|
|
return FileValidationError(error_msg, file_path)
|
|
|
|
|
|
def log_error(error: DownloadError, logger=None) -> None:
|
|
"""
|
|
Log an error with consistent formatting.
|
|
|
|
Args:
|
|
error: DownloadError instance
|
|
logger: Optional logger instance
|
|
"""
|
|
if logger:
|
|
logger.error(f"❌ {error.message}")
|
|
if error.details:
|
|
logger.error(f" Details: {error.details}")
|
|
else:
|
|
print(f"❌ {error.message}")
|
|
if error.details:
|
|
print(f" Details: {error.details}")
|
|
|
|
|
|
def create_error_context(
|
|
artist: str,
|
|
title: str,
|
|
video_id: Optional[str] = None,
|
|
channel_name: Optional[str] = None,
|
|
file_path: Optional[Path] = None,
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Create a context dictionary for error reporting.
|
|
|
|
Args:
|
|
artist: Artist name
|
|
title: Song title
|
|
video_id: YouTube video ID (optional)
|
|
channel_name: Channel name (optional)
|
|
file_path: File path (optional)
|
|
|
|
Returns:
|
|
Dictionary with error context
|
|
"""
|
|
context = {
|
|
"artist": artist,
|
|
"title": title,
|
|
"timestamp": None, # Could be added if needed
|
|
}
|
|
|
|
if video_id:
|
|
context["video_id"] = video_id
|
|
|
|
if channel_name:
|
|
context["channel_name"] = channel_name
|
|
|
|
if file_path:
|
|
context["file_path"] = str(file_path)
|
|
|
|
return context
|