Use timer decorators to trigger telethon methods - python

I want to make a decorator for a project of mine that every x seconds certain telethon task will be started.
I've asked in the telethon group and someone give me a small decorator but the problem here is that I need to start the loop using run_until_complete and I already use it when starting the client. Here is my code:
def periodic(period):
def scheduler(fcn):
async def wrapper():
while True:
asyncio.ensure_future(fcn())
await asyncio.sleep(period)
return wrapper
return scheduler
#periodic(2)
async def do_something():
await asyncio.sleep(5) # Do some heavy calculation
me = await client.get_me()
print(me.stingfy())
Now I already have a loop running in main:
if __name__ == "__main__":
async def start():
await client.start()
await client.get_me()
await client.run_until_disconnected()
loop = asyncio.get_event_loop()
loop.run_until_complete(start())
and I can't run another loop because if I do that it seems to close this one. Any ideas about how to make this work?

It looks like you desire to run multiple functions at same time. So you can use asyncio.Task or asyncio.create_task to create tasks and add them into list, then run them through using asyncio.wait or etc.
import asyncio
def periodic(period):
def scheduler(fcn):
async def wrapper():
while 1:
asyncio.ensure_future(fcn())
await asyncio.sleep(period)
return wrapper
return scheduler
#periodic(2)
async def do_something():
print("Im running")
async def client():
while 1:
await asyncio.sleep(1)
print("This is client")
if __name__ == "__main__":
async def start():
task = [asyncio.Task(client()),
asyncio.Task(do_something())]
done, pending = await asyncio.wait(task)
loop = asyncio.get_event_loop()
loop.run_until_complete(start())

Related

Error when trying to start an asynchronous task

In my start function I run 3 functions + print. I want my create_task to form 2 functions and execute them synchronously, which is what is happening now. At the same time I am trying to run asynchronous function say_hi() and I want it to be executed immediately at start time, without waiting for other functions to finish and without waiting for say_hi function itself to be executed. How can I run it? If I remove await, I get an error:
RuntimeWarning: coroutine 'say_hi' was never awaited say_hi()
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
code:
import asyncio
from datetime import datetime
async def say_hi():
task = asyncio.sleep(1)
print("HI")
await task
async def start():
asyncio.create_task(write_file())
await say_hi()
asyncio.create_task(take_time(time=datetime.now()))
print("hi")
async def main():
tasks = [
start(),
]
await asyncio.gather(*tasks)
asyncio.run(main())
If I understand you correctly, you can use another asyncio.gather to execute write_file(), take_time() and say_hi() tasks concurrently:
import asyncio
from datetime import datetime
async def write_file():
await asyncio.sleep(2)
print("write file")
async def take_time(time):
await asyncio.sleep(3)
print("take time")
async def say_hi():
await asyncio.sleep(1)
print("HI")
async def start():
t1 = asyncio.create_task(write_file())
t2 = asyncio.create_task(say_hi())
t3 = asyncio.create_task(take_time(time=datetime.now()))
await asyncio.gather(t1, t2, t3)
print("hi")
async def main():
tasks = [
start(),
]
await asyncio.gather(*tasks)
asyncio.run(main())
Prints:
HI
write file
take time
hi

Running asynchronous functions in non-asynchronous functions

I'm trying to run some asynchronous functions in her asynchronous function, the problem is, how did I understand that functions don't run like that, then how do I do it? I don't want to make the maze_move function asynchronous.
async def no_stop():
#some logic
await asyncio.sleep(4)
async def stop(stop_time):
await asyncio.sleep(stop_time)
#some logic
def maze_move():
no_stop()
stop(1.5)
async def main(websocket):
global data_from_client, data_from_server, power_l, power_r
get_params()
get_data_from_server()
get_data_from_client()
while True:
msg = await websocket.recv()
allow_data(msg)
cheker(data_from_client)
data_from_server['IsBrake'] = data_from_client['IsBrake']
data_from_server['powerL'] = power_l
data_from_server['powerR'] = power_r
await websocket.send(json.dumps(data_from_server))
print(data_from_client['IsBrake'])
start_server = websockets.serve(main, 'localhost', 8080)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
How about:
def maze_move():
loop = asyncio.get_event_loop()
loop.run_until_complete(no_stop())
loop.run_until_complete(stop(1.5))
If you wanted to run two coroutines concurrently, then:
def maze_move():
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.gather(no_stop(), stop(1.5)))
Update Based on Updated Question
I am guessing what it is you want to do (see my comment to your question):
First, you cannot call from maze_move coroutines such as stop directly since stop() does not result in calling stop it just returns a coroutine object. So maze_move has to be modified. I will assume you do not want to make it a coroutine itself (why not as long as you already have to modify it?). And further assuming you want to invoke maze_move from a coroutine that wishes to run concurrently other coroutines, then you can create a new coroutine, e.g. maze_move_runner that will run maze_move in a separate thread so that it does not block other concurrently running coroutines:
import asyncio
import concurrent.futures
async def no_stop():
#some logic
print('no stop')
await asyncio.sleep(4)
async def stop(stop_time):
await asyncio.sleep(stop_time)
print('stop')
#some logic
async def some_coroutine():
print('Enter some_coroutine')
await asyncio.sleep(2)
print('Exit some_coroutine')
return 1
def maze_move():
# In case we are being run directly and not in a separate thread:
try:
loop = asyncio.get_running_loop()
except:
# This thread has no current event loop, so:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(no_stop())
loop.run_until_complete(stop(1.5))
return 'Done!'
async def maze_move_runner():
loop = asyncio.get_running_loop()
# Run in another thread:
return await loop.run_in_executor(None, maze_move)
async def main():
loop = asyncio.get_running_loop()
results = await (asyncio.gather(some_coroutine(), maze_move_runner()))
print(results)
asyncio.run(main())
Prints:
Enter some_coroutine
no stop
Exit some_coroutine
stop
[1, 'Done!']
But this would be the most straightforward solution:
async def maze_move():
await no_stop()
await stop(1.5)
return 'Done!'
async def main():
loop = asyncio.get_running_loop()
results = await (asyncio.gather(some_coroutine(), maze_move()))
print(results)
If you have an already running event loop, you can define an async function inside of a sync function and launch it as task:
def maze_move():
async def amaze_move():
await no_stop()
await stop(1.5)
return asyncio.create_task(amaze_move())
This function returns an asyncio.Task object which can be used in an await expression, or not, depending on requirements. This way you won't have to make maze_move itself an async function, although I don't know why that would be a goal. Only a async function can run no_stop and stop, so you've got to have an async function somewhere.

