004. Error Class Provider Pattern¶
Status¶
Superseded
Superseded By: Implementation experience revealed that the error class provider pattern added excessive complexity without sufficient benefit. The approach was abandoned in favor of simpler exception handling patterns.
Context¶
Analysis of downstream package integration revealed that exception translation represents a major integration friction point. Current integration patterns require extensive boilerplate code at every call site to translate detextive exceptions into downstream exception hierarchies.
Current Integration Tax:
# Current: 8+ lines of boilerplate per call site
try:
mimetype, charset = detect_mimetype_and_charset(content_bytes, location)
if not is_textual_content(content_bytes):
raise DocumentationInaccessibility(url_s, "Non-textual data")
except TextualMimetypeInvalidity as exc:
raise DocumentationInaccessibility(url_s, str(exc)) from exc
This pattern violates DRY principles and creates maintenance overhead when exception hierarchies evolve. The pattern is repeated across multiple call sites within the same package and across different downstream packages.
Requirements Analysis:
Zero Boilerplate: Eliminate need for try/catch/re-raise patterns
Flexible Error Handling: Support graceful degradation, native exceptions, and custom hierarchies
Context Preservation: Maintain original error context and location information in translated exceptions
Type Safety: Enable static analysis of exception handling patterns
Architectural Forces:
Need to maintain detextive’s internal exception hierarchy for clarity
Want to eliminate integration friction without compromising error information
Must support diverse downstream error handling strategies
Should enable gradual migration from current exception handling patterns
Decision¶
We will implement an Error Class Provider Pattern that enables call-site control over exception handling through a provider function parameter.
Core Pattern Design:
ErrorClassProvider: TypeAlias = Callable[[str], type[Exception]]
def detect_mimetype_charset(
content: Content,
location: Absential[Location] = absent, *,
# ... other parameters ...
error_class_provider: Absential[ErrorClassProvider] = absent,
) -> tuple[Absential[str], Absential[str]]:
Three-Way Error Semantics:
None: Return
absentvalues instead of raising exceptions (graceful degradation)absent: Use detextive’s native exception hierarchy (default, current behavior)
Callable: Map exception names to downstream exception classes via provider function
Provider Function Interface:
The provider function receives detextive’s internal exception class name and returns the corresponding downstream exception class:
# Example provider for DocumentationInaccessibility mapping
def map_to_doc_errors(exception_name: str) -> type[Exception]:
return DocumentationInaccessibility
# Usage eliminates all boilerplate
mimetype, charset = detect_mimetype_charset(
content_bytes, location,
error_class_provider=map_to_doc_errors
)
Implementation Strategy:
Internal exception handling logic checks error_class_provider parameter
When provider is callable, exceptions are mapped before raising
Original exception context preserved through
fromchainingProvider function called with detextive exception class name for flexibility
Alternatives¶
Exception Mapping Dictionary
Benefits: Simple mapping structure, clear exception relationships Drawbacks: Requires pre-definition of all possible exception mappings Rejection Reason: Less flexible than callable pattern, harder to maintain
Exception Wrapper Classes
Benefits: Preserves full exception hierarchy, maintains type relationships Drawbacks: Complex wrapper implementation, unclear exception handling Rejection Reason: Over-engineering for mapping use case
Global Exception Configuration
Benefits: One-time configuration affects all function calls Drawbacks: Global state, less flexible per-call control Rejection Reason: Global state conflicts with functional approach
Result Pattern with Union Types
Benefits: No exceptions, explicit success/failure handling Drawbacks: Breaking change to all function signatures, Python typing complexity Rejection Reason: Violates backward compatibility requirement
Consequences¶
Positive Consequences
Zero Boilerplate: Eliminates try/catch/re-raise patterns entirely
Flexible Error Handling: Supports three distinct error handling strategies
Context Preservation: Original error information maintained through exception chaining
Gradual Migration: Existing code continues working while new integration patterns become available
Type Safety: Provider pattern enables static analysis of exception flows
Negative Consequences
Interface Complexity: Additional parameter increases function signature complexity
Learning Curve: New pattern requires documentation and examples
Testing Complexity: Must test all three error handling modes
Provider Function Design: Requires careful design for reusable provider functions
Neutral Consequences
Documentation Requirements: Enhanced error handling requires comprehensive examples
Migration Strategy: Teams can migrate incrementally to new pattern
Performance: Negligible overhead for provider function calls
Implementation Guidance
Provider Function Design Patterns:
# Simple mapping: all detextive exceptions → single downstream exception
lambda name: DocumentationInaccessibility
# Conditional mapping: specific exceptions → specific downstream classes
def custom_error_provider(exception_name: str) -> type[Exception]:
mapping = {
'CharsetDetectFailure': EncodingError,
'TextualMimetypeInvalidity': ContentTypeError,
}
return mapping.get(exception_name, GenericProcessingError)
Integration with Existing Functions:
All v2.0 detection functions will support the error_class_provider parameter with identical semantics, providing consistent exception handling across the entire API surface.
Backward Compatibility:
The default behavior (error_class_provider=absent) preserves current exception behavior exactly, ensuring zero breaking changes for existing integrations.
This decision establishes a reusable pattern that can be applied across other packages in the ecosystem for consistent exception handling strategy.