Browse Source

chore: some fixes of bugs reported on the bikeshedding post

pull/10166/head
DA-344 3 months ago
parent
commit
c48c512d88
  1. 11
      discord/ui/action_row.py
  2. 7
      discord/ui/button.py
  3. 90
      discord/ui/container.py
  4. 4
      discord/ui/file.py
  5. 9
      discord/ui/item.py
  6. 4
      discord/ui/media_gallery.py
  7. 4
      discord/ui/section.py
  8. 44
      discord/ui/select.py
  9. 4
      discord/ui/separator.py
  10. 4
      discord/ui/text_display.py
  11. 8
      discord/ui/thumbnail.py
  12. 8
      discord/ui/view.py

11
discord/ui/action_row.py

@ -94,19 +94,20 @@ class ActionRow(Item[V]):
Parameters
----------
id: Optional[:class:`str`]
The ID of this action row. Defaults to ``None``.
id: Optional[:class:`int`]
The ID of this component. This must be unique across the view.
"""
__action_row_children_items__: ClassVar[List[ItemCallbackType[Any, Any]]] = []
__discord_ui_action_row__: ClassVar[bool] = True
__pending_view__: ClassVar[bool] = True
def __init__(self, *, id: Optional[str] = None) -> None:
def __init__(self, *, id: Optional[int] = None) -> None:
super().__init__()
self.id: str = id or os.urandom(16).hex()
self._children: List[Item[Any]] = self._init_children()
self.id = id
def __init_subclass__(cls) -> None:
super().__init_subclass__()

7
discord/ui/button.py

@ -83,6 +83,10 @@ class Button(Item[V]):
nor ``custom_id``.
.. versionadded:: 2.4
id: Optional[:class:`int`]
The ID of this component. This must be unique across the view.
.. versionadded:: 2.6
"""
__item_repr_attributes__: Tuple[str, ...] = (
@ -106,6 +110,7 @@ class Button(Item[V]):
emoji: Optional[Union[str, Emoji, PartialEmoji]] = None,
row: Optional[int] = None,
sku_id: Optional[int] = None,
id: Optional[int] = None,
):
super().__init__()
if custom_id is not None and (url is not None or sku_id is not None):
@ -147,7 +152,7 @@ class Button(Item[V]):
)
self._parent: Optional[ActionRow] = None
self.row = row
self.id = custom_id
self.id = id
@property
def style(self) -> ButtonStyle:

90
discord/ui/container.py

