Search code examples
flaskpython-asyncioaiogram

Asynchrony in Python. How to receive requests in a Python Telegram bot and at the same time perform another function


I am coding telegram-bot, using Flask and asyncio. And I have a function for receiving and processing requests - get_messages(). And test function - sleep_func(), which will sleep for a while(20 seconds), but get_messages() will be working at this moment, when sleep_func() is sleeping. After 20 seconds sleep_func() must wake up, print some text, but doesn't finish the get_messages(). So, I want to implement asynchrony. I tried to do this, using asyncio.create_task, that worked, but only for one request(see at the comments in get_messages()). Now I tried another way, but it gives me this error:

ERROR:asyncio:Task exception was never retrieved
future: <Task finished name='Task-7' coro=<get_messages() done, defined at D:\Python_Labs\FilmMarketBot\fm_bot.py:31> exception=RuntimeError('Timeout context manager should be used inside a task')>
Traceback (most recent call last):
  File "D:\Python_Labs\FilmMarketBot\fm_bot.py", line 37, in get_messages
    await message(bot)
  File "D:\Python_Labs\FilmMarketBot\bot_files\message_handling.py", line 14, in message
    await commands(bot, chat_id)
  File "D:\Python_Labs\FilmMarketBot\bot_files\messages\bot_commands\commands.py", line 11, in commands
    await start(bot, chat_id)
  File "D:\Python_Labs\FilmMarketBot\bot_files\messages\bot_commands\commands_methods.py", line 11, in start
    await bot.send_message(chat_id, f"Hello, {first_name}!")
  File "D:\Python_Labs\FM_Bot_test\venv\lib\site-packages\aiogram\bot\bot.py", line 339, in send_message
    result = await self.request(api.Methods.SEND_MESSAGE, payload)
  File "D:\Python_Labs\FM_Bot_test\venv\lib\site-packages\aiogram\bot\base.py", line 231, in request
    return await api.make_request(await self.get_session(), self.server, self.__token, method, data, files,
  File "D:\Python_Labs\FM_Bot_test\venv\lib\site-packages\aiogram\bot\api.py", line 139, in make_request
    async with session.post(url, data=req, **kwargs) as response:
  File "D:\Python_Labs\FM_Bot_test\venv\lib\site-packages\aiohttp\client.py", line 1138, in __aenter__
    self._resp = await self._coro
  File "D:\Python_Labs\FM_Bot_test\venv\lib\site-packages\aiohttp\client.py", line 466, in _request
    with timer:
  File "D:\Python_Labs\FM_Bot_test\venv\lib\site-packages\aiohttp\helpers.py", line 701, in __enter__
    raise RuntimeError(
RuntimeError: Timeout context manager should be used inside a task

This is my fm_bot.py:

# бібліотека request для отримання запитів(повідомлень), які надходять на Flask-сервер від Телеграм-серверу і Stripe
from flask import Flask, request
import logging
from aiogram import Bot, Dispatcher
from threading import Thread
# from dotenv import load_dotenv
from time import sleep
import asyncio
import time

from bot_files.models import db, Users, Films, Purchases
from bot_files.message_handling import message, callback_query, object, other_messages
# from bot_files.other import technical_works
from bot_files.chat_member import delete_user
from bot_files.stripe import create_stripe_webhook
from bot_files.objects.stripe_requests_files.purchases import add_purchase
from bot_files.config import *

logging.basicConfig(level=logging.INFO)

bot = Bot(token=bot_token)
dp = Dispatcher(bot)

app = Flask(__name__)
app.config['DEBUG'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = db_url
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = True
db.init_app(app)


async def get_messages():
    print("Flag")
    print(request.json)
    message_type = list(request.json.keys())[1]

    if message_type == 'message':
        await message(bot)

    elif message_type == 'callback_query':
        await callback_query(bot, payment_token, stripe_token)

    elif message_type == 'my_chat_member':
        chat_id = request.json["my_chat_member"]["chat"]["id"]
        if request.json["my_chat_member"]["new_chat_member"]["status"] == "kicked":
            await delete_user(chat_id)

    elif message_type == 'object':
        await object(bot)

    elif message_type == 'pre_checkout_query':
        # Номер карти - 4242 4242 4242 4242
        chat_id = request.json["pre_checkout_query"]["from"]["id"]
        await add_purchase(bot, chat_id)
        pre_checkout_query_id = request.json["pre_checkout_query"]["id"]
        await bot.answer_pre_checkout_query(pre_checkout_query_id, True)

    # else:
    # await other_messages(bot)

    return {"get_messages is ok": True}


async def sleep_func():
    await asyncio.sleep(20)
    print("Hello!")


@app.route('/' + bot_token, methods=['POST'])
async def asynchrony():
    #start_time = time.time()
    #get_messages_task = asyncio.create_task(get_messages())
    #sleep_task1 = asyncio.create_task(sleep_func())
    #sleep_task2 = asyncio.create_task(sleep_func())
    #await asyncio.wait([get_messages_task, sleep_task1])
    #print(time.time() - start_time)
    get_messages_task = asyncio.create_task(get_messages())
    await asyncio.wait([get_messages_task])
    #await get_messages()

    return {"asynchrony is ok": True}


@app.route('/')
async def webhook():
    await bot.delete_webhook()
    await bot.set_webhook(url=app_url)
    # chat_id = None
    await create_stripe_webhook(app_url, payment_token)

    # await test_users()
    # await test_films()
    # await test_purchases()
    # await test_discounts()
    # while True:
    # time_now = datetime.datetime.now()
    # if datetime.date.today().isoweekday() == 4:
    # await technical_works(bot)

    sleep_task = asyncio.create_task(sleep_func())
    await asyncio.wait([sleep_task])

    return '!', 200


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=int(os.environ.get("PORT", 5000)))

I will be very appreciative for you help!


Solution

  • My answer is not suitable for this code, which is Flask-app. But it is answer for this question. So you must use Aiogram for asynchrony receiving and processing messages. Aiogram has special decorator async_task for this: https://docs.aiogram.dev/en/latest/_modules/aiogram/dispatcher/dispatcher.html#Dispatcher.async_task. If your bot at the same time must perform another function, for example, change data in database once a day, you can create button only for administrator that will start infinity cycle which will sleep for number of seconds in one day.