diff --git a/discord/app_commands/commands.py b/discord/app_commands/commands.py index 68a240ec0..156e1cae0 100644 --- a/discord/app_commands/commands.py +++ b/discord/app_commands/commands.py @@ -37,6 +37,7 @@ from typing import ( Set, TYPE_CHECKING, Tuple, + Type, TypeVar, Union, ) @@ -325,12 +326,16 @@ class Command(Generic[GroupT, P, T]): ): self.name: str = name self.description: str = description + self._attr: Optional[str] = None self._callback: CommandCallback[GroupT, P, T] = callback self.parent: Optional[Group] = parent self.binding: Optional[GroupT] = None self.on_error: Optional[Error[GroupT]] = None self._params: Dict[str, CommandParameter] = _extract_parameters_from_callback(callback, callback.__globals__) + def __set_name__(self, owner: Type[Any], name: str) -> None: + self._attr = name + @property def callback(self) -> CommandCallback[GroupT, P, T]: """:ref:`coroutine `: The coroutine that is executed when the command is called.""" @@ -341,6 +346,7 @@ class Command(Generic[GroupT, P, T]): copy = cls.__new__(cls) copy.name = self.name copy.description = self.description + copy._attr = self._attr copy._callback = self._callback copy.parent = self.parent copy.on_error = self.on_error @@ -621,23 +627,27 @@ class Group: The parent group. ``None`` if there isn't one. """ - __discord_app_commands_group_children__: ClassVar[Dict[str, Union[Command, Group]]] = {} + __discord_app_commands_group_children__: ClassVar[List[Union[Command, Group]]] = [] __discord_app_commands_default_guilds__: List[int] __discord_app_commands_group_name__: str = MISSING __discord_app_commands_group_description__: str = MISSING def __init_subclass__(cls, *, name: str = MISSING, description: str = MISSING) -> None: - cls.__discord_app_commands_group_children__ = children = { - name: member - for name, member in cls.__dict__.items() - if isinstance(member, (Group, Command)) and member.parent is None - } - - found = set() - for child in children.values(): - if child.name in found: - raise TypeError(f'Command {child.name!r} is a duplicate') - found.add(child.name) + if not cls.__discord_app_commands_group_children__: + cls.__discord_app_commands_group_children__ = children = [ + member + for name, member in cls.__dict__.items() + if isinstance(member, (Group, Command)) and member.parent is None + ] + + found = set() + for child in children: + if child.name in found: + raise TypeError(f'Command {child.name!r} is a duplicate') + found.add(child.name) + + if len(children) > 25: + raise TypeError('groups cannot have more than 25 commands') if name is MISSING: cls.__discord_app_commands_group_name__ = _to_kebab_case(cls.__name__) @@ -652,8 +662,6 @@ class Group: else: cls.__discord_app_commands_group_description__ = description - if len(children) > 25: - raise TypeError('groups cannot have more than 25 commands') def __init__( self, @@ -665,6 +673,7 @@ class Group: cls = self.__class__ self.name: str = name if name is not MISSING else cls.__discord_app_commands_group_name__ self.description: str = description or cls.__discord_app_commands_group_description__ + self._attr: Optional[str] = None if not self.description: raise TypeError('groups must have a description') @@ -673,21 +682,26 @@ class Group: self._children: Dict[str, Union[Command, Group]] = {} - for attr, child in self.__discord_app_commands_group_children__.items(): + for child in self.__discord_app_commands_group_children__: child = child._copy_with_binding(self) child.parent = self self._children[child.name] = child - setattr(self, attr, child) + if child._attr: + setattr(self, child._attr, child) if parent is not None and parent.parent is not None: raise ValueError('groups can only be nested at most one level') + def __set_name__(self, owner: Type[Any], name: str) -> None: + self._attr = name + def _copy_with_binding(self, binding: Group) -> Group: cls = self.__class__ copy = cls.__new__(cls) copy.name = self.name copy.description = self.description copy.parent = self.parent + copy._attr = self._attr copy._children = {child.name: child._copy_with_binding(binding) for child in self._children.values()} return copy