cjm-error-handling
Structured error handling with context propagation, user-friendly messaging, and serialization support for reusable Python libraries.
Install
pip install cjm_error_handlingProject Structure
nbs/
├── core/ (2)
│ ├── base.ipynb # Foundation classes for structured error handling with context propagation and dual messaging
│ └── errors.ipynb # Concrete error classes for common failure scenarios in library ecosystems
└── utils/ (1)
└── helpers.ipynb # Utilities for easy adoption and incremental migration to structured error handling
Total: 3 notebooks across 2 directories
Module Dependencies
graph LR
core_base[core.base<br/>Base Error Classes]
core_errors[core.errors<br/>Domain-Specific Error Types]
utils_helpers[utils.helpers<br/>Helper Utilities]
core_errors --> core_base
utils_helpers --> core_errors
utils_helpers --> core_base
3 cross-module dependencies detected
CLI Reference
No CLI commands found in this project.
Module Overview
Detailed documentation for each module in the project:
Base Error Classes (base.ipynb)
Foundation classes for structured error handling with context propagation and dual messaging
Import
from cjm_error_handling.core.base import (
ErrorSeverity,
ErrorContext,
BaseError
)Classes
class ErrorSeverity(Enum):
"Error severity levels for categorization and handling."@dataclass
class ErrorContext:
"Structured context information for errors."
job_id: Optional[str] # Job identifier
plugin_id: Optional[str] # Plugin identifier
worker_pid: Optional[int] # Worker process ID
session_id: Optional[str] # Session identifier
user_id: Optional[str] # User identifier
operation: Optional[str] # Operation being performed
timestamp: str = field(...) # When error occurred
extra: Dict[str, Any] = field(...) # Domain-specific context
def to_dict(self) -> Dict[str, Any]: # Dictionary representation
"""Convert context to dictionary for serialization."""
result = asdict(self)
# Remove None values for cleaner serialization
return {k: v for k, v in result.items() if v is not None}
"Convert context to dictionary for serialization."
def from_dict(cls, data: Dict[str, Any]) -> 'ErrorContext': # ErrorContext instance
"""Create ErrorContext from dictionary."""
# Extract known fields
known_fields = {'job_id', 'plugin_id', 'worker_pid', 'session_id',
'user_id', 'operation', 'timestamp'}
kwargs = {k: v for k, v in data.items() if k in known_fields}
"Create ErrorContext from dictionary."class BaseError:
def __init__(
self,
message: str, # User-friendly error message
debug_info: Optional[str] = None, # Optional developer details
context: Optional[ErrorContext] = None, # Structured error context
severity: ErrorSeverity = ErrorSeverity.ERROR, # Error severity level
is_retryable: bool = False, # Whether error is transient/retryable
cause: Optional[Exception] = None # Original exception if chaining
)
"Base exception class with rich context and dual messaging."
def __init__(
self,
message: str, # User-friendly error message
debug_info: Optional[str] = None, # Optional developer details
context: Optional[ErrorContext] = None, # Structured error context
severity: ErrorSeverity = ErrorSeverity.ERROR, # Error severity level
is_retryable: bool = False, # Whether error is transient/retryable
cause: Optional[Exception] = None # Original exception if chaining
)
"Initialize error with message, context, and metadata."
def get_user_message(self) -> str: # User-friendly message
"""Get the user-friendly error message."""
return self.message
def get_debug_message(self) -> str: # Debug message with details
"Get the user-friendly error message."
def get_debug_message(self) -> str: # Debug message with details
"""Get detailed debug information."""
parts = [self.message]
if self.debug_info
"Get detailed debug information."
def to_dict(self) -> Dict[str, Any]: # Dictionary representation
"""Serialize error to dictionary for transmission across process boundaries."""
result = {
'error_type': self.__class__.__name__,
"Serialize error to dictionary for transmission across process boundaries."
def from_dict(cls, data: Dict[str, Any]) -> 'BaseError': # Reconstructed error
"""Reconstruct error from dictionary representation."""
# Reconstruct context
context = ErrorContext.from_dict(data.get('context', {}))
# Reconstruct severity
severity_str = data.get('severity', 'error')
severity = ErrorSeverity(severity_str)
# Note: We can't reconstruct the original cause exception,
"Reconstruct error from dictionary representation."Domain-Specific Error Types (errors.ipynb)
Concrete error classes for common failure scenarios in library ecosystems
Import
from cjm_error_handling.core.errors import (
ValidationError,
ConfigurationError,
ResourceError,
PluginError,
WorkerError
)Classes
class ValidationError:
def __init__(
self,
message: str, # User-friendly error message
debug_info: Optional[str] = None, # Optional developer details
context: Optional[ErrorContext] = None, # Structured error context
severity: ErrorSeverity = ErrorSeverity.ERROR, # Error severity level
is_retryable: bool = False, # Validation errors typically need fixes, not retries
cause: Optional[Exception] = None, # Original exception if chaining
validation_errors: Optional[Dict[str, Any]] = None # Structured validation details
)
"Raised when validation fails."
def __init__(
self,
message: str, # User-friendly error message
debug_info: Optional[str] = None, # Optional developer details
context: Optional[ErrorContext] = None, # Structured error context
severity: ErrorSeverity = ErrorSeverity.ERROR, # Error severity level
is_retryable: bool = False, # Validation errors typically need fixes, not retries
cause: Optional[Exception] = None, # Original exception if chaining
validation_errors: Optional[Dict[str, Any]] = None # Structured validation details
)
"Initialize validation error with optional structured validation details."
def to_dict(self) -> Dict[str, Any]: # Dictionary representation
"""Serialize error including validation details."""
result = super().to_dict()
if self.validation_errors
"Serialize error including validation details."class ConfigurationError:
def __init__(
self,
message: str, # User-friendly error message
debug_info: Optional[str] = None, # Optional developer details
context: Optional[ErrorContext] = None, # Structured error context
severity: ErrorSeverity = ErrorSeverity.ERROR, # Error severity level
is_retryable: bool = False, # Config issues usually need manual fixes
cause: Optional[Exception] = None, # Original exception if chaining
config_path: Optional[str] = None # Path to problematic config file
)
"Raised when configuration operations fail."
def __init__(
self,
message: str, # User-friendly error message
debug_info: Optional[str] = None, # Optional developer details
context: Optional[ErrorContext] = None, # Structured error context
severity: ErrorSeverity = ErrorSeverity.ERROR, # Error severity level
is_retryable: bool = False, # Config issues usually need manual fixes
cause: Optional[Exception] = None, # Original exception if chaining
config_path: Optional[str] = None # Path to problematic config file
)
"Initialize configuration error with optional config file path."
def to_dict(self) -> Dict[str, Any]: # Dictionary representation
"""Serialize error including config path."""
result = super().to_dict()
if self.config_path
"Serialize error including config path."class ResourceError:
def __init__(
self,
message: str, # User-friendly error message
debug_info: Optional[str] = None, # Optional developer details
context: Optional[ErrorContext] = None, # Structured error context
severity: ErrorSeverity = ErrorSeverity.WARNING, # Often transient
is_retryable: bool = True, # Resource conflicts may be temporary
cause: Optional[Exception] = None, # Original exception if chaining
resource_type: Optional[str] = None, # "GPU", "Memory", "Disk", etc.
suggested_action: Optional[str] = None # Guidance for resolution
)
"Raised when resource conflicts or unavailability prevent operation."
def __init__(
self,
message: str, # User-friendly error message
debug_info: Optional[str] = None, # Optional developer details
context: Optional[ErrorContext] = None, # Structured error context
severity: ErrorSeverity = ErrorSeverity.WARNING, # Often transient
is_retryable: bool = True, # Resource conflicts may be temporary
cause: Optional[Exception] = None, # Original exception if chaining
resource_type: Optional[str] = None, # "GPU", "Memory", "Disk", etc.
suggested_action: Optional[str] = None # Guidance for resolution
)
"Initialize resource error with resource type and suggested action."
def to_dict(self) -> Dict[str, Any]: # Dictionary representation
"""Serialize error including resource type and suggested action."""
result = super().to_dict()
if self.resource_type
"Serialize error including resource type and suggested action."class PluginError:
def __init__(
self,
message: str, # User-friendly error message
debug_info: Optional[str] = None, # Optional developer details
context: Optional[ErrorContext] = None, # Structured error context
severity: ErrorSeverity = ErrorSeverity.ERROR, # Error severity level
is_retryable: bool = False, # Plugin errors usually need fixes
cause: Optional[Exception] = None, # Original exception if chaining
plugin_id: Optional[str] = None, # ID of problematic plugin
plugin_name: Optional[str] = None # Name of problematic plugin
)
"Raised when plugin operations fail."
def __init__(
self,
message: str, # User-friendly error message
debug_info: Optional[str] = None, # Optional developer details
context: Optional[ErrorContext] = None, # Structured error context
severity: ErrorSeverity = ErrorSeverity.ERROR, # Error severity level
is_retryable: bool = False, # Plugin errors usually need fixes
cause: Optional[Exception] = None, # Original exception if chaining
plugin_id: Optional[str] = None, # ID of problematic plugin
plugin_name: Optional[str] = None # Name of problematic plugin
)
"Initialize plugin error with plugin ID and name."
def to_dict(self) -> Dict[str, Any]: # Dictionary representation
"""Serialize error including plugin ID and name."""
result = super().to_dict()
if self.plugin_id
"Serialize error including plugin ID and name."class WorkerError:
def __init__(
self,
message: str, # User-friendly error message
debug_info: Optional[str] = None, # Optional developer details
context: Optional[ErrorContext] = None, # Structured error context
severity: ErrorSeverity = ErrorSeverity.ERROR, # Error severity level
is_retryable: bool = True, # Worker errors may be transient
cause: Optional[Exception] = None, # Original exception if chaining
worker_type: Optional[str] = None, # "transcription", "llm", etc.
job_id: Optional[str] = None # Job that failed
)
"Raised when worker process operations fail."
def __init__(
self,
message: str, # User-friendly error message
debug_info: Optional[str] = None, # Optional developer details
context: Optional[ErrorContext] = None, # Structured error context
severity: ErrorSeverity = ErrorSeverity.ERROR, # Error severity level
is_retryable: bool = True, # Worker errors may be transient
cause: Optional[Exception] = None, # Original exception if chaining
worker_type: Optional[str] = None, # "transcription", "llm", etc.
job_id: Optional[str] = None # Job that failed
)
"Initialize worker error with worker type and job ID."
def to_dict(self) -> Dict[str, Any]: # Dictionary representation
"""Serialize error including worker type and job ID."""
result = super().to_dict()
if self.worker_type
"Serialize error including worker type and job ID."Helper Utilities (helpers.ipynb)
Utilities for easy adoption and incremental migration to structured error handling
Import
from cjm_error_handling.utils.helpers import (
error_boundary,
with_error_handling,
wrap_exception,
chain_error
)Functions
@contextmanager
def error_boundary(
error_type: Type[BaseError] = BaseError, # Error type to raise
message: Optional[str] = None, # User-facing message (uses original if None)
context: Optional[ErrorContext] = None, # Error context
operation: Optional[str] = None, # Operation name (added to context)
job_id: Optional[str] = None, # Job ID (added to context)
plugin_id: Optional[str] = None, # Plugin ID (added to context)
worker_pid: Optional[int] = None, # Worker PID (added to context)
severity: ErrorSeverity = ErrorSeverity.ERROR, # Error severity
is_retryable: Optional[bool] = None, # Override retryable flag
catch: tuple = (Exception,), # Exception types to catch
**extra_context # Additional context fields
)
"Context manager that catches exceptions and wraps them in structured errors."def with_error_handling(
error_type: Type[BaseError] = BaseError, # Error type to raise
message: Optional[str] = None, # User-facing message
operation: Optional[str] = None, # Operation name (uses function name if None)
severity: ErrorSeverity = ErrorSeverity.ERROR, # Error severity
is_retryable: Optional[bool] = None, # Override retryable flag
catch: tuple = (Exception,), # Exception types to catch
context_from_args: Optional[Dict[str, str]] = None # Map arg names to context fields, e.g. {'job_id': 'job_id'}
)
"Decorator that wraps function errors with structured error handling."def wrap_exception(
exception: Exception, # Original exception to wrap
error_type: Type[BaseError] = BaseError, # Error type to create
message: Optional[str] = None, # User-facing message (uses exception if None)
context: Optional[ErrorContext] = None, # Error context
severity: ErrorSeverity = ErrorSeverity.ERROR, # Error severity
is_retryable: Optional[bool] = None, # Override retryable flag
**context_kwargs # Additional context fields
) -> BaseError: # Wrapped structured error
"Wrap an existing exception in a structured error type."def chain_error(
base_error: BaseError, # Original structured error
new_message: str, # New user-facing message
error_type: Optional[Type[BaseError]] = None, # New error type (uses base type if None)
additional_context: Optional[Dict[str, Any]] = None, # Additional context to add
operation: Optional[str] = None, # Update operation name
severity: Optional[ErrorSeverity] = None # Override severity
) -> BaseError: # New error with chained context
"Chain a structured error with additional context."