Состояния (FSM)¶
Конечный автомат (Finite State Machine, FSM) - это механизм, позволяющий боту «помнить», на каком этапе диалога находится конкретный пользователь, и сохранять связанные с этим данные.
Без FSM бот реагирует на каждое сообщение изолированно. С FSM вы можете создавать сценарии: опросы, меню, пошаговые формы.
Определение состояний¶
Состояния группируются в классы, наследуемые от StatesGroup. Каждое состояние - экземпляр класса State.
from maxo.fsm import State, StatesGroup
class Registration(StatesGroup):
waiting_name = State()
waiting_age = State()
waiting_bio = State()
Переходы между состояниями¶
Чтобы переключить пользователя в другое состояние, используется объект FSMContext, который автоматически передается в обработчик, если указать аргумент fsm_context.
from maxo.fsm import FSMContext
from maxo.routing.ctx import Ctx
from maxo.routing.filters import Command
from maxo.routing.updates import MessageCreated
# Импортируйте вашу группу состояний
# from states import Registration
@router.message_created(Command("register"))
async def start_registration(
update: MessageCreated,
ctx: Ctx,
fsm_context: FSMContext,
):
await update.answer_text("Как вас зовут?")
# Устанавливаем состояние "ожидание имени"
await fsm_context.set_state(Registration.waiting_name)
Фильтрация по состоянию¶
Чтобы обработчик сработал только в определенном состоянии, используйте StateFilter.
from maxo.fsm import FSMContext, StateFilter
from maxo.routing.ctx import Ctx
from maxo.routing.updates import MessageCreated
# Этот хендлер сработает только если пользователь находится в состоянии waiting_name
@router.message_created(StateFilter(Registration.waiting_name))
async def process_name(
update: MessageCreated,
ctx: Ctx,
fsm_context: FSMContext,
):
name = update.message.body.text
# Сохраняем данные в память FSM
await fsm_context.update_data(name=name)
await update.answer_text(f"Приятно познакомиться, {name}! Сколько вам лет?")
# Переходим к следующему шагу
await fsm_context.set_state(Registration.waiting_age)
Работа с данными¶
FSMContext позволяет не только переключать состояния, но и хранить данные, специфичные для текущего пользователя и диалога.
await fsm_context.update_data(key=value)- добавить или обновить данные.await fsm_context.get_data()- получить все сохраненные данные (словарь).await fsm_context.get_value("key")- получить конкретное значение.await fsm_context.clear()- сбросить состояние и очистить все данные (завершить диалог).
from maxo.fsm import FSMContext, StateFilter
from maxo.routing.ctx import Ctx
from maxo.routing.updates import MessageCreated
@router.message_created(StateFilter(Registration.waiting_age))
async def process_age(
update: MessageCreated,
ctx: Ctx,
fsm_context: FSMContext,
):
age = update.message.body.text
# Получаем сохраненные ранее данные
data = await fsm_context.get_data()
name = data.get("name")
await update.answer_text(f"Анкета:\nИмя: {name}\nВозраст: {age}")
# Завершаем диалог
await fsm_context.clear()
Хранилища¶
Данные FSM должны где-то храниться. maxo поддерживает несколько хранилищ.
MemoryStorage¶
Используется по умолчанию, если вы не указали иное при создании Dispatcher. Хранит данные в оперативной памяти (RAM).
Внимание: при перезапуске бота все состояния сбрасываются. Подходит для разработки.
from maxo import Dispatcher
from maxo.fsm.storages.memory import MemoryStorage
dispatcher = Dispatcher(storage=MemoryStorage())
RedisStorage¶
Сохраняет состояния в Redis. Идеально для прода: данные переживают перезапуск бота, и можно запускать несколько экземпляров бота параллельно.
Требует установки redis (pip install maxo[redis]).
from maxo import Dispatcher
from maxo.fsm.storages.redis import RedisStorage
storage = RedisStorage.from_url("redis://localhost:6379/0")
dispatcher = Dispatcher(storage=storage)