diff options
| author | Rapptz <[email protected]> | 2021-04-07 05:38:01 -0400 |
|---|---|---|
| committer | Rapptz <[email protected]> | 2021-04-07 05:39:58 -0400 |
| commit | 23fe6b46dd9c86e32d496fa8d36dc3d8f5c3eaba (patch) | |
| tree | 09622bfe3c09ab0b20f3a73cec98e8234a515689 /discord/embeds.py | |
| parent | [commands] Update Converter list in ext.commands introduction (diff) | |
| download | discord.py-23fe6b46dd9c86e32d496fa8d36dc3d8f5c3eaba.tar.xz discord.py-23fe6b46dd9c86e32d496fa8d36dc3d8f5c3eaba.zip | |
Typehint discord.Embed and introduce discord.types subpackage
The discord.types subpackage is currently only used to do type hinting
of API payloads, it's very much incomplete right now and it'll be a
rather long process.
discord.Embed was typehinted and formatted using black.
Diffstat (limited to 'discord/embeds.py')
| -rw-r--r-- | discord/embeds.py | 247 |
1 files changed, 155 insertions, 92 deletions
diff --git a/discord/embeds.py b/discord/embeds.py index 88a8117e..c44d8b9e 100644 --- a/discord/embeds.py +++ b/discord/embeds.py @@ -22,7 +22,10 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ +from __future__ import annotations + import datetime +from typing import Any, Dict, Final, List, Protocol, TYPE_CHECKING, Type, TypeVar, Union from . import utils from .colour import Colour @@ -31,32 +34,75 @@ __all__ = ( 'Embed', ) + class _EmptyEmbed: - def __bool__(self): + def __bool__(self) -> bool: return False - def __repr__(self): + def __repr__(self) -> str: return 'Embed.Empty' - def __len__(self): + def __len__(self) -> int: return 0 -EmptyEmbed = _EmptyEmbed() + +EmptyEmbed: Final = _EmptyEmbed() + class EmbedProxy: - def __init__(self, layer): + def __init__(self, layer: Dict[str, Any]): self.__dict__.update(layer) - def __len__(self): + def __len__(self) -> int: return len(self.__dict__) - def __repr__(self): + def __repr__(self) -> str: inner = ', '.join((f'{k}={v!r}' for k, v in self.__dict__.items() if not k.startswith('_'))) return f'EmbedProxy({inner})' - def __getattr__(self, attr): + def __getattr__(self, attr: str) -> _EmptyEmbed: return EmptyEmbed + +E = TypeVar('E', bound='Embed') + +if TYPE_CHECKING: + from discord.types.common import Embed as EmbedData, EmbedType + + T = TypeVar('T') + MaybeEmpty = Union[T, _EmptyEmbed] + + class _EmbedFooterProxy(Protocol): + text: MaybeEmpty[str] + icon_url: MaybeEmpty[str] + + class _EmbedFieldProxy(Protocol): + name: MaybeEmpty[str] + value: MaybeEmpty[str] + inline: bool + + class _EmbedMediaProxy(Protocol): + url: MaybeEmpty[str] + proxy_url: MaybeEmpty[str] + height: MaybeEmpty[int] + width: MaybeEmpty[int] + + class _EmbedVideoProxy(Protocol): + url: MaybeEmpty[str] + height: MaybeEmpty[int] + width: MaybeEmpty[int] + + class _EmbedProviderProxy(Protocol): + name: MaybeEmpty[str] + url: MaybeEmpty[str] + + class _EmbedAuthorProxy(Protocol): + name: MaybeEmpty[str] + url: MaybeEmpty[str] + icon_url: MaybeEmpty[str] + proxy_icon_url: MaybeEmpty[str] + + class Embed: """Represents a Discord embed. @@ -108,24 +154,41 @@ class Embed: to denote that the value or attribute is empty. """ - __slots__ = ('title', 'url', 'type', '_timestamp', '_colour', '_footer', - '_image', '_thumbnail', '_video', '_provider', '_author', - '_fields', 'description') - - Empty = EmptyEmbed - - def __init__(self, **kwargs): - # swap the colour/color aliases - try: - colour = kwargs['colour'] - except KeyError: - colour = kwargs.get('color', EmptyEmbed) - - self.colour = colour - self.title = kwargs.get('title', EmptyEmbed) - self.type = kwargs.get('type', 'rich') - self.url = kwargs.get('url', EmptyEmbed) - self.description = kwargs.get('description', EmptyEmbed) + __slots__ = ( + 'title', + 'url', + 'type', + '_timestamp', + '_colour', + '_footer', + '_image', + '_thumbnail', + '_video', + '_provider', + '_author', + '_fields', + 'description', + ) + + Empty: Final = EmptyEmbed + + def __init__( + self, + *, + colour: Union[int, Colour, _EmptyEmbed] = EmptyEmbed, + color: Union[int, Colour, _EmptyEmbed] = EmptyEmbed, + title: MaybeEmpty[str] = EmptyEmbed, + type: EmbedType = 'rich', + url: MaybeEmpty[str] = EmptyEmbed, + description: MaybeEmpty[str] = EmptyEmbed, + timestamp: datetime.datetime = None, + ): + + self.colour = colour if colour is not EmptyEmbed else color + self.title = title + self.type = type + self.url = url + self.description = description if self.title is not EmptyEmbed: self.title = str(self.title) @@ -136,17 +199,13 @@ class Embed: if self.url is not EmptyEmbed: self.url = str(self.url) - try: - timestamp = kwargs['timestamp'] - except KeyError: - pass - else: + if timestamp: if timestamp.tzinfo is None: timestamp = timestamp.astimezone() self.timestamp = timestamp @classmethod - def from_dict(cls, data): + def from_dict(cls: Type[E], data: EmbedData) -> E: """Converts a :class:`dict` to a :class:`Embed` provided it is in the format that Discord expects it to be in. @@ -162,7 +221,7 @@ class Embed: The dictionary to convert into an embed. """ # we are bypassing __init__ here since it doesn't apply here - self = cls.__new__(cls) + self: E = cls.__new__(cls) # fill in the basic fields @@ -202,11 +261,11 @@ class Embed: return self - def copy(self): + def copy(self: E) -> E: """Returns a shallow copy of the embed.""" - return Embed.from_dict(self.to_dict()) + return self.__class__.from_dict(self.to_dict()) - def __len__(self): + def __len__(self) -> int: total = len(self.title) + len(self.description) for field in getattr(self, '_fields', []): total += len(field['name']) + len(field['value']) @@ -227,28 +286,30 @@ class Embed: return total - def __bool__(self): - return any(( - self.title, - self.url, - self.description, - self.colour, - self.fields, - self.timestamp, - self.author, - self.thumbnail, - self.footer, - self.image, - self.provider, - self.video, - )) + def __bool__(self) -> bool: + return any( + ( + self.title, + self.url, + self.description, + self.colour, + self.fields, + self.timestamp, + self.author, + self.thumbnail, + self.footer, + self.image, + self.provider, + self.video, + ) + ) @property - def colour(self): + def colour(self) -> MaybeEmpty[Colour]: return getattr(self, '_colour', EmptyEmbed) @colour.setter - def colour(self, value): + def colour(self, value: Union[int, Colour, _EmptyEmbed]): # type: ignore if isinstance(value, (Colour, _EmptyEmbed)): self._colour = value elif isinstance(value, int): @@ -259,27 +320,27 @@ class Embed: color = colour @property - def timestamp(self): + def timestamp(self) -> MaybeEmpty[datetime.datetime]: return getattr(self, '_timestamp', EmptyEmbed) @timestamp.setter - def timestamp(self, value): + def timestamp(self, value: MaybeEmpty[datetime.datetime]): if isinstance(value, (datetime.datetime, _EmptyEmbed)): self._timestamp = value else: raise TypeError(f"Expected datetime.datetime or Embed.Empty received {value.__class__.__name__} instead") @property - def footer(self): - """Union[:class:`EmbedProxy`, :attr:`Empty`]: Returns an ``EmbedProxy`` denoting the footer contents. + def footer(self) -> _EmbedFooterProxy: + """Returns an ``EmbedProxy`` denoting the footer contents. See :meth:`set_footer` for possible values you can access. If the attribute has no value then :attr:`Empty` is returned. """ - return EmbedProxy(getattr(self, '_footer', {})) + return EmbedProxy(getattr(self, '_footer', {})) # type: ignore - def set_footer(self, *, text=EmptyEmbed, icon_url=EmptyEmbed): + def set_footer(self: E, *, text: MaybeEmpty[str] = EmptyEmbed, icon_url: MaybeEmpty[str] = EmptyEmbed) -> E: """Sets the footer for the embed content. This function returns the class instance to allow for fluent-style @@ -303,8 +364,8 @@ class Embed: return self @property - def image(self): - """Union[:class:`EmbedProxy`, :attr:`Empty`]: Returns an ``EmbedProxy`` denoting the image contents. + def image(self) -> _EmbedMediaProxy: + """Returns an ``EmbedProxy`` denoting the image contents. Possible attributes you can access are: @@ -315,9 +376,9 @@ class Embed: If the attribute has no value then :attr:`Empty` is returned. """ - return EmbedProxy(getattr(self, '_image', {})) + return EmbedProxy(getattr(self, '_image', {})) # type: ignore - def set_image(self, *, url): + def set_image(self: E, *, url: MaybeEmpty[str]) -> E: """Sets the image for the embed content. This function returns the class instance to allow for fluent-style @@ -339,14 +400,14 @@ class Embed: pass else: self._image = { - 'url': str(url) + 'url': str(url), } return self @property - def thumbnail(self): - """Union[:class:`EmbedProxy`, :attr:`Empty`]: Returns an ``EmbedProxy`` denoting the thumbnail contents. + def thumbnail(self) -> _EmbedMediaProxy: + """Returns an ``EmbedProxy`` denoting the thumbnail contents. Possible attributes you can access are: @@ -357,9 +418,9 @@ class Embed: If the attribute has no value then :attr:`Empty` is returned. """ - return EmbedProxy(getattr(self, '_thumbnail', {})) + return EmbedProxy(getattr(self, '_thumbnail', {})) # type: ignore - def set_thumbnail(self, *, url): + def set_thumbnail(self: E, *, url: MaybeEmpty[str]) -> E: """Sets the thumbnail for the embed content. This function returns the class instance to allow for fluent-style @@ -381,14 +442,14 @@ class Embed: pass else: self._thumbnail = { - 'url': str(url) + 'url': str(url), } return self @property - def video(self): - """Union[:class:`EmbedProxy`, :attr:`Empty`]: Returns an ``EmbedProxy`` denoting the video contents. + def video(self) -> _EmbedVideoProxy: + """Returns an ``EmbedProxy`` denoting the video contents. Possible attributes include: @@ -398,29 +459,29 @@ class Embed: If the attribute has no value then :attr:`Empty` is returned. """ - return EmbedProxy(getattr(self, '_video', {})) + return EmbedProxy(getattr(self, '_video', {})) # type: ignore @property - def provider(self): - """Union[:class:`EmbedProxy`, :attr:`Empty`]: Returns an ``EmbedProxy`` denoting the provider contents. + def provider(self) -> _EmbedProviderProxy: + """Returns an ``EmbedProxy`` denoting the provider contents. The only attributes that might be accessed are ``name`` and ``url``. If the attribute has no value then :attr:`Empty` is returned. """ - return EmbedProxy(getattr(self, '_provider', {})) + return EmbedProxy(getattr(self, '_provider', {})) # type: ignore @property - def author(self): - """Union[:class:`EmbedProxy`, :attr:`Empty`]: Returns an ``EmbedProxy`` denoting the author contents. + def author(self) -> _EmbedAuthorProxy: + """Returns an ``EmbedProxy`` denoting the author contents. See :meth:`set_author` for possible values you can access. If the attribute has no value then :attr:`Empty` is returned. """ - return EmbedProxy(getattr(self, '_author', {})) + return EmbedProxy(getattr(self, '_author', {})) # type: ignore - def set_author(self, *, name, url=EmptyEmbed, icon_url=EmptyEmbed): + def set_author(self: E, *, name: str, url: MaybeEmpty[str] = EmptyEmbed, icon_url: MaybeEmpty[str] = EmptyEmbed) -> E: """Sets the author for the embed content. This function returns the class instance to allow for fluent-style @@ -437,7 +498,7 @@ class Embed: """ self._author = { - 'name': str(name) + 'name': str(name), } if url is not EmptyEmbed: @@ -448,7 +509,7 @@ class Embed: return self - def remove_author(self): + def remove_author(self: E) -> E: """Clears embed's author information. This function returns the class instance to allow for fluent-style @@ -464,16 +525,16 @@ class Embed: return self @property - def fields(self): + def fields(self) -> List[_EmbedFieldProxy]: """Union[List[:class:`EmbedProxy`], :attr:`Empty`]: Returns a :class:`list` of ``EmbedProxy`` denoting the field contents. See :meth:`add_field` for possible values you can access. If the attribute has no value then :attr:`Empty` is returned. """ - return [EmbedProxy(d) for d in getattr(self, '_fields', [])] + return [EmbedProxy(d) for d in getattr(self, '_fields', [])] # type: ignore - def add_field(self, *, name, value, inline=True): + def add_field(self: E, *, name: str, value: str, inline: bool = True) -> E: """Adds a field to the embed object. This function returns the class instance to allow for fluent-style @@ -492,7 +553,7 @@ class Embed: field = { 'inline': inline, 'name': str(name), - 'value': str(value) + 'value': str(value), } try: @@ -502,7 +563,7 @@ class Embed: return self - def insert_field_at(self, index, *, name, value, inline=True): + def insert_field_at(self: E, index: int, *, name: str, value: str, inline: bool = True) -> E: """Inserts a field before a specified index to the embed. This function returns the class instance to allow for fluent-style @@ -525,7 +586,7 @@ class Embed: field = { 'inline': inline, 'name': str(name), - 'value': str(value) + 'value': str(value), } try: @@ -535,14 +596,14 @@ class Embed: return self - def clear_fields(self): + def clear_fields(self) -> None: """Removes all fields from this embed.""" try: self._fields.clear() except AttributeError: self._fields = [] - def remove_field(self, index): + def remove_field(self, index: int) -> None: """Removes a field at a specified index. If the index is invalid or out of bounds then the error is @@ -563,7 +624,7 @@ class Embed: except (AttributeError, IndexError): pass - def set_field_at(self, index, *, name, value, inline=True): + def set_field_at(self: E, index: int, *, name: str, value: str, inline: bool = True) -> E: """Modifies a field to the embed object. The index must point to a valid pre-existing field. @@ -598,15 +659,17 @@ class Embed: field['inline'] = inline return self - def to_dict(self): + def to_dict(self) -> EmbedData: """Converts this embed object into a dict.""" # add in the raw data into the dict + # fmt: off result = { key[1:]: getattr(self, key) for key in self.__slots__ if key[0] == '_' and hasattr(self, key) } + # fmt: on # deal with basic convenience wrappers @@ -642,4 +705,4 @@ class Embed: if self.title: result['title'] = self.title - return result + return result # type: ignore |