Инъекция бота в типы (BotMixin)

BotMixin - это mixin, который добавляет в датакласс ссылку на экземпляр бота. Все типы, наследующие MaxoType, автоматически получают эту функциональность, так как MaxoType уже включает BotMixin.

Зачем это нужно?

Иногда удобно, чтобы десериализованный объект сам мог обращаться к боту - например, для выполнения API-вызовов прямо из метода типа. Благодаря BotMixin вам не нужно вручную пробрасывать bot через аргументы каждого метода.

from maxo.types import MaxoType

class UserProfile(MaxoType):
    user_id: int
    name: str

    async def notify(self, text: str) -> None:
        await self.bot.send_message(chat_id=self.user_id, text=text)

Свойство bot

BotMixin добавляет свойство bot, которое возвращает экземпляр Bot.

profile: UserProfile = ...
bot_instance = profile.bot  # Bot

Если бот не был внедрён, обращение к свойству вызовет AttributeIsEmptyError:

from maxo.errors import AttributeIsEmptyError

profile = UserProfile(user_id=1, name="Alice")

try:
    _ = profile.bot
except AttributeIsEmptyError:
    print("Бот не внедрён в объект!")

Метод as_

as_(bot) внедряет бота в объект и возвращает его же (self), что позволяет использовать fluent-стиль:

profile = UserProfile(user_id=1, name="Alice").as_(bot)
await profile.notify("Привет!")

Автоматическая инъекция через ретроту

Основной сценарий использования BotMixin - автоматическое внедрение бота при десериализации через Retort. Реторта бота (bot.retort) настроена так, что при загрузке любого MaxoType бот автоматически присваивается каждому объекту в дереве вложенности.

from maxo.types import MaxoType

class Sub(MaxoType):
    value: int

class MyData(MaxoType):
    name: str
    sub: Sub

# bot.retort автоматически внедряет бот во все MaxoType-объекты
data = {"name": "test", "sub": {"value": 42}}
my = bot.retort.load(data, MyData)

assert my.bot is bot       # бот внедрён в корневой объект
assert my.sub.bot is bot   # и во вложенный

Обратная операция - dump - также работает корректно и не включает поле _bot в результат:

result = bot.retort.dump(my, MyData)
assert result == {"name": "test", "sub": {"value": 42}}

Использование create_retort вручную

Если вы создаёте ретроту самостоятельно через create_retort(), передайте bot= для включения инъекции:

from maxo.serialization import create_retort

retort = create_retort(bot=bot)
my = retort.load(data, MyData)
assert my.bot is bot

Если bot не передан (или передан как None), инъекция не производится, и обращение к .bot вызовет AttributeIsEmptyError:

from maxo.errors import AttributeIsEmptyError

retort = create_retort()  # bot не передан
my = retort.load(data, MyData)

try:
    _ = my.bot
except AttributeIsEmptyError:
    print("Бот не внедрён!")  # ожидаемое поведение

Создание собственных миксинов

Если вы хотите добавить функциональность бота только к отдельным типам, не используя MaxoType, можно унаследоваться напрямую от BotMixin:

from maxo.types import BaseMaxoType, BotMixin

class LightType(BaseMaxoType, BotMixin):
    value: int

Примечание

При использовании BotMixin без MaxoType инъекция через bot.retort работает только для типов, которые являются подклассами MaxoType. Для LightType потребуется вызвать as_(bot) вручную.

API

class maxo.types.BotMixin[исходный код]
property bot: Bot
as_(bot)[исходный код]
Параметры:

bot (Bot | None)

Тип результата:

Self

class maxo.types.BaseMaxoType[исходный код]

Базовые классы: object

BaseMaxoType()

class maxo.types.MaxoType[исходный код]

Базовые классы: BaseMaxoType, BotMixin

MaxoType()