Skip to content

Commit

Permalink
Merge branch 'master' into fix/modal-timeout-collision
Browse files Browse the repository at this point in the history
  • Loading branch information
shiftinv authored Dec 28, 2024
2 parents 07924ec + 4f6a371 commit a59ba25
Show file tree
Hide file tree
Showing 12 changed files with 113 additions and 143 deletions.
1 change: 1 addition & 0 deletions changelog/1190.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The ``cls`` parameter of UI component decorators (such as :func:`ui.button`) now accepts any matching callable, in addition to item subclasses.
32 changes: 13 additions & 19 deletions disnake/ui/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,16 @@
Callable,
Optional,
Tuple,
Type,
TypeVar,
Union,
get_origin,
overload,
)

from ..components import Button as ButtonComponent
from ..enums import ButtonStyle, ComponentType
from ..partial_emoji import PartialEmoji, _EmojiTag
from ..utils import MISSING
from .item import DecoratedItem, Item, ItemShape
from .item import DecoratedItem, Item

__all__ = (
"Button",
Expand Down Expand Up @@ -263,20 +261,20 @@ def button(
style: ButtonStyle = ButtonStyle.secondary,
emoji: Optional[Union[str, Emoji, PartialEmoji]] = None,
row: Optional[int] = None,
) -> Callable[[ItemCallbackType[Button[V_co]]], DecoratedItem[Button[V_co]]]:
) -> Callable[[ItemCallbackType[V_co, Button[V_co]]], DecoratedItem[Button[V_co]]]:
...


@overload
def button(
cls: Type[ItemShape[B_co, P]], *_: P.args, **kwargs: P.kwargs
) -> Callable[[ItemCallbackType[B_co]], DecoratedItem[B_co]]:
cls: Callable[P, B_co], *_: P.args, **kwargs: P.kwargs
) -> Callable[[ItemCallbackType[V_co, B_co]], DecoratedItem[B_co]]:
...


def button(
cls: Type[ItemShape[B_co, ...]] = Button[Any], **kwargs: Any
) -> Callable[[ItemCallbackType[B_co]], DecoratedItem[B_co]]:
cls: Callable[..., B_co] = Button[Any], **kwargs: Any
) -> Callable[[ItemCallbackType[V_co, B_co]], DecoratedItem[B_co]]:
"""A decorator that attaches a button to a component.
The function being decorated should have three parameters, ``self`` representing
Expand All @@ -293,13 +291,12 @@ def button(
Parameters
----------
cls: Type[:class:`Button`]
The button subclass to create an instance of. If provided, the following parameters
described below do not apply. Instead, this decorator will accept the same keywords
as the passed cls does.
cls: Callable[..., :class:`Button`]
A callable (may be a :class:`Button` subclass) to create a new instance of this component.
If provided, the other parameters described below do not apply.
Instead, this decorator will accept the same keywords as the passed callable/class does.
.. versionadded:: 2.6
label: Optional[:class:`str`]
The label of the button, if any.
custom_id: Optional[:class:`str`]
Expand All @@ -319,13 +316,10 @@ def button(
For example, row=1 will show up before row=2. Defaults to ``None``, which is automatic
ordering. The row number must be between 0 and 4 (i.e. zero indexed).
"""
if (origin := get_origin(cls)) is not None:
cls = origin

if not isinstance(cls, type) or not issubclass(cls, Button):
raise TypeError(f"cls argument must be a subclass of Button, got {cls!r}")
if not callable(cls):
raise TypeError("cls argument must be callable")

def decorator(func: ItemCallbackType[B_co]) -> DecoratedItem[B_co]:
def decorator(func: ItemCallbackType[V_co, B_co]) -> DecoratedItem[B_co]:
if not asyncio.iscoroutinefunction(func):
raise TypeError("button function must be a coroutine function")

Expand Down
31 changes: 10 additions & 21 deletions disnake/ui/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,18 @@
Optional,
Protocol,
Tuple,
Type,
TypeVar,
overload,
)

