From 7e51901d519ce9d73b17cdf0259bce856cc2da80 Mon Sep 17 00:00:00 2001 From: eldbud <76731410+eldbud@users.noreply.github.com> Date: Fri, 15 Oct 2021 19:03:56 +0300 Subject: [PATCH] Refactor MRO of InputMedia* and Some File-Like Classes (#2717) --- docs/source/telegram.animation.rst | 3 + docs/source/telegram.audio.rst | 3 + docs/source/telegram.document.rst | 2 + docs/source/telegram.photosize.rst | 2 + docs/source/telegram.sticker.rst | 3 + docs/source/telegram.video.rst | 3 + docs/source/telegram.videonote.rst | 3 + docs/source/telegram.voice.rst | 3 + telegram/_bot.py | 4 +- telegram/_files/_basemedium.py | 82 +++++++++ telegram/_files/_basethumbedmedium.py | 85 ++++++++++ telegram/_files/animation.py | 70 ++------ telegram/_files/audio.py | 72 ++------ telegram/_files/document.py | 68 ++------ telegram/_files/inputmedia.py | 234 +++++++++++--------------- telegram/_files/photosize.py | 36 +--- telegram/_files/sticker.py | 61 +++---- telegram/_files/venue.py | 8 +- telegram/_files/video.py | 72 ++------ telegram/_files/videonote.py | 66 ++------ telegram/_files/voice.py | 47 ++---- telegram/ext/_extbot.py | 6 +- tests/test_official.py | 2 + 23 files changed, 411 insertions(+), 524 deletions(-) create mode 100644 telegram/_files/_basemedium.py create mode 100644 telegram/_files/_basethumbedmedium.py diff --git a/docs/source/telegram.animation.rst b/docs/source/telegram.animation.rst index 65d2630e6..908e824e9 100644 --- a/docs/source/telegram.animation.rst +++ b/docs/source/telegram.animation.rst @@ -3,6 +3,9 @@ telegram.Animation ================== +.. Also lists methods of _BaseThumbedMedium, but not the ones of TelegramObject + .. autoclass:: telegram.Animation :members: :show-inheritance: + :inherited-members: TelegramObject diff --git a/docs/source/telegram.audio.rst b/docs/source/telegram.audio.rst index 9df1943ff..09065d8fe 100644 --- a/docs/source/telegram.audio.rst +++ b/docs/source/telegram.audio.rst @@ -3,6 +3,9 @@ telegram.Audio ============== +.. Also lists methods of _BaseThumbedMedium, but not the ones of TelegramObject + .. autoclass:: telegram.Audio :members: :show-inheritance: + :inherited-members: TelegramObject diff --git a/docs/source/telegram.document.rst b/docs/source/telegram.document.rst index 698247732..ac492ff48 100644 --- a/docs/source/telegram.document.rst +++ b/docs/source/telegram.document.rst @@ -2,7 +2,9 @@ telegram.Document ================= +.. Also lists methods of _BaseThumbedMedium, but not the ones of TelegramObject .. autoclass:: telegram.Document :members: :show-inheritance: + :inherited-members: TelegramObject diff --git a/docs/source/telegram.photosize.rst b/docs/source/telegram.photosize.rst index 8b9b20aee..b71eefc70 100644 --- a/docs/source/telegram.photosize.rst +++ b/docs/source/telegram.photosize.rst @@ -2,7 +2,9 @@ telegram.PhotoSize ================== +.. Also lists methods of _BaseThumbedMedium, but not the ones of TelegramObject .. autoclass:: telegram.PhotoSize :members: :show-inheritance: + :inherited-members: TelegramObject diff --git a/docs/source/telegram.sticker.rst b/docs/source/telegram.sticker.rst index d5c8f90c3..36c719398 100644 --- a/docs/source/telegram.sticker.rst +++ b/docs/source/telegram.sticker.rst @@ -3,6 +3,9 @@ telegram.Sticker ================ +.. Also lists methods of _BaseThumbedMedium, but not the ones of TelegramObject + .. autoclass:: telegram.Sticker :members: :show-inheritance: + :inherited-members: TelegramObject diff --git a/docs/source/telegram.video.rst b/docs/source/telegram.video.rst index 6030a1b76..3cea04e11 100644 --- a/docs/source/telegram.video.rst +++ b/docs/source/telegram.video.rst @@ -3,6 +3,9 @@ telegram.Video ============== +.. Also lists methods of _BaseThumbedMedium, but not the ones of TelegramObject + .. autoclass:: telegram.Video :members: :show-inheritance: + :inherited-members: TelegramObject \ No newline at end of file diff --git a/docs/source/telegram.videonote.rst b/docs/source/telegram.videonote.rst index ca0f99f53..0bf030418 100644 --- a/docs/source/telegram.videonote.rst +++ b/docs/source/telegram.videonote.rst @@ -3,6 +3,9 @@ telegram.VideoNote ================== +.. Also lists methods of _BaseThumbedMedium, but not the ones of TelegramObject + .. autoclass:: telegram.VideoNote :members: :show-inheritance: + :inherited-members: TelegramObject \ No newline at end of file diff --git a/docs/source/telegram.voice.rst b/docs/source/telegram.voice.rst index 9489eb0f6..89b92cd5e 100644 --- a/docs/source/telegram.voice.rst +++ b/docs/source/telegram.voice.rst @@ -3,6 +3,9 @@ telegram.Voice ============== +.. Also lists methods of _BaseThumbedMedium, but not the ones of TelegramObject + .. autoclass:: telegram.Voice :members: :show-inheritance: + :inherited-members: TelegramObject diff --git a/telegram/_bot.py b/telegram/_bot.py index 3cd65324d..a53e04fd8 100644 --- a/telegram/_bot.py +++ b/telegram/_bot.py @@ -223,9 +223,7 @@ class Bot(TelegramObject): for key, val in data.items(): # 1) if isinstance(val, InputMedia): - val.parse_mode = DefaultValue.get_value( # type: ignore[attr-defined] - val.parse_mode # type: ignore[attr-defined] - ) + val.parse_mode = DefaultValue.get_value(val.parse_mode) elif key == 'media' and isinstance(val, list): for media in val: media.parse_mode = DefaultValue.get_value(media.parse_mode) diff --git a/telegram/_files/_basemedium.py b/telegram/_files/_basemedium.py new file mode 100644 index 000000000..c89a4df1f --- /dev/null +++ b/telegram/_files/_basemedium.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2021 +# Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. +"""Common base class for media objects""" +from typing import TYPE_CHECKING + +from telegram import TelegramObject +from telegram._utils.defaultvalue import DEFAULT_NONE +from telegram._utils.types import JSONDict, ODVInput + +if TYPE_CHECKING: + from telegram import Bot, File + + +class _BaseMedium(TelegramObject): + """Base class for objects representing the various media file types. + Objects of this class are comparable in terms of equality. Two objects of this class are + considered equal, if their :attr:`file_unique_id` is equal. + + Args: + file_id (:obj:`str`): Identifier for this file, which can be used to download + or reuse the file. + file_unique_id (:obj:`str`): Unique identifier for this file, which + is supposed to be the same over time and for different bots. + Can't be used to download or reuse the file. + file_size (:obj:`int`, optional): File size. + bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods. + + Attributes: + file_id (:obj:`str`): File identifier. + file_unique_id (:obj:`str`): Unique identifier for this file, which + is supposed to be the same over time and for different bots. + Can't be used to download or reuse the file. + file_size (:obj:`int`): Optional. File size. + bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods. + + """ + + __slots__ = ('bot', 'file_id', 'file_size', 'file_unique_id') + + def __init__( + self, file_id: str, file_unique_id: str, file_size: int = None, bot: 'Bot' = None + ): + # Required + self.file_id: str = str(file_id) + self.file_unique_id = str(file_unique_id) + # Optionals + self.file_size = file_size + self.bot = bot + + self._id_attrs = (self.file_unique_id,) + + def get_file( + self, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None + ) -> 'File': + """Convenience wrapper over :attr:`telegram.Bot.get_file` + + For the documentation of the arguments, please see :meth:`telegram.Bot.get_file`. + + Returns: + :class:`telegram.File` + + Raises: + :class:`telegram.error.TelegramError` + + """ + return self.bot.get_file(file_id=self.file_id, timeout=timeout, api_kwargs=api_kwargs) diff --git a/telegram/_files/_basethumbedmedium.py b/telegram/_files/_basethumbedmedium.py new file mode 100644 index 000000000..671c210e1 --- /dev/null +++ b/telegram/_files/_basethumbedmedium.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2021 +# Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. +"""Common base class for media objects with thumbnails""" +from typing import TYPE_CHECKING, TypeVar, Type, Optional + +from telegram import PhotoSize +from telegram._files._basemedium import _BaseMedium +from telegram._utils.types import JSONDict + +if TYPE_CHECKING: + from telegram import Bot + +ThumbedMT = TypeVar('ThumbedMT', bound='_BaseThumbedMedium', covariant=True) + + +class _BaseThumbedMedium(_BaseMedium): + """Base class for objects representing the various media file types that may include a + thumbnail. + + Objects of this class are comparable in terms of equality. Two objects of this class are + considered equal, if their :attr:`file_unique_id` is equal. + + Args: + file_id (:obj:`str`): Identifier for this file, which can be used to download + or reuse the file. + file_unique_id (:obj:`str`): Unique identifier for this file, which + is supposed to be the same over time and for different bots. + Can't be used to download or reuse the file. + file_size (:obj:`int`, optional): File size. + thumb (:class:`telegram.PhotoSize`, optional): Thumbnail as defined by sender. + bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods. + + Attributes: + file_id (:obj:`str`): File identifier. + file_unique_id (:obj:`str`): Unique identifier for this file, which + is supposed to be the same over time and for different bots. + Can't be used to download or reuse the file. + file_size (:obj:`int`): Optional. File size. + thumb (:class:`telegram.PhotoSize`): Optional. Thumbnail as defined by sender. + bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods. + + """ + + __slots__ = ('thumb',) + + def __init__( + self, + file_id: str, + file_unique_id: str, + file_size: int = None, + thumb: PhotoSize = None, + bot: 'Bot' = None, + ): + super().__init__( + file_id=file_id, file_unique_id=file_unique_id, file_size=file_size, bot=bot + ) + self.thumb = thumb + + @classmethod + def de_json(cls: Type[ThumbedMT], data: Optional[JSONDict], bot: 'Bot') -> Optional[ThumbedMT]: + """See :meth:`telegram.TelegramObject.de_json`.""" + data = cls._parse_data(data) + + if not data: + return None + + data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot) + + return cls(bot=bot, **data) diff --git a/telegram/_files/animation.py b/telegram/_files/animation.py index 3a586c2b9..4d28af1c4 100644 --- a/telegram/_files/animation.py +++ b/telegram/_files/animation.py @@ -17,17 +17,16 @@ # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains an object that represents a Telegram Animation.""" -from typing import TYPE_CHECKING, Any, Optional +from typing import TYPE_CHECKING, Any -from telegram import PhotoSize, TelegramObject -from telegram._utils.defaultvalue import DEFAULT_NONE -from telegram._utils.types import JSONDict, ODVInput +from telegram import PhotoSize +from telegram._files._basethumbedmedium import _BaseThumbedMedium if TYPE_CHECKING: - from telegram import Bot, File + from telegram import Bot -class Animation(TelegramObject): +class Animation(_BaseThumbedMedium): """This object represents an animation file (GIF or H.264/MPEG-4 AVC video without sound). Objects of this class are comparable in terms of equality. Two objects of this class are @@ -65,18 +64,7 @@ class Animation(TelegramObject): """ - __slots__ = ( - 'bot', - 'width', - 'file_id', - 'file_size', - 'file_name', - 'thumb', - 'duration', - 'mime_type', - 'height', - 'file_unique_id', - ) + __slots__ = ('duration', 'height', 'file_name', 'mime_type', 'width') def __init__( self, @@ -92,45 +80,17 @@ class Animation(TelegramObject): bot: 'Bot' = None, **_kwargs: Any, ): + super().__init__( + file_id=file_id, + file_unique_id=file_unique_id, + file_size=file_size, + thumb=thumb, + bot=bot, + ) # Required - self.file_id = str(file_id) - self.file_unique_id = str(file_unique_id) self.width = int(width) self.height = int(height) self.duration = duration - # Optionals - self.thumb = thumb - self.file_name = file_name + # Optional self.mime_type = mime_type - self.file_size = file_size - self.bot = bot - - self._id_attrs = (self.file_unique_id,) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Animation']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot) - - return cls(bot=bot, **data) - - def get_file( - self, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None - ) -> 'File': - """Convenience wrapper over :attr:`telegram.Bot.get_file` - - For the documentation of the arguments, please see :meth:`telegram.Bot.get_file`. - - Returns: - :class:`telegram.File` - - Raises: - :class:`telegram.error.TelegramError` - - """ - return self.bot.get_file(file_id=self.file_id, timeout=timeout, api_kwargs=api_kwargs) + self.file_name = file_name diff --git a/telegram/_files/audio.py b/telegram/_files/audio.py index bfb250aea..0ed85f089 100644 --- a/telegram/_files/audio.py +++ b/telegram/_files/audio.py @@ -18,17 +18,16 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains an object that represents a Telegram Audio.""" -from typing import TYPE_CHECKING, Any, Optional +from typing import TYPE_CHECKING, Any -from telegram import PhotoSize, TelegramObject -from telegram._utils.defaultvalue import DEFAULT_NONE -from telegram._utils.types import JSONDict, ODVInput +from telegram import PhotoSize +from telegram._files._basethumbedmedium import _BaseThumbedMedium if TYPE_CHECKING: - from telegram import Bot, File + from telegram import Bot -class Audio(TelegramObject): +class Audio(_BaseThumbedMedium): """This object represents an audio file to be treated as music by the Telegram clients. Objects of this class are comparable in terms of equality. Two objects of this class are @@ -69,18 +68,7 @@ class Audio(TelegramObject): """ - __slots__ = ( - 'file_id', - 'bot', - 'file_size', - 'file_name', - 'thumb', - 'title', - 'duration', - 'performer', - 'mime_type', - 'file_unique_id', - ) + __slots__ = ('duration', 'file_name', 'mime_type', 'performer', 'title') def __init__( self, @@ -96,45 +84,17 @@ class Audio(TelegramObject): file_name: str = None, **_kwargs: Any, ): + super().__init__( + file_id=file_id, + file_unique_id=file_unique_id, + file_size=file_size, + thumb=thumb, + bot=bot, + ) # Required - self.file_id = str(file_id) - self.file_unique_id = str(file_unique_id) - self.duration = int(duration) - # Optionals + self.duration = duration + # Optional self.performer = performer self.title = title - self.file_name = file_name self.mime_type = mime_type - self.file_size = file_size - self.thumb = thumb - self.bot = bot - - self._id_attrs = (self.file_unique_id,) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Audio']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot) - - return cls(bot=bot, **data) - - def get_file( - self, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None - ) -> 'File': - """Convenience wrapper over :attr:`telegram.Bot.get_file` - - For the documentation of the arguments, please see :meth:`telegram.Bot.get_file`. - - Returns: - :class:`telegram.File` - - Raises: - :class:`telegram.error.TelegramError` - - """ - return self.bot.get_file(file_id=self.file_id, timeout=timeout, api_kwargs=api_kwargs) + self.file_name = file_name diff --git a/telegram/_files/document.py b/telegram/_files/document.py index e0f993893..276427928 100644 --- a/telegram/_files/document.py +++ b/telegram/_files/document.py @@ -18,17 +18,16 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains an object that represents a Telegram Document.""" -from typing import TYPE_CHECKING, Any, Optional +from typing import TYPE_CHECKING, Any -from telegram import PhotoSize, TelegramObject -from telegram._utils.defaultvalue import DEFAULT_NONE -from telegram._utils.types import JSONDict, ODVInput +from telegram import PhotoSize +from telegram._files._basethumbedmedium import _BaseThumbedMedium if TYPE_CHECKING: - from telegram import Bot, File + from telegram import Bot -class Document(TelegramObject): +class Document(_BaseThumbedMedium): """This object represents a general file (as opposed to photos, voice messages and audio files). @@ -60,15 +59,7 @@ class Document(TelegramObject): """ - __slots__ = ( - 'bot', - 'file_id', - 'file_size', - 'file_name', - 'thumb', - 'mime_type', - 'file_unique_id', - ) + __slots__ = ('file_name', 'mime_type') def __init__( self, @@ -81,42 +72,13 @@ class Document(TelegramObject): bot: 'Bot' = None, **_kwargs: Any, ): - # Required - self.file_id = str(file_id) - self.file_unique_id = str(file_unique_id) - # Optionals - self.thumb = thumb - self.file_name = file_name + super().__init__( + file_id=file_id, + file_unique_id=file_unique_id, + file_size=file_size, + thumb=thumb, + bot=bot, + ) + # Optional self.mime_type = mime_type - self.file_size = file_size - self.bot = bot - - self._id_attrs = (self.file_unique_id,) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Document']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot) - - return cls(bot=bot, **data) - - def get_file( - self, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None - ) -> 'File': - """Convenience wrapper over :attr:`telegram.Bot.get_file` - - For the documentation of the arguments, please see :meth:`telegram.Bot.get_file`. - - Returns: - :class:`telegram.File` - - Raises: - :class:`telegram.error.TelegramError` - - """ - return self.bot.get_file(file_id=self.file_id, timeout=timeout, api_kwargs=api_kwargs) + self.file_name = file_name diff --git a/telegram/_files/inputmedia.py b/telegram/_files/inputmedia.py index 659bc26d5..37527e59c 100644 --- a/telegram/_files/inputmedia.py +++ b/telegram/_files/inputmedia.py @@ -18,7 +18,7 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """Base class for Telegram InputMedia Objects.""" -from typing import Union, List, Tuple +from typing import Union, List, Tuple, Optional from telegram import ( Animation, @@ -34,18 +34,59 @@ from telegram._utils.defaultvalue import DEFAULT_NONE from telegram._utils.files import parse_file_input from telegram._utils.types import FileInput, JSONDict, ODVInput +MediaType = Union[Animation, Audio, Document, PhotoSize, Video] + class InputMedia(TelegramObject): - """Base class for Telegram InputMedia Objects. + """ + Base class for Telegram InputMedia Objects. - See :class:`telegram.InputMediaAnimation`, :class:`telegram.InputMediaAudio`, - :class:`telegram.InputMediaDocument`, :class:`telegram.InputMediaPhoto` and - :class:`telegram.InputMediaVideo` for detailed use. + .. versionchanged:: 14.0: + Added arguments and attributes :attr:`media_type`, :attr:`media`, :attr:`caption`, + :attr:`caption_entities`, :attr:`parse_mode`. + Args: + media_type (:obj:`str`) Type of media that the instance represents. + media (:obj:`str` | `filelike object` | :obj:`bytes` | :class:`pathlib.Path` | \ + :class:`telegram.Animation` | :class:`telegram.Audio` | \ + :class:`telegram.Document` | :class:`telegram.PhotoSize` | \ + :class:`telegram.Video`): + File to send. Pass a file_id to send a file that exists on the Telegram servers + (recommended), pass an HTTP URL for Telegram to get a file from the Internet. + Lastly you can pass an existing telegram media object of the corresponding type + to send. + caption (:obj:`str`, optional): Caption of the media to be sent, 0-1024 characters + after entities parsing. + caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special + entities that appear in the caption, which can be specified instead of parse_mode. + parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show + bold, italic, fixed-width text or inline URLs in the media caption. See the constants + in :class:`telegram.ParseMode` for the available modes. + + Attributes: + type (:obj:`str`): Type of the input media. + media (:obj:`str` | :class:`telegram.InputFile`): Media to send. + caption (:obj:`str`): Optional. Caption of the media to be sent. + parse_mode (:obj:`str`): Optional. The parse mode to use for text formatting. + caption_entities (List[:class:`telegram.MessageEntity`]): Optional. List of special + entities that appear in the caption. """ - __slots__ = () - caption_entities: Union[List[MessageEntity], Tuple[MessageEntity, ...], None] = None + __slots__ = ('caption', 'caption_entities', 'media', 'parse_mode', 'type') + + def __init__( + self, + media_type: str, + media: Union[str, InputFile, MediaType], + caption: str = None, + caption_entities: Union[List[MessageEntity], Tuple[MessageEntity, ...]] = None, + parse_mode: ODVInput[str] = DEFAULT_NONE, + ): + self.type = media_type + self.media = media + self.caption = caption + self.caption_entities = caption_entities + self.parse_mode = parse_mode def to_dict(self) -> JSONDict: """See :meth:`telegram.TelegramObject.to_dict`.""" @@ -58,6 +99,10 @@ class InputMedia(TelegramObject): return data + @staticmethod + def _parse_thumb_input(thumb: Optional[FileInput]) -> Optional[Union[str, InputFile]]: + return parse_file_input(thumb, attach=True) if thumb is not None else thumb + class InputMediaAnimation(InputMedia): """Represents an animation file (GIF or H.264/MPEG-4 AVC video without sound) to be sent. @@ -102,7 +147,7 @@ class InputMediaAnimation(InputMedia): duration (:obj:`int`, optional): Animation duration. Attributes: - type (:obj:`str`): ``animation``. + type (:obj:`str`): ``'animation'``. media (:obj:`str` | :class:`telegram.InputFile`): Animation to send. caption (:obj:`str`): Optional. Caption of the document to be sent. parse_mode (:obj:`str`): Optional. The parse mode to use for text formatting. @@ -115,17 +160,7 @@ class InputMediaAnimation(InputMedia): """ - __slots__ = ( - 'caption_entities', - 'width', - 'media', - 'thumb', - 'caption', - 'duration', - 'parse_mode', - 'height', - 'type', - ) + __slots__ = ('duration', 'height', 'thumb', 'width') def __init__( self, @@ -139,29 +174,19 @@ class InputMediaAnimation(InputMedia): caption_entities: Union[List[MessageEntity], Tuple[MessageEntity, ...]] = None, filename: str = None, ): - self.type = 'animation' - if isinstance(media, Animation): - self.media: Union[str, InputFile] = media.file_id - self.width = media.width - self.height = media.height - self.duration = media.duration + width = media.width if width is None else width + height = media.height if height is None else height + duration = media.duration if duration is None else duration + media = media.file_id else: - self.media = parse_file_input(media, attach=True, filename=filename) + media = parse_file_input(media, attach=True, filename=filename) - if thumb: - self.thumb = parse_file_input(thumb, attach=True) - - if caption: - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = caption_entities - if width: - self.width = width - if height: - self.height = height - if duration: - self.duration = duration + super().__init__('animation', media, caption, caption_entities, parse_mode) + self.thumb = self._parse_thumb_input(thumb) + self.width = width + self.height = height + self.duration = duration class InputMediaPhoto(InputMedia): @@ -190,7 +215,7 @@ class InputMediaPhoto(InputMedia): entities that appear in the caption, which can be specified instead of parse_mode. Attributes: - type (:obj:`str`): ``photo``. + type (:obj:`str`): ``'photo'``. media (:obj:`str` | :class:`telegram.InputFile`): Photo to send. caption (:obj:`str`): Optional. Caption of the document to be sent. parse_mode (:obj:`str`): Optional. The parse mode to use for text formatting. @@ -199,7 +224,7 @@ class InputMediaPhoto(InputMedia): """ - __slots__ = ('caption_entities', 'media', 'caption', 'parse_mode', 'type') + __slots__ = () def __init__( self, @@ -209,13 +234,8 @@ class InputMediaPhoto(InputMedia): caption_entities: Union[List[MessageEntity], Tuple[MessageEntity, ...]] = None, filename: str = None, ): - self.type = 'photo' - self.media = parse_file_input(media, PhotoSize, attach=True, filename=filename) - - if caption: - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = caption_entities + media = parse_file_input(media, PhotoSize, attach=True, filename=filename) + super().__init__('photo', media, caption, caption_entities, parse_mode) class InputMediaVideo(InputMedia): @@ -226,7 +246,7 @@ class InputMediaVideo(InputMedia): width, height and duration from that video, unless otherwise specified with the optional arguments. * ``thumb`` will be ignored for small video files, for which Telegram can easily - generate thumb nails. However, this behaviour is undocumented and might be changed + generate thumbnails. However, this behaviour is undocumented and might be changed by Telegram. Args: @@ -266,7 +286,7 @@ class InputMediaVideo(InputMedia): Accept :obj:`bytes` as input. Attributes: - type (:obj:`str`): ``video``. + type (:obj:`str`): ``'video'``. media (:obj:`str` | :class:`telegram.InputFile`): Video file to send. caption (:obj:`str`): Optional. Caption of the document to be sent. parse_mode (:obj:`str`): Optional. The parse mode to use for text formatting. @@ -281,18 +301,7 @@ class InputMediaVideo(InputMedia): """ - __slots__ = ( - 'caption_entities', - 'width', - 'media', - 'thumb', - 'supports_streaming', - 'caption', - 'duration', - 'parse_mode', - 'height', - 'type', - ) + __slots__ = ('duration', 'height', 'thumb', 'supports_streaming', 'width') def __init__( self, @@ -307,31 +316,21 @@ class InputMediaVideo(InputMedia): caption_entities: Union[List[MessageEntity], Tuple[MessageEntity, ...]] = None, filename: str = None, ): - self.type = 'video' if isinstance(media, Video): - self.media: Union[str, InputFile] = media.file_id - self.width = media.width - self.height = media.height - self.duration = media.duration + width = width if width is not None else media.width + height = height if height is not None else media.height + duration = duration if duration is not None else media.duration + media = media.file_id else: - self.media = parse_file_input(media, attach=True, filename=filename) + media = parse_file_input(media, attach=True, filename=filename) - if thumb: - self.thumb = parse_file_input(thumb, attach=True) - - if caption: - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = caption_entities - if width: - self.width = width - if height: - self.height = height - if duration: - self.duration = duration - if supports_streaming: - self.supports_streaming = supports_streaming + super().__init__('video', media, caption, caption_entities, parse_mode) + self.width = width + self.height = height + self.duration = duration + self.thumb = self._parse_thumb_input(thumb) + self.supports_streaming = supports_streaming class InputMediaAudio(InputMedia): @@ -379,7 +378,7 @@ class InputMediaAudio(InputMedia): Accept :obj:`bytes` as input. Attributes: - type (:obj:`str`): ``audio``. + type (:obj:`str`): ``'audio'``. media (:obj:`str` | :class:`telegram.InputFile`): Audio file to send. caption (:obj:`str`): Optional. Caption of the document to be sent. parse_mode (:obj:`str`): Optional. The parse mode to use for text formatting. @@ -393,17 +392,7 @@ class InputMediaAudio(InputMedia): """ - __slots__ = ( - 'caption_entities', - 'media', - 'thumb', - 'caption', - 'title', - 'duration', - 'type', - 'parse_mode', - 'performer', - ) + __slots__ = ('duration', 'performer', 'thumb', 'title') def __init__( self, @@ -417,29 +406,19 @@ class InputMediaAudio(InputMedia): caption_entities: Union[List[MessageEntity], Tuple[MessageEntity, ...]] = None, filename: str = None, ): - self.type = 'audio' - if isinstance(media, Audio): - self.media: Union[str, InputFile] = media.file_id - self.duration = media.duration - self.performer = media.performer - self.title = media.title + duration = media.duration if duration is None else duration + performer = media.performer if performer is None else performer + title = media.title if title is None else title + media = media.file_id else: - self.media = parse_file_input(media, attach=True, filename=filename) + media = parse_file_input(media, attach=True, filename=filename) - if thumb: - self.thumb = parse_file_input(thumb, attach=True) - - if caption: - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = caption_entities - if duration: - self.duration = duration - if performer: - self.performer = performer - if title: - self.title = title + super().__init__('audio', media, caption, caption_entities, parse_mode) + self.thumb = self._parse_thumb_input(thumb) + self.duration = duration + self.title = title + self.performer = performer class InputMediaDocument(InputMedia): @@ -480,7 +459,7 @@ class InputMediaDocument(InputMedia): the document is sent as part of an album. Attributes: - type (:obj:`str`): ``document``. + type (:obj:`str`): ``'document'``. media (:obj:`str` | :class:`telegram.InputFile`): File to send. caption (:obj:`str`): Optional. Caption of the document to be sent. parse_mode (:obj:`str`): Optional. The parse mode to use for text formatting. @@ -493,15 +472,7 @@ class InputMediaDocument(InputMedia): """ - __slots__ = ( - 'caption_entities', - 'media', - 'thumb', - 'caption', - 'parse_mode', - 'type', - 'disable_content_type_detection', - ) + __slots__ = ('disable_content_type_detection', 'thumb') def __init__( self, @@ -513,14 +484,7 @@ class InputMediaDocument(InputMedia): caption_entities: Union[List[MessageEntity], Tuple[MessageEntity, ...]] = None, filename: str = None, ): - self.type = 'document' - self.media = parse_file_input(media, Document, attach=True, filename=filename) - - if thumb: - self.thumb = parse_file_input(thumb, attach=True) - - if caption: - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = caption_entities + media = parse_file_input(media, Document, attach=True, filename=filename) + super().__init__('document', media, caption, caption_entities, parse_mode) + self.thumb = self._parse_thumb_input(thumb) self.disable_content_type_detection = disable_content_type_detection diff --git a/telegram/_files/photosize.py b/telegram/_files/photosize.py index 0875e3d0c..266a2800b 100644 --- a/telegram/_files/photosize.py +++ b/telegram/_files/photosize.py @@ -20,15 +20,13 @@ from typing import TYPE_CHECKING, Any -from telegram import TelegramObject -from telegram._utils.defaultvalue import DEFAULT_NONE -from telegram._utils.types import JSONDict, ODVInput +from telegram._files._basemedium import _BaseMedium if TYPE_CHECKING: - from telegram import Bot, File + from telegram import Bot -class PhotoSize(TelegramObject): +class PhotoSize(_BaseMedium): """This object represents one size of a photo or a file/sticker thumbnail. Objects of this class are comparable in terms of equality. Two objects of this class are @@ -58,7 +56,7 @@ class PhotoSize(TelegramObject): """ - __slots__ = ('bot', 'width', 'file_id', 'file_size', 'height', 'file_unique_id') + __slots__ = ('width', 'height') def __init__( self, @@ -70,29 +68,9 @@ class PhotoSize(TelegramObject): bot: 'Bot' = None, **_kwargs: Any, ): + super().__init__( + file_id=file_id, file_unique_id=file_unique_id, file_size=file_size, bot=bot + ) # Required - self.file_id = str(file_id) - self.file_unique_id = str(file_unique_id) self.width = int(width) self.height = int(height) - # Optionals - self.file_size = file_size - self.bot = bot - - self._id_attrs = (self.file_unique_id,) - - def get_file( - self, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None - ) -> 'File': - """Convenience wrapper over :attr:`telegram.Bot.get_file` - - For the documentation of the arguments, please see :meth:`telegram.Bot.get_file`. - - Returns: - :class:`telegram.File` - - Raises: - :class:`telegram.error.TelegramError` - - """ - return self.bot.get_file(file_id=self.file_id, timeout=timeout, api_kwargs=api_kwargs) diff --git a/telegram/_files/sticker.py b/telegram/_files/sticker.py index 76d1ac624..194478be4 100644 --- a/telegram/_files/sticker.py +++ b/telegram/_files/sticker.py @@ -21,14 +21,14 @@ from typing import TYPE_CHECKING, Any, List, Optional, ClassVar from telegram import PhotoSize, TelegramObject, constants -from telegram._utils.defaultvalue import DEFAULT_NONE -from telegram._utils.types import JSONDict, ODVInput +from telegram._files._basethumbedmedium import _BaseThumbedMedium +from telegram._utils.types import JSONDict if TYPE_CHECKING: - from telegram import Bot, File + from telegram import Bot -class Sticker(TelegramObject): +class Sticker(_BaseThumbedMedium): """This object represents a sticker. Objects of this class are comparable in terms of equality. Two objects of this class are @@ -85,18 +85,13 @@ class Sticker(TelegramObject): """ __slots__ = ( - 'bot', - 'width', - 'file_id', + 'emoji', + 'height', 'is_animated', 'is_video', - 'file_size', - 'thumb', - 'set_name', 'mask_position', - 'height', - 'file_unique_id', - 'emoji', + 'set_name', + 'width', ) def __init__( @@ -115,22 +110,22 @@ class Sticker(TelegramObject): bot: 'Bot' = None, **_kwargs: Any, ): + super().__init__( + file_id=file_id, + file_unique_id=file_unique_id, + file_size=file_size, + thumb=thumb, + bot=bot, + ) # Required - self.file_id = str(file_id) - self.file_unique_id = str(file_unique_id) self.width = int(width) self.height = int(height) self.is_animated = is_animated self.is_video = is_video - # Optionals - self.thumb = thumb + # Optional self.emoji = emoji - self.file_size = file_size self.set_name = set_name self.mask_position = mask_position - self.bot = bot - - self._id_attrs = (self.file_unique_id,) @classmethod def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Sticker']: @@ -145,22 +140,6 @@ class Sticker(TelegramObject): return cls(bot=bot, **data) - def get_file( - self, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None - ) -> 'File': - """Convenience wrapper over :attr:`telegram.Bot.get_file` - - For the documentation of the arguments, please see :meth:`telegram.Bot.get_file`. - - Returns: - :class:`telegram.File` - - Raises: - :class:`telegram.error.TelegramError` - - """ - return self.bot.get_file(file_id=self.file_id, timeout=timeout, api_kwargs=api_kwargs) - class StickerSet(TelegramObject): """This object represents a sticker set. @@ -200,13 +179,13 @@ class StickerSet(TelegramObject): """ __slots__ = ( + 'contains_masks', 'is_animated', 'is_video', - 'contains_masks', + 'name', + 'stickers', 'thumb', 'title', - 'stickers', - 'name', ) def __init__( @@ -226,7 +205,7 @@ class StickerSet(TelegramObject): self.is_video = is_video self.contains_masks = contains_masks self.stickers = stickers - # Optionals + # Optional self.thumb = thumb self._id_attrs = (self.name,) diff --git a/telegram/_files/venue.py b/telegram/_files/venue.py index 05bdf0f55..bb5703e65 100644 --- a/telegram/_files/venue.py +++ b/telegram/_files/venue.py @@ -61,13 +61,13 @@ class Venue(TelegramObject): """ __slots__ = ( - 'google_place_type', - 'location', - 'title', 'address', - 'foursquare_type', + 'location', 'foursquare_id', + 'foursquare_type', 'google_place_id', + 'google_place_type', + 'title', ) def __init__( diff --git a/telegram/_files/video.py b/telegram/_files/video.py index efab09e79..ee00f23e2 100644 --- a/telegram/_files/video.py +++ b/telegram/_files/video.py @@ -18,17 +18,16 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains an object that represents a Telegram Video.""" -from typing import TYPE_CHECKING, Any, Optional +from typing import TYPE_CHECKING, Any -from telegram import PhotoSize, TelegramObject -from telegram._utils.defaultvalue import DEFAULT_NONE -from telegram._utils.types import JSONDict, ODVInput +from telegram import PhotoSize +from telegram._files._basethumbedmedium import _BaseThumbedMedium if TYPE_CHECKING: - from telegram import Bot, File + from telegram import Bot -class Video(TelegramObject): +class Video(_BaseThumbedMedium): """This object represents a video file. Objects of this class are comparable in terms of equality. Two objects of this class are @@ -66,18 +65,7 @@ class Video(TelegramObject): """ - __slots__ = ( - 'bot', - 'width', - 'file_id', - 'file_size', - 'file_name', - 'thumb', - 'duration', - 'mime_type', - 'height', - 'file_unique_id', - ) + __slots__ = ('duration', 'file_name', 'height', 'mime_type', 'width') def __init__( self, @@ -93,45 +81,17 @@ class Video(TelegramObject): file_name: str = None, **_kwargs: Any, ): + super().__init__( + file_id=file_id, + file_unique_id=file_unique_id, + file_size=file_size, + thumb=thumb, + bot=bot, + ) # Required - self.file_id = str(file_id) - self.file_unique_id = str(file_unique_id) self.width = int(width) self.height = int(height) - self.duration = int(duration) - # Optionals - self.thumb = thumb - self.file_name = file_name + self.duration = duration + # Optional self.mime_type = mime_type - self.file_size = file_size - self.bot = bot - - self._id_attrs = (self.file_unique_id,) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Video']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot) - - return cls(bot=bot, **data) - - def get_file( - self, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None - ) -> 'File': - """Convenience wrapper over :attr:`telegram.Bot.get_file` - - For the documentation of the arguments, please see :meth:`telegram.Bot.get_file`. - - Returns: - :class:`telegram.File` - - Raises: - :class:`telegram.error.TelegramError` - - """ - return self.bot.get_file(file_id=self.file_id, timeout=timeout, api_kwargs=api_kwargs) + self.file_name = file_name diff --git a/telegram/_files/videonote.py b/telegram/_files/videonote.py index b7b1eda16..a9fe4958f 100644 --- a/telegram/_files/videonote.py +++ b/telegram/_files/videonote.py @@ -18,17 +18,16 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains an object that represents a Telegram VideoNote.""" -from typing import TYPE_CHECKING, Optional, Any +from typing import TYPE_CHECKING, Any -from telegram import PhotoSize, TelegramObject -from telegram._utils.defaultvalue import DEFAULT_NONE -from telegram._utils.types import JSONDict, ODVInput +from telegram import PhotoSize +from telegram._files._basethumbedmedium import _BaseThumbedMedium if TYPE_CHECKING: - from telegram import Bot, File + from telegram import Bot -class VideoNote(TelegramObject): +class VideoNote(_BaseThumbedMedium): """This object represents a video message (available in Telegram apps as of v.4.0). Objects of this class are comparable in terms of equality. Two objects of this class are @@ -61,15 +60,7 @@ class VideoNote(TelegramObject): """ - __slots__ = ( - 'bot', - 'length', - 'file_id', - 'file_size', - 'thumb', - 'duration', - 'file_unique_id', - ) + __slots__ = ('duration', 'length') def __init__( self, @@ -82,42 +73,13 @@ class VideoNote(TelegramObject): bot: 'Bot' = None, **_kwargs: Any, ): + super().__init__( + file_id=file_id, + file_unique_id=file_unique_id, + file_size=file_size, + thumb=thumb, + bot=bot, + ) # Required - self.file_id = str(file_id) - self.file_unique_id = str(file_unique_id) self.length = int(length) - self.duration = int(duration) - # Optionals - self.thumb = thumb - self.file_size = file_size - self.bot = bot - - self._id_attrs = (self.file_unique_id,) - - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['VideoNote']: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot) - - return cls(bot=bot, **data) - - def get_file( - self, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None - ) -> 'File': - """Convenience wrapper over :attr:`telegram.Bot.get_file` - - For the documentation of the arguments, please see :meth:`telegram.Bot.get_file`. - - Returns: - :class:`telegram.File` - - Raises: - :class:`telegram.error.TelegramError` - - """ - return self.bot.get_file(file_id=self.file_id, timeout=timeout, api_kwargs=api_kwargs) + self.duration = duration diff --git a/telegram/_files/voice.py b/telegram/_files/voice.py index d22ce7ecb..cc71aa2be 100644 --- a/telegram/_files/voice.py +++ b/telegram/_files/voice.py @@ -20,15 +20,13 @@ from typing import TYPE_CHECKING, Any -from telegram import TelegramObject -from telegram._utils.defaultvalue import DEFAULT_NONE -from telegram._utils.types import JSONDict, ODVInput +from telegram._files._basemedium import _BaseMedium if TYPE_CHECKING: - from telegram import Bot, File + from telegram import Bot -class Voice(TelegramObject): +class Voice(_BaseMedium): """This object represents a voice note. Objects of this class are comparable in terms of equality. Two objects of this class are @@ -58,14 +56,7 @@ class Voice(TelegramObject): """ - __slots__ = ( - 'bot', - 'file_id', - 'file_size', - 'duration', - 'mime_type', - 'file_unique_id', - ) + __slots__ = ('duration', 'mime_type') def __init__( self, @@ -77,29 +68,13 @@ class Voice(TelegramObject): bot: 'Bot' = None, **_kwargs: Any, ): + super().__init__( + file_id=file_id, + file_unique_id=file_unique_id, + file_size=file_size, + bot=bot, + ) # Required - self.file_id = str(file_id) - self.file_unique_id = str(file_unique_id) self.duration = int(duration) - # Optionals + # Optional self.mime_type = mime_type - self.file_size = file_size - self.bot = bot - - self._id_attrs = (self.file_unique_id,) - - def get_file( - self, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None - ) -> 'File': - """Convenience wrapper over :attr:`telegram.Bot.get_file` - - For the documentation of the arguments, please see :meth:`telegram.Bot.get_file`. - - Returns: - :class:`telegram.File` - - Raises: - :class:`telegram.error.TelegramError` - - """ - return self.bot.get_file(file_id=self.file_id, timeout=timeout, api_kwargs=api_kwargs) diff --git a/telegram/ext/_extbot.py b/telegram/ext/_extbot.py index 52bb673ef..59a0684d5 100644 --- a/telegram/ext/_extbot.py +++ b/telegram/ext/_extbot.py @@ -159,10 +159,8 @@ class ExtBot(Bot): ) # 3) - elif isinstance(val, InputMedia) and val.parse_mode is DEFAULT_NONE: # type: ignore - val.parse_mode = ( # type: ignore[attr-defined] - self.defaults.parse_mode if self.defaults else None - ) + elif isinstance(val, InputMedia) and val.parse_mode is DEFAULT_NONE: + val.parse_mode = self.defaults.parse_mode if self.defaults else None elif key == 'media' and isinstance(val, list): for media in val: if media.parse_mode is DEFAULT_NONE: diff --git a/tests/test_official.py b/tests/test_official.py index a611fea76..bfe0f852b 100644 --- a/tests/test_official.py +++ b/tests/test_official.py @@ -164,6 +164,8 @@ def check_object(h4): ignored |= {'credentials'} elif name == 'PassportElementError': ignored |= {'message', 'type', 'source'} + elif name == 'InputMedia': + ignored |= {'caption', 'caption_entities', 'media', 'media_type', 'parse_mode'} elif name.startswith('InputMedia'): ignored |= {'filename'} # Convenience parameter