Как правильно использовать контекстные переменные в Telethon?

Я разрабатываю бота Telegram, используя библиотеку Telethon.

Я хочу передать значение tags_cv из send_tag_list() в show_selected_tags(). Но я получаю эту ошибку:

`ERROR - Unhandled exception on show_selected_tags
Traceback (most recent call last):
  File "/home/kozorez/.cache/pypoetry/virtualenvs/content-telegram-bot-4LA9pChh-py3.10/lib/python3.10/site-packages/telethon/client/updates.py", line 570, in _dispatch_update
    await callback(event)
  File "/mnt/c/Users/kozor/PycharmProjects/content-telegram-bot/bot/bot.py", line 77, in show_selected_tags
    some_var = tags_cv.get()
LookupError: <ContextVar name='tags' at 0x7f7e3e222de0>`

Я полагаю, что show_selected_tags() не получает значение tags_cv. Возможно, мероприятия Telethon проходят в разных асинхронных контекстах?

bot.py

"""Логика взаимодействия с ботом"""

import logging
import contextvars
from telethon.sync import TelegramClient, events

from services.channel import get_channel_data, add_channel_to_db
from services.post import parse_posts
from services.tag import get_tags_list
from services.keyboard import create_tags_keyboard

from config import (
    api_id,
    api_hash,
    bot_token
)


bot = TelegramClient(
    'bot',
    api_id,
    api_hash).start(bot_token=bot_token)


channel_data_cv = contextvars.ContextVar('channel_data')
tags_cv = contextvars.ContextVar('tags')


@bot.on(events.NewMessage(pattern='/start'))
async def send_welcome(event) -> None:
    """Отправка приветственного сообщения"""

    await event.reply('Привет! Я — бот, который поможет тебе составить пост с навигацией по твоему телеграм-каналу. \
                      \nПросто пришли мне ссылку на свой телеграм-канал.')
    

@bot.on(events.NewMessage(pattern='https://t\.me/(\S+)'))
async def send_tag_list(event) -> None:
    """Анализируем посты из канала и возвращаем клавиатуру с тегами"""

    channel_link = event.text
    tags = None

    try:
        channel_data = await get_channel_data(channel_link)
        channel_data_cv.set(channel_data)
    except ValueError as error:
        if 'Cannot get entity from a channel' in str(error):
            await event.reply('Канал должен быть публичным')
        logging.error(error)

    try:
        await add_channel_to_db(channel_data)
    except Exception as error:
        logging.error(error)
        
    try:     
        await event.reply('Посты анализируются, ожидайте')
        await parse_posts(channel_link, channel_data)
    except ValueError as error:
        logging.error(error)
        await event.reply('К сожалению, мне не удалось найти ни одного тега')

    try:
        tags = await get_tags_list(channel_data)
        tags_cv.set(tags)
    except Exception as error:
        logging.error(error)

    tags_keyboard = await create_tags_keyboard(tags)

    await event.respond("Выберите теги", buttons=tags_keyboard)


@bot.on(events.CallbackQuery())
async def show_selected_tags(event) -> None:
    some_var = tags_cv.get()
    print(some_var)

run.py

"""Запуск бота"""

import asyncio
import logging

from bot import bot

from db.models import *

from config import (
    engine,
    client
    )



loop = asyncio.get_event_loop()
asyncio.set_event_loop(loop)



async def init_db() -> None:
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.drop_all)
        await conn.run_sync(Base.metadata.create_all)
        logging.info('База данных инициализирована')


async def start() -> None:
    await init_db()

    async with bot:
        await bot.run_until_disconnected()
        logging.info('Бот запущен')

    async with client:
        await client.run_until_disconnected()
        logging.info('Клиент запущен')


if __name__ == "__main__":
    loop.create_task(start())
    loop.run_forever()

Честно говоря, я не понимаю, как это исправить. Руководство по контекстным переменным слишком простое, чтобы ошибиться... Что еще, кроме контекстных переменных, можно использовать? Может кэширование?

Буду признателен за вашу помощь.

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
0
51
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

По умолчанию Telethon создает новую задачу для каждого обновления. Переменные контекста привязаны к контексту задачи. Если задача изменится, контекст изменится, и переменной у вас не будет.

В версии 1 вы можете включить sequential_updates, чтобы использовать одну задачу для всех обновлений:

client = TelegramClient(..., sequential_updates=True)

Это будет означать, что все обработчики обновлений используют одну и ту же задачу.

Спасибо за объяснение. Я попробовал, но, к сожалению, все равно вижу ту же ошибку. Я использую Телетон 1.34.0.

Kozorez-V 08.06.2024 15:44

Обратите внимание, что задача отправки обновлений по-прежнему отделена от выполняемой «основной» задачи asyncio, поэтому, если вы хотите прочитать контекстную переменную из обработчика, она должна быть заранее установлена ​​из другого обработчика.

Lonami 09.06.2024 10:25

Другие вопросы по теме