Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
ce5f50c
implement select default values
DA-344 Sep 1, 2025
49e537f
add changelog
DA-344 Sep 1, 2025
2007918
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 1, 2025
f8e990a
circular imports
DA-344 Sep 1, 2025
a0ac891
merge upstream
DA-344 Sep 1, 2025
fa76588
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 1, 2025
94d1446
does this fix circular import
DA-344 Sep 1, 2025
fcbbac3
add default value to all
DA-344 Sep 1, 2025
77202d1
Merge branch 'master' of https://github.com/Pycord-Development/pycord…
DA-344 Sep 1, 2025
929545e
comments
DA-344 Sep 1, 2025
a3e7089
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 1, 2025
7d76fef
Merge branch 'master' into feat/select-defaults
DA-344 Sep 1, 2025
b289dfc
add repr to SelectDefaultValue
DA-344 Sep 1, 2025
0c73b36
remove unused import
DA-344 Sep 1, 2025
97b8c72
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 1, 2025
7be1460
Merge branch 'master' into feat/select-defaults
Lulalaby Sep 1, 2025
acbe07b
Merge branch 'master' into feat/select-defaults
Paillat-dev Sep 3, 2025
16a6045
fix select inst check
DA-344 Sep 8, 2025
2ca6a00
Merge branch 'feat/select-defaults' of https://github.com/DA-344/pyco…
DA-344 Sep 8, 2025
3ddef7d
updates and typings and things
DA-344 Sep 8, 2025
d272686
isinstance str
DA-344 Sep 8, 2025
31663ba
isinstance -> issubclass
DA-344 Sep 8, 2025
dd5e060
type checks when SelectDefaultvalue insts
DA-344 Sep 8, 2025
f56f107
Merge branch 'master' of https://github.com/Pycord-Development/pycord…
DA-344 Sep 8, 2025
4f267c9
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 8, 2025
6ea9848
Apply suggestion from @Lulalaby
Lulalaby Sep 8, 2025
512ecb0
docs and typings errors
DA-344 Sep 10, 2025
b501ade
Merge branch 'feat/select-defaults' of https://github.com/DA-344/pyco…
DA-344 Sep 10, 2025
828f20f
more typings
DA-344 Sep 10, 2025
b2dce41
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 10, 2025
102502f
add default typings for Select
DA-344 Sep 10, 2025
9c55296
use default typings for Select in examples
DA-344 Sep 10, 2025
2d5b09d
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 10, 2025
4c93e9f
overloads
DA-344 Sep 10, 2025
cfff728
Merge branch 'feat/select-defaults' of https://github.com/DA-344/pyco…
DA-344 Sep 10, 2025
6eb33ad
add all things this pr adds/changes
DA-344 Sep 10, 2025
5e0ba3d
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 10, 2025
8ee1245
changelog
DA-344 Sep 10, 2025
3af8a89
Merge branch 'feat/select-defaults' of https://github.com/DA-344/pyco…
DA-344 Sep 10, 2025
161b0d8
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 10, 2025
4a1268d
docs for select aliases
DA-344 Sep 11, 2025
e55961b
rm funky stuff
DA-344 Sep 11, 2025
7393b2a
rm yet more funky stuff
DA-344 Sep 11, 2025
f1efb51
Merge branch 'master' of https://github.com/Pycord-Development/pycord…
DA-344 Sep 11, 2025
d6a50d1
autoclass -> class
DA-344 Sep 11, 2025
e2a8085
string
DA-344 Sep 11, 2025
9cf66d8
newlines
DA-344 Sep 11, 2025
a9726bf
Merge branch 'master' into feat/select-defaults
Lulalaby Sep 11, 2025
ba246a4
Apply suggestion from @plun1331
plun1331 Sep 12, 2025
8038149
Merge branch 'master' into feat/select-defaults
plun1331 Sep 12, 2025
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ These changes are available on the `master` branch, but have not yet been releas

### Added

- Added support for select default values.
([#2899](https://github.com/Pycord-Development/pycord/pull/2899))

### Changed

### Fixed
Expand Down
177 changes: 175 additions & 2 deletions discord/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

from __future__ import annotations

from typing import TYPE_CHECKING, Any, ClassVar, Iterator, TypeVar
from typing import TYPE_CHECKING, Any, ClassVar, Iterator, TypeVar, overload

from .asset import AssetMixin
from .colour import Colour
Expand All @@ -34,6 +34,7 @@
ChannelType,
ComponentType,
InputTextStyle,
SelectDefaultValueType,
SeparatorSpacingSize,
try_enum,
)
Expand All @@ -42,9 +43,9 @@
from .utils import MISSING, find, get_slots

if TYPE_CHECKING:
from . import abc
from .emoji import AppEmoji, GuildEmoji
from .types.components import ActionRow as ActionRowPayload
from .types.components import BaseComponent as BaseComponentPayload
from .types.components import ButtonComponent as ButtonComponentPayload
from .types.components import Component as ComponentPayload
from .types.components import ContainerComponent as ContainerComponentPayload
Expand All @@ -54,6 +55,7 @@
from .types.components import MediaGalleryComponent as MediaGalleryComponentPayload
from .types.components import MediaGalleryItem as MediaGalleryItemPayload
from .types.components import SectionComponent as SectionComponentPayload
from .types.components import SelectDefaultValue as SelectDefaultValuePayload
from .types.components import SelectMenu as SelectMenuPayload
from .types.components import SelectOption as SelectOptionPayload
from .types.components import SeparatorComponent as SeparatorComponentPayload
Expand All @@ -78,6 +80,7 @@
"Separator",
"Container",
"Label",
"SelectDefaultValue",
)

