diff --git a/interactions/api/gateway/client.py b/interactions/api/gateway/client.py index 03ad213ab..edc576e87 100644 --- a/interactions/api/gateway/client.py +++ b/interactions/api/gateway/client.py @@ -379,19 +379,15 @@ def _dispatch_event(self, event: str, data: dict) -> None: if data["type"] == InteractionType.APPLICATION_COMMAND: _name = f"command_{_context.data.name}" - if _context.data._json.get("options"): - for option in _context.data.options: + if options := _context.data.options: + for option in options: _type = self.__option_type_context( _context, - (option["type"] if isinstance(option, dict) else option.type.value), + option.type.value, ) if _type: - if isinstance(option, dict): - _type[option["value"]]._client = self._http - option.update({"value": _type[option["value"]]}) - else: - _type[option.value]._client = self._http - option._json.update({"value": _type[option.value]}) + _type[option.value]._client = self._http + option.value = _type[option.value] _option = self.__sub_command_context(option, _context) __kwargs.update(_option) @@ -399,15 +395,15 @@ def _dispatch_event(self, event: str, data: dict) -> None: elif data["type"] == InteractionType.MESSAGE_COMPONENT: _name = f"component_{_context.data.custom_id}" - if _context.data._json.get("values"): + if values := _context.data.values: if _context.data.component_type.value not in {5, 6, 7, 8}: - __args.append(_context.data.values) + __args.append(values) else: _list = [] # temp storage for items _data = self.__select_option_type_context( _context, _context.data.component_type.value ) # resolved. - for value in _context.data._json.get("values"): + for value in values: _list.append(_data[value]) __args.append(_list) @@ -415,10 +411,8 @@ def _dispatch_event(self, event: str, data: dict) -> None: elif data["type"] == InteractionType.APPLICATION_COMMAND_AUTOCOMPLETE: _name = f"autocomplete_{_context.data.name}" - if _context.data._json.get("options"): + if _context.data.options: for option in _context.data.options: - if isinstance(option, dict): - option = Option(**option) if option.type not in ( OptionType.SUB_COMMAND, OptionType.SUB_COMMAND_GROUP, @@ -432,8 +426,6 @@ def _dispatch_event(self, event: str, data: dict) -> None: elif option.type == OptionType.SUB_COMMAND: for _option in option.options: - if isinstance(_option, dict): - _option = Option(**_option) if _option.focused: __name, _value = self.__sub_command_context( _option, _context @@ -445,11 +437,7 @@ def _dispatch_event(self, event: str, data: dict) -> None: elif option.type == OptionType.SUB_COMMAND_GROUP: for _option in option.options: - if isinstance(_option, dict): - _option = Option(**_option) for __option in _option.options: - if isinstance(__option, dict): - __option = Option(**__option) if __option.focused: __name, _value = self.__sub_command_context( __option, _context @@ -552,7 +540,7 @@ def _dispatch_event(self, event: str, data: dict) -> None: old_obj = _cache.get(id) if old_obj: before = model(**old_obj._json) - old_obj.update(**obj._json) + old_obj.update(**data) else: before = None old_obj = obj @@ -757,89 +745,70 @@ def __contextualize(self, data: dict) -> "_Context": return context(**data) - def __sub_command_context( - self, data: Union[dict, Option], context: "_Context" - ) -> Union[Tuple[str], dict]: + def __sub_command_context(self, option: Option, context: "_Context") -> Union[Tuple[str], dict]: """ Checks if an application command schema has sub commands needed for argument collection. - :param Union[dict, Option] data: The data structure of the option. + :param Option option: The option object :param _Context context: The context to refer subcommands from. :return: A dictionary of the collected options, if any. :rtype: Union[Tuple[str], dict] """ __kwargs: dict = {} - _data: dict = data._json if isinstance(data, Option) else data - def _check_auto(option: dict) -> Optional[Tuple[str]]: - return (option["name"], option["value"]) if option.get("focused") else None + def _check_auto(_option: Option) -> Optional[Tuple[str]]: + return (_option.name, _option.value) if _option.focused else None - check = _check_auto(_data) + check = _check_auto(option) if check: return check - if _data.get("options"): - if _data["type"] == OptionType.SUB_COMMAND: - __kwargs["sub_command"] = _data["name"] + if options := option.options: + if option.type == OptionType.SUB_COMMAND: + __kwargs["sub_command"] = option.name - for sub_option in _data["options"]: + for sub_option in options: _check = _check_auto(sub_option) _type = self.__option_type_context( context, - ( - sub_option["type"] - if isinstance(sub_option, dict) - else sub_option.type.value - ), + sub_option.type, ) if _type: - if isinstance(sub_option, dict): - _type[sub_option["value"]]._client = self._http - sub_option.update({"value": _type[sub_option["value"]]}) - else: - _type[sub_option.value]._client = self._http - sub_option._json.update({"value": _type[sub_option.value]}) + _type[sub_option.value]._client = self._http + sub_option.value = _type[sub_option.value] if _check: return _check - __kwargs[sub_option["name"]] = sub_option["value"] - elif _data["type"] == OptionType.SUB_COMMAND_GROUP: - __kwargs["sub_command_group"] = _data["name"] - for _group_option in _data["options"]: + __kwargs[sub_option.name] = sub_option.value + elif option.type == OptionType.SUB_COMMAND_GROUP: + __kwargs["sub_command_group"] = option.name + for _group_option in option.options: _check_auto(_group_option) - __kwargs["sub_command"] = _group_option["name"] + __kwargs["sub_command"] = _group_option.name - for sub_option in _group_option["options"]: + for sub_option in _group_option.options: _check = _check_auto(sub_option) _type = self.__option_type_context( context, - ( - sub_option["type"] - if isinstance(sub_option, dict) - else sub_option.type.value - ), + sub_option.type, ) if _type: - if isinstance(sub_option, dict): - _type[sub_option["value"]]._client = self._http - sub_option.update({"value": _type[sub_option["value"]]}) - else: - _type[sub_option.value]._client = self._http - sub_option._json.update({"value": _type[sub_option.value]}) + _type[sub_option.value]._client = self._http + sub_option.value = _type[sub_option.value] if _check: return _check - __kwargs[sub_option["name"]] = sub_option["value"] + __kwargs[sub_option.name] = sub_option.value - elif _data.get("type") and _data["type"] == OptionType.SUB_COMMAND: + elif option.type == OptionType.SUB_COMMAND: # sub_command_groups must have options so there is no extra check needed for those - __kwargs["sub_command"] = _data["name"] + __kwargs["sub_command"] = option.name - elif _data.get("value") is not None and _data.get("name") is not None: - __kwargs[_data["name"]] = _data["value"] + elif (name := option.name) is not None and (value := option.value) is not None: + __kwargs[name] = value return __kwargs @@ -1162,9 +1131,10 @@ async def _update_presence(self, presence: ClientPresence) -> None: :param ClientPresence presence: The presence to change the bot to on identify. """ - payload: dict = {"op": OpCodeType.PRESENCE.value, "d": presence._json} + _presence = presence._json + payload: dict = {"op": OpCodeType.PRESENCE.value, "d": _presence} await self._send_packet(payload) - log.debug(f"UPDATE_PRESENCE: {presence._json}") + log.debug(f"UPDATE_PRESENCE: {_presence}") self.__presence = presence async def request_guild_members( diff --git a/interactions/api/models/channel.py b/interactions/api/models/channel.py index 65fa7a8d6..e74971510 100644 --- a/interactions/api/models/channel.py +++ b/interactions/api/models/channel.py @@ -1737,9 +1737,8 @@ async def create_forum_post( _content = content elif isinstance(content, Message): + _content = content._json if content.attachments and any(attach.id is None for attach in content.attachments): - del content._json["attachments"] - for attach in content.attachments: _data = await attach.download() @@ -1753,9 +1752,7 @@ async def create_forum_post( _files = [__files._json_payload(0)] __files = [__files] - content._json["attachments"] = _files - - _content = content._json + _content["attachments"] = _files elif isinstance(content, Attachment): if content.id: diff --git a/interactions/api/models/guild.py b/interactions/api/models/guild.py index 1dec4d31d..a32df7d59 100644 --- a/interactions/api/models/guild.py +++ b/interactions/api/models/guild.py @@ -1085,7 +1085,7 @@ async def modify_channel( ) _type = ch.type - payload = Channel( + payload = dict( name=_name, type=_type, topic=_topic, @@ -1098,8 +1098,6 @@ async def modify_channel( nsfw=_nsfw, ) - payload = payload._json - if ( archived is not MISSING or auto_archive_duration is not MISSING or locked is not MISSING ) and not ch.thread_metadata: diff --git a/interactions/api/models/message.py b/interactions/api/models/message.py index 4f83e0f0d..d9e6b5520 100644 --- a/interactions/api/models/message.py +++ b/interactions/api/models/message.py @@ -230,17 +230,6 @@ class EmbedImageStruct(DictSerializerMixin): height: Optional[int] = field(default=None) width: Optional[int] = field(default=None) - def __setattr__(self, key, value) -> None: - super().__setattr__(key, value) - if key not in {"_json", "_extras"} and ( - key not in self._json or value != self._json.get(key) - ): - if value is not None and value is not MISSING: - self._json.update({key: value}) - - elif value is None and key in self._json.keys(): - del self._json[key] - @define() class EmbedProvider(DictSerializerMixin): @@ -254,17 +243,6 @@ class EmbedProvider(DictSerializerMixin): name: Optional[str] = field(default=None) url: Optional[str] = field(default=None) - def __setattr__(self, key, value) -> None: - super().__setattr__(key, value) - if key not in {"_json", "_extras"} and ( - key not in self._json or value != self._json.get(key) - ): - if value is not None and value is not MISSING: - self._json.update({key: value}) - - elif value is None and key in self._json.keys(): - del self._json[key] - @define() class EmbedAuthor(DictSerializerMixin): @@ -290,17 +268,6 @@ class EmbedAuthor(DictSerializerMixin): icon_url: Optional[str] = field(default=None) proxy_icon_url: Optional[str] = field(default=None) - def __setattr__(self, key, value) -> None: - super().__setattr__(key, value) - if key not in {"_json", "_extras"} and ( - key not in self._json or value != self._json.get(key) - ): - if value is not None and value is not MISSING: - self._json.update({key: value}) - - elif value is None and key in self._json.keys(): - del self._json[key] - @define() class EmbedFooter(DictSerializerMixin): @@ -324,17 +291,6 @@ class EmbedFooter(DictSerializerMixin): icon_url: Optional[str] = field(default=None) proxy_icon_url: Optional[str] = field(default=None) - def __setattr__(self, key, value) -> None: - super().__setattr__(key, value) - if key not in {"_json", "_extras"} and ( - key not in self._json or value != self._json.get(key) - ): - if value is not None and value is not MISSING: - self._json.update({key: value}) - - elif value is None and key in self._json.keys(): - del self._json[key] - @define() class EmbedField(DictSerializerMixin): @@ -360,17 +316,6 @@ class EmbedField(DictSerializerMixin): inline: Optional[bool] = field(default=None) value: str = field() - def __setattr__(self, key, value) -> None: - super().__setattr__(key, value) - if key not in {"_json", "_extras"} and ( - key not in self._json or value != self._json.get(key) - ): - if value is not None and value is not MISSING: - self._json.update({key: value}) - - elif value is None and key in self._json.keys(): - del self._json[key] - @define() @deepcopy_kwargs() @@ -423,28 +368,6 @@ class Embed(DictSerializerMixin): author: Optional[EmbedAuthor] = field(converter=EmbedAuthor, default=None) fields: Optional[List[EmbedField]] = field(converter=convert_list(EmbedField), default=None) - def __setattr__(self, key, value) -> None: - super().__setattr__(key, value) - - if key not in {"_json", "_extras"} and ( - key not in self._json - or ( - value != self._json.get(key) - or not isinstance(value, dict) - # we don't need this instance check in components because serialisation works for them - ) - ): - if value is not None and value is not MISSING: - try: - value = [val._json for val in value] if isinstance(value, list) else value._json - except AttributeError: - if isinstance(value, datetime): - value = value.isoformat() - self._json.update({key: value}) - - elif value is None and key in self._json.keys(): - del self._json[key] - def add_field(self, name: str, value: str, inline: Optional[bool] = False) -> None: """ Adds a field to the embed @@ -454,13 +377,10 @@ def add_field(self, name: str, value: str, inline: Optional[bool] = False) -> No :param Optional[bool] inline: if the field is in the same line as the previous one """ - fields = self.fields or [] - fields.append(EmbedField(name=name, value=value, inline=inline)) + if self.fields is None: + self.fields = [] - self.fields = fields - # We must use "=" here to call __setattr__. Append does not call any magic, making it impossible to modify the - # json when using it, so the object what would be sent wouldn't be modified. - # Imo this is still better than doing a `self._json.update({"fields": [field._json for ...]})` + self.fields.append(EmbedField(name=name, value=value, inline=inline)) def clear_fields(self) -> None: """ @@ -481,13 +401,10 @@ def insert_field_at( :param Optional[bool] inline: if the field is in the same line as the previous one """ - try: - fields = self.fields - fields.insert(index, EmbedField(name=name, value=value, inline=inline)) - self.fields = fields + if self.fields is None: + self.fields = [] - except AttributeError as e: - raise AttributeError("No fields found in Embed") from e + self.fields.insert(index, EmbedField(name=name, value=value, inline=inline)) def set_field_at( self, index: int, name: str, value: str, inline: Optional[bool] = False @@ -501,14 +418,11 @@ def set_field_at( :param Optional[bool] inline: if the field is in the same line as the previous one """ - try: - fields = self.fields - fields[index] = EmbedField(name=name, value=value, inline=inline) - self.fields = fields - - except AttributeError as e: - raise AttributeError("No fields found in Embed") from e + if self.fields is None: + self.fields = [] + try: + self.fields[index] = EmbedField(name=name, value=value, inline=inline) except IndexError as e: raise IndexError("No fields at this index") from e @@ -519,14 +433,11 @@ def remove_field(self, index: int) -> None: :param int index: The field's index to remove """ - try: - fields = self.fields - fields.pop(index) - self.fields = fields - - except AttributeError as e: - raise AttributeError("No fields found in Embed") from e + if self.fields is None: + self.fields = [] + try: + self.fields.pop(index) except IndexError as e: raise IndexError("Field not Found at index") from e diff --git a/interactions/api/models/presence.py b/interactions/api/models/presence.py index eb6183660..219c2f2c7 100644 --- a/interactions/api/models/presence.py +++ b/interactions/api/models/presence.py @@ -177,11 +177,8 @@ class ClientPresence(DictSerializerMixin): afk: bool = field(default=False) def __attrs_post_init__(self): - if not self._json.get("since"): + if not self.since: # If since is not provided by the developer... self.since = int(time.time() * 1000) if self.status == "idle" else 0 - self._json["since"] = self.since - if not self._json.get("afk"): - self.afk = self._json["afk"] = False - if not self._json.get("activities"): - self.activities = self._json["activities"] = [] + if not self.activities: + self.activities = [] diff --git a/interactions/api/models/webhook.py b/interactions/api/models/webhook.py index d7addba97..7129fa643 100644 --- a/interactions/api/models/webhook.py +++ b/interactions/api/models/webhook.py @@ -1,5 +1,5 @@ from enum import IntEnum -from typing import TYPE_CHECKING, Any, List, Optional, Union +from typing import TYPE_CHECKING, List, Optional, Union from ...utils.attrs_utils import ClientSerializerMixin, define, field from ...utils.missing import MISSING @@ -10,6 +10,8 @@ if TYPE_CHECKING: from ...client.models.component import ActionRow, Button, SelectMenu from ..http import HTTPClient + from .channel import Channel + from .guild import Guild from .message import Attachment, Embed, Message __all__ = ( @@ -52,8 +54,8 @@ class Webhook(ClientSerializerMixin, IDMixin): avatar: str = field(repr=False) token: Optional[str] = field(default=None) application_id: Snowflake = field(converter=Snowflake) - source_guild: Optional[Any] = field(default=None) - source_channel: Optional[Any] = field(default=None) + source_guild: Optional["Guild"] = field(default=None) + source_channel: Optional["Channel"] = field(default=None) url: Optional[str] = field(default=None) def __attrs_post_init__(self): @@ -62,14 +64,10 @@ def __attrs_post_init__(self): from .guild import Guild self.source_guild = ( - Guild(**self.source_guild, _client=self._client) - if self._json.get("source_guild") - else None + Guild(**self.source_guild, _client=self._client) if self.source_guild else None ) self.source_channel = ( - Channel(**self.source_channel, _client=self._client) - if self._json.get("source_channel") - else None + Channel(**self.source_channel, _client=self._client) if self.source_channel else None ) @classmethod diff --git a/interactions/client/bot.py b/interactions/client/bot.py index 7be1ca0e5..61830b600 100644 --- a/interactions/client/bot.py +++ b/interactions/client/bot.py @@ -647,7 +647,7 @@ async def __sync(self) -> None: # sourcery no-metrics if isinstance(coro._command_data, list): _guild_command: dict for _guild_command in coro._command_data: - _guild_id = _guild_command.get("guild_id") + _guild_id = int(_guild_command.get("guild_id")) if _guild_id in __blocked_guilds: log.fatal(f"Cannot sync commands on guild with id {_guild_id}!") raise LibraryException(50001, message="Missing Access |") diff --git a/interactions/client/models/command.py b/interactions/client/models/command.py index ef5ca110c..c7c257db6 100644 --- a/interactions/client/models/command.py +++ b/interactions/client/models/command.py @@ -69,17 +69,10 @@ class Choice(DictSerializerMixin): name_localizations: Optional[Dict[Union[str, Locale], str]] = field(default=None) def __attrs_post_init__(self): - if self._json.get("name_localizations"): - if any( - type(x) != str for x in self._json["name_localizations"] - ): # check if Locale object is used to create localisation at any certain point. - self._json["name_localizations"] = { - k.value if isinstance(k, Locale) else k: v - for k, v in self._json["name_localizations"].items() - } + if self.name_localizations: self.name_localizations = { k if isinstance(k, Locale) else Locale(k): v - for k, v in self._json["name_localizations"].items() + for k, v in self.name_localizations.items() } @@ -151,19 +144,15 @@ class Option(DictSerializerMixin): converter: Optional[str] = field(default=None) def __attrs_post_init__(self): - self._json.pop("converter", None) - # needed for nested classes if self.options is not None: self.options = [ Option(**option) if isinstance(option, dict) else option for option in self.options ] - self._json["options"] = [option._json for option in self.options] if self.choices is not None: self.choices = [ Choice(**choice) if isinstance(choice, dict) else choice for choice in self.choices ] - self._json["choices"] = [choice._json for choice in self.choices] @define() @@ -585,7 +574,6 @@ def decorator(coro: Callable[..., Awaitable]) -> "Command": if int(option.type) == 2 and option.name == _group: break self.options[i].options.append(subcommand) - self.options[i]._json["options"].append(subcommand._json) self.coroutines[f"{_group} {_name}"] = self.__wrap_coro(coro) self.num_options[f"{_group} {_name}"] = len( {opt for opt in _options if int(opt.type) > 2} diff --git a/interactions/client/models/component.py b/interactions/client/models/component.py index 0978c21fd..db4f677db 100644 --- a/interactions/client/models/component.py +++ b/interactions/client/models/component.py @@ -1,10 +1,8 @@ -import contextlib from typing import List, Optional, Union from ...api.error import LibraryException from ...api.models.emoji import Emoji from ...utils.attrs_utils import DictSerializerMixin, convert_list, define, field -from ...utils.missing import MISSING from ..enums import ButtonStyle, ComponentType, TextStyleType __all__ = ( @@ -20,27 +18,7 @@ @define() -class ComponentMixin(DictSerializerMixin): - """ - A mixin designed to let subclasses attribute changes be mirrored to their _json - Arguably, this should be moved to the DictSerializerMixin, but testing would need to be done first - """ - - def __setattr__(self, key, value) -> None: - super().__setattr__(key, value) - if key not in {"_json", "_extras"} and ( - key not in self._json or value != self._json.get(key) - ): - if value is not None and value is not MISSING: - with contextlib.suppress(AttributeError): - value = [val._json for val in value] if isinstance(value, list) else value._json - self._json.update({key: value}) - elif value is None and key in self._json.keys(): - del self._json[key] - - -@define() -class SelectOption(ComponentMixin): +class SelectOption(DictSerializerMixin): """ A class object representing the select option of a select menu. The structure for a select option: @@ -65,13 +43,9 @@ class SelectOption(ComponentMixin): emoji: Optional[Emoji] = field(converter=Emoji, default=None) default: Optional[bool] = field(default=None) - def __attrs_post_init__(self): - if self.emoji: - self._json.update({"emoji": self.emoji._json}) - @define() -class SelectMenu(ComponentMixin): +class SelectMenu(DictSerializerMixin): """ A class object representing the select menu of a component. The structure for a select menu: @@ -104,14 +78,9 @@ class SelectMenu(ComponentMixin): disabled: Optional[bool] = field(default=None) channel_types: Optional[List[int]] = field(default=None) - def __attrs_post_init__(self) -> None: - self._json.update({"type": self.type.value}) - if self.options: - self._json.update({"options": [option._json for option in self.options]}) - @define() -class Button(ComponentMixin): +class Button(DictSerializerMixin): """ A class object representing the button of a component. The structure for a button: @@ -140,14 +109,9 @@ class Button(ComponentMixin): url: Optional[str] = field(default=None) disabled: Optional[bool] = field(default=None) - def __attrs_post_init__(self) -> None: - self._json.update({"type": self.type.value, "style": self.style.value}) - if self.emoji: - self._json.update({"emoji": self.emoji._json}) - @define() -class Component(ComponentMixin): +class Component(DictSerializerMixin): """ A class object representing the component in an interaction response/followup. @@ -200,12 +164,10 @@ def __attrs_post_init__(self): self.components = ( [Component(**components) for components in self.components] if self.components else None ) - if self._json.get("components"): - self._json["components"] = [component._json for component in self.components] @define() -class TextInput(ComponentMixin): +class TextInput(DictSerializerMixin): """ A class object representing the text input of a modal. The structure for a text input: @@ -240,12 +202,9 @@ class TextInput(ComponentMixin): min_length: Optional[int] = field(default=None) max_length: Optional[int] = field(default=None) - def __attrs_post_init__(self): - self._json.update({"type": self.type.value, "style": self.style.value}) - @define() -class Modal(ComponentMixin): +class Modal(DictSerializerMixin): """ A class object representing a modal. The structure for a modal: @@ -266,18 +225,18 @@ class Modal(ComponentMixin): title: str = field() components: List[Component] = field(converter=convert_list(Component)) - def __attrs_post_init__(self): - self._json.update( - { - "components": [ - {"type": 1, "components": [component._json]} for component in self.components - ] - } - ) + @property + def _json(self) -> dict: + json: dict = super()._json + json["components"] = [ + {"type": 1, "components": [component]} for component in json["components"] + ] + + return json @define() -class ActionRow(ComponentMixin): +class ActionRow(DictSerializerMixin): """ A class object representing the action row for interaction responses holding components. @@ -303,28 +262,8 @@ class ActionRow(ComponentMixin): type: ComponentType = field(ComponentType, default=ComponentType.ACTION_ROW) components: Optional[List[Component]] = field(converter=convert_list(Component), default=None) - def __attrs_post_init__(self) -> None: - for component in self.components: - if isinstance(component, SelectMenu): - component._json["options"] = ( - [ - option._json if isinstance(option, SelectOption) else option - for option in component._json["options"] - ] - if component._json.get("options") - else [] - ) - self.components = ( - [Component(**component._json) for component in self.components] - if self._json.get("components") - else None - ) - self._json.update({"type": self.type.value}) - if self._json.get("components"): - self._json["components"] = [component._json for component in self.components] - @classmethod - def new(cls, *components: Union[Button, SelectMenu, TextInput]) -> "ActionRow": + def new(cls, *components: Union[Button, SelectMenu, TextInput]) -> List["ActionRow"]: r""" A class method for creating a new ``ActionRow``. @@ -344,28 +283,11 @@ def __check_action_row(): ): _components = [] for action_row in components: - for component in ( - action_row if isinstance(action_row, list) else action_row.components - ): - if isinstance(component, SelectMenu): - component._json["options"] = ( - [ - option if isinstance(option, dict) else option._json - for option in component.options - ] - if component._json.get("options") - else [] - ) - _components.append( { "type": 1, "components": [ - ( - component._json - if component._json.get("custom_id") or component._json.get("url") - else [] - ) + component._json for component in ( action_row if isinstance(action_row, list) @@ -378,14 +300,7 @@ def __check_action_row(): elif isinstance(components, ActionRow): _components: List[dict] = [{"type": 1, "components": []}] - _components[0]["components"] = [ - ( - component._json - if component._json.get("custom_id") or component._json.get("url") - else [] - ) - for component in components.components - ] + _components[0]["components"] = [component._json for component in components.components] return _components else: return False @@ -394,56 +309,17 @@ def __check_components(): if isinstance(components, list) and all( isinstance(component, (Button, SelectMenu)) for component in components ): - for component in components: - if isinstance(component, SelectMenu): - component._json["options"] = ( - [ - options if isinstance(options, dict) else options._json - for options in component._json["options"] - ] - if component._json.get("options") - else [] - ) - _components = [ { "type": 1, - "components": [ - ( - component._json - if component._json.get("custom_id") or component._json.get("url") - else [] - ) - for component in components - ], + "components": [component._json for component in components], } ] return _components - elif isinstance(components, Button): - _components: List[dict] = [{"type": 1, "components": []}] - _components[0]["components"] = ( - [components._json] - if components._json.get("custom_id") or components._json.get("url") - else [] - ) - return _components - elif isinstance(components, SelectMenu): + elif isinstance(components, (Button, SelectMenu)): _components: List[dict] = [{"type": 1, "components": []}] - components._json["options"] = ( - [ - options if isinstance(options, dict) else options._json - for options in components._json["options"] - ] - if components._json.get("options") - else [] - ) - - _components[0]["components"] = ( - [components._json] - if components._json.get("custom_id") or components._json.get("url") - else [] - ) + _components[0]["components"] = [components._json] return _components else: raise LibraryException( diff --git a/interactions/utils/attrs_utils.py b/interactions/utils/attrs_utils.py index adc5b97c6..4b1320f54 100644 --- a/interactions/utils/attrs_utils.py +++ b/interactions/utils/attrs_utils.py @@ -1,6 +1,8 @@ from copy import deepcopy +from datetime import datetime +from enum import Enum from functools import wraps -from typing import Dict, Mapping, Optional, Tuple +from typing import Any, Dict, Mapping, Optional, Tuple import attrs @@ -14,7 +16,6 @@ @attrs.define(eq=False, init=False, on_setattr=attrs.setters.NO_OP) class DictSerializerMixin: - _json: dict = attrs.field(init=False, repr=False) _extras: dict = attrs.field(init=False, repr=False) """A dict containing values that were not serialized from Discord.""" @@ -29,7 +30,6 @@ def __init__(self, kwargs_dict: dict = None, /, **other_kwargs): if self.__deepcopy_kwargs__: kwargs = deepcopy(kwargs) - self._json = kwargs.copy() passed_kwargs = {} attribs: Tuple[attrs.Attribute, ...] = self.__attrs_attrs__ # type: ignore @@ -62,14 +62,6 @@ def __init__(self, kwargs_dict: dict = None, /, **other_kwargs): elif isinstance(value, DictSerializerMixin): value._client = client - # make sure json is recursively handled - if isinstance(value, list): - self._json[attrib_name] = [ - i._json if isinstance(i, DictSerializerMixin) else i for i in value - ] - elif isinstance(value, DictSerializerMixin): - self._json[attrib_name] = value._json # type: ignore - passed_kwargs[attrib_name] = value elif attrib.default is not attrs.NOTHING: @@ -87,6 +79,32 @@ def __init__(self, kwargs_dict: dict = None, /, **other_kwargs): self._extras = kwargs self.__attrs_init__(**passed_kwargs) # type: ignore + @property + def _json(self) -> dict: + """Returns the json data of the object""" + from ..api.models.misc import Snowflake + + def _filter(attrib: attrs.Attribute, value: Any): + return ( + not attrib.name.startswith("_") + and attrib.name + not in { + "converter", + } + and value is not None + ) + + def _serializer(obj: Any, attrib: attrs.Attribute, value: Any): + if isinstance(value, Snowflake): + return str(value) + if isinstance(value, Enum): + return value.value + if isinstance(value, datetime): + return value.isoformat() + return value + + return attrs.asdict(self, filter=_filter, value_serializer=_serializer) + def update(self, kwargs_dict: dict = None, /, **other_kwargs): """ Update an object with new attributes. @@ -111,7 +129,6 @@ def update(self, kwargs_dict: dict = None, /, **other_kwargs): if value is None: continue - self._json[name] = value setattr( self, name, converter(value) if (converter := attribs[name].converter) else value ) diff --git a/interactions/utils/attrs_utils.pyi b/interactions/utils/attrs_utils.pyi index 7845f3c9a..d914f0f2d 100644 --- a/interactions/utils/attrs_utils.pyi +++ b/interactions/utils/attrs_utils.pyi @@ -11,7 +11,6 @@ _P = TypeVar("_P") @attrs.define(eq=False, init=False, on_setattr=attrs.setters.NO_OP) class DictSerializerMixin: - _json: dict = attrs.field(init=False) _extras: dict = attrs.field(init=False) """A dict containing values that were not serialized from Discord.""" __deepcopy_kwargs__: bool = attrs.field(init=False) @@ -19,6 +18,9 @@ class DictSerializerMixin: def __init__(self, kwargs_dict: dict = None, /, **other_kwargs): ... + @property + def _json(self) -> dict: ... + @attrs.define(eq=False, init=False, on_setattr=attrs.setters.NO_OP) class ClientSerializerMixin(DictSerializerMixin):