__all__ = ("Item", "WrappedComponent")

ItemT = TypeVar("ItemT", bound="Item")
I = TypeVar("I", bound="Item[Any]")
V_co = TypeVar("V_co", bound="Optional[View]", covariant=True)

if TYPE_CHECKING:
from typing_extensions import ParamSpec, Self
from typing_extensions import Self

from ..client import Client
from ..components import NestedComponent
Expand All @@ -31,7 +32,7 @@
from ..types.components import Component as ComponentPayload
from .view import View

ItemCallbackType = Callable[[Any, ItemT, MessageInteraction], Coroutine[Any, Any, Any]]
ItemCallbackType = Callable[[V_co, I, MessageInteraction], Coroutine[Any, Any, Any]]

else:
ParamSpec = TypeVar
Expand Down Expand Up @@ -160,29 +161,17 @@ async def callback(self, interaction: MessageInteraction[ClientT], /) -> None:
pass


I_co = TypeVar("I_co", bound=Item, covariant=True)
SelfViewT = TypeVar("SelfViewT", bound="Optional[View]")


# while the decorators don't actually return a descriptor that matches this protocol,
# While the decorators don't actually return a descriptor that matches this protocol,
# this protocol ensures that type checkers don't complain about statements like `self.button.disabled = True`,
# which work as `View.__init__` replaces the handler with the item
class DecoratedItem(Protocol[I_co]):
# which work as `View.__init__` replaces the handler with the item.
class DecoratedItem(Protocol[I]):
@overload
def __get__(self, obj: None, objtype: Any) -> ItemCallbackType:
def __get__(self, obj: None, objtype: Type[SelfViewT]) -> ItemCallbackType[SelfViewT, I]:
...

@overload
def __get__(self, obj: Any, objtype: Any) -> I_co:
...


T_co = TypeVar("T_co", covariant=True)
P = ParamSpec("P")


class ItemShape(Protocol[T_co, P]):
def __new__(cls) -> T_co:
...

def __init__(self, *args: P.args, **kwargs: P.kwargs) -> None:
def __get__(self, obj: Any, objtype: Any) -> I:
...
20 changes: 7 additions & 13 deletions disnake/ui/select/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from abc import ABC, abstractmethod
from typing import (
TYPE_CHECKING,
Any,
Callable,
ClassVar,
Generic,
Expand All @@ -19,14 +18,13 @@
Type,
TypeVar,
Union,
get_origin,
)

from ...components import AnySelectMenu, SelectDefaultValue
from ...enums import ComponentType, SelectDefaultValueType
from ...object import Object
from ...utils import MISSING, humanize_list
from ..item import DecoratedItem, Item, ItemShape
from ..item import DecoratedItem, Item

__all__ = ("BaseSelect",)

Expand Down Expand Up @@ -239,24 +237,20 @@ def _transform_default_values(


def _create_decorator(
cls: Type[ItemShape[S_co, P]],
# only for input validation
base_cls: Type[BaseSelect[Any, Any, Any]],
# FIXME(3.0): rename `cls` parameter to more closely represent any callable argument type
cls: Callable[P, S_co],
/,
*args: P.args,
**kwargs: P.kwargs,
) -> Callable[[ItemCallbackType[S_co]], DecoratedItem[S_co]]:
) -> Callable[[ItemCallbackType[V_co, S_co]], DecoratedItem[S_co]]:
if args:
# the `*args` def above is just to satisfy the typechecker
raise RuntimeError("expected no *args")

if (origin := get_origin(cls)) is not None:
cls = origin
if not callable(cls):
raise TypeError("cls argument must be callable")

if not isinstance(cls, type) or not issubclass(cls, base_cls):
raise TypeError(f"cls argument must be a subclass of {base_cls.__name__}, got {cls!r}")

def decorator(func: ItemCallbackType[S_co]) -> DecoratedItem[S_co]:
def decorator(func: ItemCallbackType[V_co, S_co]) -> DecoratedItem[S_co]:
if not asyncio.iscoroutinefunction(func):
raise TypeError("select function must be a coroutine function")

Expand Down
22 changes: 11 additions & 11 deletions disnake/ui/select/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from typing_extensions import Self

from ...abc import AnyChannel
from ..item import DecoratedItem, ItemCallbackType, ItemShape
from ..item import DecoratedItem, ItemCallbackType


__all__ = (
Expand Down Expand Up @@ -197,20 +197,20 @@ def channel_select(
channel_types: Optional[List[ChannelType]] = None,
default_values: Optional[Sequence[SelectDefaultValueInputType[AnyChannel]]] = None,
row: Optional[int] = None,
) -> Callable[[ItemCallbackType[ChannelSelect[V_co]]], DecoratedItem[ChannelSelect[V_co]]]:
) -> Callable[[ItemCallbackType[V_co, ChannelSelect[V_co]]], DecoratedItem[ChannelSelect[V_co]]]:
...


@overload
def channel_select(
cls: Type[ItemShape[S_co, P]], *_: P.args, **kwargs: P.kwargs
) -> Callable[[ItemCallbackType[S_co]], DecoratedItem[S_co]]:
cls: Callable[P, S_co], *_: P.args, **kwargs: P.kwargs
) -> Callable[[ItemCallbackType[V_co, S_co]], DecoratedItem[S_co]]:
...


def channel_select(
cls: Type[ItemShape[S_co, ...]] = ChannelSelect[Any], **kwargs: Any
) -> Callable[[ItemCallbackType[S_co]], DecoratedItem[S_co]]:
cls: Callable[..., S_co] = ChannelSelect[Any], **kwargs: Any
) -> Callable[[ItemCallbackType[V_co, S_co]], DecoratedItem[S_co]]:
"""A decorator that attaches a channel select menu to a component.
The function being decorated should have three parameters, ``self`` representing
Expand All @@ -224,10 +224,10 @@ def channel_select(
Parameters
----------
cls: Type[:class:`ChannelSelect`]
The select subclass to create an instance of. If provided, the following parameters
described below do not apply. Instead, this decorator will accept the same keywords
as the passed cls does.
cls: Callable[..., :class:`ChannelSelect`]
A callable (may be a :class:`ChannelSelect` subclass) to create a new instance of this component.
If provided, the other parameters described below do not apply.
Instead, this decorator will accept the same keywords as the passed callable/class does.
placeholder: Optional[:class:`str`]
The placeholder text that is shown if nothing is selected, if any.
custom_id: :class:`str`
Expand Down Expand Up @@ -256,4 +256,4 @@ def channel_select(
.. versionadded:: 2.10
"""
return _create_decorator(cls, ChannelSelect, **kwargs)
return _create_decorator(cls, **kwargs)
24 changes: 13 additions & 11 deletions disnake/ui/select/mentionable.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
if TYPE_CHECKING:
from typing_extensions import Self

from ..item import DecoratedItem, ItemCallbackType, ItemShape
from ..item import DecoratedItem, ItemCallbackType


__all__ = (
Expand Down Expand Up @@ -174,20 +174,22 @@ def mentionable_select(
Sequence[SelectDefaultValueMultiInputType[Union[User, Member, Role]]]
] = None,
row: Optional[int] = None,
) -> Callable[[ItemCallbackType[MentionableSelect[V_co]]], DecoratedItem[MentionableSelect[V_co]]]:
) -> Callable[
[ItemCallbackType[V_co, MentionableSelect[V_co]]], DecoratedItem[MentionableSelect[V_co]]
]:
...


@overload
def mentionable_select(
cls: Type[ItemShape[S_co, P]], *_: P.args, **kwargs: P.kwargs
) -> Callable[[ItemCallbackType[S_co]], DecoratedItem[S_co]]:
cls: Callable[P, S_co], *_: P.args, **kwargs: P.kwargs
) -> Callable[[ItemCallbackType[V_co, S_co]], DecoratedItem[S_co]]:
...


def mentionable_select(
cls: Type[ItemShape[S_co, ...]] = MentionableSelect[Any], **kwargs: Any
) -> Callable[[ItemCallbackType[S_co]], DecoratedItem[S_co]]:
cls: Callable[..., S_co] = MentionableSelect[Any], **kwargs: Any
) -> Callable[[ItemCallbackType[V_co, S_co]], DecoratedItem[S_co]]:
"""A decorator that attaches a mentionable (user/member/role) select menu to a component.
The function being decorated should have three parameters, ``self`` representing
Expand All @@ -201,10 +203,10 @@ def mentionable_select(
Parameters
----------
cls: Type[:class:`MentionableSelect`]
The select subclass to create an instance of. If provided, the following parameters
described below do not apply. Instead, this decorator will accept the same keywords
as the passed cls does.
cls: Callable[..., :class:`MentionableSelect`]
A callable (may be a :class:`MentionableSelect` subclass) to create a new instance of this component.
If provided, the other parameters described below do not apply.
Instead, this decorator will accept the same keywords as the passed callable/class does.
placeholder: Optional[:class:`str`]
The placeholder text that is shown if nothing is selected, if any.
custom_id: :class:`str`
Expand Down Expand Up @@ -232,4 +234,4 @@ def mentionable_select(
.. versionadded:: 2.10
"""
return _create_decorator(cls, MentionableSelect, **kwargs)
return _create_decorator(cls, **kwargs)
22 changes: 11 additions & 11 deletions disnake/ui/select/role.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
if TYPE_CHECKING:
from typing_extensions import Self

from ..item import DecoratedItem, ItemCallbackType, ItemShape
from ..item import DecoratedItem, ItemCallbackType


__all__ = (
Expand Down Expand Up @@ -161,20 +161,20 @@ def role_select(
disabled: bool = False,
default_values: Optional[Sequence[SelectDefaultValueInputType[Role]]] = None,
row: Optional[int] = None,
) -> Callable[[ItemCallbackType[RoleSelect[V_co]]], DecoratedItem[RoleSelect[V_co]]]:
) -> Callable[[ItemCallbackType[V_co, RoleSelect[V_co]]], DecoratedItem[RoleSelect[V_co]]]:
...


@overload
def role_select(
cls: Type[ItemShape[S_co, P]], *_: P.args, **kwargs: P.kwargs
) -> Callable[[ItemCallbackType[S_co]], DecoratedItem[S_co]]:
cls: Callable[P, S_co], *_: P.args, **kwargs: P.kwargs
) -> Callable[[ItemCallbackType[V_co, S_co]], DecoratedItem[S_co]]:
...


def role_select(
cls: Type[ItemShape[S_co, ...]] = RoleSelect[Any], **kwargs: Any
) -> Callable[[ItemCallbackType[S_co]], DecoratedItem[S_co]]:
cls: Callable[..., S_co] = RoleSelect[Any], **kwargs: Any
) -> Callable[[ItemCallbackType[V_co, S_co]], DecoratedItem[S_co]]:
"""A decorator that attaches a role select menu to a component.
The function being decorated should have three parameters, ``self`` representing
Expand All @@ -188,10 +188,10 @@ def role_select(
Parameters
----------
cls: Type[:class:`RoleSelect`]
The select subclass to create an instance of. If provided, the following parameters
described below do not apply. Instead, this decorator will accept the same keywords
as the passed cls does.
cls: Callable[..., :class:`RoleSelect`]
A callable (may be a :class:`RoleSelect` subclass) to create a new instance of this component.
If provided, the other parameters described below do not apply.
Instead, this decorator will accept the same keywords as the passed callable/class does.
placeholder: Optional[:class:`str`]
The placeholder text that is shown if nothing is selected, if any.
custom_id: :class:`str`
Expand All @@ -217,4 +217,4 @@ def role_select(
.. versionadded:: 2.10
"""
return _create_decorator(cls, RoleSelect, **kwargs)
return _create_decorator(cls, **kwargs)
Loading

0 comments on commit a59ba25

Please sign in to comment.