Description
Summary
A slash command callback in a closure is treated as a class member
Reproduction Steps
- Create a closure with a command defined it it by either a decorator or
add_application_command
. - Call the closure during bot startup.
- The command will fail because it assumes non-module-level functions are class methods.
Minimal Reproducible Code
import discord # For great justice !!
client = discord.Bot()
def prepare_command(some_arg: str) -> discord.SlashCommand:
async def rhubarb(
ctx,
command: discord.Option(str, "The thing to do, yo."),
args: discord.Option(str, "The things to do it to, yo."),
):
print("/rhubarb", command)
slash_command = discord.SlashCommand(
rhubarb,
name="rhubarb",
description="Do wanton acts with toes.",
options=[
discord.Option(str, name="command"),
discord.Option(str, name="args"),
]
)
client.add_application_command(slash_command)
prepare_command("parsnips")
client.run("etcetcetc")
Expected Results
The command to be registered with the application.
Actually I also expected to win a complimentary vacation to Fargo, Minnesota as well, but I suspect there might be other issues with my code as well...
Actual Results
Traceback (most recent call last):
File "/root/.cache/pypoetry/virtualenvs/rhubarb-9wL-Y1vf-py3.9/lib/python3.9/site-packages/discord/commands/core.py", line 737, in _match_option_param_names
p_name, p_obj = next(params)
StopIteration
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/opt/lib/python/rhubarb/startup.py", line 60, in <module>
asyncio.run(bot.start_bot())
File "/opt/lib/python/rhubarb/discord/bot.py", line 109, in start_bot
rhubarb.prepare_command("parsnips")
File "/opt/lib/python/rhubarb/discord/commands/rhubarb.py", line 27, in prepare_command
slash_command = discord.SlashCommand(
File "/root/.cache/pypoetry/virtualenvs/rhubarb-9wL-Y1vf-py3.9/lib/python3.9/site-packages/discord/commands/core.py", line 644, in __init__
self.options: List[Option] = self._match_option_param_names(params, kwop)
File "/root/.cache/pypoetry/virtualenvs/rhubarb-9wL-Y1vf-py3.9/lib/python3.9/site-packages/discord/commands/core.py", line 740, in _match_option_param_names
raise ClientException("Too many arguments passed to the options kwarg.")
discord.errors.ClientException: Too many arguments passed to the options kwarg.
Intents
Defaults.
System Information
- Python v3.9.7-final
- py-cord v2.0.0-final
- aiohttp v3.8.1
- system info: Linux 5.10.104-linuxkit Update README.rst #1 SMP PREEMPT Thu Mar 17 17:05:54 UTC 2022
Checklist
- I have searched the open issues for duplicates.
- I have shown the entire traceback, if possible.
- I have removed my token from display, if visible.
Additional Context
The problem lies in _check_required_params
:
def _check_required_params(self, params):
params = iter(params.items())
required_params = (
["self", "context"]
if self.attached_to_group
or self.cog
or len(self.callback.__qualname__.split(".")) > 1 # <--- here be ye olde scallawag
else ["context"]
)
In a closure, the callback function's __qualname__
looks like this:
prepare_command.<locals>.rhubarb
And so the code assumes that it is a class instance method, meaning that it will be passed self
as the first argument. The required params have one too many params, the first option parameter gets swallowed in _match_option_param_names
as a result, and Bob's your uncle, stack trace ahoy!
Using a static class method for a callback probably has the same issue (because they receive neither self
nor cls
as their first parameter) but I have not tested that.