Base code
This commit is contained in:
285
backend/services/blog_writer/exceptions.py
Normal file
285
backend/services/blog_writer/exceptions.py
Normal file
@@ -0,0 +1,285 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user