aboutsummaryrefslogtreecommitdiff
path: root/discord/embeds.py
diff options
context:
space:
mode:
authorRapptz <[email protected]>2021-04-07 05:38:01 -0400
committerRapptz <[email protected]>2021-04-07 05:39:58 -0400
commit23fe6b46dd9c86e32d496fa8d36dc3d8f5c3eaba (patch)
tree09622bfe3c09ab0b20f3a73cec98e8234a515689 /discord/embeds.py
parent[commands] Update Converter list in ext.commands introduction (diff)
downloaddiscord.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.py247
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