Сообщить об ошибке.

Переход на асинхронный python-telegram-bot версии 20.x

Материал по переходу на асинхронный python-telegram-bot версия 20.x не будет охватывать все множество изменений, которые произошли в версии 20.x. Здесь будут рассмотрены моменты, которые помогут перевести простой (без наворотов) телеграмм бот на асинхронную версию.

Команда разработчиков подготовила Python скрипт (постоянно дополняется/совершенствуется), призванный облегчить переход с многопоточной версии 13.x на асинхронную версию 20.x. Обратите внимание, что этот скрипт в настоящее время просто выполняет поиск и замену на основе регулярных выражений, беря на себя только часть работы по переходу. Это никоим образом не заменит ручную корректировку кода. В дополнение к сценарию, в кодовой базе рекомендуется использовать языковой интерпретатор (например, pylint) и средство проверки статического типа (например, mypy), дабы свести к минимуму время проб и ошибок при переходе.

Структурные изменения.

Общая архитектура.

Класс telegram.ext.Updater больше не является точкой входа в приложение python-telegram-bot, а также был заменен класс telegram.ext.Dispatcher новым классом telegram.ext.Application. Теперь класс Application - это новая точка входа в приложение, которая объединяет все ее компоненты.

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

Простой пример:

from telegram.ext import Application, CommandHandler

...
app = Application.builder().token('TOKEN').build()
app.add_handler(CommandHandler('start', start_callback))
app.run_polling()

Переход на модуль asyncio.

Самым глубоким структурным изменением является введение в python-telegram-bot модуля asyncio. Модуль asyncio - это библиотека для написания параллельного кода с использованием синтаксиса async/await.

Пакет python-telegram-bot - это библиотека, основной целью которой является взаимодействие с Telegram Bot API через веб-запросы. При выполнении веб-запросов код обычно тратит много времени на ожидание сетевых соединений. А именно ждем ответа от Telegram. То же самое верно для многих так называемых задач ввода-вывода.

Для решения этой проблемы:

  • Версия python-telegram-bot 13.x использует модуль threading.
  • Версия python-telegram-bot 20.x использует модуль asyncio - это современная альтернатива многопоточности, которая имеет множество преимуществ.

Основные моменты изменения в python-telegram-bot с вводом asyncio:

  • Все методы API telegram.Bot теперь являются функциями-сопрограммами, т.е. в коде перед ними должен ставится оператор await.
  • Все функции обратных вызовов обработчиков и заданий должны быть функциями-сопрограммы, т. е. в коде перед функциями обратных вызовов должен ставится оператор async, например async def callback(update, context):.
  • Аргумент обработчиков run_async был заменен на block, имеющий аналогичный функционал.
  • Метод Dispatcher.run_async больше не существует. Близкое к его функциональности, это Application.create_task().
  • Все методы, которые вызывают сопрограммы или выполняют любые задачи, связанные с вводом-выводом, теперь являются функциями-сопрограммами.

Изменения в основном модуле telegram.

Класс telegram.Bot:

  • имеет новый аргумент get_updates_request в дополнение к request, и соответствующий экземпляр запроса будет использоваться исключительно для вызова метода Bot.getUpdates.
  • Аргумент media метода Bot.edit_message_media теперь является первым позиционным аргументом, как указано Bot API.
  • Аргумент url метода Bot.set_webhook теперь требуется, как указано в Bot API.
  • Аргумент description метода Bot.set_chat_description теперь является необязательным, как указано в Bot API.

Класс telegram.ChatAction был удален, так как он не является частью официального Bot API. Вместо него нужно использовать telegram.constants.ChatAction.

Если у telegram.InlineQuery.answer указаны оба параметра current_offset и auto_pagination, то метод теперь вызывает ValueError, а не TypeError.

Класс telegram.ParseMode был удален, так как он не является частью официального Bot API. Вместо него используйте telegram.constants.ParseMode.

Класс telegram.ReplyMarkup был удален, так как он не является частью официального Bot API.

У класса telegram.EncryptedPassportElement, аргумент hash теперь является вторым позиционным аргументом, как указано в Bot API.

У класса telegram.PassportFile, аргумент file_size теперь является необязательным, как указано в Bot API.

У класса telegram.VideoChat, аргумент users теперь является необязательным, как указано в Bot API.

В версии 20.x был удален метод telegram.InputFile.is_image().

Ранее некоторые классы, например, telegram.Message, telegram.User, telegram.Chat имели атрибут .bot, который использовался для ссылок, например, Message.reply_text. Этот атрибут был удален. Вместо него используется новый метод TelegramObject.set_bot(), TelegramObject.get_bot().

Изменения в модуле telegram.ext.

Класс telegram.ext.CallbackContext:

  • CallbackContext.from_error имеет новый необязательный аргумент job. Когда внутри обратного вызова ext.Job возникает исключение, то будет передан этот параметр. Соответственно атрибут CallbackContext.job теперь также будет присутствовать в обработчиках ошибок, если ошибка была вызвана ext.Job.
  • В версии v20.0 константа CallbackContext.DEFAULT_TYPE удалена. Теперь ее можно найти как ContextTypes.DEFAULT_TYPE.

