deb-mock/deb_mock/exceptions.py
2025-08-03 22:16:04 +00:00

403 lines
No EOL
14 KiB
Python

"""
Custom exceptions for deb-mock
This module provides a comprehensive exception hierarchy inspired by Mock's
exception handling system, adapted for Debian-based build environments.
"""
import os
import sys
import functools
from typing import Optional, Dict, Any, List
class DebMockError(Exception):
"""
Base exception for all deb-mock errors.
This is the root exception class that all other deb-mock exceptions
inherit from. It provides common functionality for error reporting
and recovery suggestions.
"""
def __init__(self, message: str,
exit_code: int = 1,
context: Optional[Dict[str, Any]] = None,
suggestions: Optional[List[str]] = None):
"""
Initialize the exception with message and optional context.
Args:
message: Human-readable error message
exit_code: Suggested exit code for CLI applications
context: Additional context information for debugging
suggestions: List of suggested actions to resolve the error
"""
super().__init__(message)
self.message = message
self.exit_code = exit_code
self.context = context or {}
self.suggestions = suggestions or []
def __str__(self) -> str:
"""Return formatted error message with context and suggestions."""
lines = [f"Error: {self.message}"]
# Add context information if available
if self.context:
lines.append("\nContext:")
for key, value in self.context.items():
lines.append(f" {key}: {value}")
# Add suggestions if available
if self.suggestions:
lines.append("\nSuggestions:")
for i, suggestion in enumerate(self.suggestions, 1):
lines.append(f" {i}. {suggestion}")
return "\n".join(lines)
def print_error(self, file=sys.stderr) -> None:
"""Print formatted error message to specified file."""
print(str(self), file=file)
def get_exit_code(self) -> int:
"""Get the suggested exit code for this error."""
return self.exit_code
class ConfigurationError(DebMockError):
"""
Raised when there's an error in configuration.
This exception is raised when configuration files are invalid,
missing required options, or contain conflicting settings.
"""
def __init__(self, message: str, config_file: Optional[str] = None,
config_section: Optional[str] = None):
context = {}
if config_file:
context['config_file'] = config_file
if config_section:
context['config_section'] = config_section
suggestions = [
"Check the configuration file syntax",
"Verify all required options are set",
"Ensure configuration values are valid for your system"
]
super().__init__(message, exit_code=2, context=context, suggestions=suggestions)
class ChrootError(DebMockError):
"""
Raised when there's an error with chroot operations.
This exception covers chroot creation, management, and cleanup errors.
"""
def __init__(self, message: str, chroot_name: Optional[str] = None,
operation: Optional[str] = None, chroot_path: Optional[str] = None):
context = {}
if chroot_name:
context['chroot_name'] = chroot_name
if operation:
context['operation'] = operation
if chroot_path:
context['chroot_path'] = chroot_path
suggestions = [
"Ensure you have sufficient disk space",
"Check that you have root privileges for chroot operations",
"Verify the chroot name is valid",
"Try cleaning up existing chroots with 'deb-mock clean-chroot'"
]
super().__init__(message, exit_code=3, context=context, suggestions=suggestions)
class SbuildError(DebMockError):
"""
Raised when there's an error with sbuild operations.
This exception covers sbuild execution, configuration, and result processing.
"""
def __init__(self, message: str, sbuild_config: Optional[str] = None,
build_log: Optional[str] = None, return_code: Optional[int] = None):
context = {}
if sbuild_config:
context['sbuild_config'] = sbuild_config
if build_log:
context['build_log'] = build_log
if return_code is not None:
context['return_code'] = return_code
suggestions = [
"Check the build log for detailed error information",
"Verify that sbuild is properly configured",
"Ensure all build dependencies are available",
"Try updating the chroot with 'deb-mock update-chroot'"
]
super().__init__(message, exit_code=4, context=context, suggestions=suggestions)
class BuildError(DebMockError):
"""
Raised when a build fails.
This exception is raised when package building fails due to
compilation errors, missing dependencies, or other build issues.
"""
def __init__(self, message: str, source_package: Optional[str] = None,
build_log: Optional[str] = None, artifacts: Optional[List[str]] = None):
context = {}
if source_package:
context['source_package'] = source_package
if build_log:
context['build_log'] = build_log
if artifacts:
context['artifacts'] = artifacts
suggestions = [
"Review the build log for specific error messages",
"Check that all build dependencies are installed",
"Verify the source package is valid and complete",
"Try building with verbose output: 'deb-mock --verbose build'"
]
super().__init__(message, exit_code=5, context=context, suggestions=suggestions)
class DependencyError(DebMockError):
"""
Raised when there are dependency issues.
This exception covers missing build dependencies, version conflicts,
and other dependency-related problems.
"""
def __init__(self, message: str, missing_packages: Optional[List[str]] = None,
conflicting_packages: Optional[List[str]] = None):
context = {}
if missing_packages:
context['missing_packages'] = missing_packages
if conflicting_packages:
context['conflicting_packages'] = conflicting_packages
suggestions = [
"Install missing build dependencies",
"Resolve package conflicts by updating or removing conflicting packages",
"Check that your chroot has access to the required repositories",
"Try updating the chroot: 'deb-mock update-chroot'"
]
super().__init__(message, exit_code=6, context=context, suggestions=suggestions)
class MetadataError(DebMockError):
"""
Raised when there's an error with metadata handling.
This exception covers metadata capture, storage, and retrieval errors.
"""
def __init__(self, message: str, metadata_file: Optional[str] = None,
operation: Optional[str] = None):
context = {}
if metadata_file:
context['metadata_file'] = metadata_file
if operation:
context['operation'] = operation
suggestions = [
"Check that the metadata directory is writable",
"Verify that the metadata file format is valid",
"Ensure sufficient disk space for metadata storage"
]
super().__init__(message, exit_code=7, context=context, suggestions=suggestions)
class CacheError(DebMockError):
"""
Raised when there's an error with cache operations.
This exception covers root cache, package cache, and ccache errors.
"""
def __init__(self, message: str, cache_type: Optional[str] = None,
cache_path: Optional[str] = None, operation: Optional[str] = None):
context = {}
if cache_type:
context['cache_type'] = cache_type
if cache_path:
context['cache_path'] = cache_path
if operation:
context['operation'] = operation
suggestions = [
"Check that cache directories are writable",
"Ensure sufficient disk space for cache operations",
"Try cleaning up old caches: 'deb-mock cleanup-caches'",
"Verify cache configuration settings"
]
super().__init__(message, exit_code=8, context=context, suggestions=suggestions)
class PluginError(DebMockError):
"""
Raised when there's an error with plugin operations.
This exception covers plugin loading, configuration, and execution errors.
"""
def __init__(self, message: str, plugin_name: Optional[str] = None,
plugin_config: Optional[Dict[str, Any]] = None):
context = {}
if plugin_name:
context['plugin_name'] = plugin_name
if plugin_config:
context['plugin_config'] = plugin_config
suggestions = [
"Check that the plugin is properly installed",
"Verify plugin configuration is valid",
"Ensure plugin dependencies are satisfied",
"Try disabling the plugin if it's causing issues"
]
super().__init__(message, exit_code=9, context=context, suggestions=suggestions)
class NetworkError(DebMockError):
"""
Raised when there are network-related errors.
This exception covers repository access, package downloads, and
other network operations.
"""
def __init__(self, message: str, url: Optional[str] = None,
proxy: Optional[str] = None, timeout: Optional[int] = None):
context = {}
if url:
context['url'] = url
if proxy:
context['proxy'] = proxy
if timeout:
context['timeout'] = timeout
suggestions = [
"Check your internet connection",
"Verify repository URLs are accessible",
"Configure proxy settings if behind a firewall",
"Try using a different mirror or repository"
]
super().__init__(message, exit_code=10, context=context, suggestions=suggestions)
class PermissionError(DebMockError):
"""
Raised when there are permission-related errors.
This exception covers insufficient privileges for chroot operations,
file access, and other permission issues.
"""
def __init__(self, message: str, operation: Optional[str] = None,
path: Optional[str] = None, required_privileges: Optional[str] = None):
context = {}
if operation:
context['operation'] = operation
if path:
context['path'] = path
if required_privileges:
context['required_privileges'] = required_privileges
suggestions = [
"Run the command with appropriate privileges (sudo)",
"Check file and directory permissions",
"Verify your user is in the required groups",
"Ensure the target paths are writable"
]
super().__init__(message, exit_code=11, context=context, suggestions=suggestions)
class ValidationError(DebMockError):
"""
Raised when input validation fails.
This exception covers validation of source packages, configuration,
and other input data.
"""
def __init__(self, message: str, field: Optional[str] = None,
value: Optional[str] = None, expected_format: Optional[str] = None):
context = {}
if field:
context['field'] = field
if value:
context['value'] = value
if expected_format:
context['expected_format'] = expected_format
suggestions = [
"Check the input format and syntax",
"Verify that required fields are provided",
"Ensure values are within acceptable ranges",
"Review the documentation for correct usage"
]
super().__init__(message, exit_code=12, context=context, suggestions=suggestions)
# Convenience functions for common error patterns
def handle_exception(func):
"""
Decorator to handle exceptions and provide consistent error reporting.
This decorator catches DebMockError exceptions and provides
formatted error output with suggestions for resolution.
"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except DebMockError as e:
e.print_error()
sys.exit(e.get_exit_code())
except Exception as e:
# Convert unexpected exceptions to DebMockError
error = DebMockError(
f"Unexpected error: {str(e)}",
context={'exception_type': type(e).__name__},
suggestions=[
"This may be a bug in deb-mock",
"Check the logs for more details",
"Report the issue with full error context"
]
)
error.print_error()
sys.exit(1)
return wrapper
def format_error_context(**kwargs) -> Dict[str, Any]:
"""
Helper function to format error context information.
Args:
**kwargs: Key-value pairs for context information
Returns:
Formatted context dictionary
"""
return {k: v for k, v in kwargs.items() if v is not None}