From 9fc0dbf32a3661fdca562b0a42dd03079b1af21b Mon Sep 17 00:00:00 2001 From: DA-344 <108473820+DA-344@users.noreply.github.com> Date: Wed, 10 Sep 2025 20:12:58 +0200 Subject: [PATCH 1/5] fix option not working with str / ForwardRef annotations --- discord/commands/core.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/discord/commands/core.py b/discord/commands/core.py index 90e7c5aa60..b36cb53519 100644 --- a/discord/commands/core.py +++ b/discord/commands/core.py @@ -26,6 +26,7 @@ from __future__ import annotations import asyncio +from collections.abc import Iterator import datetime import functools import inspect @@ -50,9 +51,7 @@ from ..enums import ( IntegrationType, InteractionContextType, - MessageType, SlashCommandOptionType, - try_enum, ) from ..errors import ( ApplicationCommandError, @@ -62,13 +61,12 @@ InvalidArgument, ValidationError, ) -from ..member import Member from ..message import Attachment, Message from ..object import Object from ..role import Role from ..threads import Thread from ..user import User -from ..utils import MISSING, async_all, find, maybe_coroutine, utcnow, warn_deprecated +from ..utils import MISSING, async_all, find, maybe_coroutine, resolve_annotation, utcnow, warn_deprecated from .context import ApplicationContext, AutocompleteContext from .options import Option, OptionChoice @@ -101,7 +99,7 @@ T = TypeVar("T") CogT = TypeVar("CogT", bound="Cog") -Coro = TypeVar("Coro", bound=Callable[..., Coroutine[Any, Any, Any]]) +Coro = Coroutine[Any, Any, T] if TYPE_CHECKING: P = ParamSpec("P") @@ -190,7 +188,7 @@ class ApplicationCommand(_BaseCommand, Generic[CogT, P, T]): cog = None def __init__(self, func: Callable, **kwargs) -> None: - from ..ext.commands.cooldowns import BucketType, CooldownMapping, MaxConcurrency + from ..ext.commands.cooldowns import BucketType, CooldownMapping cooldown = getattr(func, "__commands_cooldown__", kwargs.get("cooldown")) @@ -314,7 +312,7 @@ def guild_only(self) -> bool: "2.6", reference="https://discord.com/developers/docs/change-log#userinstallable-apps-preview", ) - return InteractionContextType.guild in self.contexts and len(self.contexts) == 1 + return self.contexts is not None and InteractionContextType.guild in self.contexts and len(self.contexts) == 1 @guild_only.setter def guild_only(self, value: bool) -> None: @@ -779,33 +777,36 @@ def _validate_parameters(self): else: self.options = self._parse_options(params) - def _check_required_params(self, params): - params = iter(params.items()) + def _check_required_params(self, params: OrderedDict[str, inspect.Parameter]) -> Iterator[tuple[str, inspect.Parameter]]: + params_iter = iter(params.items()) required_params = ( ["self", "context"] if self.attached_to_group or self.cog else ["context"] ) for p in required_params: try: - next(params) + next(params_iter) except StopIteration: raise ClientException( f'Callback for {self.name} command is missing "{p}" parameter.' ) - return params + return params_iter - def _parse_options(self, params, *, check_params: bool = True) -> list[Option]: + def _parse_options(self, params: OrderedDict[str, inspect.Parameter], *, check_params: bool = True) -> list[Option]: if check_params: - params = self._check_required_params(params) + params_iter = self._check_required_params(params) else: - params = iter(params.items()) + params_iter = iter(params.items()) final_options = [] - for p_name, p_obj in params: + cache = {} + for p_name, p_obj in params_iter: option = p_obj.annotation if option == inspect.Parameter.empty: option = str + option = resolve_annotation(option, globals(), locals(), cache) + option = Option._strip_none_type(option) if self._is_typing_literal(option): literal_values = get_args(option) From 694de6742ceba27fa5dbfab20e33173746b9e5f9 Mon Sep 17 00:00:00 2001 From: DA-344 <108473820+DA-344@users.noreply.github.com> Date: Wed, 10 Sep 2025 20:15:43 +0200 Subject: [PATCH 2/5] changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2225c67d10..d22e1f3835 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,8 @@ These changes are available on the `master` branch, but have not yet been releas ([#2905](https://github.com/Pycord-Development/pycord/pull/2905)) - `view=None` in various methods causing an AttributeError. ([#2915](https://github.com/Pycord-Development/pycord/pull/2915)) +- Commands not properly parsing string-like / forward references annotations. + ([#2919](https://github.com/Pycord-Development/pycord/pull/2919)) ### Removed From 77ec0cf15486e74d418c2e52c2e9147200da4927 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 10 Sep 2025 18:16:44 +0000 Subject: [PATCH 3/5] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/commands/core.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/discord/commands/core.py b/discord/commands/core.py index b36cb53519..6b487452d7 100644 --- a/discord/commands/core.py +++ b/discord/commands/core.py @@ -26,7 +26,6 @@ from __future__ import annotations import asyncio -from collections.abc import Iterator import datetime import functools import inspect @@ -34,6 +33,7 @@ import sys import types from collections import OrderedDict +from collections.abc import Iterator from enum import Enum from typing import ( TYPE_CHECKING, @@ -66,7 +66,15 @@ from ..role import Role from ..threads import Thread from ..user import User -from ..utils import MISSING, async_all, find, maybe_coroutine, resolve_annotation, utcnow, warn_deprecated +from ..utils import ( + MISSING, + async_all, + find, + maybe_coroutine, + resolve_annotation, + utcnow, + warn_deprecated, +) from .context import ApplicationContext, AutocompleteContext from .options import Option, OptionChoice @@ -312,7 +320,11 @@ def guild_only(self) -> bool: "2.6", reference="https://discord.com/developers/docs/change-log#userinstallable-apps-preview", ) - return self.contexts is not None and InteractionContextType.guild in self.contexts and len(self.contexts) == 1 + return ( + self.contexts is not None + and InteractionContextType.guild in self.contexts + and len(self.contexts) == 1 + ) @guild_only.setter def guild_only(self, value: bool) -> None: @@ -777,7 +789,9 @@ def _validate_parameters(self): else: self.options = self._parse_options(params) - def _check_required_params(self, params: OrderedDict[str, inspect.Parameter]) -> Iterator[tuple[str, inspect.Parameter]]: + def _check_required_params( + self, params: OrderedDict[str, inspect.Parameter] + ) -> Iterator[tuple[str, inspect.Parameter]]: params_iter = iter(params.items()) required_params = ( ["self", "context"] if self.attached_to_group or self.cog else ["context"] @@ -792,7 +806,9 @@ def _check_required_params(self, params: OrderedDict[str, inspect.Parameter]) -> return params_iter - def _parse_options(self, params: OrderedDict[str, inspect.Parameter], *, check_params: bool = True) -> list[Option]: + def _parse_options( + self, params: OrderedDict[str, inspect.Parameter], *, check_params: bool = True + ) -> list[Option]: if check_params: params_iter = self._check_required_params(params) else: From 0a07638b04a861de5be0d7b9b1536ce9ba58f1aa Mon Sep 17 00:00:00 2001 From: DA-344 <108473820+DA-344@users.noreply.github.com> Date: Thu, 11 Sep 2025 17:08:55 +0200 Subject: [PATCH 4/5] isinstance --- discord/commands/core.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/discord/commands/core.py b/discord/commands/core.py index 6b487452d7..30195912f7 100644 --- a/discord/commands/core.py +++ b/discord/commands/core.py @@ -821,7 +821,8 @@ def _parse_options( if option == inspect.Parameter.empty: option = str - option = resolve_annotation(option, globals(), locals(), cache) + if isinstance(option, str): + option = resolve_annotation(option, globals(), locals(), cache) option = Option._strip_none_type(option) if self._is_typing_literal(option): From e5c4cbe535cd44464ca7697aba33c195a316074f Mon Sep 17 00:00:00 2001 From: DA-344 <108473820+DA-344@users.noreply.github.com> Date: Sat, 13 Sep 2025 14:03:32 +0200 Subject: [PATCH 5/5] idk --- discord/commands/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/commands/core.py b/discord/commands/core.py index 30195912f7..303e67aa02 100644 --- a/discord/commands/core.py +++ b/discord/commands/core.py @@ -107,7 +107,7 @@ T = TypeVar("T") CogT = TypeVar("CogT", bound="Cog") -Coro = Coroutine[Any, Any, T] +Coro = TypeVar("Coro", bound=Callable[..., Coroutine[Any, Any, Any]]) if TYPE_CHECKING: P = ParamSpec("P") @@ -196,7 +196,7 @@ class ApplicationCommand(_BaseCommand, Generic[CogT, P, T]): cog = None def __init__(self, func: Callable, **kwargs) -> None: - from ..ext.commands.cooldowns import BucketType, CooldownMapping + from ..ext.commands.cooldowns import BucketType, CooldownMapping, MaxConcurrency cooldown = getattr(func, "__commands_cooldown__", kwargs.get("cooldown"))