(Somehow) Allow autocomplete methods to accept arguments #2668
Description
What is the feature request for?
The core library
The Problem
This stream of consciousness delves into parts of python I'm not fully familiar with, and is fairly contrived without testing, so apologies in advance for typos or incorrect concepts.
With autocompletes, I thought it might be useful to allow arguments to the autocomplete itself, which could generalize related searches with different parameters without cluttering the application with one-shot methods.
Let's assume the following basic example:
import discord
from discord.ext import commands
class Pets(commands.Cog):
async def pet_autocomplete(self, ctx: discord.AutocompleteContext) -> list[discord.OptionChoice]:
search_term = ctx.value.casefold()
return [
discord.OptionChoice(name=animal['name'], value=animal['id'])
for animal in self.pet_datasource if search_term in animal['name'].casefold()
]
@commands.slash_command(description='Get pet info')
async def pet_by_name(
self,
ctx: discord.ApplicationContext,
pet: discord.Option(str, "Target Pet", autocomplete=pet_autocomplete)
) -> None:
# ... This part doesn't matter
Now say we want to have commands that specifically only concern dogs (without adding a secondary option). Granted, we could always create a separate autocomplete method, referencing that in our new slash command:
async def dog_autocomplete(self, ctx: discord.AutocompleteContext) -> list[OptionChoice]:
search_term = ctx.value.casefold()
return [
discord.OptionChoice(name=animal['name'], value=animal['id'])
for animal in self.pet_datasource if search_term in animal['name'].casefold()
and animal['type'] == 'dog'
]
@commands.slash_command(description="Who's a good dog?")
async def vote_for_dog(
self,
ctx: discord.ApplicationContext,
dog: discord.Option(str, "Dog's Name", autocomplete=dog_autocomplete)
) -> None:
# ...
And of course, we could move the logic of the autocomplete to a generalized search function and have the AC make the . But it would be even more cool for the autocomplete method itself to be more generic, taking in arguments from the autocomplete=
arg in discord.Option. This would allow us to have something like
async def pet_autocomplete(self, ctx: discord.AutocompleteContext, pet_type: str = None) -> list[OptionChoice]:
search_term = ctx.value.casefold()
return [
discord.OptionChoice(name=animal['name'], value=animal['id'])
for animal in self.pet_datasource if search_term in animal['name'].casefold()
and (animal['type'] == pet_type if pet_type else True)
]
The first hurdle is the autocomplete handler itself. It requires an AutocompleteContext, so of course we cannot execute the callback methods directly.
Two ideas I immediately had were functools.partial
and lambdas. Neither of these work, and I suspect for the same reason. First, a visual on how these might look:
@commands.slash_command(description="Who's a good dog?")
async def vote_for_dog(
self,
ctx: discord.ApplicationContext,
dog: discord.Option(str, "Dog's Name", autocomplete=functools.partial(dog_autocomplete, pet_type='dog')
) -> None:
# ...
# -- or -- #
@commands.slash_command(description="Who's a good dog?")
async def vote_for_dog(
self,
ctx: discord.ApplicationContext,
dog: discord.Option(str, "Dog's Name", autocomplete=lambda self, *args: self.dog_autocomplete(*args, pet_type='dog')
) -> None:
# ...
As an aside: The lambda syntax came after a few different iterations. I always thought it was interesting that my autocomplete
argument was the bare def. And once I put it in the lambda, it lost all context and complained that dog_autocomplete
was an unresolved reference. I also couldn't immediately reference self.dog_autocomplete
because self
was unresolved -- passing that into the lambda solved all the reference errors.
Anyway, both approaches yielded the same outcome: dog_autocomplete was never awaited
However, when I removed async from my autocomplete method(s), the lambda approach worked! Unfortunately, in my production context, this isn't viable, but is at least some bit of progress. There are workarounds to 'async lambdas', but I think there's a valid use case in having this Just Work within Pycord.
If anyone smarter than me could take a look, it'd simplify my classes quite a bit to have something like this available!
The Ideal Solution
Easily allow argument passing to autocomplete method references within Option()
s, preferably without the complex syntax of lambdas.