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.bot → bot.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.
Спасибо за библиотеку!
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.Минимальный пример
Что происходит
Router.handlersproperty делаетdeepcopy(self._handlers). Внутри лежит bound method, и Python при его deepcopy вызываетdeepcopy(method.__self__)— то есть клонирует роутер целиком. А в роутере хранитсяself.bot→bot.session(aiohttp.ClientSession) → внутри неё_contextvars.Context, который непиклируемый.Стектрейс:
Почему это проблема
Class-based роутеры — нормальный способ организации кода (по аналогии с aiogram), особенно если хочется разделить логику по доменам и инжектить зависимости через
__init__. В документации в разделе "Роутеры" пример простой — handler снаружи класса, — но для больших ботов это неудобно.Воркараунд, который я использую
Добавил
__deepcopy__в базовый класс:И наследую все роутеры от
BaseRouter. Работает, но это воркараунд на стороне пользователя.Что, кажется, стоит сделать в библиотеке
Вариант 1 — убрать defensive
deepcopyвRouter.handlersи возвращать новый dict/list без глубокого копирования самих хэндлеров. Защита нужна только от мутации списка handlers, а не от мутации самих callable'ов.Вариант 2 — определить
__deepcopy__на самомRouter, возвращающий self (тогда bound methods роутера будут безопасны).Версия: aiomax 2.12.4, Python 3.13.3.
Спасибо за библиотеку!