asyncio print something while waiting for user input

I have a simple script, I want the user to be able to input something whenever he wants, but I want to also print something out in the meantine, this is my code:
import asyncio
async def user_input():
while True:
content = input('> ')
async def print_something():
await asyncio.sleep(10)
print('something')
async def main():
tasks = [user_input(), print_something()]
await asyncio.gather(*tasks)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()
It lets me input, but it doesn't print something, how can I achieve that?
input is a blocking function and cannot be used with inside coroutines straightforwardly. But you could start it in a separate thread by means of run_in_executor:
import asyncio
async def user_input():
while True:
loop = asyncio.get_event_loop()
content = await loop.run_in_executor(None, input, "> ")
print(content)
async def print_something():
await asyncio.sleep(5)
print('something')
async def main():
tasks = [user_input(), print_something()]
await asyncio.gather(*tasks)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()
Update: Also you can use aioconsole lib, which provides asyncio-based console functions:
from aioconsole import ainput
async def user_input():
while True:
content = await ainput(">")
print(content)
The short answer is async cannot do what you are wanting. While you did declare your functions as async the python function input is not async, it is a blocking function. So it will block the event loop and nothing else will run.
I answered a question awhile back that kinda explains how async works in python. I'll link it. But in order to do what you want you need to use threads not async.
https://stackoverflow.com/a/63237798/9270488
If you want to use threads with async then look into ThreadExecutor

Paralelize multiple calls of asynchronous function

I have the following scenario:
from time import sleep
async def do_a(a):
sleep(0.001)
return 1*a
async def do_b(a):
sleep(0.01)
return 2*a
async def do_c(b):
sleep(1)
return 3*b
async def my_func():
results = []
for i in range(3):
a = await do_a(i)
b = await do_b(a)
c = await do_c(b)
results.append(c)
return results
if __name__ == "__main__":
import asyncio
print(asyncio.run(my_func()))
Basically, I am calling asynchronous functions in a loop. Executing the above code shows it run in ~3s. I would like to call each procedure in parallel, so the expected time would drop to ~1s (I know this overhead is a little bit too optimistic, but to optimize the running time at least a bit). I have been looking into different python libraries that I think could help, but having trouble deciding which one is useful in this case. Python's multiprocessing, threading and concurrent.futures all seem to implement one form or another of parallelism/concurrency.
What should I do? Can you show me how you would proceed in this case?
You should use asyncio.sleep instead of time.sleep. If you want everything to run concurrently, this is one way you can do it with asyncio.gather:
import asyncio
async def do_a(a):
await asyncio.sleep(0.001)
return 1*a
async def do_b(a):
await asyncio.sleep(0.01)
return 2*a
async def do_c(b):
await asyncio.sleep(1)
return 3*b
async def do_abc(i):
a = await do_a(i)
b = await do_b(a)
return await do_c(b)
async def my_func():
return await asyncio.gather(*map(do_abc, range(3)))
if __name__ == "__main__":
import asyncio
print(asyncio.run(my_func()))
# [0, 6, 12]
If the actual code that runs instead of sleep is synchronous (blocking), you would do essentially the same, only you would have to defer that work to an executor.

How to queue requests until the function returns a response for a previous request?

When the user sends !bot in a special channel, the code runs a function that runs for 20-30 seconds (depends on some_var). My problem is that if several people write !bot , the code will reverse this in multiple threads. How do I queue for these requests?
I tried to understand asyncio, discord.ext.tasks, but can't figure out how it works
#client.command()
async def test(ctx, *args):
data = args
if data:
answer = some_function(data[0]) #works from 5 seconds to 1 minute or more
await ctx.send(answer)
Everything works well, but I just don't want to load the system so much, I need a loop to process requests first in - first out
You can use an asyncio.Queue to queue tasks, then process them sequentially in a background loop:
import asyncio
from discord.ext import commands, tasks
queue = asyncio.Queue()
bot = commands.Bot('!')
#tasks.loop(seconds=1.0)
async def executor():
task = await queue.get()
await task
queue.task_done()
#executor.before_loop
async def before():
await bot.wait_until_ready()
#bot.command()
async def example(ctx, num: int):
await queue.put(helper(ctx, num))
async def helper(ctx, num):
await asyncio.sleep(num)
await ctx.send(num)
executor.start()
bot.run('token')
Make some_function() to async then await it. Then all test command will be processed simultaneously.
async some_function(...):
# something to do
#client.command()
async def test(ctx, *args):
if args:
answer = await some_function(data[0])
await ctx.send(answer)

Categories

Resources