C = TypeVar("C", bound="Component")
Expand Down Expand Up @@ -437,6 +440,7 @@ class SelectMenu(Component):
"channel_types",
"disabled",
"required",
"default_values",
)

__repr_info__: ClassVar[tuple[str, ...]] = __slots__
Expand All @@ -457,6 +461,9 @@ def __init__(self, data: SelectMenuPayload):
try_enum(ChannelType, ct) for ct in data.get("channel_types", [])
]
self.required: bool | None = data.get("required")
self.default_values: list[SelectDefaultValue] = SelectDefaultValue._from_data(
data.get("default_values")
)

def to_dict(self) -> SelectMenuPayload:
payload: SelectMenuPayload = {
Expand All @@ -476,10 +483,176 @@ def to_dict(self) -> SelectMenuPayload:
payload["placeholder"] = self.placeholder
if self.required is not None:
payload["required"] = self.required
if self.type is not ComponentType.string_select:
payload["default_values"] = [dv.to_dict() for dv in self.default_values]

return payload


class SelectDefaultValue:
r"""Represents a :class:`discord.SelectMenu`\s default value.

This is only applicable to selects of type other than :attr:`ComponentType.string_select`.

.. versionadded:: 2.7

Parameters
----------
object: :class:`abc.Snowflake`
The model type this select default value is based of.

Below, is a table defining the model instance type and the default value type it will be mapped:

+-----------------------------------+--------------------------------------------------------------------------+
| Model Type | Default Value Type |
+-----------------------------------+--------------------------------------------------------------------------+
| :class:`discord.User` | :attr:`discord.SelectDefaultValueType.user` |
+-----------------------------------+--------------------------------------------------------------------------+
| :class:`discord.Member` | :attr:`discord.SelectDefaultValueType.user` |
+-----------------------------------+--------------------------------------------------------------------------+
| :class:`discord.Role` | :attr:`discord.SelectDefaultValueType.role` |
+-----------------------------------+--------------------------------------------------------------------------+
| :class:`discord.abc.GuildChannel` | :attr:`discord.SelectDefaultValueType.channel` |
+-----------------------------------+--------------------------------------------------------------------------+
| :class:`discord.Object` | depending on :attr:`discord.Object.type`, it will be mapped to any above |
+-----------------------------------+--------------------------------------------------------------------------+

If you pass a model that is not defined in the table, ``TypeError`` will be raised.

.. note::

The :class:`discord.abc.GuildChannel` protocol includes :class:`discord.TextChannel`, :class:`discord.VoiceChannel`, :class:`discord.StageChannel`,
:class:`discord.ForumChannel`, :class:`discord.Thread`, :class:`discord.MediaChannel`. This list is not exhaustive, and is bound to change
based of the new channel types Discord adds.

id: :class:`int`
The ID of the default value. This cannot be used with ``object``.
type: :class:`SelectDefaultValueType`
The default value type. This cannot be used with ``object``.

Raises
------
TypeError
You did not provide any parameter, you provided all parameters, or you provided ``id`` but not ``type``.
"""

__slots__ = ("id", "type")

@overload
def __init__(
self,
object: abc.Snowflake,
/,
) -> None: ...

@overload
def __init__(
self,
/,
*,
id: int,
type: SelectDefaultValueType,
) -> None: ...

def __init__(
self,
object: abc.Snowflake = MISSING,
/,
*,
id: int = MISSING,
type: SelectDefaultValueType = MISSING,
) -> None:
self.id: int = id
self.type: SelectDefaultValueType = type
if object is not MISSING:
if any(p is not MISSING for p in (id, type)):
raise TypeError("you cannot pass id or type when passing object")
self._handle_model(object, inst=self)
elif id is not MISSING and type is not MISSING:
self.id = id
self.type = type
else:
raise TypeError("you must provide an object model, or an id and type")

@classmethod
def _from_data(
cls, default_values: list[SelectDefaultValuePayload] | None
) -> list[SelectDefaultValue]:
if not default_values:
return []
return [
cls(id=int(d["id"]), type=try_enum(SelectDefaultValueType, d["type"]))
for d in default_values
]

@classmethod
def _handle_model(
cls,
model: abc.Snowflake,
select_type: ComponentType | None = None,
inst: SelectDefaultValue | None = None,
) -> SelectDefaultValue:
# preventing >circular imports<
from discord import Member, Object, Role, User, abc

instances_mapping: dict[
type, tuple[tuple[ComponentType, ...], SelectDefaultValueType]
] = {
Role: (
(ComponentType.role_select, ComponentType.mentionable_select),
SelectDefaultValueType.role,
),
User: (
(ComponentType.user_select, ComponentType.mentionable_select),
SelectDefaultValueType.user,
),
Member: (
(ComponentType.user_select, ComponentType.mentionable_select),
SelectDefaultValueType.user,
),
abc.User: (
(ComponentType.user_select, ComponentType.mentionable_select),
SelectDefaultValueType.user,
),
abc.GuildChannel: (
(ComponentType.channel_select,),
SelectDefaultValueType.channel,
),
}

obj_id = model.id
obj_type = model.__class__

if isinstance(model, Object):
obj_type = model.type

try:
sel_types, def_type = instances_mapping[obj_type]
except KeyError:
raise TypeError(
f"{model.__class__.__name__} is not a valid instance for a select default value",
)

# we can't actually check select types when not in a select context
if select_type is not None and select_type not in sel_types:
raise TypeError(
f"{model.__class__.__name__} objects can not be set as a default value for {select_type.value} selects",
)

if inst is None:
return cls(id=obj_id, type=def_type)
else:
inst.id = obj_id
inst.type = def_type
return inst

def to_dict(self) -> SelectDefaultValuePayload:
return {
"id": self.id,
"type": self.type.value,
}


class SelectOption:
"""Represents a :class:`discord.SelectMenu`'s option.

Expand Down
9 changes: 9 additions & 0 deletions discord/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
"ThreadArchiveDuration",
"SubscriptionStatus",
"SeparatorSpacingSize",
"SelectDefaultValueType",
)


Expand Down Expand Up @@ -1120,6 +1121,14 @@ def __int__(self):
return self.value


class SelectDefaultValueType(Enum):
"""Represents the default value type of a select menu."""

channel = "channel"
role = "role"
user = "user"


T = TypeVar("T")


Expand Down
7 changes: 6 additions & 1 deletion discord/object.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
if TYPE_CHECKING:
import datetime

from .abc import Snowflake

SupportsIntCast = Union[SupportsInt, str, bytes, bytearray]

__all__ = ("Object",)
Expand Down Expand Up @@ -70,9 +72,11 @@ class Object(Hashable):
----------
id: :class:`int`
The ID of the object.
type: type[:class:`abc.Snowflake`]
The model this object's ID is based off.
"""

def __init__(self, id: SupportsIntCast):
def __init__(self, id: SupportsIntCast, type: type[Snowflake] = utils.MISSING):
try:
id = int(id)
except ValueError:
Expand All @@ -81,6 +85,7 @@ def __init__(self, id: SupportsIntCast):
) from None
else:
self.id = id
self.type: type[Snowflake] = type or self.__class__

