Система исключений и руководство по обработке
Все пользовательские исключения ErisPulse наследуются от ErisPulseError, а исключения底层 библиотек (например, aiohttp, aiomysql и др.) перехватываются и преобразуются во внутренние исключения ErisPulse — код бизнес-логики не должен зависеть от типов исключений底层 библиотек.
{!--< tips >!--}
- Для широкого перехвата: перехватите
ErisPulseError(базовый класс всех исключений фреймворка) - Для точного обработки: перехватите по модулю (например,
ModuleCallTimeoutError,StorageUnreachableError) - Операции с хранилищем по умолчанию не вызывают исключения: при сбое записывается в лог и возвращается
False / None / default; изменение состояния подключения подписывается на событияstorage.unreachable/storage.recovered{!--< /tips >!--}
Обзор исключений
ErisPulseError # Базовый класс всех исключений фреймворка
├── ClientError # Базовый класс исключений HTTP/WS клиентов (Core/client)
│ ├── ClientConnectionError # Ошибки на уровне соединения: неудача DNS, отказ в соединении, недоступность сети
│ ├── ClientTimeoutError # Ошибка таймаута запроса
│ └── HTTPStatusError # Ошибки HTTP статус-кода (например, 4xx/5xx и raise_for_status)
├── WebSocketError # Базовый класс исключений WebSocket (WS соединения в Core/client)
│ └── WebSocketDisconnect # Отключение WebSocket (общее для сервера и клиента)
├── StorageError # Базовый класс исключений хранилища (Core/storage)
│ └── StorageUnreachableError # Недостижимость хранилища (исчерпаны попытки подключения: недоступна база данных, ошибка учетных данных)
├── InteractionError # Базовый класс исключений взаимодействия (Core/Event/interaction)
│ ├── InteractionCancelled # Отмена ожидания/аренды (wait_reply возвращает None)
│ └── SessionOccupiedError # Исключение занятости сессии (hold() не удалось получить аренду)
├── ModuleError # Базовый класс исключений модуля (Core/module)
│ └── ModuleCallError # Базовый класс исключений вызова модуля
│ ├── ModuleNotAvailableError # Целевой модуль не зарегистрирован/не включен/инициализация не удалась (включая ленивую загрузку)
│ ├── ServiceNotProvidedError # Целевой модуль не объявил эту службу (не в белом списке meta.services)
│ └── ModuleCallTimeoutError # Выполнение вызываемого метода превысило таймаут (по умолчанию 30 сек.)
└── StrictModeError # Критическое нарушение строгого режима (прерывает процесс запуска, loaders/strict)
Структурированные атрибуты
После перехвата исключения можно получить структурированные атрибуты (без разбора текста сообщения):
| Исключение | Атрибуты |
|---|---|
ClientError (включая подклассы) |
.url URL запроса, .method метод запроса, .attempts количество попыток (когда исчерпаны попытки повтора) |
HTTPStatusError |
.status код статуса, .message сообщение ответа |
WebSocketDisconnect |
.code код закрытия, .reason причина закрытия |
StorageUnreachableError |
.backend имя хранилища (sqlite/mysql/postgres), .cooldown время охлаждения в секундах |
ModuleCallError (включая подклассы) |
.module имя модуля, .method имя метода |
ModuleCallTimeoutError |
Наследует .module/.method, также .timeout лимит таймаута (сек.) |
InteractionCancelled |
.reason причина отмены, .wait_key ключ сессии |
SessionOccupiedError |
.wait_key ключ сессии, .owner владелец |
StrictModeError |
.violations список нарушений |
from ErisPulse.Core.Bases.errors import ClientError
try:
resp = await sdk.client.post(url, json=payload)
except ClientError as e:
print(f"Запрос не удался {e.method} {e.url}, всего попыток {e.attempts}: {e}")
Описание исключений и места их возникновения
Клиентские исключения — Core/client.py / Core/Bases/client.py
Исключения возникают при отправке запросов через sdk.client / HTTP клиент и WebSocket клиент:
| Исключение | Место возникновения | Типичные сценарии |
|---|---|---|
ClientError |
Уровень обертки запросов | Другие ошибки клиента (исключения底层 aiohttp уже преобразованы) |
ClientConnectionError |
Этап установки соединения | Целевой сервис недоступен, DNS неудачен, соединение отклонено |
ClientTimeoutError |
Этап выполнения запроса | Время запроса превышено |
HTTPStatusError |
raise_for_status() |
Код статуса ответа 4xx/5xx |
from ErisPulse.Core.Bases.errors import ClientTimeoutError
try:
resp = await sdk.client.get("https://api.example.com", timeout=5)
except ClientTimeoutError:
...
WebSocket исключения — Core/client.py (методы send / receive)
| Исключение | Место возникновения | Типичные сценарии |
|---|---|---|
WebSocketError |
Методы отправки/приема WebSocket | Соединение закрыто, получено неожиданное сообщение,底层 WebSocket исключения |
WebSocketDisconnect |
Методы отправки/приема WebSocket | Удаленный узел нормально закрыл соединение (фреймворк автоматически переподключится) |
Исключения хранилища — Core/storage / Core/Bases/sql_base.py
| Исключение | Место возникновения | Типичные сценарии |
|---|---|---|
StorageError |
Уровень хранилища | Базовый класс исключений хранилища |
StorageUnreachableError |
Этап создания пула | База данных недоступна / ошибка учетных данных / сетевая изоляция, исчерпаны попытки повтора |
Поведение при сбое операций с хранилищем: операции KV и запросов по умолчанию не вызывают исключений — при сбое записывается в лог уровня ERROR и возвращается
False/None/default(чтобы избежать блокировки работы фреймворка). Следовательно, код бизнес-логики обычно не перехватываетStorageUnreachableError(исключение предназначено для прямого доступа к底层 хранилищу или пользовательских backends). Для отслеживания состояния подключения во время выполнения подписывайтесь на события жизненного циклаstorage.unreachable/storage.recovered(см. События жизненного цикла).
Поведение при сбое подключения см. Backends хранилища → Поведение при сбое подключения.
Исключения взаимодействия — Core/Event/interaction.py
Связанные с wait_reply / арендой сессии / таймером напоминаний:
| Исключение | Место возникновения | Типичные сценарии |
|---|---|---|
InteractionError |
Уровень взаимодействия | Базовый класс исключений взаимодействия |
InteractionCancelled |
При отмене ожидания | Устанавливается на future ожидания (ожидающий код может перехватить и получить .reason) |
SessionOccupiedError |
При неудачной аренде hold() |
Сессия уже занята другим owner (.owner содержит владельца) |
При отмене ожидания (отключение модуля / завершение платформы / новое ожидание в той же сессии) wait_reply возвращает None, а не вызывает исключение (внутри InteractionCancelled преобразуется); при необходимости различить причину отмены перехватывайте это исключение напрямую.
Исключения модулей — Core/module.py (sdk.module.call)
| Исключение | Место возникновения | Типичные сценарии |
|---|---|---|
ModuleError |
Система модулей | Базовый класс исключений модулей |
ModuleCallError |
module.call() |
Базовый класс исключений вызова модуля |
ModuleNotAvailableError |
module.call() / ленивое получение атрибута |
Целевой модуль не зарегистрирован / не включен / инициализация не удалась |
ServiceNotProvidedError |
module.call() |
Целевой meta.services не объявил этот метод |
ModuleCallTimeoutError |
module.call() |
Вызываемый корутина превысила лимит таймаута (по умолчанию 30 сек.) |
"Целевой модуль недоступен" в разных путях доступа:
| Путь доступа | Исключение |
|---|---|
await sdk.module.call("X", "method") |
ModuleNotAvailableError (типизированное) |
sdk.module.X.attr (ленивое получение атрибута, после неудачной инициализации) |
ModuleNotAvailableError |
sdk.module.X (при попытке доступа к неактивированному модулю) |
AttributeError (Python-семантика атрибута, hasattr зависит от этой семантики) |
from ErisPulse.Core.Bases.errors import ModuleNotAvailableError, ServiceNotProvidedError
try:
history = await sdk.module.call("Chat", "get_history", session_id, n=20)
except ModuleNotAvailableError:
... # Целевой модуль не существует / не включен
except ServiceNotProvidedError:
... # Целевой модуль не предоставляет эту службу
Проверка параметров фреймворка (ValueError)
Проверка параметров построителя запросов хранилища (пустой тип столбца, Insert не dict, небезопасный тип столбца и т.д.) вызывает стандартный ValueError — такие исключения относятся к ошибкам в коде на этапе разработки, и обычный бизнес-код не должен их перехватывать, а должен исправлять вызов.
Неудача стандартных действий адаптера не вызывает исключений: возвращается словарь с кодом ответа (протокольная семантика, например, retcode=10002 означает, что действие не реализовано) — это параллельный канал ошибок, отличный от канала исключений ClientError в клиентском слое; при разработке адаптера необходимо обрабатывать оба канала.
Рекомендации по перехвату
from ErisPulse.Core import ErisPulseError # Базовый класс экспортируется из Core
try:
...
except ErisPulseError as e:
... # Общий перехват: все пользовательские исключения фреймворка
- При разработке модулей: перехватывайте по необходимости (см. таблицу выше), в самом внешнем блоке можно использовать
ErisPulseErrorдля общего перехвата - Все исключения экспортируются из
ErisPulse.Core(включаяSessionOccupiedError/InteractionCancelled/StrictModeError), также можно импортировать изErisPulse.Core.Bases.errors - Полное определение см. в
src/ErisPulse/Core/Bases/errors.py
Скрытие шума при завершении
Долгоживущие процессы при завершении (или отмене фоновых циклов) часто генерируют множество бесполезных сообщений от интерпретатора типа Task was destroyed but it is pending!. Фреймворк включает скрытие подобного шума: в 2-секундном окне одинаковые сообщения понижаются до уровня TRACE и объединяются с подсчетом, выводится только один раз, с суффиксом «(еще N одинаковых сообщений было скрыто)». Это преднамеренное уменьшение шума — появление суффикса скрытия не означает, что фреймворк "проглотил" реальные исключения, бизнес-исключения (вышеуказанные) выводятся полностью.