""" 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 functools import sys from typing import Any, Dict, List, Optional 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) class UIDManagerError(DebMockError): """Raised when UID/GID management operations fail""" def __init__(self, message, chroot_name=None, operation=None): super().__init__(message) self.chroot_name = chroot_name self.operation = operation def get_exit_code(self): return 20 # UID management error class PerformanceError(Exception): """Raised when performance monitoring or optimization fails""" pass # 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}