From 563a1ef108eea079b293cff461e2e3c79c04f8de Mon Sep 17 00:00:00 2001 From: Soheab <33902984+Soheab@users.noreply.github.com> Date: Fri, 31 Oct 2025 00:24:48 +0100 Subject: [PATCH 1/4] feat(ui): set_callback method --- discord/ui/item.py | 51 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/discord/ui/item.py b/discord/ui/item.py index 958f60a7a7..e8623ece0d 100644 --- a/discord/ui/item.py +++ b/discord/ui/item.py @@ -27,6 +27,8 @@ from typing import TYPE_CHECKING, Any, Callable, Coroutine, Generic, TypeVar +import inspect + from ..interactions import Interaction __all__ = ( @@ -48,6 +50,26 @@ M = TypeVar("M", bound="BaseModal", covariant=True) ItemCallbackType = Callable[[Any, I, Interaction], Coroutine[Any, Any, Any]] +SetItemCallbackType_Interaction = Callable[[Interaction], Coroutine[Any, Any, Any]] +SetItemCallbackType_InteractionView = Callable[[Interaction, V], Coroutine[Any, Any, Any]] +SetItemCallbackType_InteractionViewItem = Callable[[Interaction, V, I], Coroutine[Any, Any, Any]] + +SetItemCallbackType = SetItemCallbackType_Interaction | SetItemCallbackType_InteractionView | SetItemCallbackType_InteractionViewItem + + +class _ProxyItemCallback: + def __init__(self, func: SetItemCallbackType, item: ViewItem, parameters: int) -> None: + self.func: SetItemCallbackType = func + self.item: ViewItem = item + self.parameters: int = parameters + + def __call__(self, interaction: Interaction) -> Coroutine[Any, Any, Any]: + args: list[Any] = [interaction] + if self.parameters >= 2: + args.append(self.item.view) + if self.parameters >= 3: + args.append(self.item) + return self.func(*args) class Item(Generic[T]): """Represents the base UI item that all UI components inherit from. @@ -227,6 +249,35 @@ async def callback(self, interaction: Interaction): The interaction that triggered this UI item. """ + def set_callback(self, func: SetItemCallbackType, /) -> None: + """Sets the callback for this item. + + Parameters + ---------- + func: Callable[..., Coroutine[Any, Any, Any]] + The callback function to set. + + This function must be a coroutine that accepts 1 to 3 parameters: + + - :class:`.Interaction`, this will always be passed. + - :class:`.BaseView`, this will be passed if the function accepts 2 or 3 parameters. + - :class:`.Item`, this will be passed if the function accepts 3 parameters. + + Raises + ------ + TypeError + If the provided function is not a coroutine or does not have the correct number of parameters. + """ + if not inspect.iscoroutinefunction(func): + raise TypeError("callback must be a coroutine function") + + params = inspect.signature(func).parameters + if len(params) < 1 or len(params) > 3: + raise TypeError("callback must accept 1 to 3 parameters") + + + self.callback = _ProxyItemCallback(func, self, len(params)) # type: ignore + class ModalItem(Item[M]): """Represents an item used in Modals. From c796e91324f683b368089da8f2948619f15fb2f4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 30 Oct 2025 23:27:08 +0000 Subject: [PATCH 2/4] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/ui/item.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/discord/ui/item.py b/discord/ui/item.py index e8623ece0d..cda0beb242 100644 --- a/discord/ui/item.py +++ b/discord/ui/item.py @@ -25,9 +25,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Callable, Coroutine, Generic, TypeVar - import inspect +from typing import TYPE_CHECKING, Any, Callable, Coroutine, Generic, TypeVar from ..interactions import Interaction @@ -51,14 +50,24 @@ ItemCallbackType = Callable[[Any, I, Interaction], Coroutine[Any, Any, Any]] SetItemCallbackType_Interaction = Callable[[Interaction], Coroutine[Any, Any, Any]] -SetItemCallbackType_InteractionView = Callable[[Interaction, V], Coroutine[Any, Any, Any]] -SetItemCallbackType_InteractionViewItem = Callable[[Interaction, V, I], Coroutine[Any, Any, Any]] - -SetItemCallbackType = SetItemCallbackType_Interaction | SetItemCallbackType_InteractionView | SetItemCallbackType_InteractionViewItem +SetItemCallbackType_InteractionView = Callable[ + [Interaction, V], Coroutine[Any, Any, Any] +] +SetItemCallbackType_InteractionViewItem = Callable[ + [Interaction, V, I], Coroutine[Any, Any, Any] +] + +SetItemCallbackType = ( + SetItemCallbackType_Interaction + | SetItemCallbackType_InteractionView + | SetItemCallbackType_InteractionViewItem +) class _ProxyItemCallback: - def __init__(self, func: SetItemCallbackType, item: ViewItem, parameters: int) -> None: + def __init__( + self, func: SetItemCallbackType, item: ViewItem, parameters: int + ) -> None: self.func: SetItemCallbackType = func self.item: ViewItem = item self.parameters: int = parameters @@ -71,6 +80,7 @@ def __call__(self, interaction: Interaction) -> Coroutine[Any, Any, Any]: args.append(self.item) return self.func(*args) + class Item(Generic[T]): """Represents the base UI item that all UI components inherit from. @@ -270,13 +280,12 @@ def set_callback(self, func: SetItemCallbackType, /) -> None: """ if not inspect.iscoroutinefunction(func): raise TypeError("callback must be a coroutine function") - + params = inspect.signature(func).parameters if len(params) < 1 or len(params) > 3: raise TypeError("callback must accept 1 to 3 parameters") - - self.callback = _ProxyItemCallback(func, self, len(params)) # type: ignore + self.callback = _ProxyItemCallback(func, self, len(params)) # type: ignore class ModalItem(Item[M]): From 4003db1dd28b5960c62e3b2d491e48d8eb11a3e6 Mon Sep 17 00:00:00 2001 From: Soheab <33902984+Soheab@users.noreply.github.com> Date: Thu, 6 Nov 2025 00:42:38 +0100 Subject: [PATCH 3/4] Do actual impl --- discord/ui/item.py | 59 +++++++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/discord/ui/item.py b/discord/ui/item.py index cda0beb242..b426220163 100644 --- a/discord/ui/item.py +++ b/discord/ui/item.py @@ -24,9 +24,17 @@ """ from __future__ import annotations +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Coroutine, + Generic, + TypeVar, +) +from typing_extensions import Self import inspect -from typing import TYPE_CHECKING, Any, Callable, Coroutine, Generic, TypeVar from ..interactions import Interaction @@ -43,42 +51,39 @@ from .modal import BaseModal from .view import BaseView -I = TypeVar("I", bound="Item") +I = TypeVar("I", bound="Item", covariant=True) + T = TypeVar("T", bound="ItemInterface", covariant=True) V = TypeVar("V", bound="BaseView", covariant=True) M = TypeVar("M", bound="BaseModal", covariant=True) -ItemCallbackType = Callable[[Any, I, Interaction], Coroutine[Any, Any, Any]] -SetItemCallbackType_Interaction = Callable[[Interaction], Coroutine[Any, Any, Any]] -SetItemCallbackType_InteractionView = Callable[ - [Interaction, V], Coroutine[Any, Any, Any] -] -SetItemCallbackType_InteractionViewItem = Callable[ - [Interaction, V, I], Coroutine[Any, Any, Any] -] +V_co = TypeVar("V_co", bound="BaseView", covariant=True) +ItemCallbackType = Callable[[Any, I, Interaction], Coroutine[Any, Any, Any]] SetItemCallbackType = ( - SetItemCallbackType_Interaction - | SetItemCallbackType_InteractionView - | SetItemCallbackType_InteractionViewItem + Callable[[Interaction], Coroutine[object, Any, Any]] + | Callable[[Interaction, I], Coroutine[object, Any, Any]] + | Callable[[Interaction, I, V], Coroutine[object, Any, Any]] ) class _ProxyItemCallback: def __init__( - self, func: SetItemCallbackType, item: ViewItem, parameters: int + self, func: SetItemCallbackType, item: ViewItem, parameters_amount: int ) -> None: self.func: SetItemCallbackType = func self.item: ViewItem = item - self.parameters: int = parameters + self.parameters_amount: int = parameters_amount def __call__(self, interaction: Interaction) -> Coroutine[Any, Any, Any]: - args: list[Any] = [interaction] - if self.parameters >= 2: - args.append(self.item.view) - if self.parameters >= 3: - args.append(self.item) - return self.func(*args) + if self.parameters_amount == 1: + return self.func(interaction) # type: ignore # type checker doesn't like optional params + elif self.parameters_amount == 2: + return self.func(interaction, self.item) # type: ignore # type checker doesn't like optional params + elif self.parameters_amount == 3: + return self.func(interaction, self.item, self.item.view) # type: ignore # type checker doesn't like optional params + else: + raise TypeError("callback must accept 1 to 3 parameters") class Item(Generic[T]): @@ -156,7 +161,7 @@ def id(self, value) -> None: self._underlying.id = value -class ViewItem(Item[V]): +class ViewItem(Item[V_co], Generic[V_co]): """Represents an item used in Views. The following are the original items supported in :class:`discord.ui.View`: @@ -181,7 +186,7 @@ class ViewItem(Item[V]): def __init__(self): super().__init__() - self._view: V | None = None + self._view: V_co | None = None self._row: int | None = None self._rendered_row: int | None = None self.parent: ViewItem | BaseView | None = self.view @@ -229,7 +234,7 @@ def width(self) -> int: return 1 @property - def view(self) -> V | None: + def view(self) -> V_co | None: """Gets the parent view associated with this item. The view refers to the structure that holds this item. This is typically set @@ -259,12 +264,12 @@ async def callback(self, interaction: Interaction): The interaction that triggered this UI item. """ - def set_callback(self, func: SetItemCallbackType, /) -> None: + def set_callback(self, func: SetItemCallbackType[Self, V_co], /) -> None: """Sets the callback for this item. Parameters ---------- - func: Callable[..., Coroutine[Any, Any, Any]] + func The callback function to set. This function must be a coroutine that accepts 1 to 3 parameters: @@ -282,7 +287,7 @@ def set_callback(self, func: SetItemCallbackType, /) -> None: raise TypeError("callback must be a coroutine function") params = inspect.signature(func).parameters - if len(params) < 1 or len(params) > 3: + if len(params) > 3: raise TypeError("callback must accept 1 to 3 parameters") self.callback = _ProxyItemCallback(func, self, len(params)) # type: ignore From dce9c8059961360b0a4a11962af51ad2c6f0327c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 5 Nov 2025 23:43:11 +0000 Subject: [PATCH 4/4] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/ui/item.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/discord/ui/item.py b/discord/ui/item.py index b426220163..491a8df344 100644 --- a/discord/ui/item.py +++ b/discord/ui/item.py @@ -24,6 +24,8 @@ """ from __future__ import annotations + +import inspect from typing import ( TYPE_CHECKING, Any, @@ -32,9 +34,8 @@ Generic, TypeVar, ) -from typing_extensions import Self -import inspect +from typing_extensions import Self from ..interactions import Interaction @@ -77,11 +78,11 @@ def __init__( def __call__(self, interaction: Interaction) -> Coroutine[Any, Any, Any]: if self.parameters_amount == 1: - return self.func(interaction) # type: ignore # type checker doesn't like optional params + return self.func(interaction) # type: ignore # type checker doesn't like optional params elif self.parameters_amount == 2: - return self.func(interaction, self.item) # type: ignore # type checker doesn't like optional params + return self.func(interaction, self.item) # type: ignore # type checker doesn't like optional params elif self.parameters_amount == 3: - return self.func(interaction, self.item, self.item.view) # type: ignore # type checker doesn't like optional params + return self.func(interaction, self.item, self.item.view) # type: ignore # type checker doesn't like optional params else: raise TypeError("callback must accept 1 to 3 parameters")