Архитектура¶
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() выполняет по порядку:
Миграции: провайдер ролей при необходимости создаёт свою таблицу или ключи.
Регистрация middleware: Raito подключает к диспетчеру свои middleware: пагинацию, разбор команд, группировку альбомов и диалоги.
Загрузка встроенных хендлеров: первым грузится набор dev-инструментов
.rt(help, управление роутерами, роли, статистика и защищённые eval/bash).Загрузка ваших роутеров: переданная папка сканируется, и каждый роутер регистрируется.
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.