Python API for accessing and downloading content from JMComic with Cloudflare bypass and plugin system.
—
Comprehensive exception hierarchy with context support for robust error handling and debugging. Provides specific error types for different failure scenarios with detailed context information and recovery suggestions.
from typing import Dict, Any, List, Optional, UnionFoundation exception class with context support and enhanced error information.
class JmcomicException(Exception):
"""
Base exception class with context support and enhanced error information.
All JMComic exceptions inherit from this base class, providing
consistent error handling with context information and debugging support.
Attributes:
- message: str - Error message
- context: Dict[str, Any] - Additional context information
- inner_exception: Exception - Original exception if wrapped
- error_code: str - Unique error code for categorization
Methods:
- add_context(key, value): Add context information
- get_context(key, default=None): Get context value
- has_context(key): Check if context key exists
- get_full_message(): Get message with context
- to_dict(): Convert exception to dictionary
"""
def __init__(self, message: str, context: Dict[str, Any] = None,
inner_exception: Exception = None):
"""
Initialize exception with message and optional context.
Parameters:
- message: str - Error message
- context: dict, optional - Additional context information
- inner_exception: Exception, optional - Original exception
"""
super().__init__(message)
self.message = message
self.context = context or {}
self.inner_exception = inner_exception
self.error_code = self.__class__.__name__
def add_context(self, key: str, value: Any):
"""
Add context information to the exception.
Parameters:
- key: str - Context key
- value: Any - Context value
"""
self.context[key] = value
def get_context(self, key: str, default: Any = None) -> Any:
"""
Get context value by key.
Parameters:
- key: str - Context key
- default: Any - Default value if key not found
Returns:
Any - Context value or default
"""
return self.context.get(key, default)
def get_full_message(self) -> str:
"""
Get complete error message including context.
Returns:
str - Full error message with context information
"""
if not self.context:
return self.message
context_str = ", ".join(f"{k}={v}" for k, v in self.context.items())
return f"{self.message} (Context: {context_str})"
def to_dict(self) -> Dict[str, Any]:
"""
Convert exception to dictionary representation.
Returns:
dict - Exception data as dictionary
"""
return {
'error_code': self.error_code,
'message': self.message,
'context': self.context,
'inner_exception': str(self.inner_exception) if self.inner_exception else None
}Exceptions related to HTTP requests, responses, and network communication.
class ResponseUnexpectedException(JmcomicException):
"""
Exception for HTTP response errors and unexpected status codes.
Raised when HTTP requests return unexpected status codes or
response content that doesn't match expected format.
Attributes:
- status_code: int - HTTP status code
- response_url: str - URL that caused the error
- response_headers: Dict[str, str] - Response headers
- response_content: str - Response content (truncated)
"""
def __init__(self, message: str, status_code: int = None,
response_url: str = None, response_content: str = None):
"""
Initialize HTTP response exception.
Parameters:
- message: str - Error message
- status_code: int, optional - HTTP status code
- response_url: str, optional - URL that failed
- response_content: str, optional - Response content
"""
context = {}
if status_code is not None:
context['status_code'] = status_code
if response_url:
context['url'] = response_url
if response_content:
context['content_preview'] = response_content[:500]
super().__init__(message, context)
self.status_code = status_code
self.response_url = response_url
self.response_content = response_content
class RequestRetryAllFailException(JmcomicException):
"""
Exception when all retry attempts have been exhausted.
Raised when multiple retry attempts fail and no more retries
are available according to the configuration.
Attributes:
- retry_count: int - Number of retry attempts made
- last_exception: Exception - Last exception encountered
- failed_attempts: List[Exception] - All failed attempts
"""
def __init__(self, message: str, retry_count: int,
failed_attempts: List[Exception]):
"""
Initialize retry exhaustion exception.
Parameters:
- message: str - Error message
- retry_count: int - Number of retries attempted
- failed_attempts: List[Exception] - All failed attempts
"""
context = {
'retry_count': retry_count,
'attempt_count': len(failed_attempts)
}
super().__init__(message, context)
self.retry_count = retry_count
self.failed_attempts = failed_attempts
self.last_exception = failed_attempts[-1] if failed_attempts else NoneExceptions related to data parsing, validation, and processing operations.
class RegularNotMatchException(JmcomicException):
"""
Exception for HTML parsing and regex matching failures.
Raised when regular expressions fail to match expected patterns
in HTML content or when required data cannot be extracted.
Attributes:
- pattern: str - Regex pattern that failed to match
- content_sample: str - Sample of content that failed to match
"""
def __init__(self, message: str, pattern: str = None, content: str = None):
"""
Initialize regex matching exception.
Parameters:
- message: str - Error message
- pattern: str, optional - Regex pattern that failed
- content: str, optional - Content that failed to match
"""
context = {}
if pattern:
context['pattern'] = pattern
if content:
context['content_sample'] = content[:200]
super().__init__(message, context)
self.pattern = pattern
self.content_sample = content
class JsonResolveFailException(JmcomicException):
"""
Exception for JSON parsing and validation errors.
Raised when JSON content cannot be parsed or doesn't match
the expected structure for API responses.
Attributes:
- json_content: str - JSON content that failed to parse
- parse_error: str - Specific parsing error message
"""
def __init__(self, message: str, json_content: str = None,
parse_error: str = None):
"""
Initialize JSON parsing exception.
Parameters:
- message: str - Error message
- json_content: str, optional - JSON that failed to parse
- parse_error: str, optional - Specific parsing error
"""
context = {}
if json_content:
context['json_preview'] = json_content[:300]
if parse_error:
context['parse_error'] = parse_error
super().__init__(message, context)
self.json_content = json_content
self.parse_error = parse_errorExceptions related to missing or unavailable content.
class MissingAlbumPhotoException(JmcomicException):
"""
Exception for content not found or unavailable errors.
Raised when requested albums, photos, or images cannot be found
or are no longer available on the platform.
Attributes:
- content_type: str - Type of missing content ('album', 'photo', 'image')
- content_id: str - ID of the missing content
- availability_status: str - Reason for unavailability
"""
def __init__(self, message: str, content_type: str = None,
content_id: str = None, availability_status: str = None):
"""
Initialize missing content exception.
Parameters:
- message: str - Error message
- content_type: str, optional - Type of missing content
- content_id: str, optional - ID of missing content
- availability_status: str, optional - Reason for unavailability
"""
context = {}
if content_type:
context['content_type'] = content_type
if content_id:
context['content_id'] = content_id
if availability_status:
context['availability_status'] = availability_status
super().__init__(message, context)
self.content_type = content_type
self.content_id = content_id
self.availability_status = availability_statusExceptions specific to download operations and partial failures.
class PartialDownloadFailedException(JmcomicException):
"""
Exception for partial download failures.
Raised when some parts of a download operation fail while others
succeed. Contains information about successful and failed operations.
Attributes:
- successful_downloads: List[str] - IDs of successful downloads
- failed_downloads: List[str] - IDs of failed downloads
- failure_details: Dict[str, Exception] - Detailed failure information
- success_rate: float - Percentage of successful downloads
"""
def __init__(self, message: str, successful_downloads: List[str] = None,
failed_downloads: List[str] = None,
failure_details: Dict[str, Exception] = None):
"""
Initialize partial download failure exception.
Parameters:
- message: str - Error message
- successful_downloads: list, optional - Successful download IDs
- failed_downloads: list, optional - Failed download IDs
- failure_details: dict, optional - Detailed failure information
"""
successful_downloads = successful_downloads or []
failed_downloads = failed_downloads or []
total = len(successful_downloads) + len(failed_downloads)
success_rate = (len(successful_downloads) / total * 100) if total > 0 else 0
context = {
'successful_count': len(successful_downloads),
'failed_count': len(failed_downloads),
'success_rate': f"{success_rate:.1f}%"
}
super().__init__(message, context)
self.successful_downloads = successful_downloads
self.failed_downloads = failed_downloads
self.failure_details = failure_details or {}
self.success_rate = success_rateUtility class for exception creation, context management, and error handling.
class ExceptionTool:
"""
Exception creation and context management utilities.
Provides helper functions for creating exceptions with context,
validating conditions, and managing error scenarios.
Static Methods:
- require_true(condition, message, exception_class=None): Assert condition
- require_not_none(value, message): Assert value is not None
- require_not_empty(collection, message): Assert collection not empty
- wrap_exception(exception, message, context=None): Wrap existing exception
- create_with_context(exception_class, message, **context): Create with context
"""
@staticmethod
def require_true(condition: bool, message: str,
exception_class: type = None) -> None:
"""
Assert that condition is true, raise exception if false.
Parameters:
- condition: bool - Condition to check
- message: str - Error message if condition fails
- exception_class: type, optional - Exception class to raise
Raises:
JmcomicException or specified exception - If condition is false
"""
if not condition:
exc_class = exception_class or JmcomicException
raise exc_class(message)
@staticmethod
def require_not_none(value: Any, message: str) -> None:
"""
Assert that value is not None.
Parameters:
- value: Any - Value to check
- message: str - Error message if None
Raises:
JmcomicException - If value is None
"""
ExceptionTool.require_true(value is not None, message)
@staticmethod
def require_not_empty(collection: Union[List, Dict, str], message: str) -> None:
"""
Assert that collection is not empty.
Parameters:
- collection: list/dict/str - Collection to check
- message: str - Error message if empty
Raises:
JmcomicException - If collection is empty
"""
ExceptionTool.require_true(len(collection) > 0, message)
@staticmethod
def wrap_exception(exception: Exception, message: str,
context: Dict[str, Any] = None) -> JmcomicException:
"""
Wrap existing exception with additional context.
Parameters:
- exception: Exception - Original exception
- message: str - New error message
- context: dict, optional - Additional context
Returns:
JmcomicException - Wrapped exception with context
"""
wrapped = JmcomicException(message, context, exception)
wrapped.add_context('original_exception_type', type(exception).__name__)
return wrapped
@staticmethod
def create_with_context(exception_class: type, message: str,
**context) -> JmcomicException:
"""
Create exception with context from keyword arguments.
Parameters:
- exception_class: type - Exception class to create
- message: str - Error message
- **context: Additional context as keyword arguments
Returns:
JmcomicException - Created exception with context
"""
return exception_class(message, context)# Basic exception handling
try:
album = download_album("invalid_id")
except JmcomicException as e:
print(f"Error: {e.get_full_message()}")
print(f"Error code: {e.error_code}")
# Access specific context
if e.has_context('album_id'):
print(f"Failed album ID: {e.get_context('album_id')}")
# HTTP response error handling
try:
response = client.get_album_detail("123456")
except ResponseUnexpectedException as e:
print(f"HTTP Error: {e.status_code}")
print(f"URL: {e.response_url}")
print(f"Content preview: {e.get_context('content_preview')}")
# Partial download failure handling
try:
results = download_batch(download_album, album_ids)
except PartialDownloadFailedException as e:
print(f"Success rate: {e.success_rate}%")
print(f"Successful: {len(e.successful_downloads)}")
print(f"Failed: {len(e.failed_downloads)}")
# Process partial results
for album_id in e.successful_downloads:
print(f"Successfully downloaded: {album_id}")
for album_id, error in e.failure_details.items():
print(f"Failed to download {album_id}: {error}")
# Using ExceptionTool for validation
try:
ExceptionTool.require_not_none(album_id, "Album ID cannot be None")
ExceptionTool.require_not_empty(photo_list, "Photo list cannot be empty")
ExceptionTool.require_true(len(album_id) > 0, "Album ID must not be empty")
except JmcomicException as e:
print(f"Validation error: {e.message}")
# Creating exceptions with context
error = ExceptionTool.create_with_context(
MissingAlbumPhotoException,
"Album not found",
album_id="123456",
content_type="album",
availability_status="removed"
)
raise errorThe exception hierarchy provides specific error types for different scenarios:
The exception system supports various recovery strategies:
RequestRetryAllFailException to implement retry mechanismsPartialDownloadFailedException to process successful partsInstall with Tessl CLI
npx tessl i tessl/pypi-jmcomic