Исходный код tbank.core.errors

from __future__ import annotations

from typing import Any, Dict, Optional, Type

import httpx


[документация] class TBankError(Exception): """Базовое исключение SDK."""
[документация] class TBankNetworkError(TBankError): """Сетевой сбой (соединение/таймаут) после исчерпания ретраев."""
[документация] class TBankTimeoutError(TBankNetworkError): """Истёк таймаут запроса."""
[документация] class MutualTLSRequiredError(TBankError): """Вызван secured-метод, но клиент создан без mTLS-сертификата."""
[документация] class TBankAPIError(TBankError): """Логическая ошибка API (Success=false / HTTP 4xx-5xx).""" def __init__( self, *, code: str, message: str, details: Optional[str] = None, http_status: Optional[int] = None, status: Optional[str] = None, error_id: Optional[str] = None, ) -> None: self.code = code self.message = message self.details = details self.http_status = http_status self.status = status self.error_id = error_id text = f"[{code}] {message}" if details: text += f" ({details})" super().__init__(text)
[документация] class AuthenticationError(TBankAPIError): """Ошибка аутентификации/подписи."""
[документация] class InvalidRequestError(TBankAPIError): """Некорректный запрос/параметры."""
[документация] class InsufficientFundsError(TBankAPIError): """Недостаточно средств на карте."""
[документация] class ThreeDSError(TBankAPIError): """Не пройдена аутентификация 3DS."""
[документация] class TerminalBlockedError(TBankAPIError): """Операция заблокирована для терминала."""
[документация] class ForbiddenError(TBankAPIError): """Доступ запрещён (неизвестный IP / нехватка скоупов / нет прав). HTTP 403."""
[документация] class ValidationError(TBankAPIError): """Некорректные данные запроса. HTTP 400/422."""
[документация] class RateLimitError(TBankAPIError): """Превышен лимит запросов. HTTP 429."""
[документация] class ServerError(TBankAPIError): """Ошибка на стороне сервера. HTTP 5xx."""
# EACQ ErrorCode -> класс исключения (сверять с mapi_errors_list.pdf при расширении). ERROR_REGISTRY: Dict[str, Type[TBankAPIError]] = { "10": TerminalBlockedError, "101": ThreeDSError, "1051": InsufficientFundsError, }
[документация] def build_api_error( *, code: str, message: str, details: Optional[str] = None, http_status: Optional[int] = None, status: Optional[str] = None, ) -> TBankAPIError: """Собрать типизированное исключение по коду ошибки.""" cls = ERROR_REGISTRY.get(code, TBankAPIError) return cls( code=code, message=message, details=details, http_status=http_status, status=status, )
# HTTP-статус -> класс исключения (общий формат ошибок открытого банка T-API). STATUS_ERROR_MAP: Dict[int, Type[TBankAPIError]] = { 400: ValidationError, 401: AuthenticationError, 403: ForbiddenError, 404: InvalidRequestError, 422: ValidationError, 429: RateLimitError, 500: ServerError, 502: ServerError, 503: ServerError, 504: ServerError, }
[документация] def error_from_tapi_response( response: httpx.Response, *, fallback: str = "request failed" ) -> TBankAPIError: """Исключение из T-API error-ответа ({errorId, errorMessage, errorCode, errorDetails}).""" status = response.status_code try: raw = response.json() except ValueError: raw = None data: Dict[str, Any] = raw if isinstance(raw, dict) else {} code = str(data.get("errorCode") or status) message = data.get("errorMessage") or response.text or fallback details = data.get("errorDetails") cls = STATUS_ERROR_MAP.get(status, TBankAPIError) return cls( code=code, message=message, http_status=status, error_id=data.get("errorId"), details=str(details) if details is not None else None, )