186 lines
5.8 KiB
Python
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
|
|
) |