Передать аргументы при вызове функции в python-telegram-bot

Есть строка job_queue.run_repeating(ericsson_alarm, interval=10, first=10, data=controllers), которая вызывает функцию ericsson_alarm, в нее нужно передать аргумент controllers. Как это сделать? Пробовал и лямбдами и всем остальным, не получается, буду рад любой подсказке

import logging
import ericsson
import configparser

from telegram import Update
from telegram.ext import ContextTypes, CommandHandler, Application

logging.basicConfig(
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    level=logging.INFO
)


def _ericsson():
    config = configparser.ConfigParser()
    config.read('config.ini')
    controllers = {}
    for section in config.sections():
        if 'MSC' in section:
            # Распаковываем словарь в аргументы функции
            controllers[section] = ericsson.EricssonTelnet(**dict(config.items(section)))
            controllers[section].get_alarms()
    return controllers


async def ericsson_alarm(update: Update, context: ContextTypes.DEFAULT_TYPE):
    controllers = context.job.context['controllers']
    for controller in controllers.values():
        alarm = controller.get_alarms()
        if alarm:
            await context.bot.send_message(chat_id='-ID', text=f"<code>{alarm}</code>")


async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
    await context.bot.send_message(chat_id=update.effective_chat.id, text="I'm a bot, please talk to me!")


def main() -> None:
    application = Application.builder().token('TOKEN').build()

    controllers = _ericsson()

    # Для отправки сообщений по таймеру
    job_queue = application.job_queue

    # Команды
    start_handler = CommandHandler('start', start)

    # Вызываем функции по-таймеру
    job_queue.run_repeating(ericsson_alarm, interval=10, first=10, data=controllers)

    application.add_handler(start_handler)

    application.run_polling()


if __name__ == '__main__':
    main()

Ответы (4 шт):

Автор решения: Kirill

Варианты:

  1. Использовать функцию без параметров (параметры сохранить в переменные и использовать эти переменные)

  2. Пример:

     updater = Updater(token='YOUR_TOKEN', use_context=True)
     dispatcher = updater.dispatcher
    
     dispatcher.add_handler(CommandHandler('start', <func_name>, 
     <param1> = <value>, <param2> = <value>))
    
→ Ссылка
Автор решения: Medvedev

Параметр data это переименованный в версии 20.0 context. То есть из data будет браться только объект 'context'. Это прописано и в описании к самому классу JobQueue.

Если вы выведите в print context.job, то увидите объект класса Job в виде:

Job[id=ид, name=ericsson_alarm, callback=ericsson_alarm, trigger=тригер]

Просто, что бы понимали как это работает и что из чего вы пытаетесь достать. Из context.user_data так же ничего не получится извлечь, тк объект будет пустым None.

Поэтому самый простой и очевидный способ это создать словарь глобально и все необходимые данные брать из этого словаря:

def _ericsson():
    config = configparser.ConfigParser()
    config.read('config.ini')
    controllers = {}
    for section in config.sections():
        if 'MSC' in section:
            # Распаковываем словарь в аргументы функции
            controllers[section] = ericsson.EricssonTelnet(**dict(config.items(section)))
            controllers[section].get_alarms()
    return controllers

controllers = _ericsson() # будет доступна во всех функциях прописанных ниже

Важно отметить, что run_repeating начинает работу сразу после запуска бота, и отправлять сообщения из вызываемой функции можно только используя конкретный id чата. При этом пользователь по этому id должен предварительно начать диалог с ботом, иначе бот не сможет ничего ему отправить - защита ТГ от спама. Если же это групповой чат, то соответственно права в группе на отправку сообщений.

Документация

Исправленный код:

def _ericsson():
    config = configparser.ConfigParser()
    config.read('config.ini')
    controllers = {}
    for section in config.sections():
        if 'MSC' in section:
            # Распаковываем словарь в аргументы функции
            controllers[section] = ericsson.EricssonTelnet(**dict(config.items(section)))
            controllers[section].get_alarms()
    return controllers

controllers = _ericsson()


async def ericsson_alarm(context: ContextTypes.DEFAULT_TYPE):
    for controller in controllers.values():
        alarm = controller.get_alarms()
        if alarm:
            await context.bot.send_message(chat_id='-ID', text=f"<code>{alarm}</code>")


async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
    await context.bot.send_message(chat_id=update.effective_chat.id, text="I'm a bot, please talk to me!")


def main() -> None:
    application = Application.builder().token('TOKEN').build()

    # Команды
    start_handler = CommandHandler('start', start)

    # Для отправки сообщений по таймеру
    job_queue = application.job_queue
    # Вызываем функции по-таймеру
    job_queue.run_repeating(ericsson_alarm, interval=10, first=10, data=job_queue) # либо data можно вообще не указывать, по умолчанию будет передаваться context

    application.add_handler(start_handler)

    application.run_polling()

→ Ссылка
Автор решения: Qwertiy

Думаю, можно сделать как-то так:

async def ericsson_alarm(update: Update, context: ContextTypes.DEFAULT_TYPE):
async def ericsson_alarm(controllers, update: Update, context: ContextTypes.DEFAULT_TYPE):
job_queue.run_repeating(ericsson_alarm, interval=10, first=10, data=controllers)
job_queue.run_repeating(lambda update, context: ericsson_alarm(controllers, update, context), interval=10, first=10)

Или (судя по документации версии 21.3) и с учётом того, что параметр update в ericsson_alarm вообще не использовался, как-то так:

async def ericsson_alarm(update: Update, context: ContextTypes.DEFAULT_TYPE):
async def ericsson_alarm(controllers, context: ContextTypes.DEFAULT_TYPE):
job_queue.run_repeating(ericsson_alarm, interval=10, first=10, data=controllers)
job_queue.run_repeating(lambda context: ericsson_alarm(controllers, context), interval=10, first=10)
→ Ссылка
Автор решения: Andromeda

попробуйте так context.job_queue.run_repeating(ericsson_alarm, interval=10, first=10, data=[controllers])

распаковка

def ericsson_alarm(context):
  data=context.job.data
callback - функция обратного вызова.
job_kwargs=None - словарь: произвольные ключевого аргументы для передачи в scheduler.add_job().
data=None - дополнительные данные, необходимые для функции обратного вызова. Доступ к Job.data можно получить в функции обратного вызова через context.job.data. По умолчанию значение равно None. (Изменено в версии 20.0: аргумент context переименован в data.)
name=None - имя задания. По умолчанию callback.__name__.
chat_id - идентификатор чата, связанного с этим заданием. Если передано, то соответствующие данные chat_data будут доступны в обратном вызове. (Новое в версии 20.0.)
user_id - идентификатор пользователя, связанного с этим заданием. Если передано, соответствующие user_data будут доступны в обратном вызове. (Новое в версии 20.0.)
job_kwargs=None - произвольные ключевые аргументы для передачи в apscheduler.schedulers.base.BaseScheduler.add_job().
→ Ссылка