pythonmatplotlibtelegrambytesio

Returning matplotlab figure with Telegram


I am trying to return matplotlib plots using pandas_datareader, matplotlib and Telegram. I have been studying the code here which covers a similar goal: Returning matplotlib plots using telegram bot but I am using a different technique with the telegram bot which does not align.

Mainly I am using update and message.reply_photo.

Docs for message.reply_photo https://docs.pyrogram.org/api/bound-methods/Message.reply_photo

Here is my code for main.py:

#imports

def generate_chart(update, context):
    stock = f'^GSPC'
    start = datetime.date(2000, 1, 1)
    end = datetime.date.today()
    data = web.DataReader(stock, 'yahoo', start, end)
    plot = data.plot(y='Open')
    img_buf = BytesIO()
    plt.savefig(img_buf, format='png')

    prepared_data = img_buf

    update.message.reply_photo(prepared_data)

def main():
updater = Updater(keys.API_KEY, use_context=True)
dp = updater.dispatcher

dp.add_handler(CommandHandler("stocks", generate_chart))

dp.add_handler(MessageHandler(Filters.text, handle_message))

dp.add_error_handler(error)

updater.start_polling()
updater.idle()

main()

I am generating a chart using pandas DataReader and then using a file-like BytesIO() object to assign an image buffer which I then try to send with update.message.reply_photo.

The main error I am getting is:

 UserWarning: Starting a Matplotlib GUI outside of the main thread will likely fail.

Solution

  • You may need to initialize matplotlib with a non-interactive backend so it won't attempt to initiate a GUI to perform drawing.

    Try putting this at the top of your script:

    import matplotlib
    matplotlib.use('agg')
    
    # the rest of your code here
    ... 
    

    You can read more about matplotlib's backends here. The idea is that matplotlib will attempt to default into an interactive backend, which spawns a GUI. GUIs have issues being launched outside of the main thread since they themselves have their own event loop.

    So, I believe you'll want to avoid the interactive backends to get this to work.