Skip to main content

Structured error handling with context propagation, user-friendly messaging, and serialization support for reusable Python libraries.

Project description

cjm-error-handling

Install

pip install cjm_error_handling

Project 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."

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cjm_error_handling-0.0.2.tar.gz (17.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cjm_error_handling-0.0.2-py3-none-any.whl (16.3 kB view details)

Uploaded Python 3

File details

Details for the file cjm_error_handling-0.0.2.tar.gz.

File metadata

  • Download URL: cjm_error_handling-0.0.2.tar.gz
  • Upload date:
  • Size: 17.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.14

File hashes

Hashes for cjm_error_handling-0.0.2.tar.gz
Algorithm Hash digest
SHA256 2d1181a5b2f8e857594ab735441c1aba7e6f70dca48361e7d0fcc0d7f9548b81
MD5 3eeb9aa8c272bd7ca45ea584fb2c162a
BLAKE2b-256 b4b9dce3b754aff60648b3f1a9d89a079bdf9d00d19cdc4c7d743df5c917659b

See more details on using hashes here.

File details

Details for the file cjm_error_handling-0.0.2-py3-none-any.whl.

File metadata

File hashes

Hashes for cjm_error_handling-0.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 419525155e47b5c2b4755341ae389cc48fe5d614b0d2bdfcc2e081d50514978a
MD5 16e82ece2c53632672d07934362ad171
BLAKE2b-256 a8883b16545515b6962ba6a59a26df0d0d419945cf84d6cee2f5d1cac41e0697

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page