""" 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 )