Usage of threading.Timer with async based functions - python

I'm working on a Discord bot with Python and it queues music from YouTube, i'm working on something to autoqueue songs when the player is stopped, while all of the code works perfectly, the only problem is me not being able to check if the player is playing or not every 15 seconds
async def cmd_autoqueue(self,message, player,channel,author, permissions, leftover_args):
print("autoq ran")
if started == True:
if player.is_stopped:
await self.cmd_autoqadd(player, channel, author, permissions,leftover_args,song_url=last_url)
threading.Timer(15.0,await self.cmd_autoqueue(message, player,channel,author, permissions, leftover_args)).start()
i did realise that
threading.Timer(15.0,await self.cmd_autoqueue(message, player,channel,author, permissions, leftover_args)).start()
calls the function, and if i wanted to pass it as something that would be called later i would use lambda: but , async lambda?
also started boolean is managed by other stuff so well its there for the sake of the 'if', here in this question
Solution:
async def cmd_autoqueue(self,message, player,channel,author, permissions, leftover_args):
global started
print("loop")
if started == True:
await asyncio.sleep(15)
if player.is_stopped:
await self.cmd_autoqadd(player, channel, author, permissions,leftover_args,song_url=last_url)
await self.cmd_autoqueue(message, player,channel,author, permissions, leftover_args)

You could use loop.call_later, that returns you an asyncio-compatible handler to cancel the task.
class YourClass:
def init(self):
self._loop = asyncio.get_event_loop()
self._check_handler = None
def schedule_check(self):
self._check_handler = loop.call_later(
15 * 60, # Every 15 minutes
self._loop.create_task, self._periodic_check())
def stop_checking(self):
self._check_handler.cancel()
async def start_player(self):
await do something()
if not check_handler:
schedule_check()
async def stop_player(self):
await do something()
self.stop_checking()
async def _periodic_check(self):
await do_something()
self._schedule_check()
To pass a function (that needs some parameters) as a parameter to another function, that doesn't allow you to pass the parameters, you can bind the parameters using functools.partial.

Related

Await for user input/message with python-telegram-bot and asyncio

I want to create a library of components to work with telegram bot that I could execute in order. Each component would await it's input and allow execution to continue to the next line, like so:
fname = await Question().run('Enter first name')
lname = await Question().run('Enter last name')
While allowing other coroutines to run elsewhere in the code.
I implemented Question like so:
class Question:
def __init__(self):
self.result = None
async def on_message(self, update, context):
self.result = update.message.text
async def run(self, prompt):
# register on_message as handler
# prompt user
while self.result is None:
await asyncio.sleep(1)
# unregister on_message handler
return self.result
This loop would block the on_message functionality and user input is never received
I tried using series of awaited recursive calls instead of the loop. Same result.
I tried using threading and putting the loop into separate Thread. Same result.
I ended up doing this with a chain of callbacks, but this I could have done without any asynchronous stuff, and it is plain ugly.
Please tell me there is a way to achieve what I want.

How to cancel an asynchronous transition with PyTransition

I have a queued PyTransition state machine, with some state doing work in on_enter. However, I want the user to be able to stop the machine at any time without waiting. For that, I need a way to cancel transitions.
Here is what I've found for now. However, accessing the _transition_queue_dict seems like a hack. Is there a proper way?
#!/usr/bin/env python3
import asyncio
import logging
from transitions.extensions.asyncio import AsyncMachine
logging.getLogger('transitions').setLevel(logging.DEBUG)
class Model:
async def do_long_work(self):
print("Working...")
await asyncio.sleep(10)
print("Worked!")
async def print_stop(self):
print("Stopped!")
async def interrupt(self):
global machine
await asyncio.sleep(1)
for task in machine.async_tasks[id(self)]:
task.cancel()
machine._transition_queue_dict[id(model)].clear()
await self.stop()
model = Model()
machine = AsyncMachine(model=model, queued=True)
machine.add_states('running', on_enter=[model.do_long_work])
machine.add_states('stopped', on_enter=[model.print_stop])
machine.add_transition('run', 'initial', 'running')
machine.add_transition('stop', 'running', 'stopped')
async def run():
await asyncio.gather(machine.dispatch('run'), model.interrupt())
asyncio.run(run())
I use the last commit on master (3836dc4).
The problem here is that you pass queued=True which instruct a machine to put new events into model queues and process events sequentially. Since you want to be able to interrupt events, omitting queued=True or setting queued=False (default value) will cancel events when you transition away/exit a state. No need to tinker with internal queues in this case.
import asyncio
import logging
from transitions.extensions.asyncio import AsyncMachine
logging.getLogger('transitions').setLevel(logging.DEBUG)
class Model:
async def do_long_work(self):
print("Working...")
await asyncio.sleep(10)
print("Worked!")
async def print_stop(self):
print("Stopped!")
async def interrupt(self):
await asyncio.sleep(1)
await self.stop()
model = Model()
machine = AsyncMachine(model=model, queued=False)
machine.add_states('running', on_enter=[model.do_long_work])
machine.add_states('stopped', on_enter=[model.print_stop])
machine.add_transition('run', 'initial', 'running')
machine.add_transition('stop', 'running', 'stopped')
async def run():
await asyncio.gather(machine.dispatch('run'), model.interrupt())
asyncio.run(run())
# Working...
# Stopped!

Multitasks in discord py bot

I'm trying to multitask with discord py, but I have a problem
Code:
#tasks.loop(seconds=10)
async def taskLoop(ctx, something):
await ctx.send(something)
#client.command()
async def startX(ctx, something):
taskLoop.start(ctx, something)
#client.command()
async def endX(ctx):
taskLoop.cancel()
taskLoop.stop()
In discord I start the command like: -startX zzzzzzzzzz
So it works, every 10 seconds the bot sends "zzzzzzzzzz"
When I try to create a new task (while the previous one is still running), for example: -startX yyyyyyyy
I get the error:
Command raised an exception: RuntimeError: Task is already launched and is not completed.
Obviously I understand it's because the other task is still working, but I looked in the documentation and couldn't find a way to create multiple tasks.
Are there any solutions for this? Thread maybe?
You can't really start the same task more than once. You can create a "task generator" which will generate and start the tasks
started_tasks = []
async def task_loop(ctx, something): # the function that will "loop"
await ctx.send(something)
def task_generator(ctx, something):
t = tasks.loop(seconds=10)(task_loop)
started_tasks.append(t)
t.start(ctx, something)
#bot.command()
async def start(ctx, something):
task_generator(ctx, something)
#bot.command()
async def stop(ctx):
for t in started_tasks:
t.cancel()

How do I use aioschedule in cog class in discord.py

I have a cog which is ment to handel events and loops. I am using aioschedule to run ping every minute as a test if the aioschedule works. But now the problem is that when I put it inside the class it asks for self but when i give self it gives an error.pls help with this
import discord
import os
import asyncio
import time
from discord.ext import commands,tasks
import aioschedule as schedule
class Events(commands.Cog):
def __init__(self, client):
self.client = client
async def bot_test_clear(self):
channel_bot_test = self.client.get_channel(os.getenv('bot-test-text'))
messages = await channel_bot_test.history(limit=100).flatten()
if not messages:
return
embed = discord.Embed(description='It has been 1 hour, clearing chats...', color=0xff0000)
await channel_bot_test.send(embed=embed)
await asyncio.sleep(10)
await channel_bot_test.purge(limit=None)
async def ping(self):
self.client.send("pong")
schedule.every(5).seconds.do(ping)
#commands.Cog.listener()
async def on_ready(self):
self.bot_text_clear.start()
print(f'{self.client.user} is now online')
def setup(client):
client.add_cog(Events(client))
loop = asyncio.get_event_loop()
while True:
loop.run_until_complete(schedule.run_pending())
time.sleep(0.1)
img of the error
So I answered your earlier question and suggested you use aioschedule so I will show you how I use it.
def _run_scheduler(self):
""" Create the scheduler task. """
async def _run_scheduler():
self._scheduler_running = True
print("Created 'Scheduler' task")
while self._scheduler_running:
await aioschedule.run_pending()
await asyncio.sleep(0.25)
if not self._scheduler_running:
asyncio.create_task(_run_scheduler())
The above method is attached to my bot, and is called once when the bot is created. The above code will process all the jobs you schedule. I use create_task so I can call the method from a non-async method.
#commands.Cog.listener()
async def on_startup(self):
aioschedule.every().monday.at("21:15").do(self.announce_winners)
aioschedule.every().day.at("21:30").do(self.start_quiz)
I add jobs, which are then processed in that event loop in the earlier code block. I use on_startup (a custom event) which is called after on_ready since on_ready can be called multiple times if the bot disconnects (stop duplicate jobs)
Your error (which should be text, not an image) is due to schedule.every(5).seconds.do(ping) being called and it is expecting self which you do not give it.

How to multiprocess async/await functions in python?

I need to run an event every 60 seconds to all my cars. My problem with the code below is the while loop doesn't end until the timeout (60) does and hence only the first car in cars is run.
class RunCars(BaseEvent):
def __init__(self):
interval_seconds = 60 # Set the interval for this event
super().__init__(interval_seconds)
# run() method will be called once every {interval_seconds} minutes
async def run(self, client, cars):
for car in cars:
channel = get_channel(client, "general")
await client.send_message(channel, 'Running this '+str(car))
await msg.add_reaction(str(get_emoji(':smiley:')))
reaction = None
while True:
if str(reaction) == str(get_emoji(':smiley:'))
await client.send_message(channel, 'Finished with this '+str(car))
try:
reaction, user = await client.wait_for('reaction_add', timeout=60, check=check)
except:
break
I tried changing the code into a multithreaded process but had trouble with async/await inside the function and problems with pickling the function itself.
I'd appreciate any suggestions for how to go about this..
The asyncio module lets you execute multiple async method concurrently using the gather method. I think you can achieve the behavior you want by defining a method that runs a single car, and then replacing your for-loop with a call to gather, which will execute multiple run_one coroutines (methods) concurrently:
import asyncio
class RunCars(BaseEvent):
def __init__(self):
interval_seconds = 60 # Set the interval for this event
super().__init__(interval_seconds)
async def run(self, client, cars):
coroutines = [self.run_one(client, car) for car in cars]
asyncio.gather(*coroutines)
async def run_one(self, client, car):
channel = get_channel(client, "general")
await client.send_message(channel, 'Running this '+str(car))
await msg.add_reaction(str(get_emoji(':smiley:')))
reaction = None
while True:
if str(reaction) == str(get_emoji(':smiley:'))
await client.send_message(channel, 'Finished with this '+str(car))
try:
reaction, user = await client.wait_for('reaction_add', timeout=60, check=check)
except:
break
In general, when writing async code, you should try to replace sequential calls to async methods - basically for-loops that call async methods - with gather statements so they execute concurrently.

Categories

Resources