mirror of
https://github.com/MelisaDev/melisa.git
synced 2024-09-22 19:22:01 +03:00
feat(models): Add embed color model and methods
This commit is contained in:
parent
d998a0408d
commit
643a231e9b
14 changed files with 507 additions and 265 deletions
|
@ -21,8 +21,8 @@ from ..message.message import Message
|
|||
from ...exceptions import EmbedFieldError
|
||||
from ...models.message.embed import Embed
|
||||
from ...utils import Snowflake, Timestamp
|
||||
from ...utils import APIModelBase
|
||||
from ...utils.types import APINullable
|
||||
from ...utils.api_model import APIModelBase
|
||||
from ...utils.types import APINullable, UNDEFINED
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .thread import ThreadMember, ThreadMetadata
|
||||
|
@ -162,32 +162,32 @@ class Channel(APIModelBase):
|
|||
only included when part of the `resolved` data received on a slash command interaction
|
||||
"""
|
||||
|
||||
id: APINullable[Snowflake] = None
|
||||
type: APINullable[int] = None
|
||||
guild_id: APINullable[Snowflake] = None
|
||||
position: APINullable[int] = None
|
||||
permission_overwrites: APINullable[List] = None
|
||||
name: APINullable[str] = None
|
||||
topic: APINullable[str] = None
|
||||
nsfw: APINullable[bool] = None
|
||||
last_message_id: APINullable[Snowflake] = None
|
||||
bitrate: APINullable[int] = None
|
||||
user_limit: APINullable[int] = None
|
||||
rate_limit_per_user: APINullable[int] = None
|
||||
recipients: APINullable[List] = None
|
||||
icon: APINullable[str] = None
|
||||
owner_id: APINullable[Snowflake] = None
|
||||
application_id: APINullable[Snowflake] = None
|
||||
parent_id: APINullable[Snowflake] = None
|
||||
last_pin_timestamp: APINullable[Timestamp] = None
|
||||
rtc_region: APINullable[str] = None
|
||||
video_quality_mode: APINullable[int] = None
|
||||
message_count: APINullable[int] = None
|
||||
member_count: APINullable[int] = None
|
||||
thread_metadata: APINullable[ThreadMetadata] = None
|
||||
member: APINullable[List] = None
|
||||
default_auto_archive_duration: APINullable[int] = None
|
||||
permissions: APINullable[str] = None
|
||||
id: APINullable[Snowflake] = UNDEFINED
|
||||
type: APINullable[int] = UNDEFINED
|
||||
guild_id: APINullable[Snowflake] = UNDEFINED
|
||||
position: APINullable[int] = UNDEFINED
|
||||
permission_overwrites: APINullable[List] = UNDEFINED
|
||||
name: APINullable[str] = UNDEFINED
|
||||
topic: APINullable[str] = UNDEFINED
|
||||
nsfw: APINullable[bool] = UNDEFINED
|
||||
last_message_id: APINullable[Snowflake] = UNDEFINED
|
||||
bitrate: APINullable[int] = UNDEFINED
|
||||
user_limit: APINullable[int] = UNDEFINED
|
||||
rate_limit_per_user: APINullable[int] = UNDEFINED
|
||||
recipients: APINullable[List] = UNDEFINED
|
||||
icon: APINullable[str] = UNDEFINED
|
||||
owner_id: APINullable[Snowflake] = UNDEFINED
|
||||
application_id: APINullable[Snowflake] = UNDEFINED
|
||||
parent_id: APINullable[Snowflake] = UNDEFINED
|
||||
last_pin_timestamp: APINullable[Timestamp] = UNDEFINED
|
||||
rtc_region: APINullable[str] = UNDEFINED
|
||||
video_quality_mode: APINullable[int] = UNDEFINED
|
||||
message_count: APINullable[int] = UNDEFINED
|
||||
member_count: APINullable[int] = UNDEFINED
|
||||
thread_metadata: APINullable[ThreadMetadata] = UNDEFINED
|
||||
member: APINullable[List] = UNDEFINED
|
||||
default_auto_archive_duration: APINullable[int] = UNDEFINED
|
||||
permissions: APINullable[str] = UNDEFINED
|
||||
|
||||
@property
|
||||
def mention(self):
|
||||
|
@ -846,7 +846,7 @@ class ThreadsList(APIModelBase):
|
|||
|
||||
threads: List[Thread]
|
||||
members: List[ThreadMember]
|
||||
has_more: APINullable[bool] = None
|
||||
has_more: APINullable[bool] = UNDEFINED
|
||||
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
|
|
|
@ -9,8 +9,8 @@ from typing import List, Any, Optional, overload
|
|||
|
||||
from .channel import Channel, ChannelType, channel_types_for_converting, ThreadsList
|
||||
from ...utils import Snowflake, Timestamp
|
||||
from ...utils import APIModelBase
|
||||
from ...utils.types import APINullable
|
||||
from ...utils.api_model import APIModelBase
|
||||
from ...utils.types import APINullable, UNDEFINED
|
||||
|
||||
|
||||
class DefaultMessageNotificationLevel(IntEnum):
|
||||
|
@ -292,63 +292,63 @@ class Guild(APIModelBase):
|
|||
The scheduled events in the guild
|
||||
"""
|
||||
|
||||
id: APINullable[Snowflake] = None
|
||||
name: APINullable[str] = None
|
||||
icon: APINullable[str] = None
|
||||
icon_hash: APINullable[str] = None
|
||||
splash: APINullable[str] = None
|
||||
discovery_splash: APINullable[str] = None
|
||||
owner: APINullable[bool] = None
|
||||
owner_id: APINullable[Snowflake] = None
|
||||
permissions: APINullable[str] = None
|
||||
region: APINullable[str] = None
|
||||
afk_channel_id: APINullable[Snowflake] = None
|
||||
afk_timeout: APINullable[int] = None
|
||||
widget_enabled: APINullable[bool] = None
|
||||
widget_channel_id: APINullable[Snowflake] = None
|
||||
verification_level: APINullable[int] = None
|
||||
default_message_notifications: APINullable[int] = None
|
||||
explicit_content_filter: APINullable[int] = None
|
||||
features: APINullable[List[str]] = None
|
||||
roles: APINullable[List] = None
|
||||
emojis: APINullable[List] = None
|
||||
id: APINullable[Snowflake] = UNDEFINED
|
||||
name: APINullable[str] = UNDEFINED
|
||||
icon: APINullable[str] = UNDEFINED
|
||||
icon_hash: APINullable[str] = UNDEFINED
|
||||
splash: APINullable[str] = UNDEFINED
|
||||
discovery_splash: APINullable[str] = UNDEFINED
|
||||
owner: APINullable[bool] = UNDEFINED
|
||||
owner_id: APINullable[Snowflake] = UNDEFINED
|
||||
permissions: APINullable[str] = UNDEFINED
|
||||
region: APINullable[str] = UNDEFINED
|
||||
afk_channel_id: APINullable[Snowflake] = UNDEFINED
|
||||
afk_timeout: APINullable[int] = UNDEFINED
|
||||
widget_enabled: APINullable[bool] = UNDEFINED
|
||||
widget_channel_id: APINullable[Snowflake] = UNDEFINED
|
||||
verification_level: APINullable[int] = UNDEFINED
|
||||
default_message_notifications: APINullable[int] = UNDEFINED
|
||||
explicit_content_filter: APINullable[int] = UNDEFINED
|
||||
features: APINullable[List[str]] = UNDEFINED
|
||||
roles: APINullable[List] = UNDEFINED
|
||||
emojis: APINullable[List] = UNDEFINED
|
||||
# TODO: Make a structures of emoji and role
|
||||
|
||||
mfa_level: APINullable[int] = None
|
||||
application_id: APINullable[Snowflake] = None
|
||||
system_channel_id: APINullable[Snowflake] = None
|
||||
system_channel_flags: APINullable[int] = None
|
||||
rules_channel_id: APINullable[Snowflake] = None
|
||||
joined_at: APINullable[Timestamp] = None
|
||||
mfa_level: APINullable[int] = UNDEFINED
|
||||
application_id: APINullable[Snowflake] = UNDEFINED
|
||||
system_channel_id: APINullable[Snowflake] = UNDEFINED
|
||||
system_channel_flags: APINullable[int] = UNDEFINED
|
||||
rules_channel_id: APINullable[Snowflake] = UNDEFINED
|
||||
joined_at: APINullable[Timestamp] = UNDEFINED
|
||||
# TODO: Deal with joined_at
|
||||
|
||||
large: APINullable[bool] = None
|
||||
unavailable: APINullable[bool] = None
|
||||
member_count: APINullable[int] = None
|
||||
voice_states: APINullable[List] = None
|
||||
members: APINullable[List] = None
|
||||
threads: APINullable[List] = None
|
||||
presences: APINullable[List] = None
|
||||
large: APINullable[bool] = UNDEFINED
|
||||
unavailable: APINullable[bool] = UNDEFINED
|
||||
member_count: APINullable[int] = UNDEFINED
|
||||
voice_states: APINullable[List] = UNDEFINED
|
||||
members: APINullable[List] = UNDEFINED
|
||||
threads: APINullable[List] = UNDEFINED
|
||||
presences: APINullable[List] = UNDEFINED
|
||||
# TODO: Make a structure for voice_states, members, channels, threads, presences(?)
|
||||
|
||||
max_presences: APINullable[int] = None
|
||||
max_members: APINullable[int] = None
|
||||
vanity_url_code: APINullable[str] = None
|
||||
description: APINullable[str] = None
|
||||
banner: APINullable[str] = None
|
||||
premium_tier: APINullable[str] = None
|
||||
premium_subscription_count: APINullable[int] = None
|
||||
preferred_locale: APINullable[str] = None
|
||||
public_updates_channel_id: APINullable[Snowflake] = None
|
||||
max_video_channel_users: APINullable[int] = None
|
||||
approximate_member_count: APINullable[int] = None
|
||||
approximate_presence_count: APINullable[int] = None
|
||||
nsfw_level: APINullable[int] = None
|
||||
premium_progress_bar_enabled: APINullable[bool] = None
|
||||
stage_instances: APINullable[List] = None
|
||||
stickers: APINullable[List] = None
|
||||
welcome_screen: APINullable = None
|
||||
guild_scheduled_events: APINullable[List] = None
|
||||
max_presences: APINullable[int] = UNDEFINED
|
||||
max_members: APINullable[int] = UNDEFINED
|
||||
vanity_url_code: APINullable[str] = UNDEFINED
|
||||
description: APINullable[str] = UNDEFINED
|
||||
banner: APINullable[str] = UNDEFINED
|
||||
premium_tier: APINullable[str] = UNDEFINED
|
||||
premium_subscription_count: APINullable[int] = UNDEFINED
|
||||
preferred_locale: APINullable[str] = UNDEFINED
|
||||
public_updates_channel_id: APINullable[Snowflake] = UNDEFINED
|
||||
max_video_channel_users: APINullable[int] = UNDEFINED
|
||||
approximate_member_count: APINullable[int] = UNDEFINED
|
||||
approximate_presence_count: APINullable[int] = UNDEFINED
|
||||
nsfw_level: APINullable[int] = UNDEFINED
|
||||
premium_progress_bar_enabled: APINullable[bool] = UNDEFINED
|
||||
stage_instances: APINullable[List] = UNDEFINED
|
||||
stickers: APINullable[List] = UNDEFINED
|
||||
welcome_screen: APINullable = UNDEFINED
|
||||
guild_scheduled_events: APINullable[List] = UNDEFINED
|
||||
|
||||
# TODO: Make a structure for welcome_screen, stage_instances,
|
||||
# stickers and guild_scheduled_events
|
||||
|
|
|
@ -6,7 +6,7 @@ from __future__ import annotations
|
|||
from dataclasses import dataclass
|
||||
|
||||
from ...utils.api_model import APIModelBase
|
||||
from ...utils.types import APINullable
|
||||
from ...utils.types import APINullable, UNDEFINED
|
||||
from ...utils.snowflake import Snowflake
|
||||
from ...utils.timestamp import Timestamp
|
||||
|
||||
|
@ -40,8 +40,8 @@ class ThreadMetadata(APIModelBase):
|
|||
auto_archive_duration: int
|
||||
archive_timestamp: Timestamp
|
||||
locked: bool
|
||||
invitable: APINullable[bool] = None
|
||||
create_timestamp: APINullable[Timestamp] = None
|
||||
invitable: APINullable[bool] = UNDEFINED
|
||||
create_timestamp: APINullable[Timestamp] = UNDEFINED
|
||||
|
||||
|
||||
@dataclass(repr=False)
|
||||
|
@ -62,5 +62,5 @@ class ThreadMember(APIModelBase):
|
|||
|
||||
join_timestamp: Timestamp
|
||||
flags: int
|
||||
id: APINullable[Snowflake] = None
|
||||
user_id: APINullable[Snowflake] = None
|
||||
id: APINullable[Snowflake] = UNDEFINED
|
||||
user_id: APINullable[Snowflake] = UNDEFINED
|
||||
|
|
|
@ -8,8 +8,8 @@ from enum import IntEnum
|
|||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from ...utils import Snowflake
|
||||
from ...utils import APIModelBase
|
||||
from ...utils.types import APINullable
|
||||
from ...utils.api_model import APIModelBase
|
||||
from ...utils.types import APINullable, UNDEFINED
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..user.user import User
|
||||
|
@ -73,18 +73,18 @@ class Webhook(APIModelBase):
|
|||
The url used for executing the webhook (returned by the webhooks OAuth2 flow)
|
||||
"""
|
||||
|
||||
id: APINullable[Snowflake] = None
|
||||
type: APINullable[int] = None
|
||||
guild_id: APINullable[Snowflake] = None
|
||||
channel_id: APINullable[Snowflake] = None
|
||||
user: APINullable[User] = None
|
||||
name: APINullable[str] = None
|
||||
avatar: APINullable[str] = None
|
||||
token: APINullable[str] = None
|
||||
application_id: APINullable[Snowflake] = None
|
||||
source_guild: APINullable[Guild] = None
|
||||
source_channel: APINullable[Channel] = None
|
||||
url: APINullable[str] = None
|
||||
id: APINullable[Snowflake] = UNDEFINED
|
||||
type: APINullable[int] = UNDEFINED
|
||||
guild_id: APINullable[Snowflake] = UNDEFINED
|
||||
channel_id: APINullable[Snowflake] = UNDEFINED
|
||||
user: APINullable[User] = UNDEFINED
|
||||
name: APINullable[str] = UNDEFINED
|
||||
avatar: APINullable[str] = UNDEFINED
|
||||
token: APINullable[str] = UNDEFINED
|
||||
application_id: APINullable[Snowflake] = UNDEFINED
|
||||
source_guild: APINullable[Guild] = UNDEFINED
|
||||
source_channel: APINullable[Channel] = UNDEFINED
|
||||
url: APINullable[str] = UNDEFINED
|
||||
|
||||
async def delete(
|
||||
self, *, webhook_id: Optional[Snowflake] = None, reason: Optional[str] = None
|
||||
|
|
|
@ -3,3 +3,4 @@
|
|||
|
||||
from .message import *
|
||||
from .embed import *
|
||||
from .colors import *
|
||||
|
|
156
melisa/models/message/colors.py
Normal file
156
melisa/models/message/colors.py
Normal file
|
@ -0,0 +1,156 @@
|
|||
# Copyright MelisaDev 2022 - Present
|
||||
# Full MIT License can be found in `LICENSE.txt` at the project root.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
import string
|
||||
import typing
|
||||
|
||||
|
||||
CT = typing.TypeVar("CT", bound="Color")
|
||||
|
||||
|
||||
class Color:
|
||||
"""Represents a Discord colour. This class is similar
|
||||
to a (red, green, blue) :class:`tuple`.
|
||||
|
||||
.. container:: operations
|
||||
.. describe:: x == y
|
||||
Checks if two colours are equal.
|
||||
.. describe:: x != y
|
||||
Checks if two colours are not equal.
|
||||
.. describe:: hash(x)
|
||||
Return the colour's hash.
|
||||
.. describe:: str(x)
|
||||
Returns the hex format for the colour.
|
||||
.. describe:: int(x)
|
||||
Returns the raw colour value.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
value: :class:`int`
|
||||
The raw integer colour value.
|
||||
"""
|
||||
|
||||
__slots__ = ("value",)
|
||||
|
||||
def __init__(self, value: int):
|
||||
if not isinstance(value, int):
|
||||
raise TypeError(
|
||||
f"Expected int parameter, received {value.__class__.__name__} instead."
|
||||
)
|
||||
|
||||
self.value: int = value
|
||||
|
||||
def _get_byte(self, byte: int) -> int:
|
||||
return (self.value >> (8 * byte)) & 0xFF
|
||||
|
||||
def __eq__(self, other: typing.Any) -> bool:
|
||||
return isinstance(other, Color) and self.value == other.value
|
||||
|
||||
def __ne__(self, other: typing.Any) -> bool:
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"#{self.value:0>6x}"
|
||||
|
||||
def __int__(self) -> int:
|
||||
return self.value
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Colour value={self.value}>"
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.value)
|
||||
|
||||
@property
|
||||
def r(self) -> int:
|
||||
""":class:`int`: Returns the red component of the colour."""
|
||||
return self._get_byte(2)
|
||||
|
||||
@property
|
||||
def g(self) -> int:
|
||||
""":class:`int`: Returns the green component of the colour."""
|
||||
return self._get_byte(1)
|
||||
|
||||
@property
|
||||
def b(self) -> int:
|
||||
""":class:`int`: Returns the blue component of the colour."""
|
||||
return self._get_byte(0)
|
||||
|
||||
def to_rgb(self) -> typing.Tuple[int, int, int]:
|
||||
"""
|
||||
Tuple[:class:`int`, :class:`int`, :class:`int`]:
|
||||
Returns an (r, g, b) tuple representing the colour."""
|
||||
return (self.r, self.g, self.b)
|
||||
|
||||
@classmethod
|
||||
def from_rgb(cls: typing.Type[CT], r: int, g: int, b: int) -> CT:
|
||||
"""Constructs a :class:`Colour` from an RGB tuple."""
|
||||
return cls((r << 16) + (g << 8) + b)
|
||||
|
||||
@classmethod
|
||||
def from_hex_code(cls, hex_code: str, /) -> Color:
|
||||
"""Convert the given hexadecimal color code to a `Color`.
|
||||
|
||||
The inputs may be of the following format (case insensitive):
|
||||
`1a2`, `#1a2`, `0x1a2` (for web-safe colors), or
|
||||
`1a2b3c`, `#1a2b3c`, `0x1a2b3c` (for regular 3-byte color-codes).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
hex_code: :class:`str`
|
||||
A hexadecimal color code to parse. This may optionally start with
|
||||
a case insensitive `0x` or `#`.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Color
|
||||
A corresponding Color object.
|
||||
|
||||
Raises
|
||||
------
|
||||
:class:`ValueError`
|
||||
If ``hex_code`` is not a hexadecimal or is a invalid length.
|
||||
"""
|
||||
if hex_code.startswith("#"):
|
||||
hex_code = hex_code[1:]
|
||||
elif hex_code.startswith(("0x", "0X")):
|
||||
hex_code = hex_code[2:]
|
||||
|
||||
if not all(c in string.hexdigits for c in hex_code):
|
||||
raise ValueError("Color code must be hexadecimal")
|
||||
|
||||
if len(hex_code) == 3:
|
||||
r, g, b = (c << 4 | c for c in (int(c, 16) for c in hex_code))
|
||||
return cls.from_rgb(r, g, b)
|
||||
|
||||
if len(hex_code) == 6:
|
||||
return cls.from_rgb(
|
||||
int(hex_code[:2], 16), int(hex_code[2:4], 16), int(hex_code[4:6], 16)
|
||||
)
|
||||
|
||||
raise ValueError("Color code is invalid length. Must be 3 or 6 digits")
|
||||
|
||||
@classmethod
|
||||
def default(cls: typing.Type[CT]) -> CT:
|
||||
"""A factory method that returns a :class:`Colour` with a value of ``0``."""
|
||||
return cls(0)
|
||||
|
||||
@classmethod
|
||||
def random(
|
||||
cls: typing.Type[CT],
|
||||
*,
|
||||
seed: typing.Optional[typing.Union[int, str, float, bytes, bytearray]] = None,
|
||||
) -> CT:
|
||||
"""A factory method that returns a :class:`Colour` with a random hue.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
seed: Optional[Union[:class:`int`, :class:`str`,
|
||||
:class:`float`, :class:`bytes`, :class:`bytearray`]]
|
||||
The seed to initialize the RNG with. If ``None`` is passed the default RNG is used.
|
||||
"""
|
||||
rand = random if seed is None else random.Random(seed)
|
||||
return cls.from_hsv(rand.random(), 1, 1)
|
|
@ -9,9 +9,10 @@ from enum import Enum
|
|||
|
||||
from typing import List, Union, Optional
|
||||
|
||||
from .colors import Color
|
||||
from melisa.exceptions import EmbedFieldError
|
||||
from melisa.utils.types import UNDEFINED, UndefinedOr
|
||||
from melisa.utils.api_model import APIModelBase, APINullable
|
||||
from ...utils.api_model import APIModelBase
|
||||
from ...utils.types import APINullable, UNDEFINED
|
||||
from melisa.utils.timestamp import Timestamp
|
||||
|
||||
|
||||
|
@ -47,7 +48,7 @@ class EmbedType(Enum):
|
|||
|
||||
|
||||
@dataclass(repr=False)
|
||||
class EmbedThumbnail(APIModelBase):
|
||||
class EmbedThumbnail:
|
||||
"""Representation of the Embed Thumbnail
|
||||
|
||||
Attributes
|
||||
|
@ -63,13 +64,13 @@ class EmbedThumbnail(APIModelBase):
|
|||
"""
|
||||
|
||||
url: str
|
||||
proxy_url: APINullable[str] = None
|
||||
height: APINullable[int] = None
|
||||
width: APINullable[int] = None
|
||||
proxy_url: APINullable[str] = UNDEFINED
|
||||
height: APINullable[int] = UNDEFINED
|
||||
width: APINullable[int] = UNDEFINED
|
||||
|
||||
|
||||
@dataclass(repr=False)
|
||||
class EmbedVideo(APIModelBase):
|
||||
class EmbedVideo:
|
||||
"""Representation of the Embed Video
|
||||
|
||||
Attributes
|
||||
|
@ -85,13 +86,13 @@ class EmbedVideo(APIModelBase):
|
|||
"""
|
||||
|
||||
url: str
|
||||
proxy_url: APINullable[str] = None
|
||||
height: APINullable[int] = None
|
||||
width: APINullable[int] = None
|
||||
proxy_url: APINullable[str] = UNDEFINED
|
||||
height: APINullable[int] = UNDEFINED
|
||||
width: APINullable[int] = UNDEFINED
|
||||
|
||||
|
||||
@dataclass(repr=False)
|
||||
class EmbedImage(APIModelBase):
|
||||
class EmbedImage:
|
||||
"""Representation of the Embed Image
|
||||
|
||||
Attributes
|
||||
|
@ -107,13 +108,13 @@ class EmbedImage(APIModelBase):
|
|||
"""
|
||||
|
||||
url: str
|
||||
proxy_url: APINullable[str] = None
|
||||
height: APINullable[int] = None
|
||||
width: APINullable[int] = None
|
||||
proxy_url: APINullable[str] = UNDEFINED
|
||||
height: APINullable[int] = UNDEFINED
|
||||
width: APINullable[int] = UNDEFINED
|
||||
|
||||
|
||||
@dataclass(repr=False)
|
||||
class EmbedProvider(APIModelBase):
|
||||
class EmbedProvider:
|
||||
"""Representation of the Embed Provider
|
||||
|
||||
Attributes
|
||||
|
@ -124,12 +125,12 @@ class EmbedProvider(APIModelBase):
|
|||
Url of provider
|
||||
"""
|
||||
|
||||
name: APINullable[str] = None
|
||||
url: APINullable[str] = None
|
||||
name: APINullable[str] = UNDEFINED
|
||||
url: APINullable[str] = UNDEFINED
|
||||
|
||||
|
||||
@dataclass(repr=False)
|
||||
class EmbedAuthor(APIModelBase):
|
||||
class EmbedAuthor:
|
||||
"""Representation of the Embed Author
|
||||
|
||||
Attributes
|
||||
|
@ -145,13 +146,13 @@ class EmbedAuthor(APIModelBase):
|
|||
"""
|
||||
|
||||
name: str
|
||||
url: APINullable[str] = None
|
||||
icon_url: APINullable[str] = None
|
||||
proxy_icon_url: APINullable[str] = None
|
||||
url: APINullable[str] = UNDEFINED
|
||||
icon_url: APINullable[str] = UNDEFINED
|
||||
proxy_icon_url: APINullable[str] = UNDEFINED
|
||||
|
||||
|
||||
@dataclass(repr=False)
|
||||
class EmbedFooter(APIModelBase):
|
||||
class EmbedFooter:
|
||||
"""Representation of the Embed Footer
|
||||
|
||||
Attributes
|
||||
|
@ -165,12 +166,12 @@ class EmbedFooter(APIModelBase):
|
|||
"""
|
||||
|
||||
text: str
|
||||
icon_url: APINullable[str] = None
|
||||
proxy_icon_url: APINullable[str] = None
|
||||
icon_url: APINullable[str] = UNDEFINED
|
||||
proxy_icon_url: APINullable[str] = UNDEFINED
|
||||
|
||||
|
||||
@dataclass(repr=False)
|
||||
class EmbedField(APIModelBase):
|
||||
class EmbedField:
|
||||
"""Representation of the Embed Field
|
||||
|
||||
Attributes
|
||||
|
@ -185,7 +186,7 @@ class EmbedField(APIModelBase):
|
|||
|
||||
name: str
|
||||
value: str
|
||||
inline: APINullable[bool] = False
|
||||
inline: Optional[bool] = False
|
||||
|
||||
|
||||
@dataclass(repr=False)
|
||||
|
@ -202,7 +203,12 @@ class Embed(APIModelBase):
|
|||
description: Optional[:class:`str`]
|
||||
Description of embed
|
||||
color: Optional[:class:`int`]
|
||||
Color code of the embed
|
||||
Color code of the embed.
|
||||
If you really want to do something with a color,
|
||||
feel free to convert it to the ``Color``: ::
|
||||
|
||||
color = Color(embed.color)
|
||||
|
||||
fields: Optional[List[:class:`~melisa.models.message.embed.EmbedField`]]
|
||||
Fields information.
|
||||
footer: Optional[:class:`~melisa.models.message.embed.EmbedFooter`]
|
||||
|
@ -221,19 +227,19 @@ class Embed(APIModelBase):
|
|||
Video information.
|
||||
"""
|
||||
|
||||
title: APINullable[str] = None
|
||||
type: APINullable[EmbedType] = None
|
||||
description: APINullable[str] = None
|
||||
url: APINullable[str] = None
|
||||
timestamp: APINullable[Timestamp] = None
|
||||
color: APINullable[int] = None
|
||||
footer: APINullable[EmbedFooter] = None
|
||||
image: APINullable[EmbedImage] = None
|
||||
thumbnail: APINullable[EmbedThumbnail] = None
|
||||
video: APINullable[EmbedVideo] = None
|
||||
provider: APINullable[EmbedProvider] = None
|
||||
author: APINullable[EmbedAuthor] = None
|
||||
fields: APINullable[List[EmbedField]] = None
|
||||
title: APINullable[str] = UNDEFINED
|
||||
type: APINullable[EmbedType] = UNDEFINED
|
||||
description: APINullable[str] = UNDEFINED
|
||||
url: APINullable[str] = UNDEFINED
|
||||
timestamp: APINullable[Timestamp] = UNDEFINED
|
||||
color: APINullable[Color] = UNDEFINED
|
||||
footer: APINullable[EmbedFooter] = UNDEFINED
|
||||
image: APINullable[EmbedImage] = UNDEFINED
|
||||
thumbnail: APINullable[EmbedThumbnail] = UNDEFINED
|
||||
video: APINullable[EmbedVideo] = UNDEFINED
|
||||
provider: APINullable[EmbedProvider] = UNDEFINED
|
||||
author: APINullable[EmbedAuthor] = UNDEFINED
|
||||
fields: APINullable[List[EmbedField]] = UNDEFINED
|
||||
|
||||
def __post_init__(self):
|
||||
if self.title and len(self.title) > 256:
|
||||
|
@ -251,6 +257,26 @@ class Embed(APIModelBase):
|
|||
if self.fields and len(self.fields) > 25:
|
||||
raise EmbedFieldError("""You can't set more than 25 embed fields!""")
|
||||
|
||||
def set_color(self, color: Union[int, Color]) -> Embed:
|
||||
"""Sets color in the supported by discord format.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
color: Union[:class:`~melisa.models.message.color.Color`, :class:`int`]
|
||||
The datetime to set the timestamp to.
|
||||
|
||||
Returns
|
||||
-------
|
||||
:class:`~melisa.models.message.embed.Embed`
|
||||
The new embed object.
|
||||
"""
|
||||
if isinstance(color, Color):
|
||||
self.color = color.value
|
||||
elif isinstance(color, int):
|
||||
self.color = Color(value=color).value
|
||||
|
||||
return self
|
||||
|
||||
def set_timestamp(self, time: Union[Timestamp, datetime]) -> Embed:
|
||||
"""Sets timestamp in the supported by discord format.
|
||||
|
||||
|
@ -261,7 +287,7 @@ class Embed(APIModelBase):
|
|||
|
||||
Returns
|
||||
-------
|
||||
:class:`~,e;osa.models.message.embed.Embed`
|
||||
:class:`~melisa.models.message.embed.Embed`
|
||||
The new embed object.
|
||||
"""
|
||||
self.timestamp = time.isoformat()
|
||||
|
@ -272,9 +298,9 @@ class Embed(APIModelBase):
|
|||
self,
|
||||
name: str,
|
||||
*,
|
||||
url: Optional[str] = None,
|
||||
icon_url: Optional[str] = None,
|
||||
proxy_icon_url: Optional[str] = None,
|
||||
url: Optional[str] = UNDEFINED,
|
||||
icon_url: Optional[str] = UNDEFINED,
|
||||
proxy_icon_url: Optional[str] = UNDEFINED,
|
||||
) -> Embed:
|
||||
"""Set the author for the embed.
|
||||
|
||||
|
@ -303,7 +329,7 @@ class Embed(APIModelBase):
|
|||
|
||||
return self
|
||||
|
||||
def set_image(self, url: str, *, proxy_url: Optional[str] = None) -> Embed:
|
||||
def set_image(self, url: str, *, proxy_url: APINullable[str] = UNDEFINED) -> Embed:
|
||||
"""Set the image for the embed.
|
||||
|
||||
Parameters
|
||||
|
@ -322,7 +348,7 @@ class Embed(APIModelBase):
|
|||
|
||||
return self
|
||||
|
||||
def set_thumbnail(self, url: str, *, proxy_url: Optional[str] = None) -> Embed:
|
||||
def set_thumbnail(self, url: str, *, proxy_url: APINullable[str] = UNDEFINED) -> Embed:
|
||||
"""Set the thumbnail for the embed.
|
||||
|
||||
Parameters
|
||||
|
@ -345,8 +371,8 @@ class Embed(APIModelBase):
|
|||
self,
|
||||
text: str,
|
||||
*,
|
||||
icon_url: Optional[str] = None,
|
||||
proxy_icon_url: Optional[str] = None,
|
||||
icon_url: APINullable[str] = UNDEFINED,
|
||||
proxy_icon_url: APINullable[str] = UNDEFINED,
|
||||
) -> Embed:
|
||||
"""
|
||||
Sets the embed footer.
|
||||
|
@ -392,7 +418,7 @@ class Embed(APIModelBase):
|
|||
This embed.
|
||||
"""
|
||||
|
||||
if self.fields is None:
|
||||
if self.fields is UNDEFINED:
|
||||
self.fields = []
|
||||
|
||||
self.fields.append(EmbedField(name=name, value=value, inline=inline))
|
||||
|
@ -403,9 +429,9 @@ class Embed(APIModelBase):
|
|||
self,
|
||||
index: int,
|
||||
*,
|
||||
name: UndefinedOr[str] = UNDEFINED,
|
||||
value: UndefinedOr[str] = UNDEFINED,
|
||||
inline: UndefinedOr[bool] = UNDEFINED,
|
||||
name: APINullable[str] = UNDEFINED,
|
||||
value: APINullable[str] = UNDEFINED,
|
||||
inline: APINullable[bool] = UNDEFINED,
|
||||
) -> Embed:
|
||||
"""Edit an existing field on this embed.
|
||||
|
||||
|
@ -413,11 +439,11 @@ class Embed(APIModelBase):
|
|||
----------
|
||||
index: :class:`int`
|
||||
The index of the field to edit.
|
||||
name: UndefinedOr[:class:`str`]
|
||||
name: Optional[:class:`str`]
|
||||
The name of the field.
|
||||
value: UndefinedOr[:class:`str`]
|
||||
value: Optional[:class:`str`]
|
||||
The value of the field.
|
||||
inline: UndefinedOr[:class:`bool`]
|
||||
inline: Optional[:class:`bool`]
|
||||
Whether the field should be displayed inline.
|
||||
|
||||
Returns
|
||||
|
@ -468,7 +494,7 @@ class Embed(APIModelBase):
|
|||
del self.fields[index]
|
||||
|
||||
if not self.fields:
|
||||
self.fields = None
|
||||
self.fields = UNDEFINED
|
||||
|
||||
return self
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ from typing import List, TYPE_CHECKING, Optional, Dict
|
|||
|
||||
from ...utils import Snowflake, Timestamp
|
||||
from ...utils import APIModelBase
|
||||
from ...utils.types import APINullable
|
||||
from ...utils.types import APINullable, UNDEFINED
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..guild.channel import Thread
|
||||
|
@ -170,36 +170,36 @@ class Message(APIModelBase):
|
|||
Deprecated the stickers sent with the message
|
||||
"""
|
||||
|
||||
id: APINullable[Snowflake] = None
|
||||
channel_id: APINullable[Snowflake] = None
|
||||
guild_id: APINullable[Snowflake] = None
|
||||
author: APINullable[Dict] = None
|
||||
member: APINullable[Dict] = None
|
||||
content: APINullable[str] = None
|
||||
timestamp: APINullable[Timestamp] = None
|
||||
edited_timestamp: APINullable[Timestamp] = None
|
||||
tts: APINullable[bool] = None
|
||||
mention_everyone: APINullable[bool] = None
|
||||
mentions: APINullable[List] = None
|
||||
mention_roles: APINullable[List] = None
|
||||
mention_channels: APINullable[List] = None
|
||||
attachments: APINullable[List] = None
|
||||
embeds: APINullable[List] = None
|
||||
reactions: APINullable[List] = None
|
||||
nonce: APINullable[int] or APINullable[str] = None
|
||||
pinned: APINullable[bool] = None
|
||||
webhook_id: APINullable[Snowflake] = None
|
||||
type: APINullable[int] = None
|
||||
activity: APINullable[Dict] = None
|
||||
application: APINullable[Dict] = None
|
||||
application_id: APINullable[Snowflake] = None
|
||||
message_reference: APINullable[Dict] = None
|
||||
flags: APINullable[int] = None
|
||||
interaction: APINullable[Dict] = None
|
||||
thread: APINullable[Thread] = None
|
||||
components: APINullable[List] = None
|
||||
sticker_items: APINullable[List] = None
|
||||
stickers: APINullable[List] = None
|
||||
id: APINullable[Snowflake] = UNDEFINED
|
||||
channel_id: APINullable[Snowflake] = UNDEFINED
|
||||
guild_id: APINullable[Snowflake] = UNDEFINED
|
||||
author: APINullable[Dict] = UNDEFINED
|
||||
member: APINullable[Dict] = UNDEFINED
|
||||
content: APINullable[str] = UNDEFINED
|
||||
timestamp: APINullable[Timestamp] = UNDEFINED
|
||||
edited_timestamp: APINullable[Timestamp] = UNDEFINED
|
||||
tts: APINullable[bool] = UNDEFINED
|
||||
mention_everyone: APINullable[bool] = UNDEFINED
|
||||
mentions: APINullable[List] = UNDEFINED
|
||||
mention_roles: APINullable[List] = UNDEFINED
|
||||
mention_channels: APINullable[List] = UNDEFINED
|
||||
attachments: APINullable[List] = UNDEFINED
|
||||
embeds: APINullable[List] = UNDEFINED
|
||||
reactions: APINullable[List] = UNDEFINED
|
||||
nonce: APINullable[int] or APINullable[str] = UNDEFINED
|
||||
pinned: APINullable[bool] = UNDEFINED
|
||||
webhook_id: APINullable[Snowflake] = UNDEFINED
|
||||
type: APINullable[int] = UNDEFINED
|
||||
activity: APINullable[Dict] = UNDEFINED
|
||||
application: APINullable[Dict] = UNDEFINED
|
||||
application_id: APINullable[Snowflake] = UNDEFINED
|
||||
message_reference: APINullable[Dict] = UNDEFINED
|
||||
flags: APINullable[int] = UNDEFINED
|
||||
interaction: APINullable[Dict] = UNDEFINED
|
||||
thread: APINullable[Thread] = UNDEFINED
|
||||
components: APINullable[List] = UNDEFINED
|
||||
sticker_items: APINullable[List] = UNDEFINED
|
||||
stickers: APINullable[List] = UNDEFINED
|
||||
|
||||
async def pin(self, *, reason: Optional[str] = None):
|
||||
"""|coro|
|
||||
|
|
|
@ -7,7 +7,7 @@ from typing import Optional, Tuple, List, Literal
|
|||
|
||||
from ...utils import Snowflake
|
||||
from ...utils import APIModelBase
|
||||
from ...utils.types import APINullable
|
||||
from ...utils.types import APINullable, UNDEFINED
|
||||
|
||||
|
||||
class BasePresence:
|
||||
|
@ -223,19 +223,19 @@ class Activity(BasePresence, APIModelBase):
|
|||
|
||||
name: str
|
||||
type: ActivityType
|
||||
created_at: APINullable[int] = None
|
||||
url: APINullable[str] = None
|
||||
timestamps: APINullable[ActivityTimestamp] = None
|
||||
application_id: APINullable[Snowflake] = None
|
||||
details: APINullable[str] = None
|
||||
state: APINullable[str] = None
|
||||
emoji: APINullable[ActivityEmoji] = None
|
||||
party: APINullable[ActivityParty] = None
|
||||
assets: APINullable[ActivityAssets] = None
|
||||
secrets: APINullable[ActivitySecrets] = None
|
||||
instance: APINullable[bool] = None
|
||||
flags: APINullable[ActivityFlags] = None
|
||||
buttons: APINullable[List[ActivityButton]] = None
|
||||
created_at: APINullable[int] = UNDEFINED
|
||||
url: APINullable[str] = UNDEFINED
|
||||
timestamps: APINullable[ActivityTimestamp] = UNDEFINED
|
||||
application_id: APINullable[Snowflake] = UNDEFINED
|
||||
details: APINullable[str] = UNDEFINED
|
||||
state: APINullable[str] = UNDEFINED
|
||||
emoji: APINullable[ActivityEmoji] = UNDEFINED
|
||||
party: APINullable[ActivityParty] = UNDEFINED
|
||||
assets: APINullable[ActivityAssets] = UNDEFINED
|
||||
secrets: APINullable[ActivitySecrets] = UNDEFINED
|
||||
instance: APINullable[bool] = UNDEFINED
|
||||
flags: APINullable[ActivityFlags] = UNDEFINED
|
||||
buttons: APINullable[List[ActivityButton]] = UNDEFINED
|
||||
|
||||
|
||||
class StatusType(Enum):
|
||||
|
|
|
@ -8,7 +8,7 @@ from dataclasses import dataclass
|
|||
from typing import Optional
|
||||
|
||||
from ...utils.api_model import APIModelBase
|
||||
from ...utils.types import APINullable
|
||||
from ...utils.types import APINullable, UNDEFINED
|
||||
from ...utils.snowflake import Snowflake
|
||||
|
||||
|
||||
|
@ -149,20 +149,20 @@ class User(APIModelBase):
|
|||
The user their premium type in a usable enum.
|
||||
"""
|
||||
|
||||
id: APINullable[Snowflake] = None
|
||||
username: APINullable[str] = None
|
||||
discriminator: APINullable[str] = None
|
||||
avatar: APINullable[str] = None
|
||||
bot: APINullable[bool] = None
|
||||
system: APINullable[bool] = None
|
||||
mfa_enabled: APINullable[bool] = None
|
||||
banner: APINullable[str] = None
|
||||
accent_color: APINullable[int] = None
|
||||
local: APINullable[str] = None
|
||||
verified: APINullable[bool] = None
|
||||
email: APINullable[str] = None
|
||||
premium_type: APINullable[int] = None
|
||||
public_flags: APINullable[int] = None
|
||||
id: APINullable[Snowflake] = UNDEFINED
|
||||
username: APINullable[str] = UNDEFINED
|
||||
discriminator: APINullable[str] = UNDEFINED
|
||||
avatar: APINullable[str] = UNDEFINED
|
||||
bot: APINullable[bool] = UNDEFINED
|
||||
system: APINullable[bool] = UNDEFINED
|
||||
mfa_enabled: APINullable[bool] = UNDEFINED
|
||||
banner: APINullable[str] = UNDEFINED
|
||||
accent_color: APINullable[int] = UNDEFINED
|
||||
local: APINullable[str] = UNDEFINED
|
||||
verified: APINullable[bool] = UNDEFINED
|
||||
email: APINullable[str] = UNDEFINED
|
||||
premium_type: APINullable[int] = UNDEFINED
|
||||
public_flags: APINullable[int] = UNDEFINED
|
||||
|
||||
@property
|
||||
def premium(self) -> Optional[PremiumTypes]:
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import datetime
|
||||
from dataclasses import _is_dataclass_instance, fields
|
||||
from enum import Enum, EnumMeta
|
||||
from inspect import getfullargspec
|
||||
|
@ -19,45 +18,58 @@ from typing import (
|
|||
Any,
|
||||
get_origin,
|
||||
Tuple,
|
||||
get_args,
|
||||
get_args, Optional,
|
||||
)
|
||||
|
||||
from typing_extensions import get_type_hints
|
||||
|
||||
from melisa.utils.types import APINullable, TypeCache
|
||||
from melisa.utils.types import UndefinedType, TypeCache, UNDEFINED
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def to_dict_without_none(model):
|
||||
def _asdict_ignore_none(obj: Generic[T]) -> Union[Tuple, Dict, T]:
|
||||
"""
|
||||
Converts discord model or other object to dict.
|
||||
Returns a dict from a dataclass that ignores
|
||||
all values that are None
|
||||
Modification of _asdict_inner from dataclasses
|
||||
Parameters
|
||||
----------
|
||||
obj: Generic[T]
|
||||
The object to convert
|
||||
Returns
|
||||
-------
|
||||
A dict without None values
|
||||
"""
|
||||
if _is_dataclass_instance(model):
|
||||
result = []
|
||||
|
||||
for field in fields(model):
|
||||
value = to_dict_without_none(getattr(model, field.name))
|
||||
if _is_dataclass_instance(obj):
|
||||
result = []
|
||||
for f in fields(obj):
|
||||
value = _asdict_ignore_none(getattr(obj, f.name))
|
||||
|
||||
if isinstance(value, Enum):
|
||||
result.append((field.name, value.value))
|
||||
elif value is not None and not field.name.startswith("_"):
|
||||
result.append((field.name, value))
|
||||
result.append((f.name, value.value))
|
||||
# This if statement was added to the function
|
||||
elif not isinstance(value, UndefinedType) and not f.name.startswith(
|
||||
"_"
|
||||
):
|
||||
result.append((f.name, value))
|
||||
|
||||
return dict(result)
|
||||
|
||||
if isinstance(model, tuple) and hasattr(model, "_fields"):
|
||||
return type(model)(*[to_dict_without_none(v) for v in model])
|
||||
elif isinstance(obj, tuple) and hasattr(obj, "_fields"):
|
||||
return type(obj)(*[_asdict_ignore_none(v) for v in obj])
|
||||
|
||||
if isinstance(model, (list, tuple)):
|
||||
return type(model)(to_dict_without_none(v) for v in model)
|
||||
elif isinstance(obj, (list, tuple)):
|
||||
return type(obj)(_asdict_ignore_none(v) for v in obj)
|
||||
|
||||
if isinstance(model, dict):
|
||||
return type(model)(
|
||||
(to_dict_without_none(k), to_dict_without_none(v)) for k, v in model.items()
|
||||
elif isinstance(obj, dict):
|
||||
return type(obj)(
|
||||
(_asdict_ignore_none(k), _asdict_ignore_none(v))
|
||||
for k, v in obj.items()
|
||||
)
|
||||
|
||||
return copy.deepcopy(model)
|
||||
else:
|
||||
return copy.deepcopy(obj)
|
||||
|
||||
|
||||
class APIModelBase:
|
||||
|
@ -65,12 +77,12 @@ class APIModelBase:
|
|||
Represents an object which has been fetched from the Discord API.
|
||||
"""
|
||||
|
||||
_client = None
|
||||
_client: Optional[Any] = None
|
||||
|
||||
@property
|
||||
def _http(self):
|
||||
if not self._client:
|
||||
return None
|
||||
raise AttributeError("Object is not yet linked to a client")
|
||||
|
||||
return self._client.http
|
||||
|
||||
|
@ -78,17 +90,21 @@ class APIModelBase:
|
|||
def set_client(cls, client):
|
||||
cls._client = client
|
||||
|
||||
def __get_types(self, arg_type: type) -> Tuple[type]:
|
||||
def __get_types(self, attr: str, arg_type: type) -> Tuple[type]:
|
||||
origin = get_origin(arg_type)
|
||||
|
||||
if origin is Union:
|
||||
# Ahh yes, typing module has no type annotations for this...
|
||||
# noinspection PyTypeChecker
|
||||
args: Tuple[type] = get_args(arg_type)
|
||||
|
||||
if 2 <= len(args) < 4:
|
||||
return args
|
||||
|
||||
raise TypeError
|
||||
raise ValueError(
|
||||
f"Attribute `{attr}` in `{type(self).__name__}` has too many "
|
||||
f"or not enough arguments! (got {len(args)} expected 2-3)"
|
||||
)
|
||||
|
||||
return (arg_type,)
|
||||
|
||||
|
@ -99,8 +115,8 @@ class APIModelBase:
|
|||
if getattr(attr_type, "__factory__", None):
|
||||
factory = attr_type.__factory__
|
||||
|
||||
if attr_value is None:
|
||||
return None
|
||||
if attr_value is UNDEFINED:
|
||||
return UNDEFINED
|
||||
|
||||
if attr_type is not None and isinstance(attr_value, attr_type):
|
||||
return attr_value
|
||||
|
@ -108,9 +124,6 @@ class APIModelBase:
|
|||
if isinstance(attr_value, dict):
|
||||
return factory(attr_value)
|
||||
|
||||
if isinstance(attr_value, datetime.datetime):
|
||||
return attr_value
|
||||
|
||||
return factory(attr_value)
|
||||
|
||||
def __post_init__(self):
|
||||
|
@ -126,14 +139,19 @@ class APIModelBase:
|
|||
if attr.startswith("_"):
|
||||
continue
|
||||
|
||||
types = self.__get_types(attr_type)
|
||||
types = self.__get_types(attr, attr_type)
|
||||
|
||||
types = tuple(
|
||||
filter(lambda tpe: tpe is not None and tpe is not None, types)
|
||||
filter(
|
||||
lambda tpe: tpe is not None and tpe is not UNDEFINED, types
|
||||
)
|
||||
)
|
||||
|
||||
if not types:
|
||||
raise TypeError
|
||||
raise ValueError(
|
||||
f"Attribute `{attr}` in `{type(self).__name__}` only "
|
||||
"consisted of missing/optional type!"
|
||||
)
|
||||
|
||||
specific_tp = types[0]
|
||||
|
||||
|
@ -143,7 +161,7 @@ class APIModelBase:
|
|||
specific_tp = tp
|
||||
|
||||
if isinstance(specific_tp, EnumMeta) and not attr_gotten:
|
||||
attr_value = None
|
||||
attr_value = UNDEFINED
|
||||
elif tp == list and attr_gotten and (classes := get_args(types[0])):
|
||||
attr_value = [
|
||||
self.__attr_convert(attr_item, classes[0])
|
||||
|
@ -179,23 +197,30 @@ class APIModelBase:
|
|||
return super().__str__()
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls: Generic[T], data: Dict[str, Union[str, bool, int, Any]]) -> T:
|
||||
def from_dict(
|
||||
cls: Generic[T], data: Dict[str, Union[str, bool, int, Any]]
|
||||
) -> T:
|
||||
"""
|
||||
Parse an API object from a dictionary.
|
||||
"""
|
||||
if isinstance(data, cls):
|
||||
return data
|
||||
|
||||
# Disable inspection for IDE because this is valid code for the
|
||||
# inherited classes:
|
||||
# noinspection PyArgumentList
|
||||
return cls(
|
||||
**dict(
|
||||
map(
|
||||
lambda key: (
|
||||
key,
|
||||
data[key].value if isinstance(data[key], Enum) else data[key],
|
||||
data[key].value
|
||||
if isinstance(data[key], Enum)
|
||||
else data[key],
|
||||
),
|
||||
filter(
|
||||
lambda object_argument: data.get(object_argument) is not None,
|
||||
lambda object_argument: data.get(object_argument)
|
||||
is not None,
|
||||
getfullargspec(cls.__init__).args,
|
||||
),
|
||||
)
|
||||
|
@ -203,4 +228,8 @@ class APIModelBase:
|
|||
)
|
||||
|
||||
def to_dict(self) -> Dict:
|
||||
return to_dict_without_none(self)
|
||||
"""
|
||||
Transform the current object to a dictionary representation. Parameters that
|
||||
start with an underscore are not serialized.
|
||||
"""
|
||||
return _asdict_ignore_none(self)
|
||||
|
|
|
@ -35,9 +35,7 @@ T = TypeVar("T")
|
|||
|
||||
Coro = TypeVar("Coro", bound=Callable[..., Coroutine[Any, Any, Any]])
|
||||
|
||||
APINullable = Union[T, None]
|
||||
|
||||
UndefinedOr = Union[T, UndefinedType]
|
||||
APINullable = Union[T, UndefinedType]
|
||||
|
||||
|
||||
class Singleton(type):
|
||||
|
|
15
tests/test_colors.py
Normal file
15
tests/test_colors.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
from melisa import Color
|
||||
|
||||
|
||||
rgb_right_example = (3, 217, 147)
|
||||
|
||||
|
||||
class TestColor:
|
||||
def test_from_rgb_converting(self):
|
||||
assert Color.from_rgb(3, 217, 147).to_rgb() == rgb_right_example
|
||||
|
||||
def test_from_hex_code_converting(self):
|
||||
assert Color.from_hex_code("#03d993").to_rgb() == rgb_right_example
|
||||
|
||||
def test_from_decimal_converting(self):
|
||||
assert Color(252307).to_rgb() == rgb_right_example
|
|
@ -1,10 +1,11 @@
|
|||
import datetime
|
||||
|
||||
from melisa import Embed, Timestamp
|
||||
from melisa import Embed, Timestamp, Color
|
||||
|
||||
dict_embed = {
|
||||
'title': 'my title',
|
||||
'description': 'simple description',
|
||||
'color': 252307,
|
||||
'timestamp': datetime.datetime.utcfromtimestamp(1649748784).isoformat(),
|
||||
'footer': {
|
||||
'text': 'cool footer text'
|
||||
|
@ -14,6 +15,16 @@ dict_embed = {
|
|||
},
|
||||
}
|
||||
|
||||
EMBED = Embed(title="my title", description="simple description")
|
||||
EMBED.set_author(name="best author")
|
||||
EMBED.set_footer(text="cool footer text")
|
||||
EMBED.set_color(Color.from_hex_code("#03d993"))
|
||||
EMBED.set_timestamp(Timestamp.parse(1649748784))
|
||||
|
||||
|
||||
def has_key_vals(actual, required):
|
||||
return all(actual.get(key) == val for key, val in required.items())
|
||||
|
||||
|
||||
class TestEmbed:
|
||||
def test_total_length_when_embed_is_empty(self):
|
||||
|
@ -54,9 +65,15 @@ class TestEmbed:
|
|||
embed.set_footer(text="cool footer text")
|
||||
assert embed.total_length() == 53
|
||||
|
||||
def test_comparing_embeds(self):
|
||||
embed = Embed(title="my title", description="simple description")
|
||||
embed.set_author(name="best author")
|
||||
embed.set_footer(text="cool footer text")
|
||||
embed.set_timestamp(Timestamp.parse(1649748784))
|
||||
assert embed.to_dict() == dict_embed
|
||||
def test_embed_to_dict(self):
|
||||
"""
|
||||
Tests whether or not the dispatch class its string conversion
|
||||
is correct.
|
||||
"""
|
||||
assert has_key_vals(EMBED.to_dict(), dict_embed)
|
||||
|
||||
def test_embed_from_dict(self):
|
||||
assert has_key_vals(
|
||||
Embed.from_dict(dict_embed).to_dict(),
|
||||
dict_embed
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue