-
-
Notifications
You must be signed in to change notification settings - Fork 482
feat: Support default_values for Select of type ComponentType.mentionable_select and ComponentType.channel_select #2793
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
Conversation
This commit expands handling of "default_values" to include objects like users, roles, channels, and threads in select menus other than string selects. It introduces type checks and conversions for these objects and ensures proper serialization to payloads.
Add support for creating SelectDefaultValue from dictionaries and improve serialization by converting default_values elements to dictionaries. Ensure consistency by using the enum value for type in payload generation.
raise InvalidArgument( | ||
"default_values can only be set on non string selects" | ||
) | ||
if not isinstance(value, list): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure about the necessity of these checks. value
is already typed. Wait for other feedback.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most of those checks are again similar to how the options work/are checked.
My argument for stricter type checking and not just relying on the typing would be the similarities to the options could lead to confusion between both and with those checks we can avoid some reports and provide a better exception
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The specific checks I'm talking about are the list
and all(isinstance)
ones. While I feel like the other might be justified, it feels redundant to have type checks with also typed parameters. Yes, to some extent it is good to have better exceptions, but python isn't strictly typed anyways, as of itself, and it feels a little arbitrary to add them here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Lumabots maybe you have thoughts on this ?
raise InvalidArgument( | ||
"default values have to be of type GuildChannel or Thread" | ||
) | ||
elif self.type is ComponentType.channel_select: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe a dict mapping could be used for setting default_value_type
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@doluk Anything about this ?
@NeloBlivion Would this interfere with #2707 ? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor changes and docs suggestions
Add this feature to the pre-existing select example.
Signed-off-by: Lukas Dobler <[email protected]>
I addressed most of the review comments, the two left open are something which need a final decision on what to do. @Paillat-dev @JustaSqu1d |
@doluk can you take a look at the merge conflicts ? |
the component v2 merge created that, I am not sure there is an easy way out of it. I was thinking about opening another pr from the current main instead of trying to resolve the conflicts somehow |
@doluk I am going to try the merge and we'll see.. |
@doluk I merged it but I am not sure it is all good, would you mind taking a look ? |
channel_types: list[ChannelType] | None = None, | ||
disabled: bool = False, | ||
row: int | None = None, | ||
default_values: ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs to be added to the docstring
@doluk updates? |
Would be nice if either
Another upcoming feature (in a few~ish weeks) depends on that as well |
b55c125
to
82659b2
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
default_values
should still be added to the required docstrings
Channel = "channel" | ||
Role = "role" | ||
User = "user" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should follow the library style:
Channel = "channel" | |
Role = "role" | |
User = "user" | |
channel = "channel" | |
role = "role" | |
user = "user" |
self.id = id | ||
self.type = type |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
type annotations
self.id = id | |
self.type = type | |
self.id: int = id | |
self.type: SelectMenuDefaultValueType = type |
max_values: int = 1, | ||
disabled: bool = False, | ||
row: int | None = None, | ||
default_values: list[Member | User | Role] | None = None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should still allow SelectDefaultValue
instances
default_values: list[Member | User | Role] | None = None, | |
default_values: list[Member | User | Role | SelectDefaultValue] | None = None, |
max_values: int = 1, | ||
disabled: bool = False, | ||
row: int | None = None, | ||
default_values: list[Role] | None = None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
default_values: list[Role] | None = None, | |
default_values: list[Role | SelectDefaultValue] | None = None, |
custom_id: str | None = None, | ||
min_values: int = 1, | ||
max_values: int = 1, | ||
default_values: list[Member | User] | None = None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
default_values: list[Member | User] | None = None, | |
default_values: list[Member | User | SelectDefaultValue] | None = None, |
elif select_type is ComponentType.role_select: | ||
for r in default_values: | ||
if not isinstance(r, Role): | ||
raise ValueError( | ||
f"default_values must be a list of Role objects, not {r.__class__.__name__}" | ||
) | ||
_default_values.append( | ||
SelectDefaultValue( | ||
id=r.id, type=SelectMenuDefaultValueType.Role | ||
) | ||
) | ||
elif select_type is ComponentType.user_select: | ||
for u in default_values: | ||
if not isinstance(u, (User, Member)): | ||
raise ValueError( | ||
f"default_values must be a list of User/Member objects, " | ||
f"not {u.__class__.__name__}" | ||
) | ||
_default_values.append( | ||
SelectDefaultValue( | ||
id=u.id, type=SelectMenuDefaultValueType.User | ||
) | ||
) | ||
elif select_type is ComponentType.channel_select: | ||
for c in default_values: | ||
if not isinstance(c, GuildChannel): | ||
raise ValueError( | ||
f"default_values must be a list of GuildChannel objects, " | ||
f"not {c.__class__.__name__}" | ||
) | ||
if channel_types and c.type not in channel_types: | ||
raise ValueError( | ||
f"default_values must be a list of channels of type {channel_types}, " | ||
f"not {c.__class__.__name__}" | ||
) | ||
_default_values.append( | ||
SelectDefaultValue( | ||
id=c.id, type=SelectMenuDefaultValueType.Channel | ||
) | ||
) | ||
elif select_type is ComponentType.mentionable_select: | ||
for m in default_values: | ||
if not isinstance(m, (User, Member, Role)): | ||
raise ValueError( | ||
f"default_values must be a list of User/Member/Role objects, " | ||
f"not {m.__class__.__name__}" | ||
) | ||
_default_values.append( | ||
SelectDefaultValue( | ||
id=m.id, | ||
type=( | ||
SelectMenuDefaultValueType.Role | ||
if isinstance(m, Role) | ||
else SelectMenuDefaultValueType.User | ||
), | ||
) | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should still allow for SelectMenuDefaultValueType instances
elif select_type is ComponentType.role_select: | |
for r in default_values: | |
if not isinstance(r, Role): | |
raise ValueError( | |
f"default_values must be a list of Role objects, not {r.__class__.__name__}" | |
) | |
_default_values.append( | |
SelectDefaultValue( | |
id=r.id, type=SelectMenuDefaultValueType.Role | |
) | |
) | |
elif select_type is ComponentType.user_select: | |
for u in default_values: | |
if not isinstance(u, (User, Member)): | |
raise ValueError( | |
f"default_values must be a list of User/Member objects, " | |
f"not {u.__class__.__name__}" | |
) | |
_default_values.append( | |
SelectDefaultValue( | |
id=u.id, type=SelectMenuDefaultValueType.User | |
) | |
) | |
elif select_type is ComponentType.channel_select: | |
for c in default_values: | |
if not isinstance(c, GuildChannel): | |
raise ValueError( | |
f"default_values must be a list of GuildChannel objects, " | |
f"not {c.__class__.__name__}" | |
) | |
if channel_types and c.type not in channel_types: | |
raise ValueError( | |
f"default_values must be a list of channels of type {channel_types}, " | |
f"not {c.__class__.__name__}" | |
) | |
_default_values.append( | |
SelectDefaultValue( | |
id=c.id, type=SelectMenuDefaultValueType.Channel | |
) | |
) | |
elif select_type is ComponentType.mentionable_select: | |
for m in default_values: | |
if not isinstance(m, (User, Member, Role)): | |
raise ValueError( | |
f"default_values must be a list of User/Member/Role objects, " | |
f"not {m.__class__.__name__}" | |
) | |
_default_values.append( | |
SelectDefaultValue( | |
id=m.id, | |
type=( | |
SelectMenuDefaultValueType.Role | |
if isinstance(m, Role) | |
else SelectMenuDefaultValueType.User | |
), | |
) | |
) | |
elif select_type is ComponentType.role_select: | |
for r in default_values: | |
if isinstance(r, Role): | |
_default_values.append(SelectDefaultValue(id=r.id, type=SelectMenuDefaultValueType.role)) | |
elif isinstance(r, SelectDefaultValue) and r.type is SelectMenuDefaultValueType.role: | |
_default_values.append(r) | |
else: | |
raise ValueError('default_values must be a list of Role or SelectDefaultValue objects of type SelectMenuDefaultValueType.role') | |
elif select_type is ComponentType.user_select: | |
for u in default_values: | |
if isinstance(u, (User, Member)): | |
_default_values.append(SelectDefaultValue(id=u.id, type=SelectMenuDefaultValueType.user)) | |
elif isinstance(u, SelectDefaultValue) and u.type is SelectMenuDefaultValueType.user: | |
_default_values.append(u) | |
else: | |
raise ValueError('default_values must be a list of User, Member, or SelectDefaultValue objects of type SelectMenuDefaultValueType.user') | |
elif select_type is ComponentType.channel_select: | |
for c in default_values: | |
if isinstance(c, GuildChannel): | |
if channel_types and c.type not in channel_types: | |
raise TypeError(f'default_values channel types must be any of {channel_types}') | |
_default_values.append(SelectDefaultValue(id=c.id, type=SelectMenuDefaultValueType.channel)) | |
elif isinstance(c, SelectDefaultValue) and c.type is SelectMenuDefaultValueType.channel: | |
_default_values.append(r) | |
else: | |
raise ValueError('default_values must be a list of GuildChannel or SelectDefaultValue objects of type SelectMenuDefaultValueType.channel') | |
elif select_type is ComponentType.mentionable_select: | |
for m in default_values: | |
if isinstance(m, (User, Member, Role)): | |
_default_values.append(SelectDefaultValue(id=m.id, type=SelectMenuDefaultValueType.role if isinstance(m, Role) else SelectMenuDefaultValueType.user)) | |
elif isinstance(m, SelectDefaultValue) and m.type in (SelectMenuDefaultValueType.role, SelectMenuDefaultValueType.user): | |
_default_values.append(m) | |
else: | |
raise ValueError('default_values must be a list of Role, User, Member, or SelectDefaultValue objects of type SelectMenuDefaultValueType.role or SelectMenuDefaultValueType.user') |
def add_default_value( | ||
self, | ||
*, | ||
default_value: User | Member | Role | GuildChannel | Thread, | ||
): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Allow for Object instances when you don't have full User, Member, Role or GuildChannel instances
def add_default_value( | |
self, | |
*, | |
default_value: User | Member | Role | GuildChannel | Thread, | |
): | |
def add_default_value( | |
self, | |
*, | |
default_value: User | Member | Role | GuildChannel | Thread | Object, | |
type: SelectMenuDefaultValueType = MISSING, | |
): |
default_value: Union[:class:`discord.User`, :class:`discord.Member`, :class:`discord.Role`, | ||
:class:`discord.abc.GuildChannel`, :class:`discord.Thread`] | ||
The to be added default value |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
default_value: Union[:class:`discord.User`, :class:`discord.Member`, :class:`discord.Role`, | |
:class:`discord.abc.GuildChannel`, :class:`discord.Thread`] | |
The to be added default value | |
default_value: Union[:class:`discord.User`, :class:`discord.Member`, :class:`discord.Role`, | |
:class:`discord.abc.GuildChannel`, :class:`discord.Thread`, :class:`discord.Object`] | |
The to be added default value. If this is an ``Object`` instance, you must pass ``type`` if the select type is ``mentionable``. | |
type: :class:`discord.SelectMenuDefaultvalueType` | |
The default value type, required if ``default_value`` is an ``Object`` and the select type is ``mentionable``. |
default_value_type = None | ||
if self.type is ComponentType.channel_select and not isinstance( | ||
default_value, (GuildChannel, Thread) | ||
): | ||
raise InvalidArgument( | ||
"Default values have to be of type GuildChannel or Thread with type ComponentType.channel_select" | ||
) | ||
elif self.type is ComponentType.channel_select: | ||
default_value_type = SelectMenuDefaultValueType.Channel | ||
if self.type is ComponentType.user_select and not isinstance( | ||
default_value, (User, Member) | ||
): | ||
raise InvalidArgument( | ||
"Default values have to be of type User or Member with type ComponentType.user_select" | ||
) | ||
elif self.type is ComponentType.user_select: | ||
default_value_type = SelectMenuDefaultValueType.User | ||
if self.type is ComponentType.role_select and not isinstance( | ||
default_value, Role | ||
): | ||
raise InvalidArgument( | ||
"Default values have to be of type Role with type ComponentType.role_select" | ||
) | ||
elif self.type is ComponentType.role_select: | ||
default_value_type = SelectMenuDefaultValueType.Role | ||
if self.type is ComponentType.mentionable_select and not isinstance( | ||
default_value, (User, Member, Role) | ||
): | ||
raise InvalidArgument( | ||
"Default values have to be of type User, Member or Role with type ComponentType.mentionable_select" | ||
) | ||
elif self.type is ComponentType.mentionable_select and isinstance( | ||
default_value, (User, Member) | ||
): | ||
default_value_type = SelectMenuDefaultValueType.User | ||
elif self.type is ComponentType.mentionable_select and isinstance( | ||
default_value, Role | ||
): | ||
default_value_type = SelectMenuDefaultValueType.Role | ||
if default_value_type is None: | ||
raise InvalidArgument( | ||
"Default values have to be of type User, Member, Role or GuildChannel" | ||
) | ||
default_value = SelectDefaultValue( | ||
id=default_value.id, | ||
type=default_value_type, | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a mix of what paillat said and support for passing Object instances.
default_value_type = None | |
if self.type is ComponentType.channel_select and not isinstance( | |
default_value, (GuildChannel, Thread) | |
): | |
raise InvalidArgument( | |
"Default values have to be of type GuildChannel or Thread with type ComponentType.channel_select" | |
) | |
elif self.type is ComponentType.channel_select: | |
default_value_type = SelectMenuDefaultValueType.Channel | |
if self.type is ComponentType.user_select and not isinstance( | |
default_value, (User, Member) | |
): | |
raise InvalidArgument( | |
"Default values have to be of type User or Member with type ComponentType.user_select" | |
) | |
elif self.type is ComponentType.user_select: | |
default_value_type = SelectMenuDefaultValueType.User | |
if self.type is ComponentType.role_select and not isinstance( | |
default_value, Role | |
): | |
raise InvalidArgument( | |
"Default values have to be of type Role with type ComponentType.role_select" | |
) | |
elif self.type is ComponentType.role_select: | |
default_value_type = SelectMenuDefaultValueType.Role | |
if self.type is ComponentType.mentionable_select and not isinstance( | |
default_value, (User, Member, Role) | |
): | |
raise InvalidArgument( | |
"Default values have to be of type User, Member or Role with type ComponentType.mentionable_select" | |
) | |
elif self.type is ComponentType.mentionable_select and isinstance( | |
default_value, (User, Member) | |
): | |
default_value_type = SelectMenuDefaultValueType.User | |
elif self.type is ComponentType.mentionable_select and isinstance( | |
default_value, Role | |
): | |
default_value_type = SelectMenuDefaultValueType.Role | |
if default_value_type is None: | |
raise InvalidArgument( | |
"Default values have to be of type User, Member, Role or GuildChannel" | |
) | |
default_value = SelectDefaultValue( | |
id=default_value.id, | |
type=default_value_type, | |
) | |
if self.type is ComponentType.mentionable_select and isinstance(default_value, Object) and type is MISSING: | |
raise ValueError('type is required when the default_value is an Object and select type is mentionable') | |
elif self.type is ComponentType.mentionable_select and isinstance(default_value, (Member, User)): | |
default_value_type = SelectMenuDefaultValueType.user | |
elif self.type is ComponentType.mentionable_select and isinstance(default_value, Role): | |
default_value_type = SelectMenuDefaultValueType.role | |
elif self.type is ComponentType.mentionable_select: | |
default_value_type = type | |
else: | |
value_mapping = { | |
ComponentType.role_select: SelectMenuDefaultValueType.role, | |
ComponentType.channel_select: SelectMenuDefaultValueType.channel, | |
ComponentType.user_select: SelectMenuDefaultValueType.user, | |
} | |
try: | |
default_value_type = value_mapping[self.type] | |
except KeyError: | |
raise ValueError(f'invalid select type: {self.type}') | |
default_value = SelectDefaultValue( | |
id=default_value.id, | |
type=default_value_type, | |
) |
from ..components import SelectDefaultValue, SelectMenu, SelectOption | ||
from ..emoji import AppEmoji, GuildEmoji | ||
from ..enums import ChannelType, ComponentType | ||
from ..enums import ChannelType, ComponentType, SelectMenuDefaultValueType | ||
from ..errors import InvalidArgument | ||
from ..interactions import Interaction | ||
from ..member import Member |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The changes i've suggested require importing Object
from ..components import SelectDefaultValue, SelectMenu, SelectOption | |
from ..emoji import AppEmoji, GuildEmoji | |
from ..enums import ChannelType, ComponentType | |
from ..enums import ChannelType, ComponentType, SelectMenuDefaultValueType | |
from ..errors import InvalidArgument | |
from ..interactions import Interaction | |
from ..member import Member | |
from ..components import SelectDefaultValue, SelectMenu, SelectOption | |
from ..emoji import AppEmoji, GuildEmoji | |
from ..enums import ChannelType, ComponentType, SelectMenuDefaultValueType | |
from ..errors import InvalidArgument | |
from ..interactions import Interaction | |
from ..member import Member | |
from ..object import Object |
Let's do a clean new pr |
Summary
Looking at https://discord.com/developers/docs/components/reference#user-select I noticed that there is support for preselecting users, channels, roles, mentionables for the non string selects. This pr adds support for this.
Information
examples, ...).
Checklist
type: ignore
comments were used, a comment is also left explaining why.