Архитектура

Raito это тонкий слой поверх aiogram. Он не заменяет ни диспетчер, ни FSM, ни систему фильтров, а управляет ими. Если понять эту связь, становится ясно почти всё поведение библиотеки.

Объект Raito

raito.Raito это координатор. При создании вы передаёте ему Dispatcher из aiogram и папку с файлами хендлеров:

raito = Raito(dispatcher, "src/handlers")

Конструктор сразу делает две вещи, о которых стоит знать:

  • регистрирует себя в контексте диспетчера как dispatcher["raito"], поэтому любой хендлер и фильтр может получить аргумент raito через внедрение зависимостей aiogram;

  • выбирает провайдер ролей по переданному FSM-хранилищу (в памяти, JSON, Redis или SQL), с откатом на провайдер в памяти.

Пока ничего не загружено. Основная работа происходит в raito.Raito.setup().

Последовательность setup

await raito.setup() выполняет по порядку:

  1. Миграции: провайдер ролей при необходимости создаёт свою таблицу или ключи.

  2. Регистрация middleware: Raito подключает к диспетчеру свои middleware: пагинацию, разбор команд, группировку альбомов и диалоги.

  3. Загрузка встроенных хендлеров: первым грузится набор dev-инструментов .rt (help, управление роутерами, роли, статистика и защищённые eval/bash).

  4. Загрузка ваших роутеров: переданная папка сканируется, и каждый роутер регистрируется.

  5. Watchdog: в режиме разработки (production=False) запускается наблюдатель за файлами, чтобы правки применялись на лету.

Так как всё держится на диспетчере, Raito не зависит от транспорта: он одинаково работает и на поллинге, и за вебхуком.

Как находятся роутеры

Роутеры это обычные роутеры aiogram, лежащие в файлах. Три небольших класса превращают папку в живое дерево роутеров:

RouterParser

Импортирует .py-файл как изолированный модуль и находит в нём роутер: либо переменную с именем router, либо, если её нет, единственный экземпляр aiogram.Router / raito.Router, определённый в файле.

RouterLoader

Владеет жизненным циклом одного файла: load регистрирует роутер в диспетчере, unload убирает его, а reload заново импортирует файл, чтобы новый код вступил в силу. Он запоминает позицию роутера, поэтому порядок переживает перезагрузку.

RouterManager

Сканирует папку (пропуская имена с префиксом _), создаёт по загрузчику на файл, разрешает конфликты имён роутеров и грузит всё сначала с наибольшим приоритетом.

Приоритет и автозагрузка

raito.Router расширяет роутер aiogram двумя атрибутами:

  • priority: чем больше, тем раньше загрузка, поэтому роутеры с авторизацией или middleware могут зарегистрироваться раньше тех, кто от них зависит;

  • autoload: поставьте False, чтобы роутер не грузился автоматически; его всё ещё можно включить на лету через .rt load.

from raito import Router

router = Router(name="auth", priority=100)      # loads first
debug = Router(name="debug", autoload=False)     # opt-in via `.rt load debug`

Hot reload

В разработке RouterManager держит наблюдатель (watchfiles.awatch) над вашей папкой. На каждое изменение он находит загрузчик этого файла и:

  • изменёнreload (выгрузка, повторный импорт, загрузка), чтобы новый код заработал;

  • добавлен → под новый файл создаётся загрузчик, и роутер грузится;

  • удалён → роутер выгружается.

Свежесть кода берётся из повторного импорта модуля при перезагрузке, а не из правки объектов на месте. Именно поэтому hot-reload надёжен, но по этой же причине побочные эффекты на уровне модуля выполняются заново при каждой перезагрузке, поэтому держите работу на этапе импорта в файлах хендлеров минимальной.

Возможности это опциональные слои

Всё, кроме загрузки, добавляется на уровне хендлера и почти всегда через один из двух механизмов aiogram:

Флаги несут метаданные на хендлере. @rt.description, @rt.hidden, @rt.params и @rt.limiter все ставят флаги; middleware и регистратор команд читают их позже. В момент декорирования ничего не происходит: флаг это просто данные, привязанные к хендлеру.

Фильтры решают, выполнится ли хендлер. Нагляднее всего система ролей: OWNER | ADMINISTRATOR это фильтр, который спрашивает у менеджера ролей, есть ли у текущего пользователя одна из этих ролей. Как обычный фильтр aiogram, он ограничивает хендлер.

Такой подход держит Raito составным: можно взять один декоратор, не подписываясь на остальное, а ваши хендлеры остаются обычными хендлерами aiogram.

Request-scoped по умолчанию

Raito опирается на внедрение зависимостей aiogram на каждый апдейт. Middleware, дающий, скажем, сессию БД на апдейт, поднимет её и закроет вокруг каждого сообщения. Сцены Raito специально это сохраняют: каждый шаг многошагового диалога это отдельный, полностью завершающийся хендлер, поэтому сессия освобождается между шагами, а не удерживается, пока пользователь думает.

Диалоги делают обратный выбор: wait_for приостанавливает корутину хендлера до прихода следующего сообщения, что проще, но держит зависимости этого апдейта живыми всё это время. Когда что выбрать, описано в руководствах scenes и conversations.