Модуль telegram.ext.filters был переписан практически с нуля и использует новую политику пространства имен. Изменения примерно такие:

  • telegram.ext.Filters больше не существует. Вместо него нужно использовать модуль telegram.ext.filters напрямую. Например, Filters.text нужно заменить на filter.TEXT.
  • Встроенные фильтры, которым не нужны аргументы, теперь пишутся в стиле SCREAMING_SNAKE_CASE, например filter.TEXT. Классы фильтров, которым нужны аргументы, теперь пишутся в стиле CamelCase, например filters.User.
  • Для тесно связанных фильтров теперь используется класс пространства имен для их группировки. Например, filter.Document нельзя использовать в MessageHandler. Чтобы отфильтровать сообщения с вложенным документом, нужно использовать filter.Document.ALL.
  • Кроме того, фильтры больше нельзя вызывать. Чтобы проверить, принимает ли фильтр обновление, используйте новый синтаксис my_filter.check_update(update).

Класс telegram.ext.JobQueue:

  • Новые аргументы chat_id и user_id: все методы JobQueue.run_* имеют два новых аргумента chat_id и user_id, что позволяет легко связать пользователя/чат с заданием. При указании этих аргументов соответствующий идентификатор будет доступен в обратном вызове задания через context.job.chat_id и context.job.user_id.

    Кроме того, будут доступны context.job.chat_data и context.job.user_data. Это имеет некоторые тонкие преимущества по сравнению с предыдущим обходным решением job_queue.run_*(..., context=context.chat_data), и вместо этого рекомендуется использовать эту новую функциональность.

  • Переименован аргумент context в data: чтобы устранить частую путаницу между context и context.job.context, аргумент context всех методов JobQueue.run_* был переименован в аргумент data. Это также относится к соответствующему атрибуту Job.

  • Изменения в методе JobQueue.run_daily(). поведение этого метода согласовано с cron, т. е. 0 - это воскресенье, а 6 - суббота.

  • Изменения в методе JobQueue.run_monthly(): аргумент day_is_strict работал некорректно и поэтому был удален. Вместо него теперь можно передать day='last', чтобы задание выполнялось в последний день месяца.

Класс telegram.ext.Job:

  • Убран атрибут Job.job_queue: было удалено, потому что если есть доступ к заданию, то также есть доступ либо непосредственно к JobQueue, либо, по крайней мере, к экземпляру CallbackContext, который уже содержит job_queue.
  • Атрибут Job.context был переименован в Job.data.

Класс telegram.ext.ConversationHandler теперь выдает предупреждения о дополнительных обработчиках, которые добавляются в неправильном контексте или вообще не должны быть в обработчике.

В версии 20.0 переименовали базовый класс обработчика Handler в BaseHandler, чтобы подчеркнуть, что этот класс является абстрактным базовым классом.

Теперь единственной целью класса Updater является получение обновлений из Telegram. Теперь он принимает только аргументы bot и update_queue и имеет только эти атрибуты.

Другие существенные изменения.

В версии 20.x был удален модуль telegram.utils. Части этого модуля, которые считаются частью общедоступного API, были перемещены в модули telegram.helpers, telegram.request, telegram.warnings.

В версии 20.x убрали возможность устанавливать пользовательские атрибуты для всех объектов, кроме telegram.ext.CallbackContext. Для хранения данных рекомендовано использовать встроенный механизм хранения данных. Если необходимо добавить к какому-то классу дополнительную функциональность, то необходимо сделать его подклассом.

Начиная с версии 20.0, все аргументы методов бота, которые были добавлены python-telegram-bot, теперь являются только ключевыми аргументами. Самое главное, это касается аргументов *_timeout и api_kwargs.

Ранее класс telegram.utils.request.Request формировал сетевой бэкэнд. Теперь, вместо него есть новый модуль telegram.request, который содержит класс интерфейса BaseRequest, а также реализацию HTTPXRequest этого класса через библиотеку httpx. По умолчанию класс HTTPXRequest используется для серверной части сети. Опытные пользователи могут использовать настраиваемый бэкэнд, реализовав настраиваемый подкласс BaseRequest.

Модуль telegram.error и telegram.constants:

Модуль telegram.constants был переписан с нуля. Константы теперь сгруппированы с помощью перечислений.

Ранее некоторые части telegram.error, telegram.constants были доступны непосредственно через пакет telegram - например:

from telegram import TelegramError

Этот импорт больше не будет работать. Теперь непосредственно через пакет telegram доступны только классы, которые отражают официальный API бота. Константы и ошибки доступны через модули telegram.error или telegram.constants - например:

from telegram.error import TelegramError

Класс telegram.error.Unauthorized был заменен на telegram.error.Forbidden. Более того, telegram.error.Forbidden теперь вызывается только в том случае, если бот пытается выполнить действия, на которые у него недостаточно прав. Если токен бота недействителен, то возбуждается telegram.error.InvalidToken.