Skip to content

Commit 1331d76

Browse files
committed
Approximative component 19 implementation, make StateComponent a mixin, rename stuff
Signed-off-by: Paillat-dev <[email protected]>
1 parent 55e5af7 commit 1331d76

15 files changed

+229
-27
lines changed

discord/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
from .collectibles import *
3939
from .colour import *
4040
from .commands import *
41-
from .components import *
4241
from .embeds import *
4342
from .emoji import *
4443
from .enums import *

discord/components/__init__.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,12 @@
2626
from .action_row import ActionRow
2727
from .button import Button
2828
from .channel_select_menu import ChannelSelect
29-
from .component import Component, ModalComponentMixin, StateComponent, WalkableComponent
29+
from .component import Component, ModalComponentMixin, StateComponentMixin, WalkableComponent
3030
from .components_holder import ComponentsHolder
3131
from .container import Container
3232
from .default_select_option import DefaultSelectOption
3333
from .file_component import FileComponent
34+
from .file_upload import FileUpload
3435
from .input_text import TextInput
3536
from .label import Label
3637
from .media_gallery import MediaGallery
@@ -41,6 +42,7 @@
4142
PartialButton,
4243
PartialChannelSelect,
4344
PartialComponent,
45+
PartialFileUpload,
4446
PartialLabel,
4547
PartialMentionableSelect,
4648
PartialRoleSelect,
@@ -51,7 +53,7 @@
5153
PartialUserSelect,
5254
PartialWalkableComponent,
5355
UnknownPartialComponent,
54-
_interaction_component_factory, # pyright: ignore[reportPrivateUsage]
56+
_partial_component_factory, # pyright: ignore[reportPrivateUsage]
5557
)
5658
from .role_select_menu import RoleSelect
5759
from .section import Section
@@ -77,7 +79,7 @@
7779

7880
__all__ = (
7981
"Component",
80-
"StateComponent",
82+
"StateComponentMixin",
8183
"WalkableComponent",
8284
"ModalComponentMixin",
8385
"ComponentsHolder",
@@ -100,6 +102,7 @@
100102
"MediaGalleryItem",
101103
"UnfurledMediaItem",
102104
"FileComponent",
105+
"FileUpload",
103106
"Separator",
104107
"Container",
105108
"Label",
@@ -118,7 +121,8 @@
118121
"PartialTextInput",
119122
"PartialTextDisplay",
120123
"UnknownPartialComponent",
121-
"_interaction_component_factory",
124+
"PartialFileUpload",
125+
"_partial_component_factory",
122126
"AnyComponent",
123127
"AnyTopLevelModalComponent",
124128
"AnyTopLevelMessageComponent",

discord/components/_component_factory.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
from .action_row import ActionRow
3131
from .button import Button
3232
from .channel_select_menu import ChannelSelect
33-
from .component import Component, StateComponent
33+
from .component import Component, StateComponentMixin
3434
from .container import Container
3535
from .file_component import FileComponent
3636
from .input_text import TextInput
@@ -76,7 +76,7 @@
7676
def _component_factory(data: P, state: ConnectionState | None = None) -> Component[P]:
7777
component_type = data["type"]
7878
if cls := COMPONENT_MAPPINGS.get(component_type):
79-
if issubclass(cls, StateComponent):
79+
if issubclass(cls, StateComponentMixin):
8080
return cls.from_payload(data, state=state) # pyright: ignore[ reportReturnType, reportArgumentType]
8181
else:
8282
return cls.from_payload(data) # pyright: ignore[reportArgumentType, reportReturnType]

discord/components/component.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def any_is_dispatchable(self) -> bool:
101101
return self.is_dispatchable()
102102

103103

104-
class StateComponent(Component[P], ABC):
104+
class StateComponentMixin(Component[P], ABC):
105105
@classmethod
106106
@abstractmethod
107107
@override

discord/components/file_component.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030

3131
from ..enums import ComponentType
3232
from ..types.components import FileComponent as FileComponentPayload
33-
from .component import StateComponent
33+
from .component import Component, StateComponentMixin
3434
from .unfurled_media_item import UnfurledMediaItem
3535

3636
if TYPE_CHECKING:
@@ -39,7 +39,7 @@
3939
from ..state import ConnectionState
4040

4141

42-
class FileComponent(StateComponent[FileComponentPayload]):
42+
class FileComponent(StateComponentMixin[FileComponentPayload], Component[FileComponentPayload]):
4343
"""Represents a File from Components V2.
4444
4545
This component displays a downloadable file in a message.

discord/components/file_upload.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
"""
2+
The MIT License (MIT)
3+
4+
Copyright (c) 2021-present Pycord Development
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a
7+
copy of this software and associated documentation files (the "Software"),
8+
to deal in the Software without restriction, including without limitation
9+
the rights to use, copy, modify, merge, publish, distribute, sublicense,
10+
and/or sell copies of the Software, and to permit persons to whom the
11+
Software is furnished to do so, subject to the following conditions:
12+
13+
The above copyright notice and this permission notice shall be included in
14+
all copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22+
DEALINGS IN THE SOFTWARE.
23+
"""
24+
25+
from __future__ import annotations
26+
27+
from typing import TYPE_CHECKING, ClassVar, Literal
28+
29+
from typing_extensions import override
30+
31+
from ..enums import ComponentType
32+
from ..types.components import FileUpload as FileUploadPayload
33+
from .component import Component, ModalComponentMixin
34+
35+
if TYPE_CHECKING:
36+
from typing_extensions import Self
37+
38+
39+
class FileUpload(ModalComponentMixin[FileUploadPayload], Component[FileUploadPayload]):
40+
"""Represents a File Upload Component.
41+
42+
This component displays a file upload box in a :class:`Modal`.
43+
44+
This inherits from :class:`Component`.
45+
46+
.. versionadded:: 3.0
47+
48+
Attributes
49+
----------
50+
type: Literal[:data:`ComponentType.file_upload`]
51+
The type of component.
52+
custom_id: :class:`str`
53+
The custom ID of the file upload component that gets received during an interaction.
54+
min_values: :class:`int`
55+
The minimum number of files that must be uploaded.
56+
max_values: :class:`int`
57+
The maximum number of files that can be uploaded.
58+
required: :class:`bool`
59+
Whether the file upload is required to submit the modal.
60+
id: :class:`int` | :data:`None`
61+
The section's ID.
62+
63+
Parameters
64+
----------
65+
id:
66+
The component's ID. If not provided by the user, it is set sequentially by Discord.
67+
The ID `0` is treated as if no ID was provided.
68+
"""
69+
70+
__slots__: tuple[str, ...] = (
71+
"file",
72+
"spoiler",
73+
"name",
74+
"size",
75+
)
76+
77+
__repr_info__: ClassVar[tuple[str, ...]] = __slots__
78+
versions: tuple[int, ...] = (2,)
79+
type: Literal[ComponentType.file_upload] = ComponentType.file_upload # pyright: ignore[reportIncompatibleVariableOverride]
80+
81+
def __init__(
82+
self,
83+
custom_id: str,
84+
id: int | None = None,
85+
min_values: int = 1,
86+
max_values: int = 1,
87+
required: bool = True,
88+
) -> None:
89+
self.custom_id: str = custom_id
90+
self.min_values: int = min_values
91+
self.max_values: int = max_values
92+
self.required: bool = required
93+
super().__init__(id=id)
94+
95+
@classmethod
96+
@override
97+
def from_payload(cls, payload: FileUploadPayload) -> Self:
98+
return cls(
99+
custom_id=payload["custom_id"],
100+
id=payload["id"],
101+
min_values=payload.get("min_values", 1),
102+
max_values=payload.get("max_values", 1),
103+
required=payload.get("required", True),
104+
)
105+
106+
@override
107+
def to_dict(self, modal: bool = True) -> FileUploadPayload:
108+
payload: FileUploadPayload = {
109+
"type": int(self.type),
110+
"custom_id": self.custom_id,
111+
"min_values": self.min_values,
112+
"max_values": self.max_values,
113+
"required": self.required,
114+
"id": self.id,
115+
}
116+
return payload

discord/components/label.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,12 @@
3939

4040
from discord.state import ConnectionState
4141

42+
from .file_upload import FileUpload
4243
from .input_text import TextInput
4344
from .string_select_menu import StringSelect
4445
from .user_select_menu import UserSelect
4546

46-
AllowedLabelComponents: TypeAlias = "StringSelect | UserSelect | TextInput"
47+
AllowedLabelComponents: TypeAlias = StringSelect | UserSelect | TextInput | FileUpload
4748

4849

4950
class Label(

discord/components/media_gallery.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131

3232
from ..enums import ComponentType
3333
from ..types.components import MediaGalleryComponent as MediaGalleryComponentPayload
34-
from .component import StateComponent
34+
from .component import Component, StateComponentMixin
3535
from .media_gallery_item import MediaGalleryItem
3636

3737
if TYPE_CHECKING:
@@ -40,7 +40,7 @@
4040
from ..state import ConnectionState
4141

4242

43-
class MediaGallery(StateComponent[MediaGalleryComponentPayload]):
43+
class MediaGallery(StateComponentMixin[MediaGalleryComponentPayload], Component[MediaGalleryComponentPayload]):
4444
"""Represents a Media Gallery from Components V2.
4545
4646
This is a component that displays up to 10 different :class:`MediaGalleryItem` objects.

discord/components/partial_components.py

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from ..types.partial_components import PartialButton as PartialButtonPayload
3636
from ..types.partial_components import PartialChannelSelectMenu as PartialChannelSelectPayload
3737
from ..types.partial_components import PartialComponent as PartialComponentPayload
38+
from ..types.partial_components import PartialFileUpload as PartialFileUploadPayload
3839
from ..types.partial_components import PartialLabel as PartialLabelPayload
3940
from ..types.partial_components import PartialMentionableSelectMenu as PartialMentionableSelectPayload
4041
from ..types.partial_components import PartialRoleSelectMenu as PartialRoleSelectPayload
@@ -48,6 +49,7 @@
4849

4950
from .type_aliases import AnyPartialComponent
5051

52+
AllowedPartialLabelComponents: TypeAlias = "PartialStringSelect | PartialUserSelect | PartialChannelSelect | PartialRoleSelect | PartialMentionableSelect | PartialTextInput | PartialFileUpload"
5153

5254
# Below, the usage of field with kw_only=True is used to push the attribute at the end of the __init__ signature and
5355
# avoid issues with optional arguments order during class inheritance.
@@ -306,8 +308,6 @@ def from_payload(cls, payload: PartialTextInputPayload) -> Self:
306308
return cls(id=payload["id"], custom_id=payload["custom_id"], value=payload["value"])
307309

308310

309-
AllowedPartialLabelComponents: TypeAlias = "PartialStringSelect | PartialUserSelect | PartialChannelSelect | PartialRoleSelect | PartialMentionableSelect | PartialTextInput"
310-
311311
L_c = TypeVar("L_c", bound=AllowedPartialLabelComponents, default=AllowedPartialLabelComponents)
312312

313313

@@ -341,7 +341,7 @@ class PartialLabel(
341341
def from_payload(cls, payload: PartialLabelPayload) -> Self:
342342
return cls(
343343
id=payload["id"],
344-
component=cast("AllowedPartialLabelComponents", _interaction_component_factory(payload["component"])),
344+
component=cast("AllowedPartialLabelComponents", _partial_component_factory(payload["component"])),
345345
)
346346

347347
@override
@@ -374,6 +374,35 @@ def from_payload(cls, payload: PartialTextDisplayPayload) -> Self:
374374
return cls(id=payload["id"])
375375

376376

377+
@dataclass
378+
class PartialFileUpload(PartialComponent[Literal[ComponentType.file_upload], PartialFileUploadPayload]):
379+
"""Represents a :class:`TextDisplay` component as returned by Discord during a :class:`Interaction` of type :data:`InteractionType.modal_submit`.
380+
381+
.. versionadded:: 3.0
382+
383+
Attributes
384+
----------
385+
type: Literal[:data:`ComponentType.file_upload`]
386+
The type of component.
387+
id: :class:`int`
388+
The ID of this file upload component.
389+
custom_id: :class:`str`
390+
The custom ID of this file upload component.
391+
values: :class:`list` of :class:`int`
392+
The attachment IDs uploaded in the file upload component.
393+
"""
394+
395+
id: int
396+
custom_id: str
397+
values: list[int]
398+
type: Literal[ComponentType.file_upload] = field(default=ComponentType.file_upload, kw_only=True)
399+
400+
@classmethod
401+
@override
402+
def from_payload(cls, payload: PartialFileUploadPayload) -> Self:
403+
return cls(id=payload["id"], custom_id=payload["custom_id"], values=[int(value) for value in payload["values"]])
404+
405+
377406
@dataclass
378407
class UnknownPartialComponent(PartialComponent[ComponentType, PartialComponentPayload]):
379408
"""A class representing an unknown interaction component.
@@ -414,10 +443,11 @@ def from_payload(cls, payload: PartialComponentPayload) -> Self:
414443
8: PartialChannelSelect,
415444
10: PartialTextDisplay,
416445
18: PartialLabel,
446+
19: PartialFileUpload,
417447
}
418448

419449

420-
def _interaction_component_factory(payload: PartialComponentPayload, key: str = "type") -> AnyPartialComponent:
450+
def _partial_component_factory(payload: PartialComponentPayload, key: str = "type") -> AnyPartialComponent:
421451
component_type: int = cast("int", payload[key])
422452
component_class = COMPONENT_MAPPINGS.get(component_type, UnknownPartialComponent)
423453
return component_class.from_payload(payload) # pyright: ignore[reportArgumentType]

discord/components/section.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@
4343
from .text_display import TextDisplay
4444
from .thumbnail import Thumbnail
4545

46-
AllowedSectionComponents: TypeAlias = "TextDisplay"
47-
AllowedSectionAccessoryComponents: TypeAlias = "Button | Thumbnail"
46+
AllowedSectionComponents: TypeAlias = TextDisplay
47+
AllowedSectionAccessoryComponents: TypeAlias = Button | Thumbnail
4848

4949

5050
class Section(

0 commit comments

Comments
 (0)