youtube-summarizer/backend/core/exceptions.py

186 lines
5.8 KiB
Python

from enum import Enum
from typing import Optional, Dict, Any
from fastapi import status
class ErrorCode(str, Enum):
INVALID_URL = "INVALID_URL"
UNSUPPORTED_FORMAT = "UNSUPPORTED_FORMAT"
VIDEO_NOT_FOUND = "VIDEO_NOT_FOUND"
TRANSCRIPT_NOT_AVAILABLE = "TRANSCRIPT_NOT_AVAILABLE"
TRANSCRIPT_UNAVAILABLE = "TRANSCRIPT_UNAVAILABLE"
AI_SERVICE_ERROR = "AI_SERVICE_ERROR"
RATE_LIMIT_EXCEEDED = "RATE_LIMIT_EXCEEDED"
INTERNAL_ERROR = "INTERNAL_ERROR"
TOKEN_LIMIT_EXCEEDED = "TOKEN_LIMIT_EXCEEDED"
COST_LIMIT_EXCEEDED = "COST_LIMIT_EXCEEDED"
AI_SERVICE_UNAVAILABLE = "AI_SERVICE_UNAVAILABLE"
class BaseAPIException(Exception):
def __init__(
self,
message: str,
error_code: ErrorCode,
status_code: int = status.HTTP_500_INTERNAL_SERVER_ERROR,
details: Optional[Dict[str, Any]] = None,
recoverable: bool = False
):
self.message = message
self.error_code = error_code
self.status_code = status_code
self.details = details or {}
self.recoverable = recoverable
super().__init__(message)
class UserInputError(BaseAPIException):
def __init__(
self,
message: str,
error_code: ErrorCode,
details: Optional[Dict[str, Any]] = None
):
super().__init__(
message=message,
error_code=error_code,
status_code=status.HTTP_400_BAD_REQUEST,
details=details,
recoverable=True
)
class ValidationError(UserInputError):
def __init__(self, message: str, details: Optional[Dict[str, Any]] = None):
super().__init__(
message=message,
error_code=ErrorCode.INVALID_URL,
details=details
)
class UnsupportedFormatError(UserInputError):
def __init__(self, message: str, details: Optional[Dict[str, Any]] = None):
super().__init__(
message=message,
error_code=ErrorCode.UNSUPPORTED_FORMAT,
details=details
)
class YouTubeError(BaseAPIException):
"""Base exception for YouTube-related errors"""
def __init__(self, message: str, details: Optional[Dict[str, Any]] = None):
super().__init__(
message=message,
error_code=ErrorCode.VIDEO_NOT_FOUND,
status_code=status.HTTP_400_BAD_REQUEST,
details=details
)
class TranscriptExtractionError(BaseAPIException):
"""Base exception for transcript extraction failures"""
pass
class AIServiceError(BaseAPIException):
"""Base exception for AI service errors"""
pass
class TokenLimitExceededError(AIServiceError):
"""Raised when content exceeds model token limit"""
def __init__(self, token_count: int, max_tokens: int):
super().__init__(
message=f"Content ({token_count} tokens) exceeds model limit ({max_tokens} tokens)",
error_code=ErrorCode.TOKEN_LIMIT_EXCEEDED,
status_code=status.HTTP_400_BAD_REQUEST,
details={
"token_count": token_count,
"max_tokens": max_tokens,
"suggestions": [
"Use chunked processing for long content",
"Choose a briefer summary length",
"Split content into smaller sections"
]
}
)
class CostLimitExceededError(AIServiceError):
"""Raised when processing cost exceeds limits"""
def __init__(self, estimated_cost: float, cost_limit: float):
super().__init__(
message=f"Estimated cost ${estimated_cost:.3f} exceeds limit ${cost_limit:.2f}",
error_code=ErrorCode.COST_LIMIT_EXCEEDED,
status_code=status.HTTP_400_BAD_REQUEST,
details={
"estimated_cost": estimated_cost,
"cost_limit": cost_limit,
"cost_reduction_tips": [
"Choose 'brief' summary length",
"Remove less important content from transcript",
"Process content in smaller segments"
]
}
)
class AIServiceUnavailableError(AIServiceError):
"""Raised when AI service is temporarily unavailable"""
def __init__(self, message: str = "AI service is temporarily unavailable"):
super().__init__(
message=message,
error_code=ErrorCode.AI_SERVICE_UNAVAILABLE,
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
details={
"suggestions": [
"Please try again in a few moments",
"Check API status page for any ongoing issues"
]
},
recoverable=True
)
class PipelineError(BaseAPIException):
"""Base exception for pipeline processing errors"""
def __init__(
self,
message: str,
stage: str = "unknown",
recoverable: bool = True,
details: Optional[Dict[str, Any]] = None
):
super().__init__(
message=message,
error_code=ErrorCode.INTERNAL_ERROR,
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
details={
"stage": stage,
**(details or {})
},
recoverable=recoverable
)
class ServiceError(BaseAPIException):
"""General service error for business logic failures"""
def __init__(
self,
message: str,
service: str = "unknown",
recoverable: bool = True,
details: Optional[Dict[str, Any]] = None
):
super().__init__(
message=message,
error_code=ErrorCode.INTERNAL_ERROR,
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
details={
"service": service,
**(details or {})
},
recoverable=recoverable
)