Как работает hot reload

В разработке Raito перезагружает файл хендлера в момент сохранения, без перезапуска бота. Эта страница объясняет механику, чтобы вы знали, что он может, а что нет.

Три действующих лица

RouterParser

Импортирует один файл как изолированный модуль. Строит spec из пути к файлу, даёт модулю уникальное синтетическое имя, регистрирует его в sys.modules до выполнения (чтобы разрешились dataclass’ы и относительная механика), выполняет модуль и возвращает найденный роутер.

RouterLoader

Владеет жизненным циклом одного файла:

  • load включает роутер в диспетчер;

  • unload убирает его и сбрасывает распарсенный роутер;

  • reload выполняет unload, затем load.

Важно, что unload запоминает индекс роутера среди суб-роутеров диспетчера, а load его восстанавливает, поэтому перезагрузка оставляет роутер на прежнем месте, сохраняя порядок разбора хендлеров.

RouterManager

Сканирует папку, создаёт по загрузчику на файл и держит наблюдатель.

Цикл перезагрузки

Когда файл меняется, менеджер находит его загрузчик и вызывает reload:

  1. unload: роутер убирается из диспетчера, а его распарсенный объект сбрасывается (в None).

  2. load: обращение к роутеру загрузчика заново импортирует файл, поэтому новый код парсится с нуля, а затем роутер регистрируется на сохранённом индексе.

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

Что перезагружается чисто, а что нет

  • Тела хендлеров перезагружаются прозрачно. Менять то, что делает хендлер, безопасно даже для пользователя посреди сцены, потому что aiogram опознаёт FSM-шаг по строковому имени, а не по объекту Python.

  • Новые файлы подхватываются: событие added создаёт загрузчик и грузит роутер.

  • Удалённые файлы выгружают свой роутер.

  • Побочные эффекты на уровне модуля выполняются заново при каждой перезагрузке, потому что модуль исполняется повторно. Держите работу на этапе импорта в файлах хендлеров минимальной: дорогое состояние стройте в lifespan, а не на верхнем уровне модуля.

  • Общие модули (хелпер, импортируемый несколькими роутерами) сами по себе не вызывают перезагрузку импортирующих их роутеров; перезагружается только изменённый файл роутера.

Почему только в разработке

Наблюдатель (watchfiles.awatch) работает только при production=False. В продакшене нужен фиксированный, проверенный набор роутеров и никаких накладных расходов на слежение за файлами, поэтому setup просто грузит всё один раз и наблюдатель не запускает.

Приоритет и порядок

На старте роутеры грузятся сначала с наибольшим приоритетом, что фиксирует их порядок в диспетчере. Перезагрузка возвращает роутер на прежний индекс, поэтому порядок стабилен между правками. Совсем новый файл, добавленный на лету, дописывается в конец, а не вставляется по приоритету, поэтому если строгий порядок важен, перезапустите бота.