This commit is contained in:
2026-03-18 08:45:15 +03:00
parent 3094a2b58b
commit 6d64124e88
17 changed files with 1563 additions and 31159 deletions

View File

@@ -1,11 +1,10 @@
# [DEF:network:Module]
# [DEF:NetworkModule:Module]
#
# @COMPLEXITY: 3
# @SEMANTICS: network, http, client, api, requests, session, authentication
# @PURPOSE: Инкапсулирует низкоуровневую HTTP-логику для взаимодействия с Superset API, включая аутентификацию, управление сессией, retry-логику и обработку ошибок.
# @LAYER: Infra
# @RELATION: DEPENDS_ON -> backend.src.core.logger
# @RELATION: DEPENDS_ON -> requests
# @RELATION: [DEPENDS_ON] ->[LoggerModule]
# @PUBLIC_API: APIClient
# [SECTION: IMPORTS]
@@ -82,7 +81,7 @@ class DashboardNotFoundError(SupersetAPIError):
# [DEF:NetworkError:Class]
# @PURPOSE: Exception raised when a network level error occurs.
class NetworkError(Exception):
# [DEF:network.APIClient.__init__:Function]
# [DEF:NetworkError.__init__:Function]
# @PURPOSE: Initializes the network error.
# @PRE: message is a string.
# @POST: NetworkError is initialized.
@@ -90,11 +89,11 @@ class NetworkError(Exception):
with belief_scope("NetworkError.__init__"):
self.context = context
super().__init__(f"[NETWORK_FAILURE] {message} | Context: {self.context}")
# [/DEF:__init__:Function]
# [/DEF:NetworkError.__init__:Function]
# [/DEF:NetworkError:Class]
# [DEF:network.SupersetAuthCache:Class]
# [DEF:SupersetAuthCache:Class]
# @PURPOSE: Process-local cache for Superset access/csrf tokens keyed by environment credentials.
# @PRE: base_url and username are stable strings.
# @POST: Cached entries expire automatically by TTL and can be reused across requests.
@@ -152,8 +151,8 @@ class SupersetAuthCache:
# [DEF:APIClient:Class]
# @COMPLEXITY: 3
# @PURPOSE: Synchronous Superset API client with process-local auth token caching.
# @RELATION: DEPENDS_ON -> network.SupersetAuthCache
# @RELATION: DEPENDS_ON -> logger
# @RELATION: [DEPENDS_ON] ->[SupersetAuthCache]
# @RELATION: [DEPENDS_ON] ->[LoggerModule]
class APIClient:
DEFAULT_TIMEOUT = 30
@@ -256,7 +255,7 @@ class APIClient:
return f"{self.api_base_url}{normalized_endpoint}"
# [/DEF:_build_api_url:Function]
# [DEF:authenticate:Function]
# [DEF:APIClient.authenticate:Function]
# @PURPOSE: Выполняет аутентификацию в Superset API и получает access и CSRF токены.
# @PRE: self.auth and self.base_url must be valid.
# @POST: `self._tokens` заполнен, `self._authenticated` установлен в `True`.
@@ -364,7 +363,14 @@ class APIClient:
if status_code == 502 or status_code == 503 or status_code == 504:
raise NetworkError(f"Environment unavailable (Status {status_code})", status_code=status_code) from e
if status_code == 404:
raise DashboardNotFoundError(endpoint) from e
if self._is_dashboard_endpoint(endpoint):
raise DashboardNotFoundError(endpoint) from e
raise SupersetAPIError(
f"API resource not found at endpoint '{endpoint}'",
status_code=status_code,
endpoint=endpoint,
subtype="not_found",
) from e
if status_code == 403:
raise PermissionDeniedError() from e
if status_code == 401:
@@ -372,6 +378,24 @@ class APIClient:
raise SupersetAPIError(f"API Error {status_code}: {e.response.text}") from e
# [/DEF:_handle_http_error:Function]
# [DEF:_is_dashboard_endpoint:Function]
# @PURPOSE: Determine whether an API endpoint represents a dashboard resource for 404 translation.
# @PRE: endpoint may be relative or absolute.
# @POST: Returns true only for dashboard-specific endpoints.
def _is_dashboard_endpoint(self, endpoint: str) -> bool:
normalized_endpoint = str(endpoint or "").strip().lower()
if not normalized_endpoint:
return False
if normalized_endpoint.startswith("http://") or normalized_endpoint.startswith("https://"):
try:
normalized_endpoint = "/" + normalized_endpoint.split("/api/v1", 1)[1].lstrip("/")
except IndexError:
return False
if normalized_endpoint.startswith("/api/v1/"):
normalized_endpoint = normalized_endpoint[len("/api/v1"):]
return normalized_endpoint.startswith("/dashboard/") or normalized_endpoint == "/dashboard"
# [/DEF:_is_dashboard_endpoint:Function]
# [DEF:_handle_network_error:Function]
# @PURPOSE: (Helper) Преобразует сетевые ошибки в `NetworkError`.
# @PARAM: e (requests.exceptions.RequestException) - Ошибка.
@@ -505,4 +529,4 @@ class APIClient:
# [/DEF:APIClient:Class]
# [/DEF:backend.core.utils.network:Module]
# [/DEF:NetworkModule:Module]