Skip to content

Commit

Permalink
feat: implement magic methods (#633)
Browse files Browse the repository at this point in the history
* feat: ``__setattr__`` implementation for components to allow editing

* feat: ``__str__`` and ``__int__`` implementation for models.

* feat: ``__repr`` implementation for models.

* feat: setattr method for embeds

* Update gateway.py

* Update component.py

* ci: correct from checks.

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
EdVraz and pre-commit-ci[bot] committed Mar 18, 2022
1 parent 6b2fbf9 commit e456a9e
Show file tree
Hide file tree
Showing 14 changed files with 197 additions and 69 deletions.
3 changes: 2 additions & 1 deletion interactions/api/gateway.py
Expand Up @@ -365,7 +365,8 @@ def _dispatch_event(self, event: str, data: dict) -> None: # sourcery no-metric
_name: str = _event_path[0] if len(_event_path) < 3 else "".join(_event_path[:-1])
__obj: object = getattr(__import__(path), _name)

if name in {"_create", "_add"}:
# name in {"_create", "_add"} returns False (tested w message_create)
if any(_ in name for _ in {"_create", "_update", "_add", "_remove", "_delete"}):
data["_client"] = self._http

self._dispatch.dispatch(f"on_{name}", __obj(**data)) # noqa
Expand Down
3 changes: 3 additions & 0 deletions interactions/api/models/channel.py
Expand Up @@ -181,6 +181,9 @@ def __init__(self, **kwargs):
else None
)

def __repr__(self) -> str:
return self.name

@property
def mention(self) -> str:
"""
Expand Down
1 change: 1 addition & 0 deletions interactions/api/models/channel.pyi
Expand Up @@ -68,6 +68,7 @@ class Channel(DictSerializerMixin):
default_auto_archive_duration: Optional[int]
permissions: Optional[str]
def __init__(self, **kwargs): ...
def __repr__(self) -> str: ...
@property
def mention(self) -> str: ...
async def send(
Expand Down
3 changes: 3 additions & 0 deletions interactions/api/models/guild.py
Expand Up @@ -328,6 +328,9 @@ def __init__(self, **kwargs):
else None
)

def __repr__(self) -> str:
return self.name

async def ban(
self,
member_id: int,
Expand Down
1 change: 1 addition & 0 deletions interactions/api/models/guild.pyi
Expand Up @@ -131,6 +131,7 @@ class Guild(DictSerializerMixin):
lazy: Any
application_command_counts: Any
def __init__(self, **kwargs): ...
def __repr__(self) -> str: ...
async def ban(
self,
member_id: int,
Expand Down
2 changes: 1 addition & 1 deletion interactions/api/models/gw.py
Expand Up @@ -726,7 +726,7 @@ class Presence(DictSerializerMixin):
:ivar ClientStatus client_status: The client status across platforms in the event.
"""

__slots__ = ("_json", "user", "guild_id", "status", "activities", "client_status")
__slots__ = ("_json", "user", "guild_id", "status", "activities", "client_status", "_client")

def __init__(self, **kwargs):
super().__init__(**kwargs)
Expand Down
1 change: 1 addition & 0 deletions interactions/api/models/gw.pyi
Expand Up @@ -181,6 +181,7 @@ class Integration(DictSerializerMixin):
def __init__(self, **kwargs): ...

class Presence(DictSerializerMixin):
_client: HTTPClient
_json: dict
user: User
guild_id: Snowflake
Expand Down
3 changes: 3 additions & 0 deletions interactions/api/models/member.py
Expand Up @@ -81,6 +81,9 @@ def __init__(self, **kwargs):
if not self.avatar and self.user:
self.avatar = self.user.avatar

def __repr__(self) -> str:
return self.user.username if self.user else self.nick

@property
def id(self) -> Snowflake:
"""
Expand Down
1 change: 1 addition & 0 deletions interactions/api/models/member.pyi
Expand Up @@ -27,6 +27,7 @@ class Member(DictSerializerMixin):
communication_disabled_until: Optional[datetime.isoformat]
hoisted_role: Any # TODO: post-v4: Investigate what this is for when documented by Discord.
def __init__(self, **kwargs): ...
def __repr__(self) -> str: ...
@property
def mention(self) -> str: ...
@property
Expand Down
118 changes: 82 additions & 36 deletions interactions/api/models/message.py
Expand Up @@ -299,6 +299,9 @@ def __init__(self, **kwargs):
)
self.thread = Channel(**self.thread) if self._json.get("thread") else None

def __repr__(self) -> str:
return self.content

async def get_channel(self) -> Channel:
"""
Gets the channel where the message was sent.
Expand Down Expand Up @@ -854,6 +857,15 @@ class EmbedImageStruct(DictSerializerMixin):
def __init__(self, **kwargs):
super().__init__(**kwargs)