@ -23,10 +23,10 @@ DEALINGS IN THE SOFTWARE.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Tuple, Type, TypeVar
from typing import TYPE_CHECKING, Any, ClassVar, Coroutine, Dict, List, Literal, Optional, Tuple, Type, TypeVar, Union
from .item import Item
from .view import BaseView, _component_to_item, LayoutView
from .item import Item, ItemCallbackType
from .view import _component_to_item, LayoutView
from .dynamic import DynamicItem
from ..enums import ComponentType
from ..utils import MISSING
@ -36,13 +36,26 @@ if TYPE_CHECKING:
from ..colour import Colour, Color
from ..components import Container as ContainerComponent
from ..interactions import Interaction
V = TypeVar('V', bound='LayoutView', covariant=True)
__all__ = ('Container',)
class Container(BaseView, Item[V]):
class _ContainerCallback:
__slots__ = ('container', 'callback', 'item')
def __init__(self, callback: ItemCallbackType[Any, Any], container: Container, item: Item[Any]) -> None:
self.callback: ItemCallbackType[Any, Any] = callback
self.container: Container = container
self.item: Item[Any] = item
def __call__(self, interaction: Interaction) -> Coroutine[Any, Any, Any]:
return self.callback(self.container, interaction, self.item)
class Container(Item[V]):
"""Represents a UI container.
.. versionadded:: 2.6
@ -66,41 +79,86 @@ class Container(BaseView, Item[V]):
passing an index is advised. For example, row=1 will show
up before row=2. Defaults to ``None``, which is automatic
ordering. The row number must be between 0 and 9 (i.e. zero indexed)
id: Optional[:class:`str`]
id: Optional[:class:`int`]
The ID of this component. This must be unique across the view.
"""
__container_children_items__: ClassVar[List[Union[ItemCallbackType[Any, Any], Item[Any]]]] = []
__pending_view__: ClassVar[bool] = True
def __init__(
self,
children: List[Item[Any]] = MISSING,
children: List[Item[V]] = MISSING,
*,
accent_colour: Optional[Colour] = None,
accent_color: Optional[Color] = None,
spoiler: bool = False,
row: Optional[int] = None,
id: Optional[str] = None,
id: Optional[int] = None,
) -> None:
super().__init__(timeout=None)
self._children: List[Item[V]] = self._init_children()
if children is not MISSING:
if len(children) + len(self._children) > 10:
raise ValueError('maximum number of components exceeded')
self._children.extend(children)
raise ValueError('maximum number of children exceeded')
self.spoiler: bool = spoiler
self._colour = accent_colour or accent_color
self._view: Optional[V] = None
self._row: Optional[int] = None
self._rendered_row: Optional[int] = None
self.row: Optional[int] = row
self.id: Optional[str] = id
self.row = row
self.id = id
def _init_children(self) -> List[Item[Any]]:
children = []
for raw in self.__container_children_items__:
if isinstance(raw, Item):
children.append(raw)
else:
# action rows can be created inside containers, and then callbacks can exist here
# so we create items based off them
item: Item = raw.__discord_ui_model_type__(**raw.__discord_ui_model_kwargs__)
item.callback = _ContainerCallback(raw, self, item) # type: ignore
setattr(self, raw.__name__, item)
# this should not fail because in order for a function to be here it should be from
# an action row and must have passed the check in __init_subclass__, but still
# guarding it
parent = getattr(raw, '__discord_ui_parent__', None)
if parent is None:
raise RuntimeError(f'{raw.__name__} is not a valid item for a Container')
parent._children.append(item)
# we donnot append it to the children list because technically these buttons and
# selects are not from the container but the action row itself.
return children
def __init_subclass__(cls) -> None:
super().__init_subclass__()
children: Dict[str, Union[ItemCallbackType[Any, Any], Item[Any]]] = {}
for base in reversed(cls.__mro__):
for name, member in base.__dict__.items():
if isinstance(member, Item):
children[name] = member
if hasattr(member, '__discord_ui_model_type__') and hasattr(member, '__discord_ui_parent__'):
children[name] = member
cls.__container_children_items__ = list(children.values())
def _update_children_view(self, view) -> None:
for child in self._children:
child._view = view
if getattr(child, '__pending_view__', False):
# if the item is an action row which child's view can be updated, then update it
child._update_children_view(view) # type: ignore
@property
def children(self) -> List[Item[Self]]:
def children(self) -> List[Item[V]]:
"""List[:class:`Item`]: The children of this container."""
return self._children.copy()
@children.setter
def children(self, value: List[Item[Any]]) -> None:
def children(self, value: List[Item[V]]) -> None:
self._children = value
@property

4
discord/ui/file.py

@ -59,7 +59,7 @@ class File(Item[V]):
passing an index is advised. For example, row=1 will show
up before row=2. Defaults to ``None``, which is automatic
ordering. The row number must be between 0 and 9 (i.e. zero indexed)
id: Optional[:class:`str`]
id: Optional[:class:`int`]
The ID of this component. This must be unique across the view.
"""
@ -69,7 +69,7 @@ class File(Item[V]):
*,
spoiler: bool = False,
row: Optional[int] = None,
id: Optional[str] = None,
id: Optional[int] = None,
) -> None:
super().__init__()
self._underlying = FileComponent._raw_construct(

9
discord/ui/item.py

@ -70,7 +70,7 @@ class Item(Generic[V]):
# actually affect the intended purpose of this check because from_component is
# only called upon edit and we're mainly interested during initial creation time.
self._provided_custom_id: bool = False
self._id: Optional[str] = None
self._id: Optional[int] = None
self._max_row: int = 5 if not self._is_v2() else 10
def to_component_dict(self) -> Dict[str, Any]:
@ -126,14 +126,13 @@ class Item(Generic[V]):
return self._view
@property
def id(self) -> Optional[str]:
"""Optional[:class:`str`]: The ID of this component. For non v2 components this is the
equivalent to ``custom_id``.
def id(self) -> Optional[int]:
"""Optional[:class:`int`]: The ID of this component.
"""
return self._id
@id.setter
def id(self, value: Optional[str]) -> None:
def id(self, value: Optional[int]) -> None:
self._id = value
async def callback(self, interaction: Interaction[ClientT]) -> Any:

4
discord/ui/media_gallery.py

@ -60,7 +60,7 @@ class MediaGallery(Item[V]):
passing an index is advised. For example, row=1 will show
up before row=2. Defaults to ``None``, which is automatic
ordering. The row number must be between 0 and 9 (i.e. zero indexed)
id: Optional[:class:`str`]
id: Optional[:class:`int`]
The ID of this component. This must be unique across the view.
"""
@ -69,7 +69,7 @@ class MediaGallery(Item[V]):
items: List[MediaGalleryItem],
*,
row: Optional[int] = None,
id: Optional[str] = None,
id: Optional[int] = None,
) -> None:
super().__init__()

4
discord/ui/section.py

@ -59,7 +59,7 @@ class Section(Item[V]):
passing an index is advised. For example, row=1 will show
up before row=2. Defaults to ``None``, which is automatic
ordering. The row number must be between 0 and 9 (i.e. zero indexed)
id: Optional[:class:`str`]
id: Optional[:class:`int`]
The ID of this component. This must be unique across the view.
"""
@ -74,7 +74,7 @@ class Section(Item[V]):
*,
accessory: Item[Any],
row: Optional[int] = None,
id: Optional[str] = None,
id: Optional[int] = None,
) -> None:
super().__init__()
self._children: List[Item[Any]] = []

44
discord/ui/select.py

@ -239,6 +239,7 @@ class BaseSelect(Item[V]):
options: List[SelectOption] = MISSING,
channel_types: List[ChannelType] = MISSING,
default_values: Sequence[SelectDefaultValue] = MISSING,
id: Optional[int] = None,
) -> None:
super().__init__()
self._provided_custom_id = custom_id is not MISSING
@ -259,7 +260,7 @@ class BaseSelect(Item[V]):
)
self.row = row
self.id = custom_id if custom_id is not MISSING else None
self.id = id
self._parent: Optional[ActionRow] = None
self._values: List[PossibleValue] = []
@ -393,6 +394,10 @@ class Select(BaseSelect[V]):
like to control the relative positioning of the row then passing an index is advised.
For example, row=1 will show up before row=2. Defaults to ``None``, which is automatic
ordering. The row number must be between 0 and 4 (i.e. zero indexed).
id: Optional[:class:`int`]
The ID of the component. This must be unique across the view.
.. versionadded:: 2.6
"""
__component_attributes__ = BaseSelect.__component_attributes__ + ('options',)
@ -407,6 +412,7 @@ class Select(BaseSelect[V]):
options: List[SelectOption] = MISSING,
disabled: bool = False,
row: Optional[int] = None,
id: Optional[int] = None,
) -> None:
super().__init__(
self.type,
@ -417,6 +423,7 @@ class Select(BaseSelect[V]):
disabled=disabled,
options=options,
row=row,
id=id,
)
@property
@ -548,6 +555,10 @@ class UserSelect(BaseSelect[V]):
like to control the relative positioning of the row then passing an index is advised.
For example, row=1 will show up before row=2. Defaults to ``None``, which is automatic
ordering. The row number must be between 0 and 4 (i.e. zero indexed).
id: Optional[:class:`int`]
The ID of the component. This must be unique across the view.
.. versionadded:: 2.6
"""
__component_attributes__ = BaseSelect.__component_attributes__ + ('default_values',)
@ -562,6 +573,7 @@ class UserSelect(BaseSelect[V]):
disabled: bool = False,
row: Optional[int] = None,
default_values: Sequence[ValidDefaultValues] = MISSING,
id: Optional[int] = None,
) -> None:
super().__init__(
self.type,
@ -572,6 +584,7 @@ class UserSelect(BaseSelect[V]):
disabled=disabled,
row=row,
default_values=_handle_select_defaults(default_values, self.type),
id=id,
)
@property
@ -640,6 +653,10 @@ class RoleSelect(BaseSelect[V]):
like to control the relative positioning of the row then passing an index is advised.
For example, row=1 will show up before row=2. Defaults to ``None``, which is automatic
ordering. The row number must be between 0 and 4 (i.e. zero indexed).
id: Optional[:class:`int`]
The ID of the component. This must be unique across the view.
.. versionadded:: 2.6
"""
__component_attributes__ = BaseSelect.__component_attributes__ + ('default_values',)
@ -654,6 +671,7 @@ class RoleSelect(BaseSelect[V]):
disabled: bool = False,
row: Optional[int] = None,
default_values: Sequence[ValidDefaultValues] = MISSING,
id: Optional[int] = None,
) -> None:
super().__init__(
self.type,
@ -664,6 +682,7 @@ class RoleSelect(BaseSelect[V]):
disabled=disabled,
row=row,
default_values=_handle_select_defaults(default_values, self.type),
id=id,
)
@property
@ -728,6 +747,10 @@ class MentionableSelect(BaseSelect[V]):
like to control the relative positioning of the row then passing an index is advised.
For example, row=1 will show up before row=2. Defaults to ``None``, which is automatic
ordering. The row number must be between 0 and 4 (i.e. zero indexed).
id: Optional[:class:`int`]
The ID of the component. This must be unique across the view.
.. versionadded:: 2.6
"""
__component_attributes__ = BaseSelect.__component_attributes__ + ('default_values',)
@ -742,6 +765,7 @@ class MentionableSelect(BaseSelect[V]):
disabled: bool = False,
row: Optional[int] = None,
default_values: Sequence[ValidDefaultValues] = MISSING,
id: Optional[int] = None,
) -> None:
super().__init__(
self.type,
@ -752,6 +776,7 @@ class MentionableSelect(BaseSelect[V]):
disabled=disabled,
row=row,
default_values=_handle_select_defaults(default_values, self.type),
id=id,
)
@property
@ -822,6 +847,10 @@ class ChannelSelect(BaseSelect[V]):
like to control the relative positioning of the row then passing an index is advised.
For example, row=1 will show up before row=2. Defaults to ``None``, which is automatic
ordering. The row number must be between 0 and 4 (i.e. zero indexed).
id: Optional[:class:`int`]
The ID of the component. This must be unique across the view.
.. versionadded:: 2.6
"""
__component_attributes__ = BaseSelect.__component_attributes__ + (
@ -840,6 +869,7 @@ class ChannelSelect(BaseSelect[V]):
disabled: bool = False,
row: Optional[int] = None,
default_values: Sequence[ValidDefaultValues] = MISSING,
id: Optional[int] = None,
) -> None:
super().__init__(
self.type,
@ -851,6 +881,7 @@ class ChannelSelect(BaseSelect[V]):
row=row,
channel_types=channel_types,
default_values=_handle_select_defaults(default_values, self.type),
id=id,
)
@property
@ -902,6 +933,7 @@ def select(
max_values: int = ...,
disabled: bool = ...,
row: Optional[int] = ...,
id: Optional[int] = ...,
) -> SelectCallbackDecorator[V, SelectT]:
...
@ -919,6 +951,7 @@ def select(
disabled: bool = ...,
default_values: Sequence[ValidDefaultValues] = ...,
row: Optional[int] = ...,
id: Optional[int] = ...,
) -> SelectCallbackDecorator[V, UserSelectT]:
...
@ -936,6 +969,7 @@ def select(
disabled: bool = ...,
default_values: Sequence[ValidDefaultValues] = ...,
row: Optional[int] = ...,
id: Optional[int] = ...,
) -> SelectCallbackDecorator[V, RoleSelectT]:
...
@ -953,6 +987,7 @@ def select(
disabled: bool = ...,
default_values: Sequence[ValidDefaultValues] = ...,
row: Optional[int] = ...,
id: Optional[int] = ...,
) -> SelectCallbackDecorator[V, ChannelSelectT]:
...
@ -970,6 +1005,7 @@ def select(
disabled: bool = ...,
default_values: Sequence[ValidDefaultValues] = ...,
row: Optional[int] = ...,
id: Optional[int] = ...,
) -> SelectCallbackDecorator[V, MentionableSelectT]:
...
@ -986,6 +1022,7 @@ def select(
disabled: bool = False,
default_values: Sequence[ValidDefaultValues] = MISSING,
row: Optional[int] = None,
id: Optional[int] = None,
) -> SelectCallbackDecorator[V, BaseSelectT]:
"""A decorator that attaches a select menu to a component.
@ -1065,6 +1102,10 @@ def select(
Number of items must be in range of ``min_values`` and ``max_values``.
.. versionadded:: 2.4
id: Optional[:class:`int`]
The ID of the component. This must be unique across the view.
.. versionadded:: 2.6
"""
def decorator(func: ItemCallbackType[V, BaseSelectT]) -> ItemCallbackType[V, BaseSelectT]:
@ -1083,6 +1124,7 @@ def select(
'min_values': min_values,
'max_values': max_values,
'disabled': disabled,
'id': id,
}
if issubclass(callback_cls, Select):
func.__discord_ui_model_kwargs__['options'] = options

4
discord/ui/separator.py

@ -58,7 +58,7 @@ class Separator(Item[V]):
passing an index is advised. For example, row=1 will show
up before row=2. Defaults to ``None``, which is automatic
ordering. The row number must be between 0 and 9 (i.e. zero indexed)
id: Optional[:class:`str`]
id: Optional[:class:`int`]
The ID of this component. This must be unique across the view.
"""
@ -68,7 +68,7 @@ class Separator(Item[V]):
visible: bool = True,
spacing: SeparatorSize = SeparatorSize.small,
row: Optional[int] = None,
id: Optional[str] = None,
id: Optional[int] = None,
) -> None:
super().__init__()
self._underlying = SeparatorComponent._raw_construct(

4
discord/ui/text_display.py

@ -55,11 +55,11 @@ class TextDisplay(Item[V]):
passing an index is advised. For example, row=1 will show
up before row=2. Defaults to ``None``, which is automatic
ordering. The row number must be between 0 and 9 (i.e. zero indexed)
id: Optional[:class:`str`]
id: Optional[:class:`int`]
The ID of this component. This must be unique across the view.
"""
def __init__(self, content: str, *, row: Optional[int] = None, id: Optional[str] = None) -> None:
def __init__(self, content: str, *, row: Optional[int] = None, id: Optional[int] = None) -> None:
super().__init__()
self.content: str = content

8
discord/ui/thumbnail.py

@ -48,8 +48,8 @@ class Thumbnail(Item[V]):
Parameters
----------
media: Union[:class:`str`, :class:`discord.UnfurledMediaItem`]
The media of the thumbnail. This can be a string that points to a local
attachment uploaded within this item. URLs must match the ``attachment://file-name.extension``
The media of the thumbnail. This can be a URL or a reference
to an attachment that matches the ``attachment://filename.extension``
structure.
description: Optional[:class:`str`]
The description of this thumbnail. Defaults to ``None``.
@ -62,7 +62,7 @@ class Thumbnail(Item[V]):
passing an index is advised. For example, row=1 will show
up before row=2. Defaults to ``None``, which is automatic
ordering. The row number must be between 0 and 9 (i.e. zero indexed)
id: Optional[:class:`str`]
id: Optional[:class:`int`]
The ID of this component. This must be unique across the view.
"""
@ -73,7 +73,7 @@ class Thumbnail(Item[V]):
description: Optional[str] = None,
spoiler: bool = False,
row: Optional[int] = None,
id: Optional[str] = None,
id: Optional[int] = None,
) -> None:
super().__init__()

8
discord/ui/view.py

@ -223,6 +223,8 @@ class BaseView:
parent = getattr(raw, '__discord_ui_parent__', None)
if parent and parent._view is None:
parent._view = self
if getattr(raw, '__pending_view__', False):
raw._update_children_view(self) # type: ignore
children.append(raw)
else:
item: Item = raw.__discord_ui_model_type__(**raw.__discord_ui_model_kwargs__)
@ -581,6 +583,8 @@ class View(BaseView): # NOTE: maybe add a deprecation warning in favour of Layo
for name, member in base.__dict__.items():
if hasattr(member, '__discord_ui_model_type__'):
children[name] = member
elif isinstance(member, Item) and member._is_v2():
raise RuntimeError(f'{name} cannot be added to this View')
if len(children) > 25:
raise TypeError('View cannot have more than 25 children')
@ -707,10 +711,14 @@ class LayoutView(BaseView):
def __init_subclass__(cls) -> None:
children: Dict[str, Item[Any]] = {}
row = 0
for base in reversed(cls.__mro__):
for name, member in base.__dict__.items():
if isinstance(member, Item):
member._rendered_row = member._row or row
children[name] = member
row += 1
elif hasattr(member, '__discord_ui_model_type__') and getattr(member, '__discord_ui_parent__', None):
children[name] = member

Loading…
Cancel
Save