I am trying to use aiogram, but as a component of a larger application, most examples do the following call in the main after defining the callbacks>
if __name__ == '__main__':
executor.start_polling(dp, skip_updates=True)
What I am trying to implement is having the polling in a separate task which I can run concurrently with asyncio.gather() as shown below:
import asyncio
async def task1():
while True:
# Perform aiogram polling
await asyncio.sleep()
async def task2():
while True:
# Perform other application's component
await asyncio.sleep()
async def main():
await asyncio.gather(
task1(),
task2(),
)
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
I searched in the code for the executor class, yet it performs an startup sequence and then calls itself the loop.get_event_loop() and loop.run_forever(),
Does aiogram have a built-in method for performing this?
The solution was to use the dispatcher directly rather than the executor utility as shown below:
async def task1():
await dp.start_polling()
async def task2():
while True:
# Perform other application's component
await asyncio.sleep(1)
async def main():
await asyncio.gather(
task1(),
task2(),
)
if __name__ == '__main__':
asyncio.get_event_loop().run_until_complete(main())
Related
I need to be able to keep adding coroutines to the asyncio loop at runtime. I tried using create_task() thinking that this would do what I want, but it still needs to be awaited.
This is the code I had, not sure if there is a simple edit to make it work?
async def get_value_from_api():
global ASYNC_CLIENT
return ASYNC_CLIENT.get(api_address)
async def print_subs():
count = await get_value_from_api()
print(count)
async def save_subs_loop():
while True:
asyncio.create_task(print_subs())
time.sleep(0.1)
async def start():
global ASYNC_CLIENT
async with httpx.AsyncClient() as ASYNC_CLIENT:
await save_subs_loop()
asyncio.run(start())
I once created similar pattern when I was mixing trio and kivy, which was demonstration of running multiple coroutines asynchronously.
It use a trio.MemoryChannel which is roughly equivalent to asyncio.Queue, I'll just refer it as queue here.
Main idea is:
Wrap each task with class, which has run function.
Make class object's own async method to put object itself into queue when execution is done.
Create a global task-spawning loop to wait for the object in queue and schedule execution/create task for the object.
import asyncio
import traceback
import httpx
async def task_1(client: httpx.AsyncClient):
resp = await client.get("http://127.0.0.1:5000/")
print(resp.read())
await asyncio.sleep(0.1) # without this would be IP ban
async def task_2(client: httpx.AsyncClient):
resp = await client.get("http://127.0.0.1:5000/meow/")
print(resp.read())
await asyncio.sleep(0.5)
class CoroutineWrapper:
def __init__(self, queue: asyncio.Queue, coro_func, *param):
self.func = coro_func
self.param = param
self.queue = queue
async def run(self):
try:
await self.func(*self.param)
except Exception:
traceback.print_exc()
return
# put itself back into queue
await self.queue.put(self)
class KeepRunning:
def __init__(self):
# queue for gathering CoroutineWrapper
self.queue = asyncio.Queue()
def add_task(self, coro, *param):
wrapped = CoroutineWrapper(self.queue, coro, *param)
# add tasks to be executed in queue
self.queue.put_nowait(wrapped)
async def task_processor(self):
task: CoroutineWrapper
while task := await self.queue.get():
# wait for new CoroutineWrapper Object then schedule it's async method execution
asyncio.create_task(task.run())
async def main():
keep_running = KeepRunning()
async with httpx.AsyncClient() as client:
keep_running.add_task(task_1, client)
keep_running.add_task(task_2, client)
await keep_running.task_processor()
asyncio.run(main())
Server
import time
from flask import Flask
app = Flask(__name__)
#app.route("/")
def hello():
return str(time.time())
#app.route("/meow/")
def meow():
return "meow"
app.run()
Output:
b'meow'
b'1639920445.965701'
b'1639920446.0767004'
b'1639920446.1887035'
b'1639920446.2986999'
b'1639920446.4067013'
b'meow'
b'1639920446.516704'
b'1639920446.6267014'
...
You can see tasks running repeatedly on their own pace.
Old answer
Seems like you only want to cycle fixed amount of tasks.
In that case just iterate list of coroutine with itertools.cycle
But this is no different with synchronous, so lemme know if you need is asynchronous.
import asyncio
import itertools
import httpx
async def main_task(client: httpx.AsyncClient):
resp = await client.get("http://127.0.0.1:5000/")
print(resp.read())
await asyncio.sleep(0.1) # without this would be IP ban
async def main():
async with httpx.AsyncClient() as client:
for coroutine in itertools.cycle([main_task]):
await coroutine(client)
asyncio.run(main())
Server:
import time
from flask import Flask
app = Flask(__name__)
#app.route("/")
def hello():
return str(time.time())
app.run()
Output:
b'1639918937.7694323'
b'1639918937.8804302'
b'1639918937.9914327'
b'1639918938.1014295'
b'1639918938.2124324'
b'1639918938.3204308'
...
asyncio.create_task() works as you describe it. The problem you are having here is that you create an infinite loop here:
async def save_subs_loop():
while True:
asyncio.create_task(print_subs())
time.sleep(0.1) # do not use time.sleep() in async code EVER
save_subs_loop() keeps creating tasks but control is never yielded back to the event loop, because there is no await in there. Try
async def save_subs_loop():
while True:
asyncio.create_task(print_subs())
await asyncio.sleep(0.1) # yield control back to loop to give tasks a chance to actually run
This problem is so common I'm thinking python should raise a RuntimeError if it detects time.sleep() within a coroutine :-)
You might want to try the TaskThread framework
It allows you to add tasks in runtime
Tasks are re-scheduled periodically (like in your while loop up there)
There is a consumer / producer framework built in (parent/child relationships) which you seem to need
disclaimer: I wrote TaskThread out of necessity & it's been a life saver.
Update:
After few uncessful attempts to explain the problem, completely rewrote the question:
How to execute a function on startup?
from aiogram import Bot, Dispatcher, executor, types
API_TOKEN = 'API'
bot = Bot(token=API_TOKEN)
dp = Dispatcher(bot)
#dp.message_handler()
async def echo(message: types.Message):
await message.answer(message.text)
async def notify_message() # THIS FUNCTION
# await bot.sendMessage(chat.id, 'Bot Started')
await print('Hello World')
if __name__ == '__main__':
notifty_message() # doesn't work
executor.start_polling(dp, skip_updates=True)
Tried without success::
if __name__ == '__main__':
dp.loop.create_task(notify_message()) # Function to execute
executor.start_polling(dp, skip_updates=True)
AttributeError: 'NoneType' object has no attribute 'create_task'
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.create_task(notify_message()) # Function to execute
executor.start_polling(dp, skip_updates=True)
TypeError: object NoneType can't be used in 'await' expression
If you want to iniate a dialogue, it's impossible, telegram doesn't allow bots to send first message. But if you want to save your users ids, you can make a database or just a txt file and write there any new user id. You will be able to send to all of them messages by inputing id from database instead of chat.id
I give you an example of how to send a message without using message.answer just use:
#dp.message_handler()
async def echo(message: types.Message):
await bot.send_message(message.chat.id, message.text)
I don't understand why you need to print ('The bot is running') When running "aiogram" it tells you that the bot was started
2021-06-01 09:31:42,729:INFO:Bot: YourBot [#YourBot]
2021-06-01 09:31:42,729:WARNING:Updates were skipped successfully.
2021-06-01 09:31:42,729:INFO:Start polling.
It was much simpler than expected. facepalm
Working Solution:
from aiogram import Bot, Dispatcher, executor, types
API_TOKEN = 'API'
bot = Bot(token=API_TOKEN)
dp = Dispatcher(bot)
#dp.message_handler()
async def echo(message: types.Message):
await bot.send_message(message.chat.id, message.text)
def test_hi():
print("Hello World")
if __name__ == '__main__':
test_hi()
executor.start_polling(dp, skip_updates=True)
The second approach was correct, but asyncio event loop was not initialised. So, if it was important to call async function beside a bot, you would do:
from asyncio import get_event_loop
from aiogram import Bot, Dispatcher, executor, types
API_TOKEN = 'API'
bot = Bot(token=API_TOKEN)
dp = Dispatcher(bot=bot, loop=get_event_loop()) # Initialising event loop for the dispatcher
async def notify_message():
print('Hello World')
if __name__ == '__main__':
dp.loop.create_task(notify_message()) # Providing awaitable as an argument
executor.start_polling(dp, skip_updates=True)
you can call aiogram.executor.start(dispatcher, future) to run an async function (future)
from aiogram import Bot, Dispatcher, executor
bot = Bot(token='your_api_token')
dp = Dispatcher(bot)
async def notify_message() # THIS FUNCTION
# await bot.sendMessage(chat.id, 'Bot Started')
await print('Hello World')
#dp.message_handler()
async def echo(message: types.Message):
await message.answer(message.text)
if __name__ == '__main__':
executor.start(dp, notify_message())
executor.start_polling(dp, skip_updates=True)
Heyy,
how would I make my asynchronous code run on startup?
So this is the easiest example I could think of to make.
async def mainMenuCLI():
print("This is the CLI")
And I want the asynchronous mainMenuCLI to run on startup but I don't want it to take as many tasks because I'm not sure what is the max :(
I need it to be async because of async_input, discord.py and stuff, just don't tell me to make it sync, thanks!
If I understand your question... Do you need something likethis?
import asyncio
async def async_example():
print("hellooooo, I'm an async function")
async def main():
asyncio.Task(async_example())
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
You can add some tasks outside the async func if you need:
import asyncio
async def async_example():
print("hellooooo, I'm an async function")
await asyncio.sleep(1)
print("async function done")
async def main():
asyncio.Task(async_example())
print('This is before wait')
await asyncio.sleep(1)
print('This is after await')
await asyncio.sleep(1)
print('Ok, goodbye')
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
*Note: if your py version is prior to 3.7, change asyncio.Task per asyncio.ensure_future.
I use async create_task to run my task in background, but my_task() method not be executed.
async def my_task():
print("starting my task...")
time.sleep(2)
print("finished my task.")
if __name__ == '__main__':
print("1111")
loop = asyncio.get_event_loop()
loop.create_task(my_task())
print("2222")
the result is
1111
2222
There is a simple fix for this, but you still need to use await which you cannot avoid because create tasks returns a coroutine
import asyncio
async def my_task():
print("starting my task...")
await asyncio.sleep(2)
print("finished my task.")
if __name__ == '__main__':
print("1111")
loop = asyncio.get_event_loop()
loop.run_until_complete(my_task())
# or use can use asyncio.run(my_task())
print("2222")
EDIT: Made changes for pyfile, instead of notebook, thanks user4815162342 for pointing out
I want to make a timer which is started in a normal function, but in the timer function, it should be able to call an async function
I want to do something like this:
startTimer()
while True:
print("e")
def startTimer(waitForSeconds: int):
# Wait for `waitForSeconds`
await myAsyncFunc()
async def myAsyncFunc():
print("in my async func")
Where the while True loop should do its stuff and after waitForSeconds the timer the async function should execute an other async function, but waiting shouldn't block any other actions and doesn't need to be awaited
If something isn't understandable, I'm sorry, I'll try to explain it then
Thanks
If you want to run your synchronous and asynchronous code in parallel, you will need to run one of them in a separate thread. For example:
def sync_code():
while True:
print("e")
async def start_timer(secs):
await asyncio.sleep(secs)
await async_func()
async def main():
asyncio.create_task(start_timer(1))
loop = asyncio.get_event_loop()
# use run_in_executor to run sync code in a separate thread
# while this thread runs the event loop
await loop.run_in_executor(None, sync_code)
asyncio.run(main())
If the above is not acceptable for you (e.g. because it turns the whole program into an asyncio program), you can also run the event loop in a background thread, and submit tasks to it using asyncio.run_coroutine_threadsafe. That approach would allow startTimer to have the signature (and interface) like you wanted it:
def startTimer(waitForSeconds):
loop = asyncio.new_event_loop()
threading.Thread(daemon=True, target=loop.run_forever).start()
async def sleep_and_run():
await asyncio.sleep(waitForSeconds)
await myAsyncFunc()
asyncio.run_coroutine_threadsafe(sleep_and_run(), loop)
async def myAsyncFunc():
print("in my async func")
startTimer(1)
while True:
print("e")
I'm pretty sure that you are familiar with concurent processing, but you didn't show exactly what you want. So if I understand you correctly you want to have 2 processes. First is doing only while True, and the second process is the timer(waits e.g. 5s) and it will call async task. I assume that you are using asyncio according to tags:
import asyncio
async def myAsyncFunc():
print("in my async func")
async def call_after(delay):
await asyncio.sleep(delay)
await myAsyncFunc()
async def while_true():
while True:
await asyncio.sleep(1) # sleep here to avoid to large output
print("e")
async def main():
task1 = asyncio.create_task(
while_true())
task2 = asyncio.create_task(
call_after(5))
# Wait until both tasks are completed (should take
# around 2 seconds.)
await task1
await task2
asyncio.run(main())