Custom exception hierarchy for consistent error handling
Quick Reference: Logging Guide | Testing Guide
from infrastructure.core.exceptions import ValidationError, raise_with_context
from infrastructure.core.logging.utils import get_logger
logger = get_logger(__name__)
# Raise with context
raise_with_context(
ValidationError,
"Data validation failed",
file="data.csv",
line=42,
column="temperature"
)
# Catch specific errors
try:
process_data()
except ValidationError as e:
logger.error(f"Validation error: {e}", exc_info=True)
return 1classDiagram
class TemplateError {
+base exception
}
class ConfigurationError
class MissingConfigurationError
class InvalidConfigurationError
class ValidationError
class MarkdownValidationError
class PDFValidationError
class DataValidationError
class BuildError
class CompilationError
class ScriptExecutionError
class PipelineError
class FileOperationError
class FileNotFoundError
class InvalidFileFormatError
class DependencyError
class MissingDependencyError
class VersionMismatchError
class TestError
class InsufficientCoverageError
class IntegrationError
TemplateError <|-- ConfigurationError
ConfigurationError <|-- MissingConfigurationError
ConfigurationError <|-- InvalidConfigurationError
TemplateError <|-- ValidationError
ValidationError <|-- MarkdownValidationError
ValidationError <|-- PDFValidationError
ValidationError <|-- DataValidationError
TemplateError <|-- BuildError
BuildError <|-- CompilationError
BuildError <|-- ScriptExecutionError
BuildError <|-- PipelineError
TemplateError <|-- FileOperationError
FileOperationError <|-- FileNotFoundError
FileOperationError <|-- InvalidFileFormatError
TemplateError <|-- DependencyError
DependencyError <|-- MissingDependencyError
DependencyError <|-- VersionMismatchError
TemplateError <|-- TestError
TestError <|-- InsufficientCoverageError
TemplateError <|-- IntegrationError
from exceptions import ValidationError
if not is_valid(data):
raise ValidationError("Validation failed")from exceptions import raise_with_context, FileNotFoundError
if not path.exists():
raise_with_context(
FileNotFoundError,
"Required file not found",
file=str(path),
searched_in=str(Path.cwd())
)from exceptions import chain_exceptions, BuildError
try:
compile_latex()
except subprocess.CalledProcessError as e:
new_error = BuildError("Compilation failed")
raise chain_exceptions(new_error, e)# Catch specific
try:
operation()
except MissingConfigurationError as e:
logger.error(f"Config missing: {e}")
# Catch category
try:
operation()
except ConfigurationError as e:
logger.error(f"Config error: {e}")
# Catch all template errors
try:
operation()
except TemplateError as e:
logger.error(f"Template error: {e}")from exceptions import ConfigurationError, MissingConfigurationError
# Missing configuration
raise MissingConfigurationError(
"Required key not found",
context={"key": "author", "file": "config.yaml"}
)
# Invalid configuration
raise InvalidConfigurationError(
"Invalid email format",
context={"field": "email", "value": "invalid"}
)from exceptions import ValidationError, MarkdownValidationError
# Markdown validation
raise MarkdownValidationError(
"Image file not found",
context={"image": "figure.png", "file": "intro.md", "line": 42}
)
# Data validation
raise DataValidationError(
"NaN values detected",
context={"column": "temperature", "count": 5}
)from exceptions import BuildError, CompilationError
# Compilation failure
raise CompilationError(
"LaTeX compilation failed",
context={"file": "manuscript.tex", "exit_code": 1}
)
# Script failure
raise ScriptExecutionError(
"Analysis script failed",
context={"script": "analysis.py", "exit_code": 1}
)from infrastructure.core.logging.utils import get_logger
from exceptions import ValidationError
logger = get_logger(__name__)
try:
validate_data(data)
except ValidationError as e:
logger.error(f"Validation failed: {e}", exc_info=True)
# exc_info=True includes stack trace
raise- Use specific exception types
- Include context information
- Chain exceptions to preserve history
- Log exceptions with exc_info=True
- Catch specific exceptions first
- Don't use bare except clauses
- Don't lose exception context
- Don't catch Exception without good reason
- Don't raise generic Exception
- Don't ignore exceptions silently
def validate_file(path: Path) -> None:
if not path.exists():
raise_with_context(
FileNotFoundError,
"File not found",
file=str(path),
expected_location=str(path.parent)
)
if not path.suffix == '.csv':
raise_with_context(
InvalidFileFormatError,
"Expected CSV file",
file=str(path),
detected_type=path.suffix
)def run_pipeline() -> int:
try:
with log_operation("Run pipeline", logger):
for stage in stages:
run_stage(stage)
return 0
except ScriptExecutionError as e:
logger.error(f"Script failed: {e}", exc_info=True)
return 1
except PipelineError as e:
logger.error(f"Pipeline error: {e}", exc_info=True)
return 1
except TemplateError as e:
logger.error(f"Template error: {e}", exc_info=True)
return 1from exceptions import format_file_context
# Format file context
context = format_file_context("data.csv", line=42)
raise ValidationError("Error at line", context=context)