From 97a8303211bdad11a3687612cc426b8f8dba52fc Mon Sep 17 00:00:00 2001 From: Paillat Date: Tue, 4 Mar 2025 13:30:05 +0100 Subject: [PATCH 01/12] :label: Type stuff --- discord/cog.py | 24 ++++++++++++++++++++++-- discord/commands/core.py | 2 ++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/discord/cog.py b/discord/cog.py index e23def38e2..3c2b37d94c 100644 --- a/discord/cog.py +++ b/discord/cog.py @@ -31,7 +31,18 @@ import pathlib import sys import types -from typing import Any, Callable, ClassVar, Generator, Mapping, TypeVar, overload +from typing import ( + TYPE_CHECKING, + Any, + Callable, + ClassVar, + Generator, + Mapping, + TypeVar, + overload, +) + +from typing_extensions import TypeGuard import discord.utils @@ -43,6 +54,11 @@ _BaseCommand, ) +if TYPE_CHECKING: + from .ext.bridge import BridgeCommand, BridgeCommandGroup + from .ext.commands import Command + + __all__ = ( "CogMeta", "Cog", @@ -127,7 +143,11 @@ async def bar(self, ctx): __cog_name__: str __cog_settings__: dict[str, Any] - __cog_commands__: list[ApplicationCommand] + __cog_commands__: list[ + ApplicationCommand # pyright: ignore[reportMissingTypeArgument] + | Command # pyright: ignore[reportMissingTypeArgument] + | BridgeCommand + ] __cog_listeners__: list[tuple[str, str]] __cog_guild_ids__: list[int] diff --git a/discord/commands/core.py b/discord/commands/core.py index 6dd1b0d636..868b529c22 100644 --- a/discord/commands/core.py +++ b/discord/commands/core.py @@ -726,6 +726,8 @@ class SlashCommand(ApplicationCommand): type = 1 + parent: SlashCommandGroup | None + def __new__(cls, *args, **kwargs) -> SlashCommand: self = super().__new__(cls) From e12733393404e6829d70dd2b8902e412efefb394 Mon Sep 17 00:00:00 2001 From: Paillat Date: Tue, 4 Mar 2025 13:30:45 +0100 Subject: [PATCH 02/12] :recycle: Refactor weird checks and don't redefine filter every time --- discord/cog.py | 39 +++++++++++++++++++++----------------- discord/ext/bridge/core.py | 2 ++ 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/discord/cog.py b/discord/cog.py index 3c2b37d94c..64ce735afc 100644 --- a/discord/cog.py +++ b/discord/cog.py @@ -75,6 +75,18 @@ def _is_submodule(parent: str, child: str) -> bool: return parent == child or child.startswith(f"{parent}.") +def _is_bridge_command(command: Any) -> TypeGuard[BridgeCommand]: + return getattr(command, "__bridge__", False) + + +def _name_filter(c: Any) -> str: + return ( + "app" + if isinstance(c, ApplicationCommand) + else ("bridge" if not _is_bridge_command(c) else "ext") + ) + + class CogMeta(type): """A metaclass for defining a cog. @@ -208,8 +220,7 @@ def __new__(cls: type[CogMeta], *args: Any, **kwargs: Any) -> CogMeta: raise TypeError(no_bot_cog.format(base, elem)) commands[elem] = value - # a test to see if this value is a BridgeCommand - if hasattr(value, "add_to") and not getattr(value, "parent", None): + if _is_bridge_command(value) and not value.parent: if is_static_method: raise TypeError( f"Command in method {base}.{elem!r} must not be" @@ -251,16 +262,10 @@ def __new__(cls: type[CogMeta], *args: Any, **kwargs: Any) -> CogMeta: # Either update the command with the cog provided defaults or copy it. # r.e type ignore, type-checker complains about overriding a ClassVar - new_cls.__cog_commands__ = tuple(c._update_copy(cmd_attrs) if not hasattr(c, "add_to") else c for c in new_cls.__cog_commands__) # type: ignore - - name_filter = lambda c: ( - "app" - if isinstance(c, ApplicationCommand) - else ("bridge" if not hasattr(c, "add_to") else "ext") - ) + new_cls.__cog_commands__ = tuple(c._update_copy(cmd_attrs) if not _is_bridge_command(c) else c for c in new_cls.__cog_commands__) # type: ignore lookup = { - f"{name_filter(cmd)}_{cmd.qualified_name}": cmd + f"{_name_filter(cmd)}_{cmd.qualified_name}": cmd for cmd in new_cls.__cog_commands__ } @@ -273,15 +278,15 @@ def __new__(cls: type[CogMeta], *args: Any, **kwargs: Any) -> CogMeta: ): command.guild_ids = new_cls.__cog_guild_ids__ - if not isinstance(command, SlashCommandGroup) and not hasattr( - command, "add_to" + if not isinstance(command, SlashCommandGroup) and not _is_bridge_command( + command ): # ignore bridge commands cmd = getattr(new_cls, command.callback.__name__, None) - if hasattr(cmd, "add_to"): + if _is_bridge_command(cmd): setattr( cmd, - f"{name_filter(command).replace('app', 'slash')}_variant", + f"{_name_filter(command).replace('app', 'slash')}_variant", command, ) else: @@ -290,7 +295,7 @@ def __new__(cls: type[CogMeta], *args: Any, **kwargs: Any) -> CogMeta: parent = command.parent if parent is not None: # Get the latest parent reference - parent = lookup[f"{name_filter(command)}_{parent.qualified_name}"] # type: ignore + parent = lookup[f"{_name_filter(command)}_{parent.qualified_name}"] # type: ignore # Update our parent's reference to our self parent.remove_command(command.name) # type: ignore @@ -568,7 +573,7 @@ def _inject(self: CogT, bot) -> CogT: # we've added so far for some form of atomic loading. for index, command in enumerate(self.__cog_commands__): - if hasattr(command, "add_to"): + if _is_bridge_command(command): bot.bridge_commands.append(command) continue @@ -613,7 +618,7 @@ def _eject(self, bot) -> None: try: for command in self.__cog_commands__: - if hasattr(command, "add_to"): + if _is_bridge_command(command): bot.bridge_commands.remove(command) continue elif isinstance(command, ApplicationCommand): diff --git a/discord/ext/bridge/core.py b/discord/ext/bridge/core.py index 9dc58fb009..ff3a3a8d63 100644 --- a/discord/ext/bridge/core.py +++ b/discord/ext/bridge/core.py @@ -184,6 +184,8 @@ class BridgeCommand: The prefix-based version of this bridge command. """ + __bridge__: bool = True + __special_attrs__ = ["slash_variant", "ext_variant", "parent"] def __init__(self, callback, **kwargs): From 27879667dbf481a8e8a10e35fdff8bb48bacd9d2 Mon Sep 17 00:00:00 2001 From: Paillat Date: Tue, 4 Mar 2025 14:32:16 +0100 Subject: [PATCH 03/12] :recycle: Extract name validation logic to helper method `_validate_name_prefix` --- discord/cog.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/discord/cog.py b/discord/cog.py index 64ce735afc..11e04ccf91 100644 --- a/discord/cog.py +++ b/discord/cog.py @@ -87,6 +87,13 @@ def _name_filter(c: Any) -> str: ) +def _validate_name_prefix(base_class: type, name: str) -> None: + if name.startswith(("cog_", "bot_")): + raise TypeError( + f"Commands or listeners must not start with cog_ or bot_ (in method {base_class}.{name})" + ) + + class CogMeta(type): """A metaclass for defining a cog. @@ -176,10 +183,6 @@ def __new__(cls: type[CogMeta], *args: Any, **kwargs: Any) -> CogMeta: commands = {} listeners = {} - no_bot_cog = ( - "Commands or listeners must not start with cog_ or bot_ (in method" - " {0.__name__}.{1})" - ) new_cls = super().__new__(cls, name, bases, attrs, **kwargs) @@ -204,7 +207,8 @@ def __new__(cls: type[CogMeta], *args: Any, **kwargs: Any) -> CogMeta: if getattr(value, "parent", None) and isinstance( value, ApplicationCommand ): - # Skip commands if they are a part of a group + # Skip application commands if they are a part of a group + # Since they are already added when the group is added continue is_static_method = isinstance(value, staticmethod) @@ -216,8 +220,7 @@ def __new__(cls: type[CogMeta], *args: Any, **kwargs: Any) -> CogMeta: f"Command in method {base}.{elem!r} must not be" " staticmethod." ) - if elem.startswith(("cog_", "bot_")): - raise TypeError(no_bot_cog.format(base, elem)) + _validate_name_prefix(base, elem) commands[elem] = value if _is_bridge_command(value) and not value.parent: @@ -226,8 +229,7 @@ def __new__(cls: type[CogMeta], *args: Any, **kwargs: Any) -> CogMeta: f"Command in method {base}.{elem!r} must not be" " staticmethod." ) - if elem.startswith(("cog_", "bot_")): - raise TypeError(no_bot_cog.format(base, elem)) + _validate_name_prefix(base, elem) commands[f"ext_{elem}"] = value.ext_variant commands[f"app_{elem}"] = value.slash_variant @@ -243,8 +245,7 @@ def __new__(cls: type[CogMeta], *args: Any, **kwargs: Any) -> CogMeta: except AttributeError: continue else: - if elem.startswith(("cog_", "bot_")): - raise TypeError(no_bot_cog.format(base, elem)) + _validate_name_prefix(base, elem) listeners[elem] = value new_cls.__cog_commands__ = list(commands.values()) From 136acc56f0cc947758ff1fa26c362291e641ccdd Mon Sep 17 00:00:00 2001 From: Paillat Date: Tue, 4 Mar 2025 14:51:35 +0100 Subject: [PATCH 04/12] :recycle: Rename `elem, value` to `attr_name, attr_value` --- discord/cog.py | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/discord/cog.py b/discord/cog.py index 11e04ccf91..e2044c5453 100644 --- a/discord/cog.py +++ b/discord/cog.py @@ -198,55 +198,55 @@ def __new__(cls: type[CogMeta], *args: Any, **kwargs: Any) -> CogMeta: _filter = _BaseCommand for base in reversed(new_cls.__mro__): - for elem, value in base.__dict__.items(): - if elem in commands: - del commands[elem] - if elem in listeners: - del listeners[elem] - - if getattr(value, "parent", None) and isinstance( - value, ApplicationCommand + for attr_name, attr_value in base.__dict__.items(): + if attr_name in commands: + del commands[attr_name] + if attr_name in listeners: + del listeners[attr_name] + + if getattr(attr_value, "parent", None) and isinstance( + attr_value, ApplicationCommand ): # Skip application commands if they are a part of a group # Since they are already added when the group is added continue - is_static_method = isinstance(value, staticmethod) + is_static_method = isinstance(attr_value, staticmethod) if is_static_method: - value = value.__func__ - if isinstance(value, _filter): + attr_value = attr_value.__func__ + if isinstance(attr_value, _filter): if is_static_method: raise TypeError( - f"Command in method {base}.{elem!r} must not be" + f"Command in method {base}.{attr_name!r} must not be" " staticmethod." ) - _validate_name_prefix(base, elem) - commands[elem] = value + _validate_name_prefix(base, attr_name) + commands[attr_name] = attr_value - if _is_bridge_command(value) and not value.parent: + if _is_bridge_command(attr_value) and not attr_value.parent: if is_static_method: raise TypeError( - f"Command in method {base}.{elem!r} must not be" + f"Command in method {base}.{attr_name!r} must not be" " staticmethod." ) - _validate_name_prefix(base, elem) + _validate_name_prefix(base, attr_name) - commands[f"ext_{elem}"] = value.ext_variant - commands[f"app_{elem}"] = value.slash_variant - commands[elem] = value - for cmd in getattr(value, "subcommands", []): + commands[f"ext_{attr_name}"] = attr_value.ext_variant + commands[f"app_{attr_name}"] = attr_value.slash_variant + commands[attr_name] = attr_value + for cmd in getattr(attr_value, "subcommands", []): commands[f"ext_{cmd.ext_variant.qualified_name}"] = ( cmd.ext_variant ) - if inspect.iscoroutinefunction(value): + if inspect.iscoroutinefunction(attr_value): try: - getattr(value, "__cog_listener__") + getattr(attr_value, "__cog_listener__") except AttributeError: continue else: - _validate_name_prefix(base, elem) - listeners[elem] = value + _validate_name_prefix(base, attr_name) + listeners[attr_name] = attr_value new_cls.__cog_commands__ = list(commands.values()) From 14a343e135e5af57d0ab8ef88e4b7c4155121ccd Mon Sep 17 00:00:00 2001 From: Paillat Date: Tue, 4 Mar 2025 15:50:01 +0100 Subject: [PATCH 05/12] :recycle: Extract attributes processing to `_process_attributes` and simplify its logic --- discord/cog.py | 128 +++++++++++++++++++++++-------------------------- 1 file changed, 59 insertions(+), 69 deletions(-) diff --git a/discord/cog.py b/discord/cog.py index e2044c5453..086274a8be 100644 --- a/discord/cog.py +++ b/discord/cog.py @@ -55,8 +55,7 @@ ) if TYPE_CHECKING: - from .ext.bridge import BridgeCommand, BridgeCommandGroup - from .ext.commands import Command + from .ext.bridge import BridgeCommand __all__ = ( @@ -94,6 +93,58 @@ def _validate_name_prefix(base_class: type, name: str) -> None: ) +def _process_attributes(base: type) -> tuple[dict[str, Any], dict[str, Any]]: + commands: dict[str, _BaseCommand | BridgeCommand] = {} + listeners: dict[str, Callable[..., Any]] = {} + + for attr_name, attr_value in base.__dict__.items(): + if attr_name in commands: + del commands[attr_name] + if attr_name in listeners: + del listeners[attr_name] + + if getattr(attr_value, "parent", None) and isinstance( + attr_value, ApplicationCommand + ): + # Skip application commands if they are a part of a group + # Since they are already added when the group is added + continue + + if inspect.iscoroutinefunction(attr_value) and getattr( + attr_value, "__cog_listener__", False + ): + _validate_name_prefix(base, attr_name) + listeners[attr_name] = attr_value + continue + + is_static_method = isinstance(attr_value, staticmethod) + if is_static_method: + attr_value = attr_value.__func__ + + if isinstance(attr_value, _BaseCommand): + if is_static_method: + raise TypeError( + f"Command in method {base}.{attr_name!r} must not be staticmethod." + ) + _validate_name_prefix(base, attr_name) + commands[attr_name] = attr_value + + if _is_bridge_command(attr_value) and not attr_value.parent: + if is_static_method: + raise TypeError( + f"Command in method {base}.{attr_name!r} must not be staticmethod." + ) + _validate_name_prefix(base, attr_name) + + commands[f"ext_{attr_name}"] = attr_value.ext_variant + commands[f"app_{attr_name}"] = attr_value.slash_variant + commands[attr_name] = attr_value + for cmd in getattr(attr_value, "subcommands", []): + commands[f"ext_{cmd.ext_variant.qualified_name}"] = cmd.ext_variant + + return commands, listeners + + class CogMeta(type): """A metaclass for defining a cog. @@ -162,11 +213,7 @@ async def bar(self, ctx): __cog_name__: str __cog_settings__: dict[str, Any] - __cog_commands__: list[ - ApplicationCommand # pyright: ignore[reportMissingTypeArgument] - | Command # pyright: ignore[reportMissingTypeArgument] - | BridgeCommand - ] + __cog_commands__: list[_BaseCommand | BridgeCommand] __cog_listeners__: list[tuple[str, str]] __cog_guild_ids__: list[int] @@ -181,72 +228,15 @@ def __new__(cls: type[CogMeta], *args: Any, **kwargs: Any) -> CogMeta: description = inspect.cleandoc(attrs.get("__doc__", "")) attrs["__cog_description__"] = description - commands = {} - listeners = {} + commands: dict[str, _BaseCommand | BridgeCommand] = {} + listeners: dict[str, Callable[..., Any]] = {} new_cls = super().__new__(cls, name, bases, attrs, **kwargs) - valid_commands = [ - (c for i, c in j.__dict__.items() if isinstance(c, _BaseCommand)) - for j in reversed(new_cls.__mro__) - ] - if any(isinstance(i, ApplicationCommand) for i in valid_commands) and any( - not isinstance(i, _BaseCommand) for i in valid_commands - ): - _filter = ApplicationCommand - else: - _filter = _BaseCommand - for base in reversed(new_cls.__mro__): - for attr_name, attr_value in base.__dict__.items(): - if attr_name in commands: - del commands[attr_name] - if attr_name in listeners: - del listeners[attr_name] - - if getattr(attr_value, "parent", None) and isinstance( - attr_value, ApplicationCommand - ): - # Skip application commands if they are a part of a group - # Since they are already added when the group is added - continue - - is_static_method = isinstance(attr_value, staticmethod) - if is_static_method: - attr_value = attr_value.__func__ - if isinstance(attr_value, _filter): - if is_static_method: - raise TypeError( - f"Command in method {base}.{attr_name!r} must not be" - " staticmethod." - ) - _validate_name_prefix(base, attr_name) - commands[attr_name] = attr_value - - if _is_bridge_command(attr_value) and not attr_value.parent: - if is_static_method: - raise TypeError( - f"Command in method {base}.{attr_name!r} must not be" - " staticmethod." - ) - _validate_name_prefix(base, attr_name) - - commands[f"ext_{attr_name}"] = attr_value.ext_variant - commands[f"app_{attr_name}"] = attr_value.slash_variant - commands[attr_name] = attr_value - for cmd in getattr(attr_value, "subcommands", []): - commands[f"ext_{cmd.ext_variant.qualified_name}"] = ( - cmd.ext_variant - ) - - if inspect.iscoroutinefunction(attr_value): - try: - getattr(attr_value, "__cog_listener__") - except AttributeError: - continue - else: - _validate_name_prefix(base, attr_name) - listeners[attr_name] = attr_value + new_commands, new_listeners = _process_attributes(base) + commands.update(new_commands) + listeners.update(new_listeners) new_cls.__cog_commands__ = list(commands.values()) From e593b5b5767f8bda92effd3dbd60fcf659dd46e3 Mon Sep 17 00:00:00 2001 From: Paillat Date: Tue, 4 Mar 2025 15:52:29 +0100 Subject: [PATCH 06/12] :bug: Fix wrong order broke static listeners --- discord/cog.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/discord/cog.py b/discord/cog.py index 086274a8be..3203ef2bd3 100644 --- a/discord/cog.py +++ b/discord/cog.py @@ -110,6 +110,10 @@ def _process_attributes(base: type) -> tuple[dict[str, Any], dict[str, Any]]: # Since they are already added when the group is added continue + is_static_method = isinstance(attr_value, staticmethod) + if is_static_method: + attr_value = attr_value.__func__ + if inspect.iscoroutinefunction(attr_value) and getattr( attr_value, "__cog_listener__", False ): @@ -117,10 +121,6 @@ def _process_attributes(base: type) -> tuple[dict[str, Any], dict[str, Any]]: listeners[attr_name] = attr_value continue - is_static_method = isinstance(attr_value, staticmethod) - if is_static_method: - attr_value = attr_value.__func__ - if isinstance(attr_value, _BaseCommand): if is_static_method: raise TypeError( From bffa500126cb5c78d05486a904fb2231c02202c2 Mon Sep 17 00:00:00 2001 From: Paillat Date: Tue, 4 Mar 2025 16:40:52 +0100 Subject: [PATCH 07/12] :recycle: Use a list comprehension for __cog_listeners__ --- discord/cog.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/discord/cog.py b/discord/cog.py index 3203ef2bd3..12526f761a 100644 --- a/discord/cog.py +++ b/discord/cog.py @@ -240,14 +240,11 @@ def __new__(cls: type[CogMeta], *args: Any, **kwargs: Any) -> CogMeta: new_cls.__cog_commands__ = list(commands.values()) - listeners_as_list = [] - for listener in listeners.values(): - for listener_name in listener.__cog_listener_names__: - # I use __name__ instead of just storing the value, so I can inject - # the self attribute when the time comes to add them to the bot - listeners_as_list.append((listener_name, listener.__name__)) - - new_cls.__cog_listeners__ = listeners_as_list + new_cls.__cog_listeners__ = [ + (listener_name, listener.__name__) + for listener in listeners.values() + for listener_name in listener.__cog_listener_names__ + ] cmd_attrs = new_cls.__cog_settings__ From c916dbf439ff9a6bcf5eeacfc4732a5fc067e3fa Mon Sep 17 00:00:00 2001 From: Paillat Date: Tue, 4 Mar 2025 16:43:18 +0100 Subject: [PATCH 08/12] :recycle: Extract command update logic to `_update_command` --- discord/cog.py | 73 +++++++++++++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 31 deletions(-) diff --git a/discord/cog.py b/discord/cog.py index 12526f761a..f71590b9e7 100644 --- a/discord/cog.py +++ b/discord/cog.py @@ -93,7 +93,9 @@ def _validate_name_prefix(base_class: type, name: str) -> None: ) -def _process_attributes(base: type) -> tuple[dict[str, Any], dict[str, Any]]: +def _process_attributes( + base: type, +) -> tuple[dict[str, Any], dict[str, Any]]: # pyright: ignore[reportExplicitAny] commands: dict[str, _BaseCommand | BridgeCommand] = {} listeners: dict[str, Callable[..., Any]] = {} @@ -145,6 +147,43 @@ def _process_attributes(base: type) -> tuple[dict[str, Any], dict[str, Any]]: return commands, listeners +def _update_command( + command: _BaseCommand | BridgeCommand, + guild_ids: list[int], + lookup_table: dict[str, _BaseCommand | BridgeCommand], + new_cls: type[Cog], +) -> None: + if isinstance(command, ApplicationCommand) and not command.guild_ids and guild_ids: + command.guild_ids = guild_ids + + if not isinstance(command, SlashCommandGroup) and not _is_bridge_command(command): + # ignore bridge commands + cmd: BridgeCommand | _BaseCommand | None = getattr( + new_cls, command.callback.__name__, None + ) # pyright: ignore[reportUnknownMemberType,reportUnknownArgumentType,reportAttributeAccessIssue,reportAssignmentType] + if _is_bridge_command(cmd): + setattr( + cmd, + f"{_name_filter(command).replace('app', 'slash')}_variant", + command, + ) + else: + setattr( + new_cls, command.callback.__name__, command + ) # pyright: ignore[reportUnknownMemberType,reportUnknownArgumentType,reportAttributeAccessIssue] + + parent: BridgeCommand | _BaseCommand | None = ( + command.parent + ) # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType,reportAttributeAccessIssue] + if parent is not None: + # Get the latest parent reference + parent = lookup_table[f"{_name_filter(command)}_{parent.qualified_name}"] # type: ignore # pyright: ignore[reportUnknownMemberType] + + # Update our parent's reference to our self + parent.remove_command(command.name) # type: ignore # pyright: ignore[reportAttributeAccessIssue, reportUnknownMemberType] + parent.add_command(command) # type: ignore # pyright: ignore[reportAttributeAccessIssue, reportUnknownMemberType] + + class CogMeta(type): """A metaclass for defining a cog. @@ -250,7 +289,7 @@ def __new__(cls: type[CogMeta], *args: Any, **kwargs: Any) -> CogMeta: # Either update the command with the cog provided defaults or copy it. # r.e type ignore, type-checker complains about overriding a ClassVar - new_cls.__cog_commands__ = tuple(c._update_copy(cmd_attrs) if not _is_bridge_command(c) else c for c in new_cls.__cog_commands__) # type: ignore + new_cls.__cog_commands__ = list(tuple(c._update_copy(cmd_attrs) if not _is_bridge_command(c) else c for c in new_cls.__cog_commands__)) # type: ignore lookup = { f"{_name_filter(cmd)}_{cmd.qualified_name}": cmd @@ -259,35 +298,7 @@ def __new__(cls: type[CogMeta], *args: Any, **kwargs: Any) -> CogMeta: # Update the Command instances dynamically as well for command in new_cls.__cog_commands__: - if ( - isinstance(command, ApplicationCommand) - and not command.guild_ids - and new_cls.__cog_guild_ids__ - ): - command.guild_ids = new_cls.__cog_guild_ids__ - - if not isinstance(command, SlashCommandGroup) and not _is_bridge_command( - command - ): - # ignore bridge commands - cmd = getattr(new_cls, command.callback.__name__, None) - if _is_bridge_command(cmd): - setattr( - cmd, - f"{_name_filter(command).replace('app', 'slash')}_variant", - command, - ) - else: - setattr(new_cls, command.callback.__name__, command) - - parent = command.parent - if parent is not None: - # Get the latest parent reference - parent = lookup[f"{_name_filter(command)}_{parent.qualified_name}"] # type: ignore - - # Update our parent's reference to our self - parent.remove_command(command.name) # type: ignore - parent.add_command(command) # type: ignore + _update_command(command, new_cls.__cog_guild_ids__, lookup, new_cls) return new_cls From ffbb147d5c272668b005b56fe36710539d930a48 Mon Sep 17 00:00:00 2001 From: Paillat Date: Tue, 4 Mar 2025 16:56:33 +0100 Subject: [PATCH 09/12] :recycle: Avoid repeating staticmethod check --- discord/cog.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/discord/cog.py b/discord/cog.py index f71590b9e7..4f62099e70 100644 --- a/discord/cog.py +++ b/discord/cog.py @@ -123,21 +123,17 @@ def _process_attributes( listeners[attr_name] = attr_value continue - if isinstance(attr_value, _BaseCommand): + if isinstance(attr_value, _BaseCommand) or _is_bridge_command(attr_value): if is_static_method: raise TypeError( f"Command in method {base}.{attr_name!r} must not be staticmethod." ) _validate_name_prefix(base, attr_name) + + if isinstance(attr_value, _BaseCommand): commands[attr_name] = attr_value if _is_bridge_command(attr_value) and not attr_value.parent: - if is_static_method: - raise TypeError( - f"Command in method {base}.{attr_name!r} must not be staticmethod." - ) - _validate_name_prefix(base, attr_name) - commands[f"ext_{attr_name}"] = attr_value.ext_variant commands[f"app_{attr_name}"] = attr_value.slash_variant commands[attr_name] = attr_value From 231211e60e56a909b07016eb969c2aa3d8cd42df Mon Sep 17 00:00:00 2001 From: Paillat-dev Date: Sun, 9 Mar 2025 18:46:39 +0100 Subject: [PATCH 10/12] :recycle: Import `Generator` & `Mapping` from `collections.abc` instead of `typing` Deprecated since python 3.9, py-cord supports 3.9+ --- discord/cog.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/discord/cog.py b/discord/cog.py index 4f62099e70..30598fe88c 100644 --- a/discord/cog.py +++ b/discord/cog.py @@ -31,13 +31,12 @@ import pathlib import sys import types +from collections.abc import Generator, Mapping from typing import ( TYPE_CHECKING, Any, Callable, ClassVar, - Generator, - Mapping, TypeVar, overload, ) From faee08001a36934b2ff3863de011c56fbd3f6e47 Mon Sep 17 00:00:00 2001 From: Paillat-dev Date: Sun, 9 Mar 2025 19:05:32 +0100 Subject: [PATCH 11/12] :label: Fix ignore comments --- discord/cog.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/discord/cog.py b/discord/cog.py index 30598fe88c..202e4fd9e5 100644 --- a/discord/cog.py +++ b/discord/cog.py @@ -154,8 +154,10 @@ def _update_command( if not isinstance(command, SlashCommandGroup) and not _is_bridge_command(command): # ignore bridge commands cmd: BridgeCommand | _BaseCommand | None = getattr( - new_cls, command.callback.__name__, None - ) # pyright: ignore[reportUnknownMemberType,reportUnknownArgumentType,reportAttributeAccessIssue,reportAssignmentType] + new_cls, + command.callback.__name__, + None, # pyright: ignore[reportUnknownMemberType,reportUnknownArgumentType,reportAttributeAccessIssue] + ) if _is_bridge_command(cmd): setattr( cmd, @@ -164,12 +166,16 @@ def _update_command( ) else: setattr( - new_cls, command.callback.__name__, command - ) # pyright: ignore[reportUnknownMemberType,reportUnknownArgumentType,reportAttributeAccessIssue] + new_cls, + command.callback.__name__, + command, # pyright: ignore [reportAttributeAccessIssue, reportUnknownArgumentType, reportUnknownMemberType] + ) - parent: BridgeCommand | _BaseCommand | None = ( - command.parent - ) # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType,reportAttributeAccessIssue] + parent: ( + BridgeCommand | _BaseCommand | None + ) = ( # pyright: ignore [reportUnknownMemberType, reportUnknownVariableType] + command.parent # pyright: ignore [reportAttributeAccessIssue] + ) if parent is not None: # Get the latest parent reference parent = lookup_table[f"{_name_filter(command)}_{parent.qualified_name}"] # type: ignore # pyright: ignore[reportUnknownMemberType] From aed9e197fc7a09315b98389d9f35dd74aba54893 Mon Sep 17 00:00:00 2001 From: Paillat-dev Date: Sun, 9 Mar 2025 19:10:31 +0100 Subject: [PATCH 12/12] :memo: Change comment wording --- discord/cog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/cog.py b/discord/cog.py index 202e4fd9e5..5fbe6e61a4 100644 --- a/discord/cog.py +++ b/discord/cog.py @@ -180,7 +180,7 @@ def _update_command( # Get the latest parent reference parent = lookup_table[f"{_name_filter(command)}_{parent.qualified_name}"] # type: ignore # pyright: ignore[reportUnknownMemberType] - # Update our parent's reference to our self + # Update the parent's reference to our self parent.remove_command(command.name) # type: ignore # pyright: ignore[reportAttributeAccessIssue, reportUnknownMemberType] parent.add_command(command) # type: ignore # pyright: ignore[reportAttributeAccessIssue, reportUnknownMemberType]