diff --git a/discord/app_commands/commands.py b/discord/app_commands/commands.py index 818cde230..11af21f8f 100644 --- a/discord/app_commands/commands.py +++ b/discord/app_commands/commands.py @@ -971,6 +971,7 @@ class Group: self.name: str = validate_name(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 + self._owner_cls: Optional[Type[Any]] = None self._guild_ids: Optional[List[int]] = guild_ids if not self.description: @@ -1004,12 +1005,15 @@ class Group: if copy._attr and not cls.__discord_app_commands_skip_init_binding__: setattr(self, copy._attr, copy) - if parent is not None and parent.parent is not None: - raise ValueError('groups can only be nested at most one level') + if parent is not None: + if parent.parent is not None: + raise ValueError('groups can only be nested at most one level') + parent.add_command(self) def __set_name__(self, owner: Type[Any], name: str) -> None: self._attr = name self.module = owner.__module__ + self._owner_cls = owner def _copy_with( self, @@ -1029,6 +1033,7 @@ class Group: copy.parent = parent copy.module = self.module copy._attr = self._attr + copy._owner_cls = self._owner_cls copy._children = {} bindings[self] = copy @@ -1038,6 +1043,12 @@ class Group: child_copy.parent = copy copy._children[child_copy.name] = child_copy + if isinstance(child_copy, Group) and child_copy._attr and set_on_binding: + if binding.__class__ is child_copy._owner_cls: + setattr(binding, child_copy._attr, child_copy) + elif child_copy._owner_cls is copy.__class__: + setattr(copy, child_copy._attr, child_copy) + if copy._attr and set_on_binding: setattr(parent or binding, copy._attr, copy) diff --git a/tests/test_app_commands_group.py b/tests/test_app_commands_group.py index 11f482b3b..827c38d5d 100644 --- a/tests/test_app_commands_group.py +++ b/tests/test_app_commands_group.py @@ -95,8 +95,7 @@ def test_group_subclass_with_group_subclass(): assert my_group.my_group_command.parent is my_group assert my_group.my_group_command.binding is my_group assert my_group.sub_group.my_sub_group_command.parent is my_group.sub_group - print(my_group.sub_group.my_sub_group_command.binding) - print(MyGroup.sub_group) + assert not hasattr(my_group, 'my_sub_group_command') assert my_group.sub_group.my_sub_group_command.binding is my_group.sub_group @@ -127,6 +126,32 @@ def test_cog_with_group_with_commands(): assert cog.my_command.binding is cog +def test_cog_with_nested_group_with_commands(): + class MyCog(commands.Cog): + first = app_commands.Group(name='test', description='Test 1') + second = app_commands.Group(name='test2', parent=first, description='Test 2') + + @first.command(name='cmd') + async def test_cmd(self, interaction: discord.Interaction) -> None: + ... + + @second.command(name='cmd2') + async def test2_cmd(self, interaction: discord.Interaction) -> None: + ... + + cog = MyCog() + + assert len(MyCog.__cog_app_commands__) == 1 + assert cog.first.parent is None + assert cog.first is not MyCog.first + assert cog.second is not MyCog.second + assert cog.second.parent is cog.first + assert cog.test_cmd.parent is cog.first + assert cog.test2_cmd.parent is cog.second + assert cog.test_cmd.binding is cog + assert cog.test2_cmd.binding is cog + + def test_cog_with_group_subclass_with_commands(): class MyGroup(app_commands.Group, name='mygroup'): @app_commands.command() @@ -175,6 +200,8 @@ def test_cog_with_group_subclass_with_group(): assert cog.my_group.my_command is not MyGroup.my_command assert cog.my_cog_command is not MyCog.my_cog_command assert not hasattr(cog.my_group, 'my_cog_command') + assert not hasattr(cog, 'sub_group') + assert not hasattr(cog, 'my_command') assert cog.my_group.parent is None assert cog.my_group.sub_group.parent is cog.my_group assert cog.my_group.my_command.parent is cog.my_group.sub_group @@ -215,6 +242,10 @@ def test_cog_with_group_subclass_with_group_subclass(): assert cog.my_group.sub_group is not MyGroup.sub_group assert cog.my_cog_command is not MyCog.my_cog_command assert not hasattr(cog.my_group, 'my_cog_command') + assert not hasattr(cog, 'sub_group') + assert not hasattr(cog, 'my_group_command') + assert not hasattr(cog, 'my_sub_group_command') + assert not hasattr(cog.my_group, 'my_sub_group_command') assert cog.my_group.sub_group.my_sub_group_command is not MyGroup.sub_group.my_sub_group_command assert cog.my_group.sub_group.my_sub_group_command is not MySubGroup.my_sub_group_command assert cog.my_group.sub_group.parent is cog.my_group