Fix container items having out of date internal state

This commit is contained in:
DA344
2025-11-08 00:53:04 +01:00
committed by GitHub
parent 8f2cb60700
commit e2cf721e9c
6 changed files with 68 additions and 54 deletions

View File

@@ -24,12 +24,12 @@ DEALINGS IN THE SOFTWARE.
from __future__ import annotations
import copy
from typing import (
TYPE_CHECKING,
Any,
Callable,
ClassVar,
Coroutine,
Dict,
Generator,
List,
@@ -42,7 +42,7 @@ from typing import (
overload,
)
from .item import Item, ContainedItemCallbackType as ItemCallbackType
from .item import Item, ContainedItemCallbackType as ItemCallbackType, _ItemCallback
from .button import Button, button as _button
from .select import select as _select, Select, UserSelect, RoleSelect, ChannelSelect, MentionableSelect
from ..components import ActionRow as ActionRowComponent
@@ -65,7 +65,6 @@ if TYPE_CHECKING:
)
from ..emoji import Emoji
from ..components import SelectOption
from ..interactions import Interaction
from .container import Container
from .dynamic import DynamicItem
@@ -77,18 +76,6 @@ V = TypeVar('V', bound='LayoutView', covariant=True)
__all__ = ('ActionRow',)
class _ActionRowCallback:
__slots__ = ('row', 'callback', 'item')
def __init__(self, callback: ItemCallbackType[S, Any], row: ActionRow, item: Item[Any]) -> None:
self.callback: ItemCallbackType[Any, Any] = callback
self.row: ActionRow = row
self.item: Item[Any] = item
def __call__(self, interaction: Interaction) -> Coroutine[Any, Any, Any]:
return self.callback(self.row, interaction, self.item)
class ActionRow(Item[V]):
r"""Represents a UI action row.
@@ -143,8 +130,9 @@ class ActionRow(Item[V]):
) -> None:
super().__init__()
self._children: List[Item[V]] = self._init_children()
self._children.extend(children)
self._weight: int = sum(i.width for i in self._children)
for child in children:
self.add_item(child)
if self._weight > 5:
raise ValueError('maximum number of children exceeded')
@@ -173,8 +161,8 @@ class ActionRow(Item[V]):
for func in self.__action_row_children_items__:
item: Item = func.__discord_ui_model_type__(**func.__discord_ui_model_kwargs__)
item.callback = _ActionRowCallback(func, self, item) # type: ignore
item._parent = getattr(func, '__discord_ui_parent__', self)
item.callback = _ItemCallback(func, self, item) # type: ignore
item._parent = self
setattr(self, func.__name__, item)
children.append(item)
return children
@@ -184,6 +172,23 @@ class ActionRow(Item[V]):
for child in self._children:
child._view = view
def copy(self) -> ActionRow[V]:
new = copy.copy(self)
children = []
for child in new._children:
newch = child.copy()
newch._parent = new
if isinstance(newch.callback, _ItemCallback):
newch.callback.parent = new
children.append(newch)
new._children = children
new._parent = self._parent
new._update_view(self.view)
return new
def __deepcopy__(self, memo) -> ActionRow[V]:
return self.copy()
def _has_children(self):
return True

View File

@@ -30,7 +30,7 @@ import inspect
import os
from .item import Item, ContainedItemCallbackType as ItemCallbackType
from .item import Item, ContainedItemCallbackType as ItemCallbackType, _ItemCallback
from ..enums import ButtonStyle, ComponentType
from ..partial_emoji import PartialEmoji, _EmojiTag
from ..components import Button as ButtonComponent
@@ -304,6 +304,9 @@ class Button(Item[V]):
sku_id=self.sku_id,
id=self.id,
)
if isinstance(new.callback, _ItemCallback):
new.callback.item = new
new._update_view(self.view)
return new
def __deepcopy__(self, memo) -> Self:

View File

@@ -29,7 +29,6 @@ from typing import (
TYPE_CHECKING,
Any,
ClassVar,
Coroutine,
Dict,
Generator,
List,
@@ -39,7 +38,7 @@ from typing import (
Union,
)
from .item import Item, ContainedItemCallbackType as ItemCallbackType
from .item import Item, ContainedItemCallbackType as ItemCallbackType, _ItemCallback
from .view import _component_to_item, LayoutView
from ..enums import ComponentType
from ..utils import get as _utils_get
@@ -49,7 +48,6 @@ if TYPE_CHECKING:
from typing_extensions import Self
from ..components import Container as ContainerComponent
from ..interactions import Interaction
from .dynamic import DynamicItem
S = TypeVar('S', bound='Container', covariant=True)
@@ -58,18 +56,6 @@ V = TypeVar('V', bound='LayoutView', covariant=True)
__all__ = ('Container',)
class _ContainerCallback:
__slots__ = ('container', 'callback', 'item')
def __init__(self, callback: ItemCallbackType[S, 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]):
r"""Represents a UI container.
@@ -163,7 +149,7 @@ class Container(Item[V]):
# 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
item.callback = _ItemCallback(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
@@ -196,6 +182,15 @@ class Container(Item[V]):
child._update_view(view)
return True
def copy(self) -> Container[V]:
new = copy.deepcopy(self)
for child in new._children:
newch = child.copy()
newch._parent = new
new._parent = self._parent
new._update_view(self.view)
return new
def _has_children(self):
return True

View File

@@ -55,6 +55,21 @@ ItemCallbackType = Callable[[V, Interaction[Any], I], Coroutine[Any, Any, Any]]
ContainedItemCallbackType = Callable[[C, Interaction[Any], I], Coroutine[Any, Any, Any]]
class _ItemCallback:
__slots__ = ('parent', 'callback', 'item')
def __init__(self, callback: ContainedItemCallbackType[Any, Any], parent: Any, item: Item[Any]) -> None:
self.callback: ItemCallbackType[Any, Any] = callback
self.parent: Any = parent
self.item: Item[Any] = item
def __repr__(self) -> str:
return f'<ItemCallback callback={self.callback!r} parent={self.parent!r} item={self.item!r}>'
def __call__(self, interaction: Interaction) -> Coroutine[Any, Any, Any]:
return self.callback(self.parent, interaction, self.item)
class Item(Generic[V]):
"""Represents the base UI item that all UI components inherit from.

View File

@@ -39,10 +39,11 @@ from typing import (
Sequence,
)
from contextvars import ContextVar
import copy
import inspect
import os
from .item import Item, ContainedItemCallbackType as ItemCallbackType
from .item import Item, ContainedItemCallbackType as ItemCallbackType, _ItemCallback
from ..enums import ChannelType, ComponentType, SelectDefaultValueType
from ..partial_emoji import PartialEmoji
from ..emoji import Emoji
@@ -70,7 +71,7 @@ __all__ = (
)
if TYPE_CHECKING:
from typing_extensions import TypeAlias, TypeGuard
from typing_extensions import TypeAlias, TypeGuard, Self
from .view import BaseView
from .action_row import ActionRow
@@ -269,6 +270,14 @@ class BaseSelect(Item[V]):
self.row = row
self._values: List[PossibleValue] = []
def copy(self) -> Self:
new = copy.copy(self)
if isinstance(new.callback, _ItemCallback):
new.callback.item = new
new._parent = self._parent
new._update_view(self.view)
return new
@property
def id(self) -> Optional[int]:
"""Optional[:class:`int`]: The ID of this select."""

View File

@@ -28,7 +28,6 @@ from typing import (
Any,
Callable,
ClassVar,
Coroutine,
Dict,
Generator,
Iterator,
@@ -50,7 +49,7 @@ import sys
import time
import os
from .item import Item, ItemCallbackType
from .item import Item, ItemCallbackType, _ItemCallback
from .select import Select
from .dynamic import DynamicItem
from ..components import (
@@ -207,18 +206,6 @@ class _ViewWeights:
self.weights = [0, 0, 0, 0, 0]
class _ViewCallback:
__slots__ = ('view', 'callback', 'item')
def __init__(self, callback: ItemCallbackType[Any, Any], view: BaseView, item: Item[BaseView]) -> None:
self.callback: ItemCallbackType[Any, Any] = callback
self.view: BaseView = view
self.item: Item[BaseView] = item
def __call__(self, interaction: Interaction) -> Coroutine[Any, Any, Any]:
return self.callback(self.view, interaction, self.item)
class BaseView:
__discord_ui_view__: ClassVar[bool] = False
__discord_ui_modal__: ClassVar[bool] = False
@@ -252,13 +239,13 @@ class BaseView:
item._update_view(self)
parent = getattr(item, '__discord_ui_parent__', None)
if parent and parent._view is None:
parent._view = self
parent._update_view(self)
children.append(item)
parents[raw] = item
else:
item: Item = raw.__discord_ui_model_type__(**raw.__discord_ui_model_kwargs__)
item.callback = _ViewCallback(raw, self, item) # type: ignore
item._view = self
item.callback = _ItemCallback(raw, self, item) # type: ignore
item._update_view(self)
if isinstance(item, Select):
item.options = [option.copy() for option in item.options]
setattr(self, raw.__name__, item)