Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ui): don't require cls argument in select decorators to be positional #1111

Merged
merged 6 commits into from
Oct 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/1111.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Allow ``cls`` argument in select menu decorators (e.g. :func`ui.string_select`) to be specified by keyword instead of being positional-only.
2 changes: 1 addition & 1 deletion disnake/ui/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ def button(
----------
cls: Type[:class:`Button`]
The button subclass to create an instance of. If provided, the following parameters
described below do no apply. Instead, this decorator will accept the same keywords
described below do not apply. Instead, this decorator will accept the same keywords
as the passed cls does.

.. versionadded:: 2.6
Expand Down
6 changes: 2 additions & 4 deletions disnake/ui/select/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,7 @@ def channel_select(


def channel_select(
cls: Type[Object[S_co, P]] = ChannelSelect[Any],
/,
**kwargs: Any,
cls: Type[Object[S_co, P]] = ChannelSelect[Any], **kwargs: Any
) -> Callable[[ItemCallbackType[S_co]], DecoratedItem[S_co]]:
"""A decorator that attaches a channel select menu to a component.

Expand All @@ -187,7 +185,7 @@ def channel_select(
----------
cls: Type[:class:`ChannelSelect`]
The select subclass to create an instance of. If provided, the following parameters
described below do no apply. Instead, this decorator will accept the same keywords
described below do not apply. Instead, this decorator will accept the same keywords
as the passed cls does.
placeholder: Optional[:class:`str`]
The placeholder text that is shown if nothing is selected, if any.
Expand Down
6 changes: 2 additions & 4 deletions disnake/ui/select/mentionable.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,7 @@ def mentionable_select(


def mentionable_select(
cls: Type[Object[S_co, P]] = MentionableSelect[Any],
/,
**kwargs: Any,
cls: Type[Object[S_co, P]] = MentionableSelect[Any], **kwargs: Any
) -> Callable[[ItemCallbackType[S_co]], DecoratedItem[S_co]]:
"""A decorator that attaches a mentionable (user/member/role) select menu to a component.

Expand All @@ -163,7 +161,7 @@ def mentionable_select(
----------
cls: Type[:class:`MentionableSelect`]
The select subclass to create an instance of. If provided, the following parameters
described below do no apply. Instead, this decorator will accept the same keywords
described below do not apply. Instead, this decorator will accept the same keywords
as the passed cls does.
placeholder: Optional[:class:`str`]
The placeholder text that is shown if nothing is selected, if any.
Expand Down
6 changes: 2 additions & 4 deletions disnake/ui/select/role.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,7 @@ def role_select(


def role_select(
cls: Type[Object[S_co, P]] = RoleSelect[Any],
/,
**kwargs: Any,
cls: Type[Object[S_co, P]] = RoleSelect[Any], **kwargs: Any
) -> Callable[[ItemCallbackType[S_co]], DecoratedItem[S_co]]:
"""A decorator that attaches a role select menu to a component.

Expand All @@ -161,7 +159,7 @@ def role_select(
----------
cls: Type[:class:`RoleSelect`]
The select subclass to create an instance of. If provided, the following parameters
described below do no apply. Instead, this decorator will accept the same keywords
described below do not apply. Instead, this decorator will accept the same keywords
as the passed cls does.
placeholder: Optional[:class:`str`]
The placeholder text that is shown if nothing is selected, if any.
Expand Down
6 changes: 2 additions & 4 deletions disnake/ui/select/string.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,9 +268,7 @@ def string_select(


def string_select(
cls: Type[Object[S_co, P]] = StringSelect[Any],
/,
**kwargs: Any,
cls: Type[Object[S_co, P]] = StringSelect[Any], **kwargs: Any
) -> Callable[[ItemCallbackType[S_co]], DecoratedItem[S_co]]:
"""A decorator that attaches a string select menu to a component.

Expand All @@ -288,7 +286,7 @@ def string_select(
----------
cls: Type[:class:`StringSelect`]
The select subclass to create an instance of. If provided, the following parameters
described below do no apply. Instead, this decorator will accept the same keywords
described below do not apply. Instead, this decorator will accept the same keywords
as the passed cls does.

.. versionadded:: 2.6
Expand Down
6 changes: 2 additions & 4 deletions disnake/ui/select/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,7 @@ def user_select(


def user_select(
cls: Type[Object[S_co, P]] = UserSelect[Any],
/,
**kwargs: Any,
cls: Type[Object[S_co, P]] = UserSelect[Any], **kwargs: Any
) -> Callable[[ItemCallbackType[S_co]], DecoratedItem[S_co]]:
"""A decorator that attaches a user select menu to a component.

Expand All @@ -162,7 +160,7 @@ def user_select(
----------
cls: Type[:class:`UserSelect`]
The select subclass to create an instance of. If provided, the following parameters
described below do no apply. Instead, this decorator will accept the same keywords
described below do not apply. Instead, this decorator will accept the same keywords
as the passed cls does.
placeholder: Optional[:class:`str`]
The placeholder text that is shown if nothing is selected, if any.
Expand Down
2 changes: 1 addition & 1 deletion docs/api/ui.rst
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ TextInput
Functions
---------

.. autofunction:: button(cls=Button, *, style=ButtonStyle.secondary, label=None, disabled=False, custom_id=..., url=None, emoji=None, row=None)
.. autofunction:: button(cls=Button, *, custom_id=..., style=ButtonStyle.secondary, label=None, disabled=False, url=None, emoji=None, row=None)
:decorator:

.. autofunction:: string_select(cls=StringSelect, *, custom_id=..., placeholder=None, min_values=1, max_values=1, options=..., disabled=False, row=None)
Expand Down
21 changes: 16 additions & 5 deletions tests/ui/test_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def test_default(self) -> None:
assert func.__discord_ui_model_type__ is ui.StringSelect
assert func.__discord_ui_model_kwargs__ == {"custom_id": "123"}

# from here on out we're only testing the button decorator,
# from here on out we're mostly only testing the button decorator,
# as @ui.string_select etc. works identically

@pytest.mark.parametrize("cls", [_CustomButton, _CustomButton[Any]])
Expand All @@ -64,7 +64,18 @@ def _test_typing_cls(self) -> None:
this_should_not_work="h", # type: ignore
)

@pytest.mark.parametrize("cls", [123, int, ui.StringSelect])
def test_cls_invalid(self, cls) -> None:
with pytest.raises(TypeError, match=r"cls argument must be"):
ui.button(cls=cls) # type: ignore
@pytest.mark.parametrize(
("decorator", "invalid_cls"),
[
(ui.button, ui.StringSelect),
(ui.string_select, ui.Button),
(ui.user_select, ui.Button),
(ui.role_select, ui.Button),
(ui.mentionable_select, ui.Button),
(ui.channel_select, ui.Button),
],
)
def test_cls_invalid(self, decorator, invalid_cls) -> None:
for cls in [123, int, invalid_cls]:
with pytest.raises(TypeError, match=r"cls argument must be"):
decorator(cls=cls)