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

View File

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

View File

@@ -29,7 +29,6 @@ from typing import (
TYPE_CHECKING, TYPE_CHECKING,
Any, Any,
ClassVar, ClassVar,
Coroutine,
Dict, Dict,
Generator, Generator,
List, List,
@@ -39,7 +38,7 @@ from typing import (
Union, Union,
) )
from .item import Item, ContainedItemCallbackType as ItemCallbackType from .item import Item, ContainedItemCallbackType as ItemCallbackType, _ItemCallback
from .view import _component_to_item, LayoutView from .view import _component_to_item, LayoutView
from ..enums import ComponentType from ..enums import ComponentType
from ..utils import get as _utils_get from ..utils import get as _utils_get
@@ -49,7 +48,6 @@ if TYPE_CHECKING:
from typing_extensions import Self from typing_extensions import Self
from ..components import Container as ContainerComponent from ..components import Container as ContainerComponent
from ..interactions import Interaction
from .dynamic import DynamicItem from .dynamic import DynamicItem
S = TypeVar('S', bound='Container', covariant=True) S = TypeVar('S', bound='Container', covariant=True)
@@ -58,18 +56,6 @@ V = TypeVar('V', bound='LayoutView', covariant=True)
__all__ = ('Container',) __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]): class Container(Item[V]):
r"""Represents a UI container. 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 # action rows can be created inside containers, and then callbacks can exist here
# so we create items based off them # so we create items based off them
item: Item = raw.__discord_ui_model_type__(**raw.__discord_ui_model_kwargs__) 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) setattr(self, raw.__name__, item)
# this should not fail because in order for a function to be here it should be from # 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 # 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) child._update_view(view)
return True 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): def _has_children(self):
return True 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]] 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]): class Item(Generic[V]):
"""Represents the base UI item that all UI components inherit from. """Represents the base UI item that all UI components inherit from.

View File

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

View File

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