deb-mock/deb_mock/exceptions.py
robojerk c51819c836
Some checks failed
Build Deb-Mock Package / build (push) Failing after 1m9s
Lint Code / Lint All Code (push) Failing after 1s
Test Deb-Mock Build / test (push) Failing after 35s
Add comprehensive testing framework, performance monitoring, and plugin system
- Add complete pytest testing framework with conftest.py and test files
- Add performance monitoring and benchmarking capabilities
- Add plugin system with ccache plugin example
- Add comprehensive documentation (API, deployment, testing, etc.)
- Add Docker API wrapper for service deployment
- Add advanced configuration examples
- Remove old wget package file
- Update core modules with enhanced functionality
2025-08-19 20:49:32 -07:00

475 lines
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 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}