disnake icon indicating copy to clipboard operation
disnake copied to clipboard

feat: allow passing autocompleters that take a cog as first argument to commands.Param

Open Sharp-Eyes opened this issue 1 year ago • 2 comments

Summary

Currently, commands.Param assumes the provided autocompleter does not take a cog and is typehinted as such, despite somewhat working with alternative autocomplete options such as a simple sequence.

This PR ensures cog-based autocompleters work with commands.Param. The typehint is changed to work with any valid autocompleter (callback without cog, callback with cog, sequence), and callbacks are internally marked so that slash commands handle them correctly.

Checklist

  • [ ] If code changes were made, then they have been tested
    • [ ] I have updated the documentation to reflect the changes
    • [ ] I have formatted the code properly by running pdm lint
    • [ ] I have type-checked the code by running pdm pyright
  • [ ] This PR fixes an issue
  • [ ] This PR adds something new (e.g. new method or parameters)
  • [ ] This PR is a breaking change (e.g. methods or parameters removed/renamed)
  • [ ] This PR is not a code change (e.g. documentation, README, ...)

Sharp-Eyes avatar Jan 03 '25 21:01 Sharp-Eyes

This technically solves the original issue (ref); however, and correct me if i'm wrong, at least in that case, it just happens to work out as both cogs have a bot member. If we consider:

class Autocompleters(commands.Cog):
    def __init__(self, bot) -> None:
        self.bot: commands.Bot = bot

    async def category(self, inter: disnake.CommandInteraction, query: str):
        print(type(self))  # <<<<<

        categories = ["things", "more", "stuff"]
        return [c for c in categories if query.lower() in c]


class Misc(commands.Cog):
    def __init__(self, bot) -> None:
        self.bot: commands.Bot = bot

    @commands.slash_command()
    async def test(
        self,
        inter: disnake.GuildCommandInteraction,
        cat: str = commands.Param(autocomplete=Autocompleters.category),
    ) -> None:
        await inter.send(cat)

The print in this case shows the self parameter being a Misc instance, since autocompleters (at least those marked as requiring a cog parameter) receive the application command's cog. It's fairly reasonable for the library to assume the command and autocomplete callback are defined in the same cog imho, there isn't really a way to guarantee the existence of a class instance otherwise.

shiftinv avatar Jan 21 '25 19:01 shiftinv

(EDIT: forgot the cog in the code example mmlol)

Absolutely valid concern. I personally don't think "inter-cog" autocompleters really make sense either, but I could see a more top-level approach work fine:

async def some_autocompleter(
    self: commands.Cog,  # Or a protocol
    inter: disnake.CommandInteraction,
    data: str,
):
    # if need be can isinstance(self, ...) with a cog class or a protocol
    return ...

class Misc(commands.Cog):
    @commands.slash_command()
    async def test(
        self,
        inter: disnake.GuildCommandInteraction,
        cat: str = commands.Param(autocomplete=some_autocompleter),
    ) -> None:
        await inter.send(cat)

Sharp-Eyes avatar Jan 21 '25 21:01 Sharp-Eyes

I personally don't think "inter-cog" autocompleters really make sense either, but I could see a more top-level approach work fine

Yea, fair point. In any case, I suppose adding the classify_autocompleter call here won't hurt, so, lgtm. thank you!

shiftinv avatar Aug 24 '25 09:08 shiftinv