154 lines
9.0 KiB
Python
154 lines
9.0 KiB
Python
# [MODULE] Иерархия исключений
|
||
# @contract: Все ошибки наследуют `SupersetToolError` для единой точки обработки.
|
||
# @semantic: Каждый тип исключения соответствует конкретной проблемной области в инструменте Superset.
|
||
# @coherence:
|
||
# - Полное покрытие всех сценариев ошибок клиента и утилит.
|
||
# - Четкая классификация по уровню серьезности (от общей до специфичной).
|
||
# - Дополнительный `context` для каждой ошибки, помогающий в диагностике.
|
||
|
||
# [IMPORTS] Standard library
|
||
from pathlib import Path
|
||
|
||
# [IMPORTS] Typing
|
||
from typing import Optional, Dict, Any,Union
|
||
|
||
class SupersetToolError(Exception):
|
||
"""[BASE] Базовый класс для всех ошибок инструмента Superset.
|
||
@semantic: Обеспечивает стандартизированный формат сообщений об ошибках с контекстом.
|
||
@invariant:
|
||
- `message` всегда присутствует.
|
||
- `context` всегда является словарем, даже если пустой.
|
||
"""
|
||
def __init__(self, message: str, context: Optional[Dict[str, Any]] = None):
|
||
# [PRECONDITION] Проверка типа контекста
|
||
if not isinstance(context, (dict, type(None))):
|
||
# [COHERENCE_CHECK_FAILED] Ошибка в передаче контекста
|
||
raise TypeError("Контекст ошибки должен быть словарем или None")
|
||
self.context = context or {}
|
||
super().__init__(f"{message} | Context: {self.context}")
|
||
# [POSTCONDITION] Логирование создания ошибки
|
||
# Можно добавить здесь логирование, но обычно ошибки логируются в месте их перехвата/подъема,
|
||
# чтобы избежать дублирования и получить полный стек вызовов.
|
||
|
||
# [ERROR-GROUP] Проблемы аутентификации и авторизации
|
||
class AuthenticationError(SupersetToolError):
|
||
"""[AUTH] Ошибки аутентификации (неверные учетные данные) или авторизации (проблемы с сессией).
|
||
@context: url, username, error_detail (опционально).
|
||
"""
|
||
# [CONTRACT]
|
||
# Description: Исключение, возникающее при ошибках аутентификации в Superset API.
|
||
def __init__(self, message: str = "Authentication failed", **context: Any):
|
||
super().__init__(
|
||
f"[AUTH_FAILURE] {message}",
|
||
{"type": "authentication", **context}
|
||
)
|
||
|
||
class PermissionDeniedError(AuthenticationError):
|
||
"""[AUTH] Ошибка отказа в доступе из-за недостаточных прав пользователя.
|
||
@semantic: Указывает на то, что операция не разрешена.
|
||
@context: required_permission (опционально), user_roles (опционально), endpoint (опционально).
|
||
@invariant: Наследует от `AuthenticationError`, так как это разновидность проблемы доступа.
|
||
"""
|
||
def __init__(self, message: str = "Permission denied", required_permission: Optional[str] = None, **context: Any):
|
||
full_message = f"Permission denied: {required_permission}" if required_permission else message
|
||
super().__init__(
|
||
full_message,
|
||
{"type": "authorization", "required_permission": required_permission, **context}
|
||
)
|
||
|
||
# [ERROR-GROUP] Проблемы API-вызовов
|
||
class SupersetAPIError(SupersetToolError):
|
||
"""[API] Общие ошибки взаимодействия с Superset API.
|
||
@semantic: Для ошибок, возвращаемых Superset API, или проблем с парсингом ответа.
|
||
@context: endpoint, method, status_code, response_body (опционально), error_message (из API).
|
||
"""
|
||
# [CONTRACT]
|
||
# Description: Исключение, возникающее при получении ошибки от Superset API (статус код >= 400).
|
||
def __init__(self, message: str = "Superset API error", **context: Any):
|
||
super().__init__(
|
||
f"[API_FAILURE] {message}",
|
||
{"type": "api_call", **context}
|
||
)
|
||
|
||
# [ERROR-SUBCLASS] Детализированные ошибки API
|
||
class ExportError(SupersetAPIError):
|
||
"""[API:EXPORT] Проблемы, специфичные для операций экспорта дашбордов.
|
||
@semantic: Может быть вызвано невалидным форматом ответа, ошибками Superset при экспорте.
|
||
@context: dashboard_id (опционально), details (опционально).
|
||
"""
|
||
def __init__(self, message: str = "Dashboard export failed", **context: Any):
|
||
super().__init__(f"[EXPORT_FAILURE] {message}", {"subtype": "export", **context})
|
||
|
||
class DashboardNotFoundError(SupersetAPIError):
|
||
"""[API:404] Запрошенный дашборд или ресурс не существует.
|
||
@semantic: Соответствует HTTP 404 Not Found.
|
||
@context: dashboard_id_or_slug, url.
|
||
"""
|
||
# [CONTRACT]
|
||
# Description: Исключение, специфичное для случая, когда дашборд не найден (статус 404).
|
||
def __init__(self, dashboard_id_or_slug: Union[int, str], message: str = "Dashboard not found", **context: Any):
|
||
super().__init__(
|
||
f"[NOT_FOUND] Dashboard '{dashboard_id_or_slug}' {message}",
|
||
{"subtype": "not_found", "resource_id": dashboard_id_or_slug, **context}
|
||
)
|
||
|
||
class DatasetNotFoundError(SupersetAPIError):
|
||
"""[API:404] Запрашиваемый набор данных не существует.
|
||
@semantic: Соответствует HTTP 404 Not Found.
|
||
@context: dataset_id_or_slug, url.
|
||
"""
|
||
# [CONTRACT]
|
||
# Description: Исключение, специфичное для случая, когда набор данных не найден (статус 404).
|
||
def __init__(self, dataset_id_or_slug: Union[int, str], message: str = "Dataset not found", **context: Any):
|
||
super().__init__(
|
||
f"[NOT_FOUND] Dataset '{dataset_id_or_slug}' {message}",
|
||
{"subtype": "not_found", "resource_id": dataset_id_or_slug, **context}
|
||
)
|
||
|
||
# [ERROR-SUBCLASS] Детализированные ошибки обработки файлов
|
||
class InvalidZipFormatError(SupersetToolError):
|
||
"""[FILE:ZIP] Некорректный формат ZIP-архива или содержимого для импорта/экспорта.
|
||
@semantic: Указывает на проблемы с целостностью или структурой ZIP-файла.
|
||
@context: file_path, expected_content (например, metadata.yaml), error_detail.
|
||
"""
|
||
def __init__(self, message: str = "Invalid ZIP format or content", file_path: Optional[Union[str, Path]] = None, **context: Any):
|
||
super().__init__(
|
||
f"[FILE_ERROR] {message}",
|
||
{"type": "file_validation", "file_path": str(file_path) if file_path else "N/A", **context}
|
||
)
|
||
|
||
# [ERROR-GROUP] Системные и network-ошибки
|
||
class NetworkError(SupersetToolError):
|
||
"""[NETWORK] Проблемы соединения, таймауты, DNS-ошибки и т.п.
|
||
@semantic: Ошибки, связанные с невозможностью установить или поддерживать сетевое соединение.
|
||
@context: url, original_exception (опционально), timeout (опционально).
|
||
"""
|
||
# [CONTRACT]
|
||
# Description: Исключение, возникающее при сетевых ошибках во время взаимодействия с Superset API.
|
||
def __init__(self, message: str = "Network connection failed", **context: Any):
|
||
super().__init__(
|
||
f"[NETWORK_FAILURE] {message}",
|
||
{"type": "network", **context}
|
||
)
|
||
|
||
class FileOperationError(SupersetToolError):
|
||
"""
|
||
# [CONTRACT]
|
||
# Description: Исключение, возникающее при ошибках файловых операций (чтение, запись, архивирование).
|
||
"""
|
||
pass
|
||
|
||
class InvalidFileStructureError(FileOperationError):
|
||
"""
|
||
# [CONTRACT]
|
||
# Description: Исключение, возникающее при обнаружении некорректной структуры файлов/директорий.
|
||
"""
|
||
pass
|
||
|
||
class ConfigurationError(SupersetToolError):
|
||
"""
|
||
# [CONTRACT]
|
||
# Description: Исключение, возникающее при ошибках в конфигурации инструмента.
|
||
"""
|
||
pass
|