From e21f08d891cf3f45197a5bf9d500192995fc6bd6 Mon Sep 17 00:00:00 2001 From: Dorukyum Date: Wed, 6 Jul 2022 17:09:23 +0300 Subject: [PATCH 01/11] Update min/max types for better type checking --- discord/commands/options.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/discord/commands/options.py b/discord/commands/options.py index d183371b1b..f46d4301d0 100644 --- a/discord/commands/options.py +++ b/discord/commands/options.py @@ -194,17 +194,20 @@ def __init__(self, input_type: Any = str, /, description: Optional[str] = None, if self.input_type == SlashCommandOptionType.integer: minmax_types = (int, type(None)) + minmax_typehint = Optional[int] elif self.input_type == SlashCommandOptionType.number: minmax_types = (int, float, type(None)) + minmax_typehint = Optional[Union[int, float]] else: minmax_types = (type(None),) - minmax_typehint = Optional[Union[minmax_types]] # type: ignore + minmax_typehint = type(None) if self.input_type == SlashCommandOptionType.string: minmax_length_types = (int, type(None)) + minmax_length_typehint = Optional[int] else: minmax_length_types = (type(None),) - minmax_length_typehint = Optional[Union[minmax_length_types]] # type: ignore + minmax_length_typehint = type(None) self.min_value: minmax_typehint = kwargs.pop("min_value", None) self.max_value: minmax_typehint = kwargs.pop("max_value", None) From 620627df47bff6cbe20895b3f0570f6ced4e63fe Mon Sep 17 00:00:00 2001 From: Dorukyum Date: Wed, 6 Jul 2022 20:13:11 +0300 Subject: [PATCH 02/11] Create InputType type --- discord/commands/options.py | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/discord/commands/options.py b/discord/commands/options.py index f46d4301d0..ac8bce57ce 100644 --- a/discord/commands/options.py +++ b/discord/commands/options.py @@ -23,13 +23,40 @@ """ import inspect -from typing import Any, Dict, List, Literal, Optional, Union +from typing import TYPE_CHECKING, Dict, List, Literal, Optional, Type, Union from enum import Enum -from ..abc import GuildChannel +from ..abc import GuildChannel, Mentionable from ..channel import TextChannel, VoiceChannel, StageChannel, CategoryChannel, Thread from ..enums import ChannelType, SlashCommandOptionType, Enum as DiscordEnum +if TYPE_CHECKING: + from ..ext.commands import Converter + from ..user import User + from ..member import Member + from ..message import Attachment + from ..role import Role + + InputType = Union[ + Type[str], + Type[bool], + Type[int], + Type[float], + Type[GuildChannel], + Type[Thread], + Type["ThreadOption"], + Type[Member], + Type[User], + Type[Attachment], + Type[Role], + Type[Mentionable], + SlashCommandOptionType, + Converter, + Type[Converter], + Type[Enum], + Type[DiscordEnum], + ] + __all__ = ( "ThreadOption", "Option", @@ -129,7 +156,7 @@ async def hello( See `here `_ for a list of valid locales. """ - def __init__(self, input_type: Any = str, /, description: Optional[str] = None, **kwargs) -> None: + def __init__(self, input_type: InputType = str, /, description: Optional[str] = None, **kwargs) -> None: self.name: Optional[str] = kwargs.pop("name", None) if self.name is not None: self.name = str(self.name) @@ -187,7 +214,7 @@ def __init__(self, input_type: Any = str, /, description: Optional[str] = None, if description is not None: self.description = description - elif issubclass(self._raw_type, Enum) and (doc := inspect.getdoc(self._raw_type)) is not None: + elif isinstance(self._raw_type, type) and issubclass(self._raw_type, Enum) and (doc := inspect.getdoc(self._raw_type)) is not None: self.description = doc else: self.description = "No description provided" From b3bccda0c652734e3e9e14e8ad4ee67c05c1cf13 Mon Sep 17 00:00:00 2001 From: Dorukyum Date: Wed, 6 Jul 2022 20:17:20 +0300 Subject: [PATCH 03/11] Revert commit added by mistake This reverts commit e21f08d891cf3f45197a5bf9d500192995fc6bd6. --- discord/commands/options.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/discord/commands/options.py b/discord/commands/options.py index ac8bce57ce..76143c08f5 100644 --- a/discord/commands/options.py +++ b/discord/commands/options.py @@ -221,20 +221,17 @@ def __init__(self, input_type: InputType = str, /, description: Optional[str] = if self.input_type == SlashCommandOptionType.integer: minmax_types = (int, type(None)) - minmax_typehint = Optional[int] elif self.input_type == SlashCommandOptionType.number: minmax_types = (int, float, type(None)) - minmax_typehint = Optional[Union[int, float]] else: minmax_types = (type(None),) - minmax_typehint = type(None) + minmax_typehint = Optional[Union[minmax_types]] # type: ignore if self.input_type == SlashCommandOptionType.string: minmax_length_types = (int, type(None)) - minmax_length_typehint = Optional[int] else: minmax_length_types = (type(None),) - minmax_length_typehint = type(None) + minmax_length_typehint = Optional[Union[minmax_length_types]] # type: ignore self.min_value: minmax_typehint = kwargs.pop("min_value", None) self.max_value: minmax_typehint = kwargs.pop("max_value", None) From 2545350e2ff5219e4db3197b1c021760cb045205 Mon Sep 17 00:00:00 2001 From: Dorukyum Date: Wed, 6 Jul 2022 20:27:44 +0300 Subject: [PATCH 04/11] Add missing annotations import --- discord/commands/options.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/discord/commands/options.py b/discord/commands/options.py index 76143c08f5..7f4a134f29 100644 --- a/discord/commands/options.py +++ b/discord/commands/options.py @@ -22,6 +22,8 @@ DEALINGS IN THE SOFTWARE. """ +from __future__ import annotations + import inspect from typing import TYPE_CHECKING, Dict, List, Literal, Optional, Type, Union from enum import Enum From 95b7b034c4e2b48cf4facfc15f6955c77a286e7e Mon Sep 17 00:00:00 2001 From: Dorukyum Date: Thu, 7 Jul 2022 11:29:48 +0300 Subject: [PATCH 05/11] Remove ThreadOption from InputType --- discord/commands/options.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/discord/commands/options.py b/discord/commands/options.py index 7f4a134f29..4c732c9817 100644 --- a/discord/commands/options.py +++ b/discord/commands/options.py @@ -46,7 +46,6 @@ Type[float], Type[GuildChannel], Type[Thread], - Type["ThreadOption"], Type[Member], Type[User], Type[Attachment], @@ -200,10 +199,6 @@ def __init__(self, input_type: InputType = str, /, description: Optional[str] = for i in input_type: if i is GuildChannel: continue - if isinstance(i, ThreadOption): - self.channel_types.append(i._type) - continue - channel_type = CHANNEL_TYPE_MAP[i] self.channel_types.append(channel_type) input_type = _type From 68204d0a62e8e797a975372cb080988e84aead5f Mon Sep 17 00:00:00 2001 From: Dorukyum Date: Thu, 7 Jul 2022 12:04:02 +0300 Subject: [PATCH 06/11] Fix all type issues in options.py --- discord/commands/options.py | 77 ++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 39 deletions(-) diff --git a/discord/commands/options.py b/discord/commands/options.py index 4c732c9817..861b54fd1a 100644 --- a/discord/commands/options.py +++ b/discord/commands/options.py @@ -157,83 +157,82 @@ async def hello( See `here `_ for a list of valid locales. """ + input_type: SlashCommandOptionType + converter: Optional[Union[Converter, Type[Converter]]] = None + def __init__(self, input_type: InputType = str, /, description: Optional[str] = None, **kwargs) -> None: self.name: Optional[str] = kwargs.pop("name", None) if self.name is not None: self.name = str(self.name) self._parameter_name = self.name # default + + enum_choices = [] + input_type_is_class = isinstance(input_type, type) + if input_type_is_class and issubclass(input_type, (Enum, DiscordEnum)): + description = inspect.getdoc(input_type) + enum_choices = [OptionChoice(e.name, e.value) for e in input_type] + value_class = enum_choices[0].value.__class__ + if all(isinstance(elem.value, value_class) for elem in enum_choices): + self.input_type = SlashCommandOptionType.from_datatype(enum_choices[0].value.__class__) + else: + enum_choices = [OptionChoice(e.name, str(e.value)) for e in input_type] + self.input_type = SlashCommandOptionType.string self.description = description or "No description provided" - self.converter = None - self._raw_type = input_type + + self._raw_type: Union[InputType, tuple] = input_type self.channel_types: List[ChannelType] = kwargs.pop("channel_types", []) - enum_choices = [] + if not isinstance(input_type, SlashCommandOptionType): - if hasattr(input_type, "convert"): + if isinstance(input_type, Converter) or input_type_is_class and issubclass(input_type, Converter): self.converter = input_type self._raw_type = str - input_type = SlashCommandOptionType.string - elif isinstance(input_type, type) and issubclass(input_type, (Enum, DiscordEnum)): - enum_choices = [OptionChoice(e.name, e.value) for e in input_type] - if len(enum_choices) != len([elem for elem in enum_choices if elem.value.__class__ == enum_choices[0].value.__class__]): - enum_choices = [OptionChoice(e.name, str(e.value)) for e in input_type] - input_type = SlashCommandOptionType.string - else: - input_type = SlashCommandOptionType.from_datatype(enum_choices[0].value.__class__) + self.input_type = SlashCommandOptionType.string else: try: - _type = SlashCommandOptionType.from_datatype(input_type) + self.input_type = SlashCommandOptionType.from_datatype(input_type) except TypeError as exc: from ..ext.commands.converter import CONVERTER_MAPPING if input_type not in CONVERTER_MAPPING: raise exc self.converter = CONVERTER_MAPPING[input_type] - input_type = SlashCommandOptionType.string + self._raw_type = str + self.input_type = SlashCommandOptionType.string else: - if _type == SlashCommandOptionType.channel: - if not isinstance(input_type, tuple): - if hasattr(input_type, "__args__"): # Union - input_type = input_type.__args__ + if self.input_type == SlashCommandOptionType.channel: + if not isinstance(self._raw_type, tuple): + if hasattr(input_type, "__args__"): + self._raw_type = input_type.__args__ # type: ignore # Union.__args__ else: - input_type = (input_type,) - for i in input_type: - if i is GuildChannel: - continue - channel_type = CHANNEL_TYPE_MAP[i] - self.channel_types.append(channel_type) - input_type = _type - self.input_type = input_type + self._raw_type = (input_type,) + self.channel_types = [CHANNEL_TYPE_MAP[t] for t in self._raw_type if t is not GuildChannel] self.required: bool = kwargs.pop("required", True) if "default" not in kwargs else False self.default = kwargs.pop("default", None) self.choices: List[OptionChoice] = enum_choices or [ o if isinstance(o, OptionChoice) else OptionChoice(o) for o in kwargs.pop("choices", list()) ] - if description is not None: - self.description = description - elif isinstance(self._raw_type, type) and issubclass(self._raw_type, Enum) and (doc := inspect.getdoc(self._raw_type)) is not None: - self.description = doc - else: - self.description = "No description provided" - if self.input_type == SlashCommandOptionType.integer: minmax_types = (int, type(None)) + minmax_typehint = Optional[int] elif self.input_type == SlashCommandOptionType.number: minmax_types = (int, float, type(None)) + minmax_typehint = Optional[Union[int, float]] else: minmax_types = (type(None),) - minmax_typehint = Optional[Union[minmax_types]] # type: ignore + minmax_typehint = type(None) if self.input_type == SlashCommandOptionType.string: minmax_length_types = (int, type(None)) + minmax_length_typehint = Optional[int] else: minmax_length_types = (type(None),) - minmax_length_typehint = Optional[Union[minmax_length_types]] # type: ignore + minmax_length_typehint = type(None) - self.min_value: minmax_typehint = kwargs.pop("min_value", None) - self.max_value: minmax_typehint = kwargs.pop("max_value", None) - self.min_length: minmax_length_typehint = kwargs.pop("min_length", None) - self.max_length: minmax_length_typehint = kwargs.pop("max_length", None) + self.min_value: Optional[Union[int, float]] = kwargs.pop("min_value", None) + self.max_value: Optional[Union[int, float]] = kwargs.pop("max_value", None) + self.min_length: Optional[int] = kwargs.pop("min_length", None) + self.max_length: Optional[int] = kwargs.pop("max_length", None) if (input_type != SlashCommandOptionType.integer and input_type != SlashCommandOptionType.number and (self.min_value or self.max_value)): From 491092f3fbfe371dbcaa9276da91edeca5fd14bf Mon Sep 17 00:00:00 2001 From: Dorukyum Date: Fri, 8 Jul 2022 23:31:52 +0300 Subject: [PATCH 07/11] Add missing import --- discord/commands/options.py | 1 + 1 file changed, 1 insertion(+) diff --git a/discord/commands/options.py b/discord/commands/options.py index 861b54fd1a..6036de20d0 100644 --- a/discord/commands/options.py +++ b/discord/commands/options.py @@ -183,6 +183,7 @@ def __init__(self, input_type: InputType = str, /, description: Optional[str] = self.channel_types: List[ChannelType] = kwargs.pop("channel_types", []) if not isinstance(input_type, SlashCommandOptionType): + from ..ext.commands import Converter if isinstance(input_type, Converter) or input_type_is_class and issubclass(input_type, Converter): self.converter = input_type self._raw_type = str From 7cb55cfa83ee4255b9abc86c1628d93fe8981a85 Mon Sep 17 00:00:00 2001 From: Dorukyum Date: Fri, 8 Jul 2022 23:43:38 +0300 Subject: [PATCH 08/11] Document input type --- discord/commands/options.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/discord/commands/options.py b/discord/commands/options.py index 6036de20d0..e226854a41 100644 --- a/discord/commands/options.py +++ b/discord/commands/options.py @@ -114,8 +114,9 @@ async def hello( Attributes ---------- - input_type: :class:`Any` - The type of input that is expected for this option. + input_type: Union[Type[:class:`str`], Type[:class:`bool`], Type[:class:`int`], Type[:class:`float`], Type[:class:`.abc.GuildChannel`], Type[:class:`Thread`], Type[:class:`Member`], Type[:class:`User`], Type[:class:`Attachment`], Type[:class:`Role`], Type[:class:`.abc.Mentionable`], :class:`SlashCommandOptionType`, :class:`Converter`, Type[:class:`Converter`], Type[:class:`Enum`], Type[:class:`DiscordEnum`]] + The type of input that is expected for this option. This can be a :class:`SlashCommandOptionType`, + an associated class, a channel type, a :class:`Converter`, a converter class or an :class:`enum.Enum`. name: :class:`str` The name of this option visible in the UI. Inherits from the variable name if not provided as a parameter. From 6dc8a9b0f5e8db56b1b95418791ea5e8f7ced6e2 Mon Sep 17 00:00:00 2001 From: Lala Sabathil Date: Tue, 12 Jul 2022 20:32:34 +0200 Subject: [PATCH 09/11] Update discord/commands/options.py Co-authored-by: Middledot <78228142+Middledot@users.noreply.github.com> --- discord/commands/options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/commands/options.py b/discord/commands/options.py index e226854a41..7185367003 100644 --- a/discord/commands/options.py +++ b/discord/commands/options.py @@ -114,7 +114,7 @@ async def hello( Attributes ---------- - input_type: Union[Type[:class:`str`], Type[:class:`bool`], Type[:class:`int`], Type[:class:`float`], Type[:class:`.abc.GuildChannel`], Type[:class:`Thread`], Type[:class:`Member`], Type[:class:`User`], Type[:class:`Attachment`], Type[:class:`Role`], Type[:class:`.abc.Mentionable`], :class:`SlashCommandOptionType`, :class:`Converter`, Type[:class:`Converter`], Type[:class:`Enum`], Type[:class:`DiscordEnum`]] + input_type: Union[:class:`str`, :class:`bool`, :class:`int`, :class:`float`, :class:`.abc.GuildChannel`, :class:`Thread`, :class:`Member`, :class:`User`, :class:`Attachment`, :class:`Role`, :class:`abc.Mentionable`, :class:`SlashCommandOptionType`, :class:`.ext.commands.Converter`, :class:`enum.Enum`, :class:`Enum`] The type of input that is expected for this option. This can be a :class:`SlashCommandOptionType`, an associated class, a channel type, a :class:`Converter`, a converter class or an :class:`enum.Enum`. name: :class:`str` From bb5321e1450b9d8ba29c998049d2effd93eba974 Mon Sep 17 00:00:00 2001 From: Lala Sabathil Date: Tue, 12 Jul 2022 20:34:30 +0200 Subject: [PATCH 10/11] Revert "Update discord/commands/options.py" This reverts commit 6dc8a9b0f5e8db56b1b95418791ea5e8f7ced6e2. --- discord/commands/options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/commands/options.py b/discord/commands/options.py index 7185367003..e226854a41 100644 --- a/discord/commands/options.py +++ b/discord/commands/options.py @@ -114,7 +114,7 @@ async def hello( Attributes ---------- - input_type: Union[:class:`str`, :class:`bool`, :class:`int`, :class:`float`, :class:`.abc.GuildChannel`, :class:`Thread`, :class:`Member`, :class:`User`, :class:`Attachment`, :class:`Role`, :class:`abc.Mentionable`, :class:`SlashCommandOptionType`, :class:`.ext.commands.Converter`, :class:`enum.Enum`, :class:`Enum`] + input_type: Union[Type[:class:`str`], Type[:class:`bool`], Type[:class:`int`], Type[:class:`float`], Type[:class:`.abc.GuildChannel`], Type[:class:`Thread`], Type[:class:`Member`], Type[:class:`User`], Type[:class:`Attachment`], Type[:class:`Role`], Type[:class:`.abc.Mentionable`], :class:`SlashCommandOptionType`, :class:`Converter`, Type[:class:`Converter`], Type[:class:`Enum`], Type[:class:`DiscordEnum`]] The type of input that is expected for this option. This can be a :class:`SlashCommandOptionType`, an associated class, a channel type, a :class:`Converter`, a converter class or an :class:`enum.Enum`. name: :class:`str` From ced16009427840298a6393af8397a1d8c97b89e8 Mon Sep 17 00:00:00 2001 From: Lala Sabathil Date: Tue, 12 Jul 2022 20:40:32 +0200 Subject: [PATCH 11/11] Update discord/commands/options.py Co-authored-by: Middledot <78228142+Middledot@users.noreply.github.com> --- discord/commands/options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/commands/options.py b/discord/commands/options.py index e226854a41..49e80d5ec8 100644 --- a/discord/commands/options.py +++ b/discord/commands/options.py @@ -114,7 +114,7 @@ async def hello( Attributes ---------- - input_type: Union[Type[:class:`str`], Type[:class:`bool`], Type[:class:`int`], Type[:class:`float`], Type[:class:`.abc.GuildChannel`], Type[:class:`Thread`], Type[:class:`Member`], Type[:class:`User`], Type[:class:`Attachment`], Type[:class:`Role`], Type[:class:`.abc.Mentionable`], :class:`SlashCommandOptionType`, :class:`Converter`, Type[:class:`Converter`], Type[:class:`Enum`], Type[:class:`DiscordEnum`]] + input_type: Union[Type[:class:`str`], Type[:class:`bool`], Type[:class:`int`], Type[:class:`float`], Type[:class:`.abc.GuildChannel`], Type[:class:`Thread`], Type[:class:`Member`], Type[:class:`User`], Type[:class:`Attachment`], Type[:class:`Role`], Type[:class:`.abc.Mentionable`], :class:`SlashCommandOptionType`, Type[:class:`.ext.commands.Converter`], Type[:class:`enums.Enum`], Type[:class:`Enum`]] The type of input that is expected for this option. This can be a :class:`SlashCommandOptionType`, an associated class, a channel type, a :class:`Converter`, a converter class or an :class:`enum.Enum`. name: :class:`str`