def __setattr__(self, key, value) -> None:
super().__setattr__(key, value)
if key != "_json" 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]


class EmbedProvider(DictSerializerMixin):
"""
Expand All @@ -868,6 +880,15 @@ class EmbedProvider(DictSerializerMixin):
def __init__(self, **kwargs):
super().__init__(**kwargs)

def __setattr__(self, key, value) -> None:
super().__setattr__(key, value)
if key != "_json" 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]


class EmbedAuthor(DictSerializerMixin):
"""
Expand All @@ -892,6 +913,15 @@ class EmbedAuthor(DictSerializerMixin):
def __init__(self, **kwargs):
super().__init__(**kwargs)

def __setattr__(self, key, value) -> None:
super().__setattr__(key, value)
if key != "_json" 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]


class EmbedFooter(DictSerializerMixin):
"""
Expand All @@ -915,6 +945,15 @@ class EmbedFooter(DictSerializerMixin):
def __init__(self, **kwargs):
super().__init__(**kwargs)

def __setattr__(self, key, value) -> None:
super().__setattr__(key, value)
if key != "_json" 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]


class EmbedField(DictSerializerMixin):
"""
Expand All @@ -940,6 +979,15 @@ class EmbedField(DictSerializerMixin):
def __init__(self, **kwargs):
super().__init__(**kwargs)

def __setattr__(self, key, value) -> None:
super().__setattr__(key, value)
if key != "_json" 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]


class Embed(DictSerializerMixin):
"""
Expand Down Expand Up @@ -1037,30 +1085,36 @@ def __init__(self, **kwargs):
else None
)

# TODO: Complete partial fix.
# (Complete partial fix.)
# The issue seems to be that this itself is not updating
# JSON result correctly. After numerous attempts I seem to
# have the attribute to do it, but _json won't budge at all.
# a genexpr is a poor way to go about this, but I know later
# on we'll be refactoring this anyhow. What the fuck is breaking
# it?
if self.fields:
self._json.update({"fields": [field._json for field in self.fields]})

if self.author:
self._json.update({"author": self.author._json})

if self.footer:
self._json.update({"footer": self.footer._json})

if self.thumbnail:
self._json.update({"thumbnail": self.thumbnail._json})
# the __setattr__ method fixes this issue :)

if self.image:
self._json.update({"image": self.image._json})

if self.video:
self._json.update({"video": self.video._json})
def __setattr__(self, key, value) -> None:
super().__setattr__(key, value)
if key != "_json" 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:
"""
Expand All @@ -1074,19 +1128,20 @@ def add_field(self, name: str, value: str, inline: Optional[bool] = False) -> No
:type inline?: Optional[bool]
"""

if self.fields is None:
self.fields = []
fields = self.fields or []
fields.append(EmbedField(name=name, value=value, inline=inline))

self.fields.append(EmbedField(name=name, value=value, inline=inline))
self._json.update({"fields": [field._json for field in 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 ...]})`

def clear_fields(self) -> None:
"""
Clears all the fields of the embed
"""

self.fields = []
self._json.update({"fields": []})

def insert_field_at(
self, index: int, name: str = None, value: str = None, inline: Optional[bool] = False
Expand All @@ -1104,13 +1159,9 @@ def insert_field_at(
:type inline?: Optional[bool]
"""

try:
self.fields.insert(index, EmbedField(name=name, value=value, inline=inline))

except AttributeError:
self.fields = [EmbedField(name=name, value=value, inline=inline)]

self._json.update({"fields": [field._json for field in self.fields]})
fields = self.fields or []
fields.insert(index, EmbedField(name=name, value=value, inline=inline))
self.fields = fields

def set_field_at(
self, index: int, name: str, value: str, inline: Optional[bool] = False
Expand All @@ -1130,7 +1181,6 @@ def set_field_at(

try:
self.fields[index] = EmbedField(name=name, value=value, inline=inline)
self._json.update({"fields": [field._json for field in self.fields]})

except AttributeError as e:
raise AttributeError("No fields found in Embed") from e
Expand All @@ -1147,8 +1197,9 @@ def remove_field(self, index: int) -> None:
"""

try:
self.fields.pop(index)
self._json.update({"fields": [field._json for field in self.fields]})
fields = self.fields
fields.pop(index)
self.fields = fields

except AttributeError as e:
raise AttributeError("No fields found in Embed") from e
Expand All @@ -1163,7 +1214,6 @@ def remove_author(self) -> None:

try:
del self.author
self._json.update({"author": None})
except AttributeError:
pass

Expand All @@ -1190,7 +1240,6 @@ def set_author(
self.author = EmbedAuthor(
name=name, url=url, icon_url=icon_url, proxy_icon_url=proxy_icon_url
)
self._json.update({"author": self.author._json})

def set_footer(
self, text: str, icon_url: Optional[str] = None, proxy_icon_url: Optional[str] = None
Expand All @@ -1207,7 +1256,6 @@ def set_footer(
"""

self.footer = EmbedFooter(text=text, icon_url=icon_url, proxy_icon_url=proxy_icon_url)
self._json.update({"footer": self.footer._json})

def set_image(
self,
Expand All @@ -1230,7 +1278,6 @@ def set_image(
"""

self.image = EmbedImageStruct(url=url, proxy_url=proxy_url, height=height, width=width)
self._json.update({"image": self.image._json})

def set_thumbnail(
self,
Expand All @@ -1253,4 +1300,3 @@ def set_thumbnail(
"""

self.thumbnail = EmbedImageStruct(url=url, proxy_url=proxy_url, height=height, width=width)
self._json.update({"thumbnail": self.thumbnail._json})
7 changes: 7 additions & 0 deletions interactions/api/models/message.pyi
Expand Up @@ -92,6 +92,7 @@ class Message(DictSerializerMixin):
sticker_items: Optional[List["PartialSticker"]]
stickers: Optional[List["Sticker"]] # deprecated
def __init__(self, **kwargs): ...
def __repr__(self) -> str: ...
async def delete(self, reason: Optional[str] = None) -> None: ...
async def edit(
self,
Expand Down Expand Up @@ -235,12 +236,14 @@ class EmbedImageStruct(DictSerializerMixin):
height: Optional[str]
width: Optional[str]
def __init__(self, **kwargs): ...
def __setattr__(self, key, value) -> None: ...

class EmbedProvider(DictSerializerMixin):
_json: dict
name: Optional[str]
url: Optional[str]
def __init__(self, **kwargs): ...
def __setattr__(self, key, value) -> None: ...

class EmbedAuthor(DictSerializerMixin):
_json: dict
Expand All @@ -249,20 +252,23 @@ class EmbedAuthor(DictSerializerMixin):
icon_url: Optional[str]
proxy_icon_url: Optional[str]
def __init__(self, **kwargs): ...
def __setattr__(self, key, value) -> None: ...

class EmbedFooter(DictSerializerMixin):
_json: dict
text: str
icon_url: Optional[str]
proxy_icon_url: Optional[str]
def __init__(self, **kwargs): ...
def __setattr__(self, key, value) -> None: ...

class EmbedField(DictSerializerMixin):
_json: dict
name: str
inline: Optional[bool]
value: str
def __init__(self, **kwargs): ...
def __setattr__(self, key, value) -> None: ...

class Embed(DictSerializerMixin):
_json: dict
Expand All @@ -280,6 +286,7 @@ class Embed(DictSerializerMixin):
author: Optional[EmbedAuthor]
fields: Optional[List[EmbedField]]
def __init__(self, **kwargs): ...
def __setattr__(self, key, value) -> None: ...
def add_field(self, name: str, value: str, inline: Optional[bool] = False) -> None: ...
def clear_fields(self) -> None: ...
def insert_field_at(self, index: int, name: str, value: str, inline: Optional[bool] = False) -> None: ...
Expand Down
3 changes: 3 additions & 0 deletions interactions/api/models/user.py
Expand Up @@ -60,6 +60,9 @@ def __init__(self, **kwargs):

self.flags = UserFlags(int(self._json.get("flags"))) if self._json.get("flags") else None

def __repr__(self) -> str:
return self.username

@property
def mention(self) -> str:
"""
Expand Down
1 change: 1 addition & 0 deletions interactions/api/models/user.pyi
Expand Up @@ -22,6 +22,7 @@ class User(DictSerializerMixin):
premium_type: Optional[int]
public_flags: Optional[UserFlags]
def __init__(self, **kwargs): ...
def __repr__(self) -> str: ...
@property
def mention(self) -> str: ...
@property
Expand Down

0 comments on commit e456a9e

Please sign in to comment.