def __repr__(self) -> str:
return f"<Object id={self.id!r}>"
Expand Down
11 changes: 9 additions & 2 deletions discord/types/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
ButtonStyle = Literal[1, 2, 3, 4, 5, 6]
InputTextStyle = Literal[1, 2]
SeparatorSpacingSize = Literal[1, 2]
SelectDefaultValueType = Literal["channel", "role", "user"]


class BaseComponent(TypedDict):
Expand All @@ -46,7 +47,7 @@ class BaseComponent(TypedDict):

class ActionRow(BaseComponent):
type: Literal[1]
components: list[ButtonComponent, InputText, SelectMenu]
components: list[ButtonComponent | InputText | SelectMenu]


class ButtonComponent(BaseComponent):
Expand Down Expand Up @@ -80,6 +81,11 @@ class SelectOption(TypedDict):
default: bool


class SelectDefaultValue(TypedDict):
id: Snowflake
type: SelectDefaultValueType


class SelectMenu(BaseComponent):
placeholder: NotRequired[str]
min_values: NotRequired[int]
Expand All @@ -90,6 +96,7 @@ class SelectMenu(BaseComponent):
type: Literal[3, 5, 6, 7, 8]
custom_id: str
required: NotRequired[bool]
default_values: NotRequired[list[SelectDefaultValue]]


class TextDisplayComponent(BaseComponent):
Expand All @@ -100,7 +107,7 @@ class TextDisplayComponent(BaseComponent):
class SectionComponent(BaseComponent):
type: Literal[9]
components: list[TextDisplayComponent]
accessory: NotRequired[ThumbnailComponent, ButtonComponent]
accessory: NotRequired[ThumbnailComponent | ButtonComponent]


class UnfurledMediaItem(TypedDict):
Expand Down
4 changes: 2 additions & 2 deletions discord/ui/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ def button(
emoji: str | GuildEmoji | AppEmoji | PartialEmoji | None = None,
row: int | None = None,
id: int | None = None,
) -> Callable[[ItemCallbackType], ItemCallbackType]:
) -> Callable[[ItemCallbackType[Button[V]]], Button[V]]:
"""A decorator that attaches a button to a component.
The function being decorated should have three parameters, ``self`` representing
Expand Down Expand Up @@ -347,4 +347,4 @@ def decorator(func: ItemCallbackType) -> ItemCallbackType:
}
return func

return decorator
return decorator # type: ignore # lie to the type checkers, because after a View is instated, the button callback is converted into a Button instance
Loading
Loading