Browse Source

Ensure explicitly set parents on nested groups are respected

This had a multiple layer bug that needed to be squashed. The first
issue was that setting `parent=...` inside a `Group` wouldn't actually
add the `Group` into the `parent` children mapping. This meant that
it didn't actually end up copying the children when it came time to
within e.g. a Cog.

The second issue was that even if it was added into the children
listing, it wouldn't properly set the attribute.

This commit fixes both of these issues.

Fix #7818
pull/7821/head
Rapptz 3 years ago
parent
commit
25ad5b675c
  1. 15
      discord/app_commands/commands.py
  2. 35
      tests/test_app_commands_group.py

15
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)

35
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

Loading…
Cancel
Save