Skip to content

Conversation

Soheab
Copy link
Contributor

@Soheab Soheab commented May 16, 2025

Summary

https://canary.discord.com/channels/336642139381301249/1371429669395759195

Tracking

  • discord
    • app_commands
      • checks.py
      • commands.py
    • ext
      • commands
        • bot.py
        • cog.py
        • core.py
        • help.py
        • hybrid.py
    • abc.py
    • channel.py
    • client.py
    • flags.py
    • permissions.py
    • shard.py

Checklist

  • If code changes were made then they have been tested.
    • I have updated the documentation to reflect the changes.
  • 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, ...)

@Soheab Soheab changed the title [WIP] Unpack usage where possible Unpack usage where possible May 16, 2025
@Soheab Soheab marked this pull request as ready for review May 17, 2025 20:12
"""

def __init__(self, *, intents: Intents, **options: Any) -> None:
def __init__(self, *, intents: Intents, **options: Unpack[_ClientOptions]) -> None:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, the reason why I didn't do this before for Client is because as part of the contract that you can inherit from Client this means that any extra args would cause type errors even though they might use the extra kwargs themselves.

I'm unsure where I really stand on this from a usability POV, and I don't know if it's that annoying.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unsure what you mean as extra kwargs are ignored anyways and you lose the typed kwargs when subclassing and overriding the __init__ to handle the extra args anyways.

class MyClient(discord.Client):
    ...

# Argument missing for parameter "intents" (expected)
# No parameter named "hello" (expected but ignored anyways)
MyClient(hello="lol")


class ClientWithInit(discord.Client):
    # *args = tuple[Unknown, ...], **kwargs = dict[str, Unknown]
    def __init__(self, *args, **kwargs) -> None:

        # handling extra kwarg
        self.foo = kwargs.pop("foo")

        super().__init__(*args, **kwargs)
        


ClientWithInit(foo="bar") # works fine
Suggested change
def __init__(self, *, intents: Intents, **options: Unpack[_ClientOptions]) -> None:
def __init__(self, *, intents: Intents, **options: Unpack[_ClientOptions]) -> None:

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other issue is that it doesn't allow people who inherit to have the same ability, e.g. ClientWithInit(intents=None) passes despite it not meeting the type signature. I guess it's fine just unfortunate.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, there’s unfortunately no good way to make these public. The user can, if they want, import them in a TYPE_CHECKING block, but that’s probably not good advice to give.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not going to be supported -- and what I'm talking about is an actual supported way of doing this. I guess it has no impact on this PR. The most annoying thing right now is the duplication of all the flags/permissions.

allowed_contexts: app_commands.AppCommandContext = MISSING,
allowed_installs: app_commands.AppInstallationType = MISSING,
intents: discord.Intents,
**kwargs: Unpack[_AutoShardedBotOptions],
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same nit as before.

__cog_listeners__: List[Tuple[str, str]]

def __new__(cls, *args: Any, **kwargs: Any) -> CogMeta:
def __new__(cls, *args: Any, **kwargs: Unpack[_CogKwargs]) -> CogMeta:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this even work from a user's POV?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately not. It does error out when the user has typed the correct name but the wrong type.

__slots__ = ()

def __init__(self, value: int = 0, **kwargs: bool) -> None:
def __init__(self, value: int = 0, **kwargs: Unpack[_IntentsFlagsKwargs]) -> None:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another reason why I was not a fan of doing this is because now there are more places to edit when adding a single thing. This is especially egregious with permissions which need to have an added value both in its TYPE_CHECKING block and now in its Unpack dictionary.

I suppose it's unavoidable, but honestly it's just really annoying.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point, it's a bit annoying to update things in more than one place. But for users, it's kinda nice to have. Also, most people don’t know you can use kwargs to construct flags like this, so showing them helps with that too.

_connection: AutoShardedConnectionState

def __init__(self, *args: Any, intents: Intents, **kwargs: Any) -> None:
def __init__(self, *args: Any, intents: Intents, **kwargs: Unpack[_AutoShardedClientOptions]) -> None:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same nit as Client and AutoShardedBot.

raise TypeError(f'{key!r} is not a valid permission name.') from None
else:
self._set_flag(flag, value)
self._set_flag(flag, kwvalue) # type: ignore
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this need a type ignore?

Copy link
Contributor Author

@Soheab Soheab Aug 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all values are object when iterating over a TypedDict, so it throws the following:

Argument of type "object" cannot be assigned to parameter "toggle" of type "bool" in function "_set_flag"
  "object" is not assignable to "bool"PylancereportArgumentType

AFAIK this is a not-fixable TypedDict annoyance.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's pretty goofy. Add it in the comments.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@Rapptz Rapptz merged commit 983a9b8 into Rapptz:master Aug 15, 2025
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants