I am working on a Discord bot which handles music requests among other things. Whenever I change stuff in a cog and then reload it, I get an error message like AttributeError: 'NoneType' object has no attribute 'XXXX'
error for the particular command.
For example, starting with this code for the cog:
@commands.command()
@commands.guild_only()
@commands.check(audio_playing)
@commands.check(in_voice_channel)
@commands.check(is_audio_requester)
async def loop(self, ctx):
"""Activates/Deactivates the loop for a song."""
state = self.get_state(ctx.guild)
status = state.now_playing.toggle_loop()
if status is None:
return await ctx.send(
embed=discord.Embed(title=":no_entry: Unable to toggle loop.", color=discord.Color.red()))
else:
return await ctx.send(embed=discord.Embed(
title=f":repeat: Loop: {'**Enabled**' if state.now_playing.loop else '**Disabled**'}.",
color=self.bot.get_embed_color(ctx.guild)))
which calls a helper method get_state
:
def get_state(self, guild):
"""Gets the state for `guild`, creating it if it does not exist."""
if guild.id in self.states:
return self.states[guild.id]
else:
self.states[guild.id] = GuildState()
return self.states[guild.id]
which creates an instance of this GuildState
class:
class GuildState:
"""Helper class managing per-guild state."""
def __init__(self):
self.volume = 1.0
self.playlist = []
self.message_queue = []
self.skip_votes = set()
self.now_playing = None
self.control_message = None
self.loop = False
self.skipped = False
def is_requester(self, user):
return self.now_playing.requested_by == user
Then I use code like this to make the bot respond to the command play URL
by joining a voice channel:
if not state.now_playing:
self._play_song(client, state, video)
where _play_song
does:
def _play_song(self, client, state, song):
state.now_playing = song
# Other things are not relevant
Now if I try to reload the cog, the next time loop
is called I get an error like:
In loop:
File "C:\Users\Dominik\PycharmProjects\AlchiReWrite\venv\lib\site-packages\discord\ext\commands\core.py", line 85, in wrapped
ret = await coro(*args, **kwargs)
File "C:\Users\Dominik\PycharmProjects\AlchiReWrite\cogs\music.py", line 220, in loop
status = state.now_playing.toggle_loop()
AttributeError: 'NoneType' object has no attribute 'toggle_loop'
I tried using exception handling with try/except AttributeError
to ignore the error, but it didn't resolve the problem.
Why does this occur, and how can I fix or prevent the problem? I query state
for every command which has to do with music, is it maybe related to that?
When you reload the cog, the states
dictionary in your cog will be empty. With state = self.get_state(ctx.guild)
, a new GuildState
object is created. From the __init__
function of the GuildState
class, self.now_playing
is set to None
.
Because of this, status = state.now_playing.toggle_loop()
will throw an AttributeError
as None
has no attributes (in this case, no toggle_loop
attribute).
If you want to get rid of these errors, you will need to set self.now_playing
correctly to something that does have the needed attributes.
If you want to keep the states
dictionary as is, you can save it before reloading your cog and restore it. The below example assumes that the cog class is named TestCog
.
@client.command()
async def reload(ctx):
temp = client.get_cog('TestCog').states
client.reload_extension('cog')
client.get_cog('TestCog').states = temp
Note that this may break your cog if you change how GuildState
is created, as you are restoring the previous version of states
.