gman1230321
gman1230321

Reputation: 103

Async race condition problem in discord.py

I'm trying to make a simple command that allows a user to give themselves a role, but if the role doesn't exist, it will create the role first, then give them the role. Here is my current code:

@bot.command()
async def SetRole(ctx, arg):
    roles = ctx.guild.roles
    user = ctx.message.author

    #check if role does not exist
    if arg not in [role.name for role in roles]:
        await ctx.guild.create_role(name=arg)
    await user.add_roles(discord.utils.get(roles, name=arg))

The expected result when I run $SetRole test_role if test_role does not exist, would be that the bot creates the role and then gives me the role. However, the role is created but is not given to me. Here is there error and stack trace:

Ignoring exception in command SetRole:
Traceback (most recent call last):
  File "/opt/virtualenvs/python3/lib/python3.8/site-packages/discord/ext/commands/core.py", line 85, in wrapped
    ret = await coro(*args, **kwargs)
  File "main.py", line 17, in SetRole
    await user.add_roles(discord.utils.get(roles, name=arg))
  File "/opt/virtualenvs/python3/lib/python3.8/site-packages/discord/member.py", line 777, in add_roles
    await req(guild_id, user_id, role.id, reason=reason)
AttributeError: 'NoneType' object has no attribute 'id'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/opt/virtualenvs/python3/lib/python3.8/site-packages/discord/ext/commands/bot.py", line 939, in invoke
    await ctx.command.invoke(ctx)
  File "/opt/virtualenvs/python3/lib/python3.8/site-packages/discord/ext/commands/core.py", line 863, in invoke
    await injected(*ctx.args, **ctx.kwargs)
  File "/opt/virtualenvs/python3/lib/python3.8/site-packages/discord/ext/commands/core.py", line 94, in wrapped
    raise CommandInvokeError(exc) from exc
discord.ext.commands.errors.CommandInvokeError: Command raised an exception: AttributeError: 'NoneType' object has no attribute 'id'

If I run the command a second time, the role is successfully given to me. What I presume is happening is for some reason, the user.add_roles() is happening before the create_role most likely because of some weirdness with async. How can I make sure that it adds the role after its created?

Upvotes: 0

Views: 477

Answers (1)

FalseDev
FalseDev

Reputation: 484

This isn't a race condition, just that discord.py doesn't track and mutate objects everywhere

A better implementation would be:

from typing import Union

@bot.command()
async def SetRole(ctx, role: Union[Role, str]):
    if isinstance(role, str):
        role = await ctx.guild.create_role(name=role)
    await user.add_roles(role)

Union is just a way to represent "any of the given types", so discord.py [cleverly] either return a role or a string as fallback, we can then create the role if it doesn't already exist by checking if role is a string

Upvotes: 1

Related Questions