from __future__ import annotations
import os
from typing import TYPE_CHECKING
from ..components import FileUpload as FileUploadComponent
from ..enums import ComponentType
from ..message import Attachment
from .item import ModalItem
__all__ = ("FileUpload",)
if TYPE_CHECKING:
from ..interactions import Interaction
from ..types.components import FileUploadComponent as FileUploadComponentPayload
[docs]
class FileUpload(ModalItem):
"""Represents a UI File Upload component.
.. versionadded:: 2.7
Parameters
----------
custom_id: Optional[:class:`str`]
The ID of the input text field that gets received during an interaction.
min_values: Optional[:class:`int`]
The minimum number of files that must be uploaded.
Defaults to 0 and must be between 0 and 10, inclusive.
max_values: Optional[:class:`int`]
The maximum number of files that can be uploaded.
Must be between 1 and 10, inclusive.
required: Optional[:class:`bool`]
Whether the file upload field is required or not. Defaults to ``True``.
id: Optional[:class:`int`]
The file upload field's ID.
"""
__item_repr_attributes__: tuple[str, ...] = (
"required",
"min_values",
"max_values",
"custom_id",
"id",
)
def __init__(
self,
*,
custom_id: str | None = None,
min_values: int | None = None,
max_values: int | None = None,
required: bool = True,
id: int | None = None,
):
super().__init__()
if min_values and (min_values < 0 or min_values > 10):
raise ValueError("min_values must be between 0 and 10")
if max_values and (max_values < 1 or max_values > 10):
raise ValueError("max_length must be between 1 and 10")
if custom_id is not None and not isinstance(custom_id, str):
raise TypeError(
f"expected custom_id to be str, not {custom_id.__class__.__name__}"
)
if not isinstance(required, bool):
raise TypeError(f"required must be bool not {required.__class__.__name__}") # type: ignore
custom_id = os.urandom(16).hex() if custom_id is None else custom_id
self._attachments: list[Attachment] | None = None
self._underlying: FileUploadComponent = self._generate_underlying(
custom_id=custom_id,
min_values=min_values,
max_values=max_values,
required=required,
id=id,
)
def __repr__(self) -> str:
attrs = " ".join(
f"{key}={getattr(self, key)!r}" for key in self.__item_repr_attributes__
)
return f"<{self.__class__.__name__} {attrs}>"
def _generate_underlying(
self,
custom_id: str | None = None,
min_values: int | None = None,
max_values: int | None = None,
required: bool = None,
id: int | None = None,
) -> FileUploadComponent:
super()._generate_underlying(FileUploadComponent)
return FileUploadComponent._raw_construct(
type=ComponentType.file_upload,
custom_id=custom_id or self.custom_id,
min_values=min_values or self.min_values,
max_values=max_values or self.max_values,
required=required if required is not None else self.required,
id=id or self.id,
)
@property
def type(self) -> ComponentType:
return self.underlying.type
@property
def id(self) -> int | None:
"""The ID of this component. If not provided by the user, it is set sequentially by Discord."""
return self.underlying.id
@property
def custom_id(self) -> str:
"""The custom id that gets received during an interaction."""
return self.underlying.custom_id
@custom_id.setter
def custom_id(self, value: str):
if not isinstance(value, str):
raise TypeError(
f"custom_id must be None or str not {value.__class__.__name__}"
)
self.underlying.custom_id = value
@property
def min_values(self) -> int | None:
"""The minimum number of files that must be uploaded. Defaults to 0."""
return self.underlying.min_values
@min_values.setter
def min_values(self, value: int | None):
if value and not isinstance(value, int):
raise TypeError(f"min_values must be None or int not {value.__class__.__name__}") # type: ignore
if value and (value < 0 or value > 10):
raise ValueError("min_values must be between 0 and 10")
self.underlying.min_values = value
@property
def max_values(self) -> int | None:
"""The maximum number of files that can be uploaded."""
return self.underlying.max_values
@max_values.setter
def max_values(self, value: int | None):
if value and not isinstance(value, int):
raise TypeError(f"max_values must be None or int not {value.__class__.__name__}") # type: ignore
if value and (value < 1 or value > 10):
raise ValueError("max_values must be between 1 and 10")
self.underlying.max_values = value
@property
def required(self) -> bool:
"""Whether the input file upload is required or not. Defaults to ``True``."""
return self.underlying.required
@required.setter
def required(self, value: bool):
if not isinstance(value, bool):
raise TypeError(f"required must be bool not {value.__class__.__name__}") # type: ignore
self.underlying.required = bool(value)
@property
def values(self) -> list[Attachment] | None:
"""The files that were uploaded to the field."""
return self._attachments
def to_component_dict(self) -> FileUploadComponentPayload:
return self.underlying.to_dict()
def refresh_from_modal(self, interaction: Interaction, data: dict) -> None:
values = data.get("values", [])
self._attachments = [
Attachment(
state=interaction._state,
data=interaction.data["resolved"]["attachments"][attachment_id],
)
for attachment_id in values
]
@classmethod
def from_component(
cls: type[FileUpload], component: FileUploadComponent
) -> FileUpload:
return cls(
custom_id=component.custom_id,
min_values=component.min_values,
max_values=component.max_values,
required=component.required,
id=component.id,
)