Search code examples
pythonbotspython-asyncioaiogrampyrogram

The error "RuntimeError: Timeout context manager should be used inside a task" occurs in Pyrogram


I have 2 threads in main.py file, the first one is created via Thread (bot thread) the second one is the main python thread where Pyrogram runs screenshot of main.py code
But when I run the code, Pyrogram terminates code execution when it gets to the point of sending the error message "Timeout context manager should be used inside a task", here is the full log full log error

future: <Task finished name='Task-157' coro=<__send_message() done, defined at C:\Programming\BybitTradeBot\services\trade.py:7> exception=RuntimeError('Timeout context manager should be used inside a task')>
Traceback (most recent call last):
  File "C:\Programming\BybitTradeBot\services\trade.py", line 8, in __send_message
    await bot.send_message(chat_id=db.get_user_value('tg_id'), text=msg)
  File "C:\Programming\BybitTradeBot\venv\Lib\site-packages\aiogram\bot\bot.py", line 346, in send_message
    result = await self.request(api.Methods.SEND_MESSAGE, payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Programming\BybitTradeBot\venv\Lib\site-packages\aiogram\bot\base.py", line 236, in request
    return await api.make_request(await self.get_session(), self.server, self.__token, method, data, files,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Programming\BybitTradeBot\venv\Lib\site-packages\aiogram\bot\api.py", line 139, in make_request
    async with session.post(url, data=req, **kwargs) as response:
  File "C:\Programming\BybitTradeBot\venv\Lib\site-packages\aiohttp\client.py", line 1167, in __aenter__
    self._resp = await self._coro
                 ^^^^^^^^^^^^^^^^
  File "C:\Programming\BybitTradeBot\venv\Lib\site-packages\aiohttp\client.py", line 493, in _request
    with timer:
  File "C:\Programming\BybitTradeBot\venv\Lib\site-packages\aiohttp\helpers.py", line 705, in __enter__
    raise RuntimeError(
RuntimeError: Timeout context manager should be used inside a task

Here's the code that was used userbot code code from trade.py

If userbot is not given any asynchronous tasks, it does not cause an error "Timeout context manager should be used inside a task"

If run just Pyrogram, there are no problems and this code runs without any errors, even if it is asynchronous code

What is the cause of this error and how can it be fixed?


main.py

import asyncio
from aiogram import executor
from threading import Thread

from loader import dp, logging
import handlers
from userbot import main_client


async def start(_):
    logging.info('The bot is up and running')
    print('[+] The bot is up and running')


def bot_thread():
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    executor.start_polling(dp, skip_updates=True, on_startup=start)


if __name__ == '__main__':
    try:
        process = Thread(target=bot_thread)
        process.start()
        main_client(logging)  # Pyrogram
    except Exception as error:
        logging.warning(error)
        print(error)

userbot.py

from pyrogram import Client, filters
from pyrogram.handlers import MessageHandler

from loader import logging as log
from config import USERBOT_API_HASH, USERBOT_API_ID, CHAT_ID_FOR_PARSING
from services.trade import do_futures_trade


last_photo_id = None


def main_client(logging):
    client = Client(name='my_bot', api_id=USERBOT_API_ID, api_hash=USERBOT_API_HASH)

    async def message_photo(client: Client, message):  # pass the chat ID or Username to the function
        global last_photo_id
        async for msg in client.get_chat_history(chat_id=CHAT_ID_FOR_PARSING, limit=1, offset_id=-1):  # looking at the end of the story
            try:
                if msg.photo and msg.caption and msg.photo.file_id != last_photo_id:  # if there's a photo
                    logging.info(f'A prediction was sent to the group file_id - {msg.photo.file_id} caption - {msg.caption}')
                    await client.download_media(msg.photo, file_name=f'forecast.jpg')  # Downloading a photo
                    last_photo_id = msg.photo.file_id
                    logging.info('A new forecast {msg.photo.file_id} has been sent')

                    do_futures_trade(msg.caption, client.loop)
            except Exception as e:
                logging.warning(f"There's been an error {e}")

    client.add_handler(MessageHandler(message_photo, filters=filters.photo & filters.chat(chats=int(CHAT_ID_FOR_PARSING))))
    client.run()


if __name__ == '__main__':
    main_client(log)

trade.py

from loader import bot
import asyncio
from services.connection_db import db

async def __send_message(msg):
    await bot.send_message(chat_id=db.get_user_value('tg_id'), text=msg)

def send_msg(msg, client_loop):
    asyncio.run_coroutine_threadsafe(__send_message(msg), client_loop)

def do_futures_trade(msg, client_loop):
    send_msg(msg, client_loop)
    ...

handlers.py

from loader import dp, bot

@dp.message_handler(commands=['start'])
async def start_command(message: Message):
    await bot.send_message(chat_id=message.from_user.id, text="Hello User")

Solution

  • From the solution I came up with is using the nest_asyncio library
    Sample solution:

    import asyncio
    import nest_asyncio
    from pyrogram import Client
    from aiogram import executor, Bot, Dispatcher
    
    
    nest_asyncio.apply()   # apply before triggering event loops
    
    client = Client("my_userbot", api_id=USERBOT_API_ID, api_hash=USERBOT_API_HASH)
    
    @client.on_message()
    async def my_handler(client, message):
        if message.text == "ping":
            await message.reply("pong")
    
    
    bot = Bot(token=API_TOKEN)
    dp = Dispatcher(bot)
    
    @dp.message_handler(commands=['start', 'help'])
    async def send_welcome(message: types.Message):
        await message.reply("Hi! I'm a bot on aiogram.")
    
    
    async def start_up(_):
        logging.info('Bot launched successfully!')
        print('[+] Bot launched successfully')
    
    
    async def start_bot():
        """ Start aiogram """
        executor.start_polling(dp, skip_updates=True, on_startup=start_up)
    
    
    async def start_userbot():
        """ Start pyrogram """
        await client.start()
    
    
    
    async def main():
        bot_task = asyncio.create_task(start_bot())
        userbot_task = asyncio.create_task(start_userbot())
        await asyncio.gather(bot_task, userbot_task)
    
    
    
    if __name__ == '__main__':
        try:
            loop = asyncio.get_event_loop()
            loop.run_until_complete(main())
        except Exception as error:
            logging.error("Error", exc_info=True)
        finally:
            logging.info('\n[+] Bye...')
            print('\n[+] Bye...')
    

    The nest_asyncio library connects all event loops into a single thread, which solves the problem when using libraries that create their own event loop