Skip to content

Router.handlers breaks when handler is a bound method and router stores a reference to the Bot #36

@Exzentttt

Description

@Exzentttt

Router.handlers breaks when handler is a bound method and router stores a reference to the Bot

Привет! Столкнулся с проблемой при использовании class-based роутеров.

Если роутер - это класс, хранящий ссылку на aiomax.Bot, и хэндлеры регистрируются как bound methods, то падает start_polling с TypeError: cannot pickle '_contextvars.Context' object.

Минимальный пример

import asyncio
import aiomax


class MyRouter:
    def __init__(self, bot: aiomax.Bot):
        self.bot = bot

    async def on_start(self, ctx: aiomax.CommandContext):
        await ctx.reply("hi")

    def register(self):
        self.bot.on_command("start")(self.on_start)


async def main():
    bot = aiomax.Bot("TOKEN")
    router = MyRouter(bot)
    router.register()
    await bot.start_polling()


asyncio.run(main())

Что происходит

Router.handlers property делает deepcopy(self._handlers). Внутри лежит bound method, и Python при его deepcopy вызывает deepcopy(method.__self__) — то есть клонирует роутер целиком. А в роутере хранится self.botbot.session (aiohttp.ClientSession) → внутри неё _contextvars.Context, который непиклируемый.

Стектрейс:

File ".../aiomax/bot.py", line 1028, in start_polling
    for i in self.handlers["on_ready"]:
File ".../aiomax/router.py", line 90, in handlers
    out = deepcopy(self._handlers)
...
TypeError: cannot pickle '_contextvars.Context' object

Почему это проблема

Class-based роутеры — нормальный способ организации кода (по аналогии с aiogram), особенно если хочется разделить логику по доменам и инжектить зависимости через __init__. В документации в разделе "Роутеры" пример простой — handler снаружи класса, — но для больших ботов это неудобно.

Воркараунд, который я использую

Добавил __deepcopy__ в базовый класс:

class BaseRouter:
    def __deepcopy__(self, memo):
        # Роутер — singleton со ссылками на bot и сервисы,
        # клонировать его нет смысла.
        return self

И наследую все роутеры от BaseRouter. Работает, но это воркараунд на стороне пользователя.

Что, кажется, стоит сделать в библиотеке

Вариант 1 — убрать defensive deepcopy в Router.handlers и возвращать новый dict/list без глубокого копирования самих хэндлеров. Защита нужна только от мутации списка handlers, а не от мутации самих callable'ов.

Вариант 2 — определить __deepcopy__ на самом Router, возвращающий self (тогда bound methods роутера будут безопасны).

Версия: aiomax 2.12.4, Python 3.13.3.

Спасибо за библиотеку!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions