Manage asyncio task - python-asyncio

I need a some asyncio's task manager.
Wrapper for create task:
def create_task(self,
coro: Coroutine[Any, Any, Any],
timeout: Optional[int] = None) -> str:
task_uuid = uuid()
task = asyncio.create_task(
asyncio.wait_for(coro, timeout)
)
return task.uuid
But I have to know when task is done and write info about it to Redis.
I see two variants:
Use add_done_callback but in that case I should pass not async function. But redis client is async. So:
def task_done(self, task: asyncio.Task) ->
async def write_to_redis(params: dict) -> None:
redis.write(...)
asyncio.create_task(write_to_redis)
Use wrapper for coro:
def create_task(self,
coro: Coroutine[Any, Any, Any],
timeout: Optional[int] = None) -> LTaskUuid:
async def wrapper():
try:
await coro()
finally:
await redis.write(...)
asyncio.create_task(
asyncio.wait_for(wrapper(), timeout)
)
return task.uuid
Which approach is better? Or maybe there is another solution?

Related

Discord.py | How do I detect if an argument is empty? (with a if statement)

My bot is supposed to send a error message if no arguments are passed.
#bot.command(pass_context = True , aliases=['sl'])
async def slow (ctx, arg):
if arg > '21600':
await ctx.send("You are restricted to ``21600 seconds``")
else:
if arg == None:
await ctx.send(f"Error 00: Please specify slowmode time. e.g: !slow 2")
else:
await ctx.channel.edit(slowmode_delay=arg)
await ctx.send(f"Slowmode set to ``{arg}`` seconds.")
It isn't responding with the error message if no arguments are passed.
2 things; 1, check if the arg is None first. 2, if arg is None, it will show up in the console as an error instead, so make the arg optional
#client.command(pass_context = True , aliases=['sl'])
async def slow (ctx, duration: int=None): #=None makes it optional or you can use Optional[int] and import Optional from typing
if duration == None:
await ctx.send(f"Error 00: Please specify slowmode time. e.g: !slow 2")
if duration > 21600:
await ctx.send("You are restricted to ``21600 seconds``")
else:
await ctx.channel.edit(slowmode_delay=duration)
await ctx.send(f"Slowmode set to ``{duration}`` seconds.")
btw, I changed arg=None to duration: int=None because it's easier to read
You can initialise the argument arg in the function definition, like this:
async def slow(ctx, arg=None):
if arg is None:
await ctx.send("Error! ...")
return
arg = int(arg) # converts arg to an int
# do something...
Also, I don't recommend using string comparisons (such as arg > '21600'). Instead, first convert arg to an int, then compare it with another int object.

python coroutine asyncio/ await / aiohttp

new in asyncio world.
going straight to the point...
I want to do/make a request(aiohttp) to a site.
if the wait for an answer pass than N seconds I want to stop the process of waiting.
Do the process again by setting a limit of attempts if needed.
async def search_skiping_add(name_search):
start_time = time()
async with aiohttp.ClientSession() as session:
url = f'https://somesitehere.com'
r = await session.get(url)
final_time = time()
result_time =round(final_time-start_time)
print(result_time)
Maybe, I know, have some way to do it synchronously, but it's an excuse to start using asyncio somehow too.
This should give you an idea of how to use async with aiohttp:
from aiohttp import ClientSession
from asyncio import gather, create_task, sleep, run
from traceback import format_exc
def returnPartionedList(inputlist: list, x: int = 100) -> list: # returns inputlist split into x parts, default is 100
return([inputlist[i:i + x] for i in range(0, len(inputlist), x)])
# You could change validate to an integer and thereby increasing the retry count as needed.
async def GetRessource(url: str, session: ClientSession, validate: bool = False) -> dict:
try:
async with session.get(url) as response:
if response.status == 200:
r: dict = await response.json() # Set equal to .text() to get results as a string
return(r)
else:
r: str = await response.text()
if not validate:
await sleep(3) # Sleep for x amount of seconds before retry
return(await GetRessource(url, session, True))
print(f"Error, got response code: {response.status} message: {r}")
except Exception:
print(f"General Exception:\n{format_exc()}")
return({})
async def GetUrls(urls: list) -> list:
resultsList: list = []
UrlPartitions: list = returnPartionedList(urls, 20) # Rate limit to 20 requests per loop
async with ClientSession(timeout=15) as session: # Timeout is equal to the time to wait in seconds before terminating, default is 300 seconds or 5 minutes.
for partition in UrlPartitions:
partitionTasks: list = [create_task(GetRessource(url, session)) for url in partition]
resultsList.append(await gather(*partitionTasks, return_exceptions=False))
return(resultsList) # Or you can do more processing here before returning
async def main():
urls: list = ["...", "...", "..."] # list of urls to get from
results: list = await GetUrls(urls)
print(results)
if __name__ == "__main__":
run(main())

How would i play a song using only the title of the song no url and how would i fix the queue

list(song_queue) = []
FFMPEG_OPTIONS = {'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5', 'options': '-vn'}
YDL_OPTIONS = {'format': 'bestaudio', 'noplaylist':'True'}
def search(arg):
try: requests.get("".join(arg))
except: arg = " ".join(arg)
else: arg = "".join(arg)
with youtube_dl.YoutubeDL(YDL_OPTIONS ) as ydl:
info = ydl.extract_info(f"ytsearch:{arg}", download=False)['entries'][0]
return {'source': info['formats'][0]['url'], 'title': info['title']}
def play_next(ctx):
voice = get(client.voice_clients, guild=ctx.guild)
if len(song_queue) > 1:
del song_queue[0]
voice.play(discord.FFmpegPCMAudio(song_queue[0][source], **FFMPEG_OPTIONS), after=lambda e: play_next(ctx))
voice.is_playing()
#client.command()
async def play(ctx, *arg):
channel = ctx.message.author.voice.channel
if channel:
voice = get(client.voice_clients, guild=ctx.guild)
song = search(arg)
song_queue.append(song)
if voice and voice.is_connected():
await voice.move_to(channel)
else:
voice = await channel.connect()
if not voice.is_playing():
voice.play(discord.FFmpegPCMAudio(song[0]['source'], **FFMPEG_OPTIONS), after=lambda e: play_next(ctx))
voice.is_playing()
else:
await ctx.send("Added to queue")
else:
await ctx.send("You're not connected to any channel!")
this code doesn't play the music and the song queue and idk how to fix it to help me please
I need it so that it can play music and preferably not have the queue ANd i need it so that it can
I have code for my music bot that will play the video using the title of the song. It also has a working queue feature. Try copy-pasting this code into your cog, and it should work for you without any issues.
import discord
from discord.ext import commands
import random
import asyncio
import itertools
import sys
import traceback
from async_timeout import timeout
from functools import partial
import youtube_dl
from youtube_dl import YoutubeDL
# Suppress noise about console usage from errors
youtube_dl.utils.bug_reports_message = lambda: ''
ytdlopts = {
'format': 'bestaudio/best',
'outtmpl': 'downloads/%(extractor)s-%(id)s-%(title)s.%(ext)s',
'restrictfilenames': True,
'noplaylist': True,
'nocheckcertificate': True,
'ignoreerrors': False,
'logtostderr': False,
'quiet': True,
'no_warnings': True,
'default_search': 'auto',
'source_address': '0.0.0.0' # ipv6 addresses cause issues sometimes
}
ffmpegopts = {
'before_options': '-nostdin',
'options': '-vn'
}
ytdl = YoutubeDL(ytdlopts)
class VoiceConnectionError(commands.CommandError):
"""Custom Exception class for connection errors."""
class InvalidVoiceChannel(VoiceConnectionError):
"""Exception for cases of invalid Voice Channels."""
class YTDLSource(discord.PCMVolumeTransformer):
def __init__(self, source, *, data, requester):
super().__init__(source)
self.requester = requester
self.title = data.get('title')
self.web_url = data.get('webpage_url')
self.duration = data.get('duration')
# YTDL info dicts (data) have other useful information you might want
# https://github.com/rg3/youtube-dl/blob/master/README.md
def __getitem__(self, item: str):
"""Allows us to access attributes similar to a dict.
This is only useful when you are NOT downloading.
"""
return self.__getattribute__(item)
#classmethod
async def create_source(cls, ctx, search: str, *, loop, download=False):
loop = loop or asyncio.get_event_loop()
to_run = partial(ytdl.extract_info, url=search, download=download)
data = await loop.run_in_executor(None, to_run)
if 'entries' in data:
# take first item from a playlist
data = data['entries'][0]
embed = discord.Embed(title="", description=f"Queued [{data['title']}]({data['webpage_url']}) [{ctx.author.mention}]", color=discord.Color.green())
await ctx.send(embed=embed)
if download:
source = ytdl.prepare_filename(data)
else:
return {'webpage_url': data['webpage_url'], 'requester': ctx.author, 'title': data['title']}
return cls(discord.FFmpegPCMAudio(source), data=data, requester=ctx.author)
#classmethod
async def regather_stream(cls, data, *, loop):
"""Used for preparing a stream, instead of downloading.
Since Youtube Streaming links expire."""
loop = loop or asyncio.get_event_loop()
requester = data['requester']
to_run = partial(ytdl.extract_info, url=data['webpage_url'], download=False)
data = await loop.run_in_executor(None, to_run)
return cls(discord.FFmpegPCMAudio(data['url']), data=data, requester=requester)
class MusicPlayer:
"""A class which is assigned to each guild using the bot for Music.
This class implements a queue and loop, which allows for different guilds to listen to different playlists
simultaneously.
When the bot disconnects from the Voice it's instance will be destroyed.
"""
__slots__ = ('bot', '_guild', '_channel', '_cog', 'queue', 'next', 'current', 'np', 'volume')
def __init__(self, ctx):
self.bot = ctx.bot
self._guild = ctx.guild
self._channel = ctx.channel
self._cog = ctx.cog
self.queue = asyncio.Queue()
self.next = asyncio.Event()
self.np = None # Now playing message
self.volume = .5
self.current = None
ctx.bot.loop.create_task(self.player_loop())
async def player_loop(self):
"""Our main player loop."""
await self.bot.wait_until_ready()
while not self.bot.is_closed():
self.next.clear()
try:
# Wait for the next song. If we timeout cancel the player and disconnect...
async with timeout(300): # 5 minutes...
source = await self.queue.get()
except asyncio.TimeoutError:
return self.destroy(self._guild)
if not isinstance(source, YTDLSource):
# Source was probably a stream (not downloaded)
# So we should regather to prevent stream expiration
try:
source = await YTDLSource.regather_stream(source, loop=self.bot.loop)
except Exception as e:
await self._channel.send(f'There was an error processing your song.\n'
f'```css\n[{e}]\n```')
continue
source.volume = self.volume
self.current = source
self._guild.voice_client.play(source, after=lambda _: self.bot.loop.call_soon_threadsafe(self.next.set))
embed = discord.Embed(title="Now playing", description=f"[{source.title}]({source.web_url}) [{source.requester.mention}]", color=discord.Color.green())
self.np = await self._channel.send(embed=embed)
await self.next.wait()
# Make sure the FFmpeg process is cleaned up.
source.cleanup()
self.current = None
def destroy(self, guild):
"""Disconnect and cleanup the player."""
return self.bot.loop.create_task(self._cog.cleanup(guild))
class Music(commands.Cog):
"""Music related commands."""
__slots__ = ('bot', 'players')
def __init__(self, bot):
self.bot = bot
self.players = {}
async def cleanup(self, guild):
try:
await guild.voice_client.disconnect()
except AttributeError:
pass
try:
del self.players[guild.id]
except KeyError:
pass
async def __local_check(self, ctx):
"""A local check which applies to all commands in this cog."""
if not ctx.guild:
raise commands.NoPrivateMessage
return True
async def __error(self, ctx, error):
"""A local error handler for all errors arising from commands in this cog."""
if isinstance(error, commands.NoPrivateMessage):
try:
return await ctx.send('This command can not be used in Private Messages.')
except discord.HTTPException:
pass
elif isinstance(error, InvalidVoiceChannel):
await ctx.send('Error connecting to Voice Channel. '
'Please make sure you are in a valid channel or provide me with one')
print('Ignoring exception in command {}:'.format(ctx.command), file=sys.stderr)
traceback.print_exception(type(error), error, error.__traceback__, file=sys.stderr)
def get_player(self, ctx):
"""Retrieve the guild player, or generate one."""
try:
player = self.players[ctx.guild.id]
except KeyError:
player = MusicPlayer(ctx)
self.players[ctx.guild.id] = player
return player
#commands.command(name='join', aliases=['connect', 'j'], description="connects to voice")
async def connect_(self, ctx, *, channel: discord.VoiceChannel=None):
"""Connect to voice.
Parameters
------------
channel: discord.VoiceChannel [Optional]
The channel to connect to. If a channel is not specified, an attempt to join the voice channel you are in
will be made.
This command also handles moving the bot to different channels.
"""
if not channel:
try:
channel = ctx.author.voice.channel
except AttributeError:
embed = discord.Embed(title="", description="No channel to join. Please call `,join` from a voice channel.", color=discord.Color.green())
await ctx.send(embed=embed)
raise InvalidVoiceChannel('No channel to join. Please either specify a valid channel or join one.')
vc = ctx.voice_client
if vc:
if vc.channel.id == channel.id:
return
try:
await vc.move_to(channel)
except asyncio.TimeoutError:
raise VoiceConnectionError(f'Moving to channel: <{channel}> timed out.')
else:
try:
await channel.connect()
except asyncio.TimeoutError:
raise VoiceConnectionError(f'Connecting to channel: <{channel}> timed out.')
if (random.randint(0, 1) == 0):
await ctx.message.add_reaction('πŸ‘')
await ctx.send(f'**Joined `{channel}`**')
#commands.command(name='play', aliases=['sing','p'], description="streams music")
async def play_(self, ctx, *, search: str):
"""Request a song and add it to the queue.
This command attempts to join a valid voice channel if the bot is not already in one.
Uses YTDL to automatically search and retrieve a song.
Parameters
------------
search: str [Required]
The song to search and retrieve using YTDL. This could be a simple search, an ID or URL.
"""
await ctx.trigger_typing()
vc = ctx.voice_client
if not vc:
await ctx.invoke(self.connect_)
player = self.get_player(ctx)
# If download is False, source will be a dict which will be used later to regather the stream.
# If download is True, source will be a discord.FFmpegPCMAudio with a VolumeTransformer.
source = await YTDLSource.create_source(ctx, search, loop=self.bot.loop, download=False)
await player.queue.put(source)
#commands.command(name='pause', description="pauses music")
async def pause_(self, ctx):
"""Pause the currently playing song."""
vc = ctx.voice_client
if not vc or not vc.is_playing():
embed = discord.Embed(title="", description="I am currently not playing anything", color=discord.Color.green())
return await ctx.send(embed=embed)
elif vc.is_paused():
return
vc.pause()
await ctx.send("Paused ⏸️")
#commands.command(name='resume', description="resumes music")
async def resume_(self, ctx):
"""Resume the currently paused song."""
vc = ctx.voice_client
if not vc or not vc.is_connected():
embed = discord.Embed(title="", description="I'm not connected to a voice channel", color=discord.Color.green())
return await ctx.send(embed=embed)
elif not vc.is_paused():
return
vc.resume()
await ctx.send("Resuming ⏯️")
#commands.command(name='skip', description="skips to next song in queue")
async def skip_(self, ctx):
"""Skip the song."""
vc = ctx.voice_client
if not vc or not vc.is_connected():
embed = discord.Embed(title="", description="I'm not connected to a voice channel", color=discord.Color.green())
return await ctx.send(embed=embed)
if vc.is_paused():
pass
elif not vc.is_playing():
return
vc.stop()
#commands.command(name='remove', aliases=['rm', 'rem'], description="removes specified song from queue")
async def remove_(self, ctx, pos : int=None):
"""Removes specified song from queue"""
vc = ctx.voice_client
if not vc or not vc.is_connected():
embed = discord.Embed(title="", description="I'm not connected to a voice channel", color=discord.Color.green())
return await ctx.send(embed=embed)
player = self.get_player(ctx)
if pos == None:
player.queue._queue.pop()
else:
try:
s = player.queue._queue[pos-1]
del player.queue._queue[pos-1]
embed = discord.Embed(title="", description=f"Removed [{s['title']}]({s['webpage_url']}) [{s['requester'].mention}]", color=discord.Color.green())
await ctx.send(embed=embed)
except:
embed = discord.Embed(title="", description=f'Could not find a track for "{pos}"', color=discord.Color.green())
await ctx.send(embed=embed)
#commands.command(name='clear', aliases=['clr', 'cl', 'cr'], description="clears entire queue")
async def clear_(self, ctx):
"""Deletes entire queue of upcoming songs."""
vc = ctx.voice_client
if not vc or not vc.is_connected():
embed = discord.Embed(title="", description="I'm not connected to a voice channel", color=discord.Color.green())
return await ctx.send(embed=embed)
player = self.get_player(ctx)
player.queue._queue.clear()
await ctx.send('**Cleared**')
#commands.command(name='queue', aliases=['q', 'playlist', 'que'], description="shows the queue")
async def queue_info(self, ctx):
"""Retrieve a basic queue of upcoming songs."""
vc = ctx.voice_client
if not vc or not vc.is_connected():
embed = discord.Embed(title="", description="I'm not connected to a voice channel", color=discord.Color.green())
return await ctx.send(embed=embed)
player = self.get_player(ctx)
if player.queue.empty():
embed = discord.Embed(title="", description="queue is empty", color=discord.Color.green())
return await ctx.send(embed=embed)
seconds = vc.source.duration % (24 * 3600)
hour = seconds // 3600
seconds %= 3600
minutes = seconds // 60
seconds %= 60
if hour > 0:
duration = "%dh %02dm %02ds" % (hour, minutes, seconds)
else:
duration = "%02dm %02ds" % (minutes, seconds)
# Grabs the songs in the queue...
upcoming = list(itertools.islice(player.queue._queue, 0, int(len(player.queue._queue))))
fmt = '\n'.join(f"`{(upcoming.index(_)) + 1}.` [{_['title']}]({_['webpage_url']}) | ` {duration} Requested by: {_['requester']}`\n" for _ in upcoming)
fmt = f"\n__Now Playing__:\n[{vc.source.title}]({vc.source.web_url}) | ` {duration} Requested by: {vc.source.requester}`\n\n__Up Next:__\n" + fmt + f"\n**{len(upcoming)} songs in queue**"
embed = discord.Embed(title=f'Queue for {ctx.guild.name}', description=fmt, color=discord.Color.green())
embed.set_footer(text=f"{ctx.author.display_name}", icon_url=ctx.author.avatar_url)
await ctx.send(embed=embed)
#commands.command(name='leave', aliases=["stop", "dc", "disconnect", "bye"], description="stops music and disconnects from voice")
async def leave_(self, ctx):
"""Stop the currently playing song and destroy the player.
!Warning!
This will destroy the player assigned to your guild, also deleting any queued songs and settings.
"""
vc = ctx.voice_client
if not vc or not vc.is_connected():
embed = discord.Embed(title="", description="I'm not connected to a voice channel", color=discord.Color.green())
return await ctx.send(embed=embed)
if (random.randint(0, 1) == 0):
await ctx.message.add_reaction('πŸ‘‹')
await ctx.send('**Successfully disconnected**')
await self.cleanup(ctx.guild)
def setup(bot):
bot.add_cog(Music(bot))

discord.py not finding commands within a class

If I use .join in discord with the code below, I get the following error:
Ignoring exception in command None:
discord.ext.commands.errors.CommandNotFound: Command "join" is not found
I'm not sure how to fix this (this is turning into being part of a music bot). Here is the relevant code:
import discord
import discord.voice_client
from discord import utils
from discord.ext import commands
from discord.utils import *
import discord.utils
import lavalink
bot = commands.Bot(command_prefix= ".", intents=intents)
class MusicCog(commands.Cog):
def __init__(self, bot):
self.bot = bot
self.bot.music = lavalink.Client(self.bot.user.id)
self.bot.music.add_node('localhost', 7000, 'testing', 'na' , 'music-node')
self.bot.add_listener(self.bot.music.voice_update_handler, 'on-socket-response')
self.bot.music.add_event_hook(self.track_hook)
#commands.command(name= 'Join')
async def join(self, ctx):
print("join command worked")
member = utils.find(lambda m: m.id == ctx.author.id, ctx.guild.members)
if member is not None and member.voice is not None:
vc = member.voice.channel
player = self.bot.music.player_manager.create(ctx.guild.id, endpoint= str(ctx.guild.region))
if not player.is_connected:
player.store('channel', ctx.channel.id)
await self.connect_to(ctx.guild.id, str(vc.id))
#commands.command(name= "Play")
async def play(self, ctx, *, query):
try:
player = self.bot.music.player_manager.get(ctx.guild.id)
query = f'ytsearch: {query}'
results = await player.node.get_tracks(query)
tracks = results['tracks'][0:10]
i = 0
query_result = ''
for track in tracks:
i = i + 1
query_result = query_result + f'{i}) {track["info"]["title"]} - {track["info"]["url"]}\n'
show_songs = discord.Embed(
title= None,
description= None,
colour= discord.Colour.blue()
)
show_songs.description = query_result
await ctx.channel.send(embed= show_songs)
def check(m):
return m.author.id == ctx.author.id
response = await self.bot.wait_for('message', check=check)
track = tracks[int(response.conetent)-1]
player.add(requester = ctx.author.id, track = track)
if not player.is_playing:
await player.play()
except Exception as error:
print(error)
async def track_hook(self, event):
if isinstance(event, lavalink.events.QueueEndEvent):
guild_id = int(event.player.guild.id)
await self.connect_to(guild_id, None)
async def connect_to(self, guild_id: int, channel_id: str):
ws = self.bot._connection._get_websocket(guild_id)
await ws.voice_state(str(guild_id), channel_id)
def setup(bot):
bot.add_cog(MusicCog(bot))
I've tried changing #commands.command to #bot.command, which didn't end up working, and I'm almost certain the indentation is correct throughout the code, so at this point, I'm not sure how to fix it. Any help would be appreciated!
By default, commands are case sensitive so you'd have to type .Join or .Play in order to invoke your them.
If you want your commands to be case insensitive, simply type :
bot = commands.Bot(command_prefix='.', intents=intents, case_insensitive=True)
PS : the name argument in the commands.command() decorator refers to what needs to be typed after your prefix to invoke the function.

how can i use async/await to call Future obj in python3.6

I have doubts about Python's await and hava an example,it tries to use await to fetch results from the future obj.
import time
import asyncio
import time
import random
import threading
db = {
"yzh": "pig",
"zhh": "big pig"
}
loop = asyncio.get_event_loop()
def _get_redis(username, clb):
def foo():
data = db[username]
time.sleep(0.1)
print("start clb")
clb(data)
t1 = threading.Thread(target=foo)
t1.start()
def get_redis(username):
print("start get redis")
myfuture = asyncio.Future()
def clb(result):
print("clb call")
myfuture.set_result(result)
_get_redis(username, clb)
return myfuture
async def main():
print("start main")
data = await get_redis("yzh")
print("data is {}".format(data))
loop.run_until_complete(asyncio.ensure_future(main()))
loop.close()
and i got output without future's result:
start main
start get redis
start clb
clb call
How should i use await to get the future's result?I tried many times. Thanks for your help.
As you said in your comment, you're supposed to use loop.call_soon_threadsafe when running an asyncio callback from a thread:
loop.call_soon_threadsafe(myfuture.set_result, result)
However, a better approach for calling a synchronous function from asyncio is to use loop.run_in_executor:
def _get_redis(username):
time.sleep(0.1)
return db[username]
async def get_redis(username):
return await loop.run_in_executor(None, _get_redis, username)
This way, you won't have to deal with futures and thread-safe callbacks.

Resources