Browse Source

Remove Embed.Empty in favour of None

pull/7683/head
Rapptz 3 years ago
parent
commit
c6ab67420e
  1. 188
      discord/embeds.py
  2. 19
      docs/migrating.rst

188
discord/embeds.py

@ -25,7 +25,7 @@ DEALINGS IN THE SOFTWARE.
from __future__ import annotations from __future__ import annotations
import datetime import datetime
from typing import Any, Dict, Final, List, Mapping, Protocol, TYPE_CHECKING, TypeVar, Union from typing import Any, Dict, List, Mapping, Optional, Protocol, TYPE_CHECKING, TypeVar, Union
from . import utils from . import utils
from .colour import Colour from .colour import Colour
@ -37,20 +37,6 @@ __all__ = (
# fmt: on # fmt: on
class _EmptyEmbed:
def __bool__(self) -> bool:
return False
def __repr__(self) -> str:
return 'Embed.Empty'
def __len__(self) -> int:
return 0
EmptyEmbed: Final = _EmptyEmbed()
class EmbedProxy: class EmbedProxy:
def __init__(self, layer: Dict[str, Any]): def __init__(self, layer: Dict[str, Any]):
self.__dict__.update(layer) self.__dict__.update(layer)
@ -62,8 +48,8 @@ class EmbedProxy:
inner = ', '.join((f'{k}={v!r}' for k, v in self.__dict__.items() if not k.startswith('_'))) inner = ', '.join((f'{k}={v!r}' for k, v in self.__dict__.items() if not k.startswith('_')))
return f'EmbedProxy({inner})' return f'EmbedProxy({inner})'
def __getattr__(self, attr: str) -> _EmptyEmbed: def __getattr__(self, attr: str) -> None:
return EmptyEmbed return None
if TYPE_CHECKING: if TYPE_CHECKING:
@ -72,37 +58,36 @@ if TYPE_CHECKING:
from .types.embed import Embed as EmbedData, EmbedType from .types.embed import Embed as EmbedData, EmbedType
T = TypeVar('T') T = TypeVar('T')
MaybeEmpty = Union[T, _EmptyEmbed]
class _EmbedFooterProxy(Protocol): class _EmbedFooterProxy(Protocol):
text: MaybeEmpty[str] text: Optional[str]
icon_url: MaybeEmpty[str] icon_url: Optional[str]
class _EmbedFieldProxy(Protocol): class _EmbedFieldProxy(Protocol):
name: MaybeEmpty[str] name: Optional[str]
value: MaybeEmpty[str] value: Optional[str]
inline: bool inline: bool
class _EmbedMediaProxy(Protocol): class _EmbedMediaProxy(Protocol):
url: MaybeEmpty[str] url: Optional[str]
proxy_url: MaybeEmpty[str] proxy_url: Optional[str]
height: MaybeEmpty[int] height: Optional[int]
width: MaybeEmpty[int] width: Optional[int]
class _EmbedVideoProxy(Protocol): class _EmbedVideoProxy(Protocol):
url: MaybeEmpty[str] url: Optional[str]
height: MaybeEmpty[int] height: Optional[int]
width: MaybeEmpty[int] width: Optional[int]
class _EmbedProviderProxy(Protocol): class _EmbedProviderProxy(Protocol):
name: MaybeEmpty[str] name: Optional[str]
url: MaybeEmpty[str] url: Optional[str]
class _EmbedAuthorProxy(Protocol): class _EmbedAuthorProxy(Protocol):
name: MaybeEmpty[str] name: Optional[str]
url: MaybeEmpty[str] url: Optional[str]
icon_url: MaybeEmpty[str] icon_url: Optional[str]
proxy_icon_url: MaybeEmpty[str] proxy_icon_url: Optional[str]
class Embed: class Embed:
@ -121,18 +106,15 @@ class Embed:
.. versionadded:: 2.0 .. versionadded:: 2.0
Certain properties return an ``EmbedProxy``, a type
that acts similar to a regular :class:`dict` except using dotted access,
e.g. ``embed.author.icon_url``. If the attribute
is invalid or empty, then a special sentinel value is returned,
:attr:`Embed.Empty`.
For ease of use, all parameters that expect a :class:`str` are implicitly For ease of use, all parameters that expect a :class:`str` are implicitly
casted to :class:`str` for you. casted to :class:`str` for you.
.. versionchanged:: 2.0
``Embed.Empty`` has been removed in favour of ``None``.
Attributes Attributes
----------- -----------
title: :class:`str` title: Optional[:class:`str`]
The title of the embed. The title of the embed.
This can be set during initialisation. This can be set during initialisation.
type: :class:`str` type: :class:`str`
@ -140,22 +122,19 @@ class Embed:
This can be set during initialisation. This can be set during initialisation.
Possible strings for embed types can be found on discord's Possible strings for embed types can be found on discord's
`api docs <https://discord.com/developers/docs/resources/channel#embed-object-embed-types>`_ `api docs <https://discord.com/developers/docs/resources/channel#embed-object-embed-types>`_
description: :class:`str` description: Optional[:class:`str`]
The description of the embed. The description of the embed.
This can be set during initialisation. This can be set during initialisation.
url: :class:`str` url: Optional[:class:`str`]
The URL of the embed. The URL of the embed.
This can be set during initialisation. This can be set during initialisation.
timestamp: :class:`datetime.datetime` timestamp: Optional[:class:`datetime.datetime`]
The timestamp of the embed content. This is an aware datetime. The timestamp of the embed content. This is an aware datetime.
If a naive datetime is passed, it is converted to an aware If a naive datetime is passed, it is converted to an aware
datetime with the local timezone. datetime with the local timezone.
colour: Union[:class:`Colour`, :class:`int`] colour: Optional[Union[:class:`Colour`, :class:`int`]]
The colour code of the embed. Aliased to ``color`` as well. The colour code of the embed. Aliased to ``color`` as well.
This can be set during initialisation. This can be set during initialisation.
Empty
A special sentinel value used by ``EmbedProxy`` and this class
to denote that the value or attribute is empty.
""" """
__slots__ = ( __slots__ = (
@ -174,36 +153,34 @@ class Embed:
'description', 'description',
) )
Empty: Final = EmptyEmbed
def __init__( def __init__(
self, self,
*, *,
colour: Union[int, Colour, _EmptyEmbed] = EmptyEmbed, colour: Optional[Union[int, Colour]] = None,
color: Union[int, Colour, _EmptyEmbed] = EmptyEmbed, color: Optional[Union[int, Colour]] = None,
title: MaybeEmpty[Any] = EmptyEmbed, title: Optional[Any] = None,
type: EmbedType = 'rich', type: EmbedType = 'rich',
url: MaybeEmpty[Any] = EmptyEmbed, url: Optional[Any] = None,
description: MaybeEmpty[Any] = EmptyEmbed, description: Optional[Any] = None,
timestamp: MaybeEmpty[datetime.datetime] = EmptyEmbed, timestamp: Optional[datetime.datetime] = None,
): ):
self.colour = colour if colour is not EmptyEmbed else color self.colour = colour if colour is not None else color
self.title: MaybeEmpty[str] = title self.title: Optional[str] = title
self.type: EmbedType = type self.type: EmbedType = type
self.url: MaybeEmpty[str] = url self.url: Optional[str] = url
self.description: MaybeEmpty[str] = description self.description: Optional[str] = description
if self.title is not EmptyEmbed: if self.title is not None:
self.title = str(self.title) self.title = str(self.title)
if self.description is not EmptyEmbed: if self.description is not None:
self.description = str(self.description) self.description = str(self.description)
if self.url is not EmptyEmbed: if self.url is not None:
self.url = str(self.url) self.url = str(self.url)
if timestamp is not EmptyEmbed: if timestamp is not None:
self.timestamp = timestamp self.timestamp = timestamp
@classmethod @classmethod
@ -227,18 +204,18 @@ class Embed:
# fill in the basic fields # fill in the basic fields
self.title = data.get('title', EmptyEmbed) self.title = data.get('title', None)
self.type = data.get('type', EmptyEmbed) self.type = data.get('type', None)
self.description = data.get('description', EmptyEmbed) self.description = data.get('description', None)
self.url = data.get('url', EmptyEmbed) self.url = data.get('url', None)
if self.title is not EmptyEmbed: if self.title is not None:
self.title = str(self.title) self.title = str(self.title)
if self.description is not EmptyEmbed: if self.description is not None:
self.description = str(self.description) self.description = str(self.description)
if self.url is not EmptyEmbed: if self.url is not None:
self.url = str(self.url) self.url = str(self.url)
# try to fill in the more rich fields # try to fill in the more rich fields
@ -268,7 +245,7 @@ class Embed:
return self.__class__.from_dict(self.to_dict()) return self.__class__.from_dict(self.to_dict())
def __len__(self) -> int: def __len__(self) -> int:
total = len(self.title) + len(self.description) total = len(self.title or '') + len(self.description or '')
for field in getattr(self, '_fields', []): for field in getattr(self, '_fields', []):
total += len(field['name']) + len(field['value']) total += len(field['name']) + len(field['value'])
@ -307,34 +284,36 @@ class Embed:
) )
@property @property
def colour(self) -> MaybeEmpty[Colour]: def colour(self) -> Optional[Colour]:
return getattr(self, '_colour', EmptyEmbed) return getattr(self, '_colour', None)
@colour.setter @colour.setter
def colour(self, value: Union[int, Colour, _EmptyEmbed]) -> None: def colour(self, value: Optional[Union[int, Colour]]) -> None:
if isinstance(value, (Colour, _EmptyEmbed)): if value is None:
self._colour = None
elif isinstance(value, Colour):
self._colour = value self._colour = value
elif isinstance(value, int): elif isinstance(value, int):
self._colour = Colour(value=value) self._colour = Colour(value=value)
else: else:
raise TypeError(f'Expected discord.Colour, int, or Embed.Empty but received {value.__class__.__name__} instead.') raise TypeError(f'Expected discord.Colour, int, or None but received {value.__class__.__name__} instead.')
color = colour color = colour
@property @property
def timestamp(self) -> MaybeEmpty[datetime.datetime]: def timestamp(self) -> Optional[datetime.datetime]:
return getattr(self, '_timestamp', EmptyEmbed) return getattr(self, '_timestamp', None)
@timestamp.setter @timestamp.setter
def timestamp(self, value: MaybeEmpty[datetime.datetime]) -> None: def timestamp(self, value: Optional[datetime.datetime]) -> None:
if isinstance(value, datetime.datetime): if isinstance(value, datetime.datetime):
if value.tzinfo is None: if value.tzinfo is None:
value = value.astimezone() value = value.astimezone()
self._timestamp = value self._timestamp = value
elif isinstance(value, _EmptyEmbed): elif value is None:
self._timestamp = value self._timestamp = None
else: else:
raise TypeError(f"Expected datetime.datetime or Embed.Empty received {value.__class__.__name__} instead") raise TypeError(f"Expected datetime.datetime or None received {value.__class__.__name__} instead")
@property @property
def footer(self) -> _EmbedFooterProxy: def footer(self) -> _EmbedFooterProxy:
@ -342,12 +321,12 @@ class Embed:
See :meth:`set_footer` for possible values you can access. See :meth:`set_footer` for possible values you can access.
If the attribute has no value then :attr:`Empty` is returned. If the attribute has no value then ``None`` is returned.
""" """
# Lying to the type checker for better developer UX. # Lying to the type checker for better developer UX.
return EmbedProxy(getattr(self, '_footer', {})) # type: ignore return EmbedProxy(getattr(self, '_footer', {})) # type: ignore
def set_footer(self, *, text: MaybeEmpty[Any] = EmptyEmbed, icon_url: MaybeEmpty[Any] = EmptyEmbed) -> Self: def set_footer(self, *, text: Optional[Any] = None, icon_url: Optional[Any] = None) -> Self:
"""Sets the footer for the embed content. """Sets the footer for the embed content.
This function returns the class instance to allow for fluent-style This function returns the class instance to allow for fluent-style
@ -362,10 +341,10 @@ class Embed:
""" """
self._footer = {} self._footer = {}
if text is not EmptyEmbed: if text is not None:
self._footer['text'] = str(text) self._footer['text'] = str(text)
if icon_url is not EmptyEmbed: if icon_url is not None:
self._footer['icon_url'] = str(icon_url) self._footer['icon_url'] = str(icon_url)
return self return self
@ -396,27 +375,24 @@ class Embed:
- ``width`` - ``width``
- ``height`` - ``height``
If the attribute has no value then :attr:`Empty` is returned. If the attribute has no value then ``None`` is returned.
""" """
# Lying to the type checker for better developer UX. # Lying to the type checker for better developer UX.
return EmbedProxy(getattr(self, '_image', {})) # type: ignore return EmbedProxy(getattr(self, '_image', {})) # type: ignore
def set_image(self, *, url: MaybeEmpty[Any]) -> Self: def set_image(self, *, url: Optional[Any]) -> Self:
"""Sets the image for the embed content. """Sets the image for the embed content.
This function returns the class instance to allow for fluent-style This function returns the class instance to allow for fluent-style
chaining. chaining.
.. versionchanged:: 1.4
Passing :attr:`Empty` removes the image.
Parameters Parameters
----------- -----------
url: :class:`str` url: :class:`str`
The source URL for the image. Only HTTP(S) is supported. The source URL for the image. Only HTTP(S) is supported.
""" """
if url is EmptyEmbed: if url is None:
try: try:
del self._image del self._image
except AttributeError: except AttributeError:
@ -439,19 +415,19 @@ class Embed:
- ``width`` - ``width``
- ``height`` - ``height``
If the attribute has no value then :attr:`Empty` is returned. If the attribute has no value then ``None`` is returned.
""" """
# Lying to the type checker for better developer UX. # Lying to the type checker for better developer UX.
return EmbedProxy(getattr(self, '_thumbnail', {})) # type: ignore return EmbedProxy(getattr(self, '_thumbnail', {})) # type: ignore
def set_thumbnail(self, *, url: MaybeEmpty[Any]) -> Self: def set_thumbnail(self, *, url: Optional[Any]) -> Self:
"""Sets the thumbnail for the embed content. """Sets the thumbnail for the embed content.
This function returns the class instance to allow for fluent-style This function returns the class instance to allow for fluent-style
chaining. chaining.
.. versionchanged:: 1.4 .. versionchanged:: 1.4
Passing :attr:`Empty` removes the thumbnail. Passing ``None`` removes the thumbnail.
Parameters Parameters
----------- -----------
@ -459,7 +435,7 @@ class Embed:
The source URL for the thumbnail. Only HTTP(S) is supported. The source URL for the thumbnail. Only HTTP(S) is supported.
""" """
if url is EmptyEmbed: if url is None:
try: try:
del self._thumbnail del self._thumbnail
except AttributeError: except AttributeError:
@ -481,7 +457,7 @@ class Embed:
- ``height`` for the video height. - ``height`` for the video height.
- ``width`` for the video width. - ``width`` for the video width.
If the attribute has no value then :attr:`Empty` is returned. If the attribute has no value then ``None`` is returned.
""" """
# Lying to the type checker for better developer UX. # Lying to the type checker for better developer UX.
return EmbedProxy(getattr(self, '_video', {})) # type: ignore return EmbedProxy(getattr(self, '_video', {})) # type: ignore
@ -492,7 +468,7 @@ class Embed:
The only attributes that might be accessed are ``name`` and ``url``. The only attributes that might be accessed are ``name`` and ``url``.
If the attribute has no value then :attr:`Empty` is returned. If the attribute has no value then ``None`` is returned.
""" """
# Lying to the type checker for better developer UX. # Lying to the type checker for better developer UX.
return EmbedProxy(getattr(self, '_provider', {})) # type: ignore return EmbedProxy(getattr(self, '_provider', {})) # type: ignore
@ -503,12 +479,12 @@ class Embed:
See :meth:`set_author` for possible values you can access. See :meth:`set_author` for possible values you can access.
If the attribute has no value then :attr:`Empty` is returned. If the attribute has no value then ``None`` is returned.
""" """
# Lying to the type checker for better developer UX. # Lying to the type checker for better developer UX.
return EmbedProxy(getattr(self, '_author', {})) # type: ignore return EmbedProxy(getattr(self, '_author', {})) # type: ignore
def set_author(self, *, name: Any, url: MaybeEmpty[Any] = EmptyEmbed, icon_url: MaybeEmpty[Any] = EmptyEmbed) -> Self: def set_author(self, *, name: Any, url: Optional[Any] = None, icon_url: Optional[Any] = None) -> Self:
"""Sets the author for the embed content. """Sets the author for the embed content.
This function returns the class instance to allow for fluent-style This function returns the class instance to allow for fluent-style
@ -528,10 +504,10 @@ class Embed:
'name': str(name), 'name': str(name),
} }
if url is not EmptyEmbed: if url is not None:
self._author['url'] = str(url) self._author['url'] = str(url)
if icon_url is not EmptyEmbed: if icon_url is not None:
self._author['icon_url'] = str(icon_url) self._author['icon_url'] = str(icon_url)
return self return self
@ -553,11 +529,11 @@ class Embed:
@property @property
def fields(self) -> List[_EmbedFieldProxy]: def fields(self) -> List[_EmbedFieldProxy]:
"""List[Union[``EmbedProxy``, :attr:`Empty`]]: Returns a :class:`list` of ``EmbedProxy`` denoting the field contents. """List[``EmbedProxy``]: Returns a :class:`list` of ``EmbedProxy`` denoting the field contents.
See :meth:`add_field` for possible values you can access. See :meth:`add_field` for possible values you can access.
If the attribute has no value then :attr:`Empty` is returned. If the attribute has no value then ``None`` is returned.
""" """
# Lying to the type checker for better developer UX. # Lying to the type checker for better developer UX.
return [EmbedProxy(d) for d in getattr(self, '_fields', [])] # type: ignore return [EmbedProxy(d) for d in getattr(self, '_fields', [])] # type: ignore

19
docs/migrating.rst

@ -842,6 +842,25 @@ The return type of the following methods has been changed to an :term:`asynchron
The ``NoMoreItems`` exception was removed as calling :func:`anext` or :meth:`~object.__anext__` on an The ``NoMoreItems`` exception was removed as calling :func:`anext` or :meth:`~object.__anext__` on an
:term:`asynchronous iterator` will now raise :class:`StopAsyncIteration`. :term:`asynchronous iterator` will now raise :class:`StopAsyncIteration`.
Removal of ``Embed.Empty``
---------------------------
Originally, embeds used a special sentinel to denote emptiness or remove an attribute from display. The ``Embed.Empty`` sentinel was made when Discord's embed design was in a nebulous state of flux. Since then, the embed design has stabilised and thus the sentinel is seen as legacy.
Therefore, ``Embed.Empty`` has been removed in favour of ``None``.
.. code-block:: python
# Before
embed = discord.Embed(title='foo')
embed.title = discord.Embed.Empty
# After
embed = discord.Embed(title='foo')
embed.title = None
Removal of ``InvalidArgument`` Exception Removal of ``InvalidArgument`` Exception
------------------------------------------- -------------------------------------------

Loading…
Cancel
Save