""" Blog Writer Exception Hierarchy Defines custom exception classes for different failure modes in the AI Blog Writer. Each exception includes error_code, user_message, retry_suggested, and actionable_steps. """ from typing import List, Optional, Dict, Any from enum import Enum class ErrorCategory(Enum): """Categories for error classification.""" TRANSIENT = "transient" # Temporary issues, retry recommended PERMANENT = "permanent" # Permanent issues, no retry USER_ERROR = "user_error" # User input issues, fix input API_ERROR = "api_error" # External API issues VALIDATION_ERROR = "validation_error" # Data validation issues SYSTEM_ERROR = "system_error" # Internal system issues class BlogWriterException(Exception): """Base exception for all Blog Writer errors.""" def __init__( self, message: str, error_code: str, user_message: str, retry_suggested: bool = False, actionable_steps: Optional[List[str]] = None, error_category: ErrorCategory = ErrorCategory.SYSTEM_ERROR, context: Optional[Dict[str, Any]] = None ): super().__init__(message) self.error_code = error_code self.user_message = user_message self.retry_suggested = retry_suggested self.actionable_steps = actionable_steps or [] self.error_category = error_category self.context = context or {} def to_dict(self) -> Dict[str, Any]: """Convert exception to dictionary for API responses.""" return { "error_code": self.error_code, "user_message": self.user_message, "retry_suggested": self.retry_suggested, "actionable_steps": self.actionable_steps, "error_category": self.error_category.value, "context": self.context } class ResearchFailedException(BlogWriterException): """Raised when research operation fails.""" def __init__( self, message: str, user_message: str = "Research failed. Please try again with different keywords or check your internet connection.", retry_suggested: bool = True, context: Optional[Dict[str, Any]] = None ): super().__init__( message=message, error_code="RESEARCH_FAILED", user_message=user_message, retry_suggested=retry_suggested, actionable_steps=[ "Try with different keywords", "Check your internet connection", "Wait a few minutes and try again", "Contact support if the issue persists" ], error_category=ErrorCategory.API_ERROR, context=context ) class OutlineGenerationException(BlogWriterException): """Raised when outline generation fails.""" def __init__( self, message: str, user_message: str = "Outline generation failed. Please try again or adjust your research data.", retry_suggested: bool = True, context: Optional[Dict[str, Any]] = None ): super().__init__( message=message, error_code="OUTLINE_GENERATION_FAILED", user_message=user_message, retry_suggested=retry_suggested, actionable_steps=[ "Try generating outline again", "Check if research data is complete", "Try with different research keywords", "Contact support if the issue persists" ], error_category=ErrorCategory.API_ERROR, context=context ) class ContentGenerationException(BlogWriterException): """Raised when content generation fails.""" def __init__( self, message: str, user_message: str = "Content generation failed. Please try again or adjust your outline.", retry_suggested: bool = True, context: Optional[Dict[str, Any]] = None ): super().__init__( message=message, error_code="CONTENT_GENERATION_FAILED", user_message=user_message, retry_suggested=retry_suggested, actionable_steps=[ "Try generating content again", "Check if outline is complete", "Try with a shorter outline", "Contact support if the issue persists" ], error_category=ErrorCategory.API_ERROR, context=context ) class SEOAnalysisException(BlogWriterException): """Raised when SEO analysis fails.""" def __init__( self, message: str, user_message: str = "SEO analysis failed. Content was generated but SEO optimization is unavailable.", retry_suggested: bool = True, context: Optional[Dict[str, Any]] = None ): super().__init__( message=message, error_code="SEO_ANALYSIS_FAILED", user_message=user_message, retry_suggested=retry_suggested, actionable_steps=[ "Try SEO analysis again", "Continue without SEO optimization", "Contact support if the issue persists" ], error_category=ErrorCategory.API_ERROR, context=context ) class APIRateLimitException(BlogWriterException): """Raised when API rate limit is exceeded.""" def __init__( self, message: str, retry_after: Optional[int] = None, context: Optional[Dict[str, Any]] = None ): retry_message = f"Rate limit exceeded. Please wait {retry_after} seconds before trying again." if retry_after else "Rate limit exceeded. Please wait a few minutes before trying again." super().__init__( message=message, error_code="API_RATE_LIMIT", user_message=retry_message, retry_suggested=True, actionable_steps=[ f"Wait {retry_after or 60} seconds before trying again", "Reduce the frequency of requests", "Try again during off-peak hours", "Contact support if you need higher limits" ], error_category=ErrorCategory.API_ERROR, context=context ) class APITimeoutException(BlogWriterException): """Raised when API request times out.""" def __init__( self, message: str, timeout_seconds: int = 60, context: Optional[Dict[str, Any]] = None ): super().__init__( message=message, error_code="API_TIMEOUT", user_message=f"Request timed out after {timeout_seconds} seconds. Please try again.", retry_suggested=True, actionable_steps=[ "Try again with a shorter request", "Check your internet connection", "Try again during off-peak hours", "Contact support if the issue persists" ], error_category=ErrorCategory.TRANSIENT, context=context ) class ValidationException(BlogWriterException): """Raised when input validation fails.""" def __init__( self, message: str, field: str, user_message: str = "Invalid input provided. Please check your data and try again.", context: Optional[Dict[str, Any]] = None ): super().__init__( message=message, error_code="VALIDATION_ERROR", user_message=user_message, retry_suggested=False, actionable_steps=[ f"Check the {field} field", "Ensure all required fields are filled", "Verify data format is correct", "Contact support if you need help" ], error_category=ErrorCategory.USER_ERROR, context=context ) class CircuitBreakerOpenException(BlogWriterException): """Raised when circuit breaker is open.""" def __init__( self, message: str, retry_after: int, context: Optional[Dict[str, Any]] = None ): super().__init__( message=message, error_code="CIRCUIT_BREAKER_OPEN", user_message=f"Service temporarily unavailable. Please wait {retry_after} seconds before trying again.", retry_suggested=True, actionable_steps=[ f"Wait {retry_after} seconds before trying again", "Try again during off-peak hours", "Contact support if the issue persists" ], error_category=ErrorCategory.TRANSIENT, context=context ) class PartialSuccessException(BlogWriterException): """Raised when operation partially succeeds.""" def __init__( self, message: str, partial_results: Dict[str, Any], failed_operations: List[str], user_message: str = "Operation partially completed. Some sections were generated successfully.", context: Optional[Dict[str, Any]] = None ): super().__init__( message=message, error_code="PARTIAL_SUCCESS", user_message=user_message, retry_suggested=True, actionable_steps=[ "Review the generated content", "Retry failed sections individually", "Contact support if you need help with failed sections" ], error_category=ErrorCategory.TRANSIENT, context=context ) self.partial_results = partial_results self.failed_operations = failed_operations