homelab_automation/app/core/exceptions.py

259 lines
7.1 KiB
Python

"""
Exceptions personnalisées de l'application.
Centralise la gestion des erreurs avec des exceptions typées.
"""
from typing import Any, Dict, Optional
class HomelabException(Exception):
"""Exception de base pour l'application Homelab."""
def __init__(
self,
message: str,
status_code: int = 500,
details: Optional[Dict[str, Any]] = None
):
self.message = message
self.status_code = status_code
self.details = details or {}
super().__init__(self.message)
def to_dict(self) -> Dict[str, Any]:
"""Convertit l'exception en dictionnaire pour la réponse API."""
return {
"error": self.__class__.__name__,
"message": self.message,
"details": self.details,
}
# === Exceptions 404 Not Found ===
class NotFoundException(HomelabException):
"""Exception de base pour les ressources non trouvées."""
def __init__(self, resource_type: str, identifier: str):
super().__init__(
message=f"{resource_type} '{identifier}' non trouvé(e)",
status_code=404,
details={"resource_type": resource_type, "identifier": identifier}
)
class HostNotFoundException(NotFoundException):
"""Hôte non trouvé."""
def __init__(self, identifier: str):
super().__init__("Hôte", identifier)
class TaskNotFoundException(NotFoundException):
"""Tâche non trouvée."""
def __init__(self, identifier: str):
super().__init__("Tâche", identifier)
class ScheduleNotFoundException(NotFoundException):
"""Schedule non trouvé."""
def __init__(self, identifier: str):
super().__init__("Schedule", identifier)
class PlaybookNotFoundException(NotFoundException):
"""Playbook non trouvé."""
def __init__(self, identifier: str):
super().__init__("Playbook", identifier)
class GroupNotFoundException(NotFoundException):
"""Groupe non trouvé."""
def __init__(self, identifier: str):
super().__init__("Groupe", identifier)
class LogNotFoundException(NotFoundException):
"""Log non trouvé."""
def __init__(self, identifier: str):
super().__init__("Log", identifier)
# === Exceptions 400 Bad Request ===
class ValidationException(HomelabException):
"""Erreur de validation des données."""
def __init__(self, message: str, field: Optional[str] = None):
details = {"field": field} if field else {}
super().__init__(
message=message,
status_code=400,
details=details
)
class DuplicateResourceException(HomelabException):
"""Ressource déjà existante."""
def __init__(self, resource_type: str, identifier: str):
super().__init__(
message=f"{resource_type} '{identifier}' existe déjà",
status_code=400,
details={"resource_type": resource_type, "identifier": identifier}
)
class InvalidOperationException(HomelabException):
"""Opération non valide dans l'état actuel."""
def __init__(self, message: str, current_state: Optional[str] = None):
details = {"current_state": current_state} if current_state else {}
super().__init__(
message=message,
status_code=400,
details=details
)
class IncompatiblePlaybookException(HomelabException):
"""Playbook incompatible avec la cible."""
def __init__(self, playbook: str, target: str, playbook_hosts: str):
super().__init__(
message=f"Le playbook '{playbook}' (hosts: {playbook_hosts}) n'est pas compatible avec la cible '{target}'",
status_code=400,
details={
"playbook": playbook,
"target": target,
"playbook_hosts": playbook_hosts,
}
)
# === Exceptions 401/403 Auth ===
class AuthenticationException(HomelabException):
"""Erreur d'authentification."""
def __init__(self, message: str = "Authentification requise"):
super().__init__(message=message, status_code=401)
class AuthorizationException(HomelabException):
"""Erreur d'autorisation."""
def __init__(self, message: str = "Accès non autorisé"):
super().__init__(message=message, status_code=403)
# === Exceptions 500 Server Error ===
class AnsibleExecutionException(HomelabException):
"""Erreur lors de l'exécution Ansible."""
def __init__(
self,
message: str,
return_code: int = -1,
stdout: str = "",
stderr: str = ""
):
super().__init__(
message=message,
status_code=500,
details={
"return_code": return_code,
"stdout": stdout,
"stderr": stderr,
}
)
class BootstrapException(HomelabException):
"""Erreur lors du bootstrap d'un hôte."""
def __init__(
self,
host: str,
message: str,
return_code: int = -1,
stdout: str = "",
stderr: str = ""
):
super().__init__(
message=f"Échec bootstrap pour {host}: {message}",
status_code=500,
details={
"host": host,
"return_code": return_code,
"stdout": stdout,
"stderr": stderr,
}
)
class SSHConnectionException(HomelabException):
"""Erreur de connexion SSH."""
def __init__(self, host: str, message: str):
super().__init__(
message=f"Connexion SSH échouée vers {host}: {message}",
status_code=500,
details={"host": host}
)
class DatabaseException(HomelabException):
"""Erreur de base de données."""
def __init__(self, message: str, operation: Optional[str] = None):
details = {"operation": operation} if operation else {}
super().__init__(
message=f"Erreur base de données: {message}",
status_code=500,
details=details
)
class SchedulerException(HomelabException):
"""Erreur du service de planification."""
def __init__(self, message: str, schedule_id: Optional[str] = None):
details = {"schedule_id": schedule_id} if schedule_id else {}
super().__init__(
message=f"Erreur scheduler: {message}",
status_code=500,
details=details
)
class NotificationException(HomelabException):
"""Erreur lors de l'envoi de notification."""
def __init__(self, message: str, topic: Optional[str] = None):
details = {"topic": topic} if topic else {}
super().__init__(
message=f"Erreur notification: {message}",
status_code=500,
details=details
)
class FileOperationException(HomelabException):
"""Erreur lors d'une opération sur fichier."""
def __init__(self, message: str, path: Optional[str] = None):
details = {"path": path} if path else {}
super().__init__(
message=message,
status_code=500,
details=details
)