Explicit local_mode Setting (#3154)

This commit is contained in:
Bibo-Joshi 2022-09-19 22:31:23 +02:00 committed by GitHub
parent aed8e68fca
commit fdfbcdf51e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 801 additions and 485 deletions

View file

@ -64,6 +64,9 @@ source_suffix = ".rst"
# The master toctree document.
master_doc = "index"
# Global substitutions
rst_prolog = (Path.cwd() / "../substitutions/global.rst").read_text(encoding="utf-8")
# -- Extension settings ------------------------------------------------
napoleon_use_admonition_for_examples = True

View file

@ -304,6 +304,8 @@
- The first name of the bot
* - :attr:`~telegram.Bot.last_name`
- The last name of the bot
* - :attr:`~telegram.Bot.local_mode`
- Whether the bot is running in local mode
* - :attr:`~telegram.Bot.username`
- The username of the bot, without leading ``@``
* - :attr:`~telegram.Bot.link`

View file

@ -0,0 +1,17 @@
.. |uploadinput| replace:: To upload a file, you can either pass a :term:`file object` (e.g. ``open("filename", "rb")``), the file contents as bytes or the path of the file (as string or :class:`pathlib.Path` object). In the latter case, the file contents will either be read as bytes or the file path will be passed to Telegram, depending on the :paramref:`~telegram.Bot.local_mode` setting.
.. |uploadinputnopath| replace:: To upload a file, you can either pass a :term:`file object` (e.g. ``open("filename", "rb")``) or the file contents as bytes. If the bot is running in :paramref:`~telegram.Bot.local_mode`, passing the path of the file (as string or :class:`pathlib.Path` object) is supported as well.
.. |fileinputbase| replace:: Pass a ``file_id`` as String to send a file that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one.
.. |fileinput| replace:: |fileinputbase| |uploadinput|
.. |fileinputnopath| replace:: |fileinputbase| |uploadinputnopath|
.. |thumbdocstringbase| replace:: Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file.
.. |thumbdocstring| replace:: |thumbdocstringbase| |uploadinput|
.. |thumbdocstringnopath| replace:: |thumbdocstringbase| |uploadinputnopath|
.. |editreplymarkup| replace:: It is currently only possible to edit messages without :attr:`telegram.Message.reply_markup` or with inline keyboards.

View file

@ -99,6 +99,7 @@ from telegram.request._requestparameter import RequestParameter
if TYPE_CHECKING:
from telegram import (
InlineQueryResult,
InputFile,
InputMediaAudio,
InputMediaDocument,
InputMediaPhoto,
@ -160,6 +161,10 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
``location``, ``filename``, ``venue``, ``contact``,
``{read, write, connect, pool}_timeout``, ``api_kwargs``. Use a named argument for those,
and notice that some positional arguments changed position as a result.
* For uploading files, file paths are now always accepted. If :paramref:`local_mode` is
:obj:`False`, the file contents will be read in binary mode and uploaded. Otherwise,
the file path will be passed in the
`file URI scheme <https://en.wikipedia.org/wiki/File_URI_scheme>`_.
Args:
token (:obj:`str`): Bot's unique authentication token.
@ -175,6 +180,14 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
:class:`telegram.request.HTTPXRequest` will be used.
private_key (:obj:`bytes`, optional): Private key for decryption of telegram passport data.
private_key_password (:obj:`bytes`, optional): Password for above private key.
local_mode (:obj:`bool`, optional): Set to :obj:`True`, if the :paramref:`base_url` is
the URI of a `Local Bot API Server <https://core.telegram.org/bots/api#using-a-local\
-bot-api-server>`_ that runs with the ``--local`` flag. Currently, the only effect of
this is that files are uploaded using their local path in the
`file URI scheme <https://en.wikipedia.org/wiki/File_URI_scheme>`_.
Defaults to :obj:`False`.
.. versionadded:: 20.0.
.. include:: inclusions/bot_methods.rst
@ -189,6 +202,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
"_request",
"_logger",
"_initialized",
"_local_mode",
)
def __init__(
@ -200,6 +214,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
get_updates_request: BaseRequest = None,
private_key: bytes = None,
private_key_password: bytes = None,
local_mode: bool = False,
):
if not token:
raise InvalidToken("You must pass the token you received from https://t.me/Botfather!")
@ -207,6 +222,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
self._base_url = base_url + self._token
self._base_file_url = base_file_url + self._token
self._local_mode = local_mode
self._bot_user: Optional[User] = None
self._private_key = None
self._logger = logging.getLogger(__name__)
@ -253,6 +269,14 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
"""
return self._base_file_url
@property
def local_mode(self) -> bool:
""":obj:`bool`: Whether this bot is running in local mode.
.. versionadded:: 20.0
"""
return self._local_mode
# Proper type hints are difficult because:
# 1. cryptography doesn't have a nice base class, so it would get lengthy
# 2. we can't import cryptography if it's not installed
@ -283,6 +307,21 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
return decorator
def _parse_file_input(
self,
file_input: Union[FileInput, "TelegramObject"],
tg_type: Type["TelegramObject"] = None,
filename: str = None,
attach: bool = False,
) -> Union[str, "InputFile", Any]:
return parse_file_input(
file_input=file_input,
tg_type=tg_type,
filename=filename,
attach=attach,
local_mode=self._local_mode,
)
def _insert_defaults(self, data: Dict[str, object]) -> None: # skipcq: PYL-R0201
"""This method is here to make ext.Defaults work. Because we need to be able to tell
e.g. `send_message(chat_id, text)` from `send_message(chat_id, text, parse_mode=None)`, the
@ -888,10 +927,6 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
) -> Message:
"""Use this method to send photos.
Note:
The photo argument can be either a file_id, an URL or a file from disk
``open(filename, 'rb')``
.. seealso:: :attr:`telegram.Message.reply_photo`, :attr:`telegram.Chat.send_photo`,
:attr:`telegram.User.send_photo`
@ -900,13 +935,15 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
of the target channel (in the format ``@channelusername``).
photo (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
:class:`telegram.PhotoSize`): Photo to send.
Pass a file_id as String to send a photo that exists on the Telegram servers
(recommended), pass an HTTP URL as a String for Telegram to get a photo from the
Internet, or upload a new photo using multipart/form-data. Lastly you can pass
an existing :class:`telegram.PhotoSize` object to send.
|fileinput|
Lastly you can pass an existing :class:`telegram.PhotoSize` object to send.
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
.. versionchanged:: 20.0
File paths as input is also accepted for bots *not* running in
:paramref:`~telegram.Bot.local_mode`.
caption (:obj:`str`, optional): Photo caption (may also be used when resending photos
by file_id), 0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH`
characters after entities parsing.
@ -961,7 +998,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
"""
data: JSONDict = {
"chat_id": chat_id,
"photo": parse_file_input(photo, PhotoSize, filename=filename),
"photo": self._parse_file_input(photo, PhotoSize, filename=filename),
"parse_mode": parse_mode,
}
@ -1021,10 +1058,6 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
For sending voice messages, use the :meth:`send_voice` method instead.
Note:
The audio argument can be either a file_id, an URL or a file from disk
``open(filename, 'rb')``
.. seealso:: :attr:`telegram.Message.reply_audio`, :attr:`telegram.Chat.send_audio`,
:attr:`telegram.User.send_audio`
@ -1033,13 +1066,15 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
of the target channel (in the format ``@channelusername``).
audio (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
:class:`telegram.Audio`): Audio file to send.
Pass a file_id as String to send an audio file that exists on the Telegram servers
(recommended), pass an HTTP URL as a String for Telegram to get an audio file from
the Internet, or upload a new one using multipart/form-data. Lastly you can pass
an existing :class:`telegram.Audio` object to send.
|fileinput|
Lastly you can pass an existing :class:`telegram.Audio` object to send.
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
.. versionchanged:: 20.0
File paths as input is also accepted for bots *not* running in
:paramref:`~telegram.Bot.local_mode`.
caption (:obj:`str`, optional): Audio caption,
0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters after
entities parsing.
@ -1067,16 +1102,16 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
:class:`ReplyKeyboardRemove` | :class:`ForceReply`, optional):
Additional interface options. An object for an inline keyboard, custom reply
keyboard, instructions to remove reply keyboard or to force a reply from the user.
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path`, optional): Thumbnail
of the file sent; can be ignored if
thumbnail generation for the file is supported server-side. The thumbnail should be
in JPEG format and less than 200 kB in size. A thumbnail's width and height should
not exceed 320. Ignored if the file is not uploaded using multipart/form-data.
Thumbnails can't be reused and can be only uploaded as a new file.
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \
optional): |thumbdocstring|
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
.. versionchanged:: 20.0
File paths as input is also accepted for bots *not* running in
:paramref:`~telegram.Bot.local_mode`.
Keyword Args:
filename (:obj:`str`, optional): Custom file name for the audio, when uploading a
new file. Convenience parameter, useful e.g. when sending files generated by the
@ -1106,7 +1141,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
"""
data: JSONDict = {
"chat_id": chat_id,
"audio": parse_file_input(audio, Audio, filename=filename),
"audio": self._parse_file_input(audio, Audio, filename=filename),
"parse_mode": parse_mode,
}
@ -1122,7 +1157,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
if caption_entities:
data["caption_entities"] = caption_entities
if thumb:
data["thumb"] = parse_file_input(thumb, attach=True)
data["thumb"] = self._parse_file_input(thumb, attach=True)
return await self._send_message( # type: ignore[return-value]
"sendAudio",
@ -1169,12 +1204,6 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
:tg-const:`telegram.constants.FileSizeLimit.FILESIZE_UPLOAD` in size, this limit may be
changed in the future.
Note:
* The document argument can be either a file_id, an URL or a file from disk
``open(filename, 'rb')``.
* Sending by URL will currently only work ``GIF``, ``PDF`` & ``ZIP`` files.
.. seealso:: :attr:`telegram.Message.reply_document`, :attr:`telegram.Chat.send_document`,
:attr:`telegram.User.send_document`
@ -1183,13 +1212,18 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
of the target channel (in the format ``@channelusername``).
document (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
:class:`telegram.Document`): File to send.
Pass a file_id as String to send a file that exists on the Telegram servers
(recommended), pass an HTTP URL as a String for Telegram to get a file from the
Internet, or upload a new one using multipart/form-data. Lastly you can pass
an existing :class:`telegram.Document` object to send.
|fileinput|
Lastly you can pass an existing :class:`telegram.Document` object to send.
Note:
Sending by URL will currently only work ``GIF``, ``PDF`` & ``ZIP`` files.
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
.. versionchanged:: 20.0
File paths as input is also accepted for bots *not* running in
:paramref:`~telegram.Bot.local_mode`.
caption (:obj:`str`, optional): Document caption (may also be used when resending
documents by file_id), 0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH`
characters after entities parsing.
@ -1216,16 +1250,16 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
:class:`ReplyKeyboardRemove` | :class:`ForceReply`, optional):
Additional interface options. An object for an inline keyboard, custom reply
keyboard, instructions to remove reply keyboard or to force a reply from the user.
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path`, optional): Thumbnail
of the file sent; can be ignored if
thumbnail generation for the file is supported server-side. The thumbnail should be
in JPEG format and less than 200 kB in size. A thumbnail's width and height should
not exceed 320. Ignored if the file is not uploaded using multipart/form-data.
Thumbnails can't be reused and can be only uploaded as a new file.
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \
optional): |thumbdocstring|
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
.. versionchanged:: 20.0
File paths as input is also accepted for bots *not* running in
:paramref:`~telegram.Bot.local_mode`.
Keyword Args:
filename (:obj:`str`, optional): Custom file name for the document, when uploading a
new file. Convenience parameter, useful e.g. when sending files generated by the
@ -1253,7 +1287,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
"""
data: JSONDict = {
"chat_id": chat_id,
"document": parse_file_input(document, Document, filename=filename),
"document": self._parse_file_input(document, Document, filename=filename),
"parse_mode": parse_mode,
}
@ -1265,7 +1299,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
if disable_content_type_detection is not None:
data["disable_content_type_detection"] = disable_content_type_detection
if thumb:
data["thumb"] = parse_file_input(thumb, attach=True)
data["thumb"] = self._parse_file_input(thumb, attach=True)
return await self._send_message( # type: ignore[return-value]
"sendDocument",
@ -1302,10 +1336,6 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
"""
Use this method to send static ``.WEBP``, animated ``.TGS``, or video ``.WEBM`` stickers.
Note:
The :paramref:`sticker` argument can be either a file_id, an URL or a file from disk
``open(filename, 'rb')``
.. seealso:: :attr:`telegram.Message.reply_sticker`, :attr:`telegram.Chat.send_sticker`,
:attr:`telegram.User.send_sticker`
@ -1314,13 +1344,15 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
of the target channel (in the format ``@channelusername``).
sticker (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
:class:`telegram.Sticker`): Sticker to send.
Pass a file_id as String to send a file that exists on the Telegram servers
(recommended), pass an HTTP URL as a String for Telegram to get a .webp file from
the Internet, or upload a new one using multipart/form-data. Lastly you can pass
an existing :class:`telegram.Sticker` object to send.
|fileinput|
Lastly you can pass an existing :class:`telegram.Sticker` object to send.
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
.. versionchanged:: 20.0
File paths as input is also accepted for bots *not* running in
:paramref:`~telegram.Bot.local_mode`.
disable_notification (:obj:`bool`, optional): Sends the message silently. Users will
receive a notification with no sound.
protect_content (:obj:`bool`, optional): Protects the contents of the sent message from
@ -1359,7 +1391,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
:class:`telegram.error.TelegramError`
"""
data: JSONDict = {"chat_id": chat_id, "sticker": parse_file_input(sticker, Sticker)}
data: JSONDict = {"chat_id": chat_id, "sticker": self._parse_file_input(sticker, Sticker)}
return await self._send_message( # type: ignore[return-value]
"sendSticker",
data,
@ -1410,11 +1442,9 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
changed in the future.
Note:
* The :paramref:`video` argument can be either a file_id, an URL or a file from disk
``open(filename, 'rb')``
* :paramref:`thumb` will be ignored for small video files, for which Telegram can
easily generate thumbnails. However, this behaviour is undocumented and might be
changed by Telegram.
:paramref:`thumb` will be ignored for small video files, for which Telegram can
easily generate thumbnails. However, this behaviour is undocumented and might be
changed by Telegram.
.. seealso:: :attr:`telegram.Message.reply_video`, :attr:`telegram.Chat.send_video`,
:attr:`telegram.User.send_video`
@ -1424,13 +1454,15 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
of the target channel (in the format ``@channelusername``).
video (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
:class:`telegram.Video`): Video file to send.
Pass a file_id as String to send an video file that exists on the Telegram servers
(recommended), pass an HTTP URL as a String for Telegram to get an video file from
the Internet, or upload a new one using multipart/form-data. Lastly you can pass
an existing :class:`telegram.Video` object to send.
|fileinput|
Lastly you can pass an existing :class:`telegram.Video` object to send.
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
.. versionchanged:: 20.0
File paths as input is also accepted for bots *not* running in
:paramref:`~telegram.Bot.local_mode`.
duration (:obj:`int`, optional): Duration of sent video in seconds.
width (:obj:`int`, optional): Video width.
height (:obj:`int`, optional): Video height.
@ -1460,16 +1492,16 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
:class:`ReplyKeyboardRemove` | :class:`ForceReply`, optional):
Additional interface options. An object for an inline keyboard, custom reply
keyboard, instructions to remove reply keyboard or to force a reply from the user.
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path`, optional): Thumbnail
of the file sent; can be ignored if
thumbnail generation for the file is supported server-side. The thumbnail should be
in JPEG format and less than 200 kB in size. A thumbnail's width and height should
not exceed 320. Ignored if the file is not uploaded using multipart/form-data.
Thumbnails can't be reused and can be only uploaded as a new file.
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \
optional): |thumbdocstring|
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
.. versionchanged:: 20.0
File paths as input is also accepted for bots *not* running in
:paramref:`~telegram.Bot.local_mode`.
Keyword Args:
filename (:obj:`str`, optional): Custom file name for the video, when uploading a
new file. Convenience parameter, useful e.g. when sending files generated by the
@ -1499,7 +1531,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
"""
data: JSONDict = {
"chat_id": chat_id,
"video": parse_file_input(video, Video, filename=filename),
"video": self._parse_file_input(video, Video, filename=filename),
"parse_mode": parse_mode,
}
@ -1516,7 +1548,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
if height:
data["height"] = height
if thumb:
data["thumb"] = parse_file_input(thumb, attach=True)
data["thumb"] = self._parse_file_input(thumb, attach=True)
return await self._send_message( # type: ignore[return-value]
"sendVideo",
@ -1559,11 +1591,9 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
Use this method to send video messages.
Note:
* The :paramref:`video_note` argument can be either a file_id or a file from disk
``open(filename, 'rb')``
* :paramref:`thumb` will be ignored for small video files, for which Telegram can
easily generate thumbnails. However, this behaviour is undocumented and might be
changed by Telegram.
:paramref:`thumb` will be ignored for small video files, for which Telegram can
easily generate thumbnails. However, this behaviour is undocumented and might be
changed by Telegram.
.. seealso:: :attr:`telegram.Message.reply_video_note`,
:attr:`telegram.Chat.send_video_note`,
@ -1573,14 +1603,19 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target channel (in the format ``@channelusername``).
video_note (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
:class:`telegram.VideoNote`): Video note
to send. Pass a file_id as String to send a video note that exists on the Telegram
servers (recommended) or upload a new video using multipart/form-data. Or you can
pass an existing :class:`telegram.VideoNote` object to send. Sending video notes by
a URL is currently unsupported.
:class:`telegram.VideoNote`): Video note to send.
Pass a file_id as String to send a video note that exists on the Telegram
servers (recommended) or upload a new video using multipart/form-data.
|uploadinput|
Lastly you can pass an existing :class:`telegram.VideoNote` object to send.
Sending video notes by a URL is currently unsupported.
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
.. versionchanged:: 20.0
File paths as input is also accepted for bots *not* running in
:paramref:`~telegram.Bot.local_mode`.
duration (:obj:`int`, optional): Duration of sent video in seconds.
length (:obj:`int`, optional): Video width and height, i.e. diameter of the video
message.
@ -1599,16 +1634,16 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
:class:`ReplyKeyboardRemove` | :class:`ForceReply`, optional):
Additional interface options. An object for an inline keyboard, custom reply
keyboard, instructions to remove reply keyboard or to force a reply from the user.
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path`, optional): Thumbnail
of the file sent; can be ignored if
thumbnail generation for the file is supported server-side. The thumbnail should be
in JPEG format and less than 200 kB in size. A thumbnail's width and height should
not exceed 320. Ignored if the file is not uploaded using multipart/form-data.
Thumbnails can't be reused and can be only uploaded as a new file.
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \
optional): |thumbdocstring|
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
.. versionchanged:: 20.0
File paths as input is also accepted for bots *not* running in
:paramref:`~telegram.Bot.local_mode`.
Keyword Args:
filename (:obj:`str`, optional): Custom file name for the video note, when uploading a
new file. Convenience parameter, useful e.g. when sending files generated by the
@ -1638,7 +1673,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
"""
data: JSONDict = {
"chat_id": chat_id,
"video_note": parse_file_input(video_note, VideoNote, filename=filename),
"video_note": self._parse_file_input(video_note, VideoNote, filename=filename),
}
if duration is not None:
@ -1646,7 +1681,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
if length is not None:
data["length"] = length
if thumb:
data["thumb"] = parse_file_input(thumb, attach=True)
data["thumb"] = self._parse_file_input(thumb, attach=True)
return await self._send_message( # type: ignore[return-value]
"sendVideoNote",
@ -1696,7 +1731,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
Note:
:paramref:`thumb` will be ignored for small 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.
.. seealso:: :attr:`telegram.Message.reply_animation`,
@ -1707,10 +1742,8 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target channel (in the format ``@channelusername``).
animation (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
:class:`telegram.Animation`): Animation to
send. Pass a file_id as String to send an animation that exists on the Telegram
servers (recommended), pass an HTTP URL as a String for Telegram to get an
animation from the Internet, or upload a new animation using multipart/form-data.
:class:`telegram.Animation`): Animation to send.
|fileinput|
Lastly you can pass an existing :class:`telegram.Animation` object to send.
.. versionchanged:: 13.2
@ -1718,15 +1751,16 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
duration (:obj:`int`, optional): Duration of sent animation in seconds.
width (:obj:`int`, optional): Animation width.
height (:obj:`int`, optional): Animation height.
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path`, optional): Thumbnail
of the file sent; can be ignored if
thumbnail generation for the file is supported server-side. The thumbnail should be
in JPEG format and less than 200 kB in size. A thumbnail's width and height should
not exceed 320. Ignored if the file is not uploaded using multipart/form-data.
Thumbnails can't be reused and can be only uploaded as a new file.
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \
optional): |thumbdocstring|
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
.. versionchanged:: 20.0
File paths as input is also accepted for bots *not* running in
:paramref:`~telegram.Bot.local_mode`.
caption (:obj:`str`, optional): Animation caption (may also be used when resending
animations by file_id),
0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters after
@ -1782,7 +1816,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
"""
data: JSONDict = {
"chat_id": chat_id,
"animation": parse_file_input(animation, Animation, filename=filename),
"animation": self._parse_file_input(animation, Animation, filename=filename),
"parse_mode": parse_mode,
}
@ -1793,7 +1827,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
if height:
data["height"] = height
if thumb:
data["thumb"] = parse_file_input(thumb, attach=True)
data["thumb"] = self._parse_file_input(thumb, attach=True)
if caption:
data["caption"] = caption
if caption_entities:
@ -1844,11 +1878,8 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
in size, this limit may be changed in the future.
Note:
* The :paramref:`voice` argument can be either a file_id, an URL or a file from disk
``open(filename, 'rb')``.
* To use this method, the file must have the type :mimetype:`audio/ogg` and be no more
than ``1MB`` in size. ``1-20MB`` voice notes will be sent as files.
To use this method, the file must have the type :mimetype:`audio/ogg` and be no more
than ``1MB`` in size. ``1-20MB`` voice notes will be sent as files.
.. seealso:: :attr:`telegram.Message.reply_voice`, :attr:`telegram.Chat.send_voice`,
:attr:`telegram.User.send_voice`
@ -1858,13 +1889,15 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
of the target channel (in the format ``@channelusername``).
voice (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
:class:`telegram.Voice`): Voice file to send.
Pass a file_id as String to send an voice file that exists on the Telegram servers
(recommended), pass an HTTP URL as a String for Telegram to get an voice file from
the Internet, or upload a new one using multipart/form-data. Lastly you can pass
an existing :class:`telegram.Voice` object to send.
|fileinput|
Lastly you can pass an existing :class:`telegram.Voice` object to send.
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
.. versionchanged:: 20.0
File paths as input is also accepted for bots *not* running in
:paramref:`~telegram.Bot.local_mode`.
caption (:obj:`str`, optional): Voice message caption,
0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters after
entities parsing.
@ -1920,7 +1953,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
"""
data: JSONDict = {
"chat_id": chat_id,
"voice": parse_file_input(voice, Voice, filename=filename),
"voice": self._parse_file_input(voice, Voice, filename=filename),
"parse_mode": parse_mode,
}
@ -3502,8 +3535,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
Use this method to edit text and game messages.
Note:
It is currently only possible to edit messages without
:attr:`telegram.Message.reply_markup` or with inline keyboards.
|editreplymarkup|.
.. seealso:: :attr:`telegram.Message.edit_text`,
:attr:`telegram.CallbackQuery.edit_message_text`
@ -3601,8 +3633,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
Use this method to edit captions of messages.
Note:
It is currently only possible to edit messages without
:attr:`telegram.Message.reply_markup` or with inline keyboards
|editreplymarkup|
.. seealso:: :attr:`telegram.Message.edit_caption`,
:attr:`telegram.CallbackQuery.edit_message_caption`
@ -3698,8 +3729,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
:attr:`~telegram.File.file_id` or specify a URL.
Note:
It is currently only possible to edit messages without
:attr:`telegram.Message.reply_markup` or with inline keyboards
|editreplymarkup|
.. seealso:: :attr:`telegram.Message.edit_media`,
:attr:`telegram.CallbackQuery.edit_message_media`
@ -3779,8 +3809,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
(for inline bots).
Note:
It is currently only possible to edit messages without
:attr:`telegram.Message.reply_markup` or with inline keyboards
|editreplymarkup|
.. seealso:: :attr:`telegram.Message.edit_reply_markup`,
:attr:`telegram.CallbackQuery.edit_message_reply_markup`
@ -3856,6 +3885,12 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
) -> List[Update]:
"""Use this method to receive incoming updates using long polling.
Note:
1. This method will not work if an outgoing webhook is set up.
2. In order to avoid getting duplicate updates, recalculate offset after each
server response.
3. To take full advantage of this library take a look at :class:`telegram.ext.Updater`
Args:
offset (:obj:`int`, optional): Identifier of the first update to be returned. Must be
greater by one than the highest among the identifiers of previously received
@ -3895,12 +3930,6 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
Telegram API.
Note:
1. This method will not work if an outgoing webhook is set up.
2. In order to avoid getting duplicate updates, recalculate offset after each
server response.
3. To take full advantage of this library take a look at :class:`telegram.ext.Updater`
Returns:
List[:class:`telegram.Update`]
@ -3970,15 +3999,25 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
``X-Telegram-Bot-Api-Secret-Token`` with the secret token as content.
Note:
The certificate argument should be a file from disk ``open(filename, 'rb')``.
1. You will not be able to receive updates using :meth:`get_updates` for long as an
outgoing webhook is set up.
2. To use a self-signed certificate, you need to upload your public key certificate
using :paramref:`certificate` parameter. Please upload as
:class:`~telegram.InputFile`, sending a String will not work.
3. Ports currently supported for Webhooks:
:attr:`telegram.constants.SUPPORTED_WEBHOOK_PORTS`.
If you're having any trouble setting up webhooks, please check out this `guide to
Webhooks`_.
Args:
url (:obj:`str`): HTTPS url to send updates to. Use an empty string to remove webhook
integration.
certificate (:term:`file object`): Upload your public key certificate so that the root
certificate in use can be checked. See our self-signed guide for details.
(https://github.com/python-telegram-bot/python-telegram-bot/wiki/Webhooks#\
creating-a-self-signed-certificate-using-openssl)
certificate (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`):
Upload your public key certificate so that the root
certificate in use can be checked. See our `self-signed guide <https://github.com/\
python-telegram-bot/python-telegram-bot/wiki/Webhooks#creating-a-self-signed-\
certificate-using-openssl>`_ for details. |uploadinputnopath|
ip_address (:obj:`str`, optional): The fixed IP address which will be used to send
webhook requests instead of the IP address resolved through DNS.
max_connections (:obj:`int`, optional): Maximum allowed number of simultaneous HTTPS
@ -4021,18 +4060,6 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
Telegram API.
Note:
1. You will not be able to receive updates using :meth:`get_updates` for long as an
outgoing webhook is set up.
2. To use a self-signed certificate, you need to upload your public key certificate
using certificate parameter. Please upload as InputFile, sending a String will not
work.
3. Ports currently supported for Webhooks:
:attr:`telegram.constants.SUPPORTED_WEBHOOK_PORTS`.
If you're having any trouble setting up webhooks, please check out this `guide to
Webhooks`_.
Returns:
:obj:`bool` On success, :obj:`True` is returned.
@ -4045,7 +4072,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
data: JSONDict = {"url": url}
if certificate:
data["certificate"] = parse_file_input(certificate)
data["certificate"] = self._parse_file_input(certificate)
if max_connections is not None:
data["max_connections"] = max_connections
if allowed_updates is not None:
@ -5492,6 +5519,13 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
link is revoked. The bot must be an administrator in the chat for this to work and must
have the appropriate admin rights.
Note:
Each administrator in a chat generates their own invite links. Bots can't use invite
links generated by other administrators. If you want your bot to work with invite
links, it will need to generate its own link using :meth:`export_chat_invite_link` or
by calling the :meth:`get_chat` method. If your bot needs to generate a new primary
invite link replacing its previous one, use :attr:`export_chat_invite_link` again.
.. seealso:: :attr:`telegram.Chat.export_invite_link`
Args:
@ -5514,13 +5548,6 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
Telegram API.
Note:
Each administrator in a chat generates their own invite links. Bots can't use invite
links generated by other administrators. If you want your bot to work with invite
links, it will need to generate its own link using :meth:`export_chat_invite_link` or
by calling the :meth:`get_chat` method. If your bot needs to generate a new primary
invite link replacing its previous one, use :attr:`export_chat_invite_link` again.
Returns:
:obj:`str`: New invite link on success.
@ -5955,10 +5982,15 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target channel (in the format ``@channelusername``).
photo (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path`): New chat photo.
|uploadinput|
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
.. versionchanged:: 20.0
File paths as input is also accepted for bots *not* running in
:paramref:`~telegram.Bot.local_mode`.
Keyword Args:
read_timeout (:obj:`float` | :obj:`None`, optional): Value to pass to
:paramref:`telegram.request.BaseRequest.post.read_timeout`. Defaults to
@ -5982,7 +6014,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
:class:`telegram.error.TelegramError`
"""
data: JSONDict = {"chat_id": chat_id, "photo": parse_file_input(photo)}
data: JSONDict = {"chat_id": chat_id, "photo": self._parse_file_input(photo)}
result = await self._post(
"setChatPhoto",
data,
@ -6488,19 +6520,20 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
:meth:`create_new_sticker_set` and :meth:`add_sticker_to_set` methods (can be used multiple
times).
Note:
The :paramref:`png_sticker` argument can be either a file_id, an URL or a file from
disk ``open(filename, 'rb')``
Args:
user_id (:obj:`int`): User identifier of sticker file owner.
png_sticker (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path`):
**PNG** image with the sticker, must be up to 512 kilobytes in size,
dimensions must not exceed 512px, and either width or height must be exactly 512px.
|uploadinput|
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
.. versionchanged:: 20.0
File paths as input is also accepted for bots *not* running in
:paramref:`~telegram.Bot.local_mode`.
Keyword Args:
read_timeout (:obj:`float` | :obj:`None`, optional): Value to pass to
:paramref:`telegram.request.BaseRequest.post.read_timeout`. Defaults to
@ -6524,7 +6557,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
:class:`telegram.error.TelegramError`
"""
data: JSONDict = {"user_id": user_id, "png_sticker": parse_file_input(png_sticker)}
data: JSONDict = {"user_id": user_id, "png_sticker": self._parse_file_input(png_sticker)}
result = await self._post(
"uploadStickerFile",
data,
@ -6566,10 +6599,6 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
of the arguments had to be changed. Use keyword arguments to make sure that the
arguments are passed correctly.
Note:
The :paramref:`png_sticker` and :paramref:`tgs_sticker` argument can be either a
file_id, an URL or a file from disk ``open(filename, 'rb')``
.. versionchanged:: 20.0
The parameter ``contains_masks`` has been removed. Use :paramref:`sticker_type`
instead.
@ -6585,27 +6614,37 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
png_sticker (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path`, \
optional): **PNG** image with the sticker,
must be up to 512 kilobytes in size, dimensions must not exceed 512px,
and either width or height must be exactly 512px. Pass a file_id as a String to
send a file that already exists on the Telegram servers, pass an HTTP URL as a
String for Telegram to get a file from the Internet, or upload a new one
using multipart/form-data.
and either width or height must be exactly 512px.
|fileinput|
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
.. versionchanged:: 20.0
File paths as input is also accepted for bots *not* running in
:paramref:`~telegram.Bot.local_mode`.
tgs_sticker (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path`, \
optional): **TGS** animation with the sticker, uploaded using multipart/form-data.
optional): **TGS** animation with the sticker. |uploadinput|
See https://core.telegram.org/stickers#animated-sticker-requirements for technical
requirements.
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
.. versionchanged:: 20.0
File paths as input is also accepted for bots *not* running in
:paramref:`~telegram.Bot.local_mode`.
webm_sticker (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path`,\
optional): **WEBM** video with the sticker, uploaded using multipart/form-data.
optional): **WEBM** video with the sticker. |uploadinput|
See https://core.telegram.org/stickers#video-sticker-requirements for
technical requirements.
.. versionadded:: 13.11
.. versionchanged:: 20.0
File paths as input is also accepted for bots *not* running in
:paramref:`~telegram.Bot.local_mode`.
emojis (:obj:`str`): One or more emoji corresponding to the sticker.
mask_position (:class:`telegram.MaskPosition`, optional): Position where the mask
should be placed on faces.
@ -6642,11 +6681,11 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
data: JSONDict = {"user_id": user_id, "name": name, "title": title, "emojis": emojis}
if png_sticker is not None:
data["png_sticker"] = parse_file_input(png_sticker)
data["png_sticker"] = self._parse_file_input(png_sticker)
if tgs_sticker is not None:
data["tgs_sticker"] = parse_file_input(tgs_sticker)
data["tgs_sticker"] = self._parse_file_input(tgs_sticker)
if webm_sticker is not None:
data["webm_sticker"] = parse_file_input(webm_sticker)
data["webm_sticker"] = self._parse_file_input(webm_sticker)
if mask_position is not None:
data["mask_position"] = mask_position
if sticker_type is not None:
@ -6693,10 +6732,6 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
of the arguments had to be changed. Use keyword arguments to make sure that the
arguments are passed correctly.
Note:
The :paramref:`png_sticker` and :paramref:`tgs_sticker` argument can be either a
file_id, an URL or a file from disk ``open(filename, 'rb')``
Args:
user_id (:obj:`int`): User identifier of created sticker set owner.
@ -6704,26 +6739,36 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
png_sticker (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path`, \
optional): **PNG** image with the sticker,
must be up to 512 kilobytes in size, dimensions must not exceed 512px,
and either width or height must be exactly 512px. Pass a file_id as a String to
send a file that already exists on the Telegram servers, pass an HTTP URL as a
String for Telegram to get a file from the Internet, or upload a new one
using multipart/form-data.
and either width or height must be exactly 512px.
|fileinput|
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
.. versionchanged:: 20.0
File paths as input is also accepted for bots *not* running in
:paramref:`~telegram.Bot.local_mode`.
tgs_sticker (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path`, \
optional): **TGS** animation with the sticker, uploaded using multipart/form-data.
optional): **TGS** animation with the sticker. |uploadinput|
See https://core.telegram.org/stickers#animated-sticker-requirements for technical
requirements.
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
.. versionchanged:: 20.0
File paths as input is also accepted for bots *not* running in
:paramref:`~telegram.Bot.local_mode`.
webm_sticker (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path`,\
optional): **WEBM** video with the sticker, uploaded using multipart/form-data.
optional): **WEBM** video with the sticker. |uploadinput|
See https://core.telegram.org/stickers#video-sticker-requirements for
technical requirements.
.. versionadded:: 13.11
.. versionchanged:: 20.0
File paths as input is also accepted for bots *not* running in
:paramref:`~telegram.Bot.local_mode`.
emojis (:obj:`str`): One or more emoji corresponding to the sticker.
mask_position (:class:`telegram.MaskPosition`, optional): Position where the mask
should be placed on faces.
@ -6754,11 +6799,11 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
data: JSONDict = {"user_id": user_id, "name": name, "emojis": emojis}
if png_sticker is not None:
data["png_sticker"] = parse_file_input(png_sticker)
data["png_sticker"] = self._parse_file_input(png_sticker)
if tgs_sticker is not None:
data["tgs_sticker"] = parse_file_input(tgs_sticker)
data["tgs_sticker"] = self._parse_file_input(tgs_sticker)
if webm_sticker is not None:
data["webm_sticker"] = parse_file_input(webm_sticker)
data["webm_sticker"] = self._parse_file_input(webm_sticker)
if mask_position is not None:
data["mask_position"] = mask_position
@ -6895,10 +6940,6 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
for animated sticker sets only. Video thumbnails can be set only for video sticker sets
only.
Note:
The :paramref:`thumb` can be either a file_id, an URL or a file from disk
``open(filename, 'rb')``
Args:
name (:obj:`str`): Sticker set name
user_id (:obj:`int`): User identifier of created sticker set owner.
@ -6910,9 +6951,8 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
sticker technical requirements, or a **WEBM** video with the thumbnail up to 32
kilobytes in size; see
https://core.telegram.org/stickers#video-sticker-requirements for video sticker
technical requirements. Pass a file_id as a String to send a file that
already exists on the Telegram servers, pass an HTTP URL as a String for Telegram
to get a file from the Internet, or upload a new one using multipart/form-data.
technical requirements.
|fileinput|
Animated sticker set thumbnails can't be uploaded via HTTP URL.
.. versionchanged:: 13.2
@ -6943,7 +6983,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
"""
data: JSONDict = {"name": name, "user_id": user_id}
if thumb is not None:
data["thumb"] = parse_file_input(thumb)
data["thumb"] = self._parse_file_input(thumb)
result = await self._post(
"setStickerSetThumb",

View file

@ -20,10 +20,10 @@
import logging
import mimetypes
from pathlib import Path
from typing import IO, Optional, Union
from uuid import uuid4
from telegram._utils.files import load_file
from telegram._utils.types import FieldTuple
_DEFAULT_MIME_TYPE = "application/octet-stream"
@ -75,15 +75,10 @@ class InputFile:
elif isinstance(obj, str):
self.input_file_content = obj.encode("utf-8")
else:
self.input_file_content = obj.read()
self.attach_name: Optional[str] = "attached" + uuid4().hex if attach else None
reported_filename, self.input_file_content = load_file(obj)
filename = filename or reported_filename
if (
not filename
and hasattr(obj, "name")
and not isinstance(obj.name, int) # type: ignore[union-attr]
):
filename = Path(obj.name).name # type: ignore[union-attr]
self.attach_name: Optional[str] = "attached" + uuid4().hex if attach else None
if filename:
self.mimetype = mimetypes.guess_type(filename, strict=False)[0] or _DEFAULT_MIME_TYPE

View file

@ -48,9 +48,8 @@ class InputMedia(TelegramObject):
media (:obj:`str` | :term:`file 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.
:class:`telegram.Video`): File to send.
|fileinputnopath|
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,
@ -99,7 +98,11 @@ class InputMedia(TelegramObject):
@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
# We use local_mode=True because we don't have access to the actual setting and want
# things to work in local mode.
return (
parse_file_input(thumb, attach=True, local_mode=True) if thumb is not None else thumb
)
class InputMediaAnimation(InputMedia):
@ -112,10 +115,8 @@ class InputMediaAnimation(InputMedia):
Args:
media (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
:class:`telegram.Animation`): 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
:class:`telegram.Animation` object to send.
:class:`telegram.Animation`): File to send. |fileinputnopath|
Lastly you can pass an existing :class:`telegram.Animation` object to send.
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
@ -123,13 +124,9 @@ class InputMediaAnimation(InputMedia):
new file. Convenience parameter, useful e.g. when sending files generated by the
:obj:`tempfile` module.
.. versionadded:: 13.1
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path`, optional): Thumbnail of
the file sent; can be ignored if
thumbnail generation for the file is supported server-side. The thumbnail should be
in JPEG format and less than ``200`` kB in size. A thumbnail's width and height should
not exceed ``320``. Ignored if the file is not uploaded using multipart/form-data.
Thumbnails can't be reused and can be only uploaded as a new file.
.. versionadded:: 13.1
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \
optional): |thumbdocstringnopath|
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
@ -180,7 +177,9 @@ class InputMediaAnimation(InputMedia):
duration = media.duration if duration is None else duration
media = media.file_id
else:
media = parse_file_input(media, filename=filename, attach=True)
# We use local_mode=True because we don't have access to the actual setting and want
# things to work in local mode.
media = parse_file_input(media, filename=filename, attach=True, local_mode=True)
super().__init__(InputMediaType.ANIMATION, media, caption, caption_entities, parse_mode)
self.thumb = self._parse_thumb_input(thumb)
@ -194,10 +193,8 @@ class InputMediaPhoto(InputMedia):
Args:
media (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
:class:`telegram.PhotoSize`): 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
:class:`telegram.PhotoSize` object to send.
:class:`telegram.PhotoSize`): File to send. |fileinputnopath|
Lastly you can pass an existing :class:`telegram.PhotoSize` object to send.
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
@ -205,7 +202,7 @@ class InputMediaPhoto(InputMedia):
new file. Convenience parameter, useful e.g. when sending files generated by the
:obj:`tempfile` module.
.. versionadded:: 13.1
.. versionadded:: 13.1
caption (:obj:`str`, optional ): Caption of the photo to be sent,
0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters after
entities parsing.
@ -236,7 +233,9 @@ class InputMediaPhoto(InputMedia):
caption_entities: Union[List[MessageEntity], Tuple[MessageEntity, ...]] = None,
filename: str = None,
):
media = parse_file_input(media, PhotoSize, filename=filename, attach=True)
# We use local_mode=True because we don't have access to the actual setting and want
# things to work in local mode.
media = parse_file_input(media, PhotoSize, filename=filename, attach=True, local_mode=True)
super().__init__(InputMediaType.PHOTO, media, caption, caption_entities, parse_mode)
@ -253,10 +252,8 @@ class InputMediaVideo(InputMedia):
Args:
media (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
: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
:class:`telegram.Video` object to send.
:class:`telegram.Video`): File to send. |fileinputnopath|
Lastly you can pass an existing :class:`telegram.Video` object to send.
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
@ -264,7 +261,7 @@ class InputMediaVideo(InputMedia):
new file. Convenience parameter, useful e.g. when sending files generated by the
:obj:`tempfile` module.
.. versionadded:: 13.1
.. versionadded:: 13.1
caption (:obj:`str`, optional): Caption of the video to be sent,
0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters after
entities parsing.
@ -279,12 +276,8 @@ class InputMediaVideo(InputMedia):
duration (:obj:`int`, optional): Video duration in seconds.
supports_streaming (:obj:`bool`, optional): Pass :obj:`True`, if the uploaded video is
suitable for streaming.
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path`, optional): Thumbnail of
the file sent; can be ignored if
thumbnail generation for the file is supported server-side. The thumbnail should be
in JPEG format and less than ``200`` kB in size. A thumbnail's width and height should
not exceed ``320``. Ignored if the file is not uploaded using multipart/form-data.
Thumbnails can't be reused and can be only uploaded as a new file.
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \
optional): |thumbdocstringnopath|
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
@ -327,7 +320,9 @@ class InputMediaVideo(InputMedia):
duration = duration if duration is not None else media.duration
media = media.file_id
else:
media = parse_file_input(media, filename=filename, attach=True)
# We use local_mode=True because we don't have access to the actual setting and want
# things to work in local mode.
media = parse_file_input(media, filename=filename, attach=True, local_mode=True)
super().__init__(InputMediaType.VIDEO, media, caption, caption_entities, parse_mode)
self.width = width
@ -347,11 +342,8 @@ class InputMediaAudio(InputMedia):
Args:
media (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
:class:`telegram.Audio`):
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
:class:`telegram.Audio` object to send.
:class:`telegram.Audio`): File to send. |fileinputnopath|
Lastly you can pass an existing :class:`telegram.Audio` object to send.
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
@ -359,7 +351,7 @@ class InputMediaAudio(InputMedia):
new file. Convenience parameter, useful e.g. when sending files generated by the
:obj:`tempfile` module.
.. versionadded:: 13.1
.. versionadded:: 13.1
caption (:obj:`str`, optional): Caption of the audio to be sent,
0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters after
entities parsing.
@ -373,12 +365,8 @@ class InputMediaAudio(InputMedia):
performer (:obj:`str`, optional): Performer of the audio as defined by sender or by audio
tags.
title (:obj:`str`, optional): Title of the audio as defined by sender or by audio tags.
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path`, optional): Thumbnail of
the file sent; can be ignored if
thumbnail generation for the file is supported server-side. The thumbnail should be
in JPEG format and less than ``200`` kB in size. A thumbnail's width and height should
not exceed ``320``. Ignored if the file is not uploaded using multipart/form-data.
Thumbnails can't be reused and can be only uploaded as a new file.
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \
optional): |thumbdocstringnopath|
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
@ -418,7 +406,9 @@ class InputMediaAudio(InputMedia):
title = media.title if title is None else title
media = media.file_id
else:
media = parse_file_input(media, filename=filename, attach=True)
# We use local_mode=True because we don't have access to the actual setting and want
# things to work in local mode.
media = parse_file_input(media, filename=filename, attach=True, local_mode=True)
super().__init__(InputMediaType.AUDIO, media, caption, caption_entities, parse_mode)
self.thumb = self._parse_thumb_input(thumb)
@ -432,10 +422,8 @@ class InputMediaDocument(InputMedia):
Args:
media (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
:class:`telegram.Document`): 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
:class:`telegram.Document` object to send.
:class:`telegram.Document`): File to send. |fileinputnopath|
Lastly you can pass an existing :class:`telegram.Document` object to send.
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
@ -443,7 +431,7 @@ class InputMediaDocument(InputMedia):
new file. Convenience parameter, useful e.g. when sending files generated by the
:obj:`tempfile` module.
.. versionadded:: 13.1
.. versionadded:: 13.1
caption (:obj:`str`, optional): Caption of the document to be sent,
0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters after
entities parsing.
@ -453,12 +441,8 @@ class InputMediaDocument(InputMedia):
caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special
entities that appear in the caption, which can be specified instead of
:paramref:`parse_mode`.
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path`, optional): Thumbnail of
the file sent; can be ignored if
thumbnail generation for the file is supported server-side. The thumbnail should be
in JPEG format and less than ``200`` kB in size. A thumbnail's width and height should
not exceed ``320``. Ignored if the file is not uploaded using multipart/form-data.
Thumbnails can't be reused and can be only uploaded as a new file.
thumb (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \
optional): |thumbdocstringnopath|
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
@ -492,7 +476,9 @@ class InputMediaDocument(InputMedia):
caption_entities: Union[List[MessageEntity], Tuple[MessageEntity, ...]] = None,
filename: str = None,
):
media = parse_file_input(media, Document, filename=filename, attach=True)
# We use local_mode=True because we don't have access to the actual setting and want
# things to work in local mode.
media = parse_file_input(media, Document, filename=filename, attach=True, local_mode=True)
super().__init__(InputMediaType.DOCUMENT, media, caption, caption_entities, parse_mode)
self.thumb = self._parse_thumb_input(thumb)
self.disable_content_type_detection = disable_content_type_detection

View file

@ -29,13 +29,47 @@ Warning:
"""
from pathlib import Path
from typing import IO, TYPE_CHECKING, Any, Optional, Type, Union, cast
from typing import IO, TYPE_CHECKING, Any, Optional, Tuple, Type, TypeVar, Union, cast, overload
from telegram._utils.types import FileInput, FilePathInput
if TYPE_CHECKING:
from telegram import InputFile, TelegramObject
_T = TypeVar("_T", bound=Union[bytes, "InputFile", str, Path, None])
@overload
def load_file(obj: IO[bytes]) -> Tuple[Optional[str], bytes]:
...
@overload
def load_file(obj: _T) -> Tuple[None, _T]:
...
def load_file(
obj: Optional[FileInput],
) -> Tuple[Optional[str], Union[bytes, "InputFile", str, Path, None]]:
"""If the input is a file handle, read the data and name and return it. Otherwise, return
the input unchanged.
"""
if obj is None:
return None, None
try:
contents = obj.read() # type: ignore[union-attr]
except AttributeError:
return None, cast(Union[bytes, "InputFile", str, Path], obj)
if hasattr(obj, "name") and not isinstance(obj.name, int): # type: ignore[union-attr]
filename = Path(obj.name).name # type: ignore[union-attr]
else:
filename = None
return filename, contents
def is_local_file(obj: Optional[FilePathInput]) -> bool:
"""
@ -54,18 +88,24 @@ def is_local_file(obj: Optional[FilePathInput]) -> bool:
return False
def parse_file_input(
def parse_file_input( # pylint: disable=too-many-return-statements
file_input: Union[FileInput, "TelegramObject"],
tg_type: Type["TelegramObject"] = None,
filename: str = None,
attach: bool = False,
local_mode: bool = False,
) -> Union[str, "InputFile", Any]:
"""
Parses input for sending files:
* For string input, if the input is an absolute path of a local file,
adds the ``file://`` prefix. If the input is a relative path of a local file, computes the
absolute path and adds the ``file://`` prefix. Returns the input unchanged, otherwise.
* For string input, if the input is an absolute path of a local file:
* if ``local_mode`` is ``True``, adds the ``file://`` prefix. If the input is a relative
path of a local file, computes the absolute path and adds the ``file://`` prefix.
* if ``local_mode`` is ``False``, loads the file as binary data and builds an
:class:`InputFile` from that
Returns the input unchanged, otherwise.
* :class:`pathlib.Path` objects are treated the same way as strings.
* For IO and bytes input, returns an :class:`telegram.InputFile`.
* If :attr:`tg_type` is specified and the input is of that type, returns the ``file_id``
@ -81,6 +121,8 @@ def parse_file_input(
attach (:obj:`bool`, optional): Pass :obj:`True` if the parameter this file belongs to in
the request to Telegram should point to the multipart data via an ``attach://`` URI.
Defaults to `False`. Only relevant if an :class:`telegram.InputFile` is returned.
local_mode (:obj:`bool`, optional): Pass :obj:`True` if the bot is running an api server
in ``--local`` mode.
Returns:
:obj:`str` | :class:`telegram.InputFile` | :obj:`object`: The parsed input or the untouched
@ -90,13 +132,17 @@ def parse_file_input(
from telegram import InputFile # pylint: disable=import-outside-toplevel
if isinstance(file_input, str) and file_input.startswith("file://"):
if not local_mode:
raise ValueError("Specified file input is a file URI, but local mode is not enabled.")
return file_input
if isinstance(file_input, (str, Path)):
if is_local_file(file_input):
out = Path(file_input).absolute().as_uri()
else:
out = file_input # type: ignore[assignment]
return out
path = Path(file_input)
if local_mode:
return path.absolute().as_uri()
return InputFile(path.open(mode="rb"), filename=filename, attach=attach)
return file_input
if isinstance(file_input, bytes):
return InputFile(file_input, filename=filename, attach=attach)
if hasattr(file_input, "read"):

View file

@ -44,8 +44,7 @@ FilePathInput = Union[str, Path]
FileInput = Union[FilePathInput, FileLike, bytes, str]
"""Valid input for passing files to Telegram. Either a file id as string, a file like object,
a local file path as string, :class:`pathlib.Path` or the file contents as :obj:`bytes` or
:obj:`str`."""
a local file path as string, :class:`pathlib.Path` or the file contents as :obj:`bytes`."""
JSONDict = Dict[str, Any]
"""Dictionary containing response from Telegram or data to send to the API."""

View file

@ -83,6 +83,7 @@ _BOT_CHECKS = [
("arbitrary_callback_data", "arbitrary_callback_data"),
("private_key", "private_key"),
("rate_limiter", "rate_limiter instance"),
("local_mode", "local_mode setting"),
]
_TWO_ARGS_REQ = "The parameter `{}` may only be set, if no {} was set."
@ -148,6 +149,7 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
"_update_queue",
"_updater",
"_write_timeout",
"_local_mode",
)
def __init__(self: "InitApplicationBuilder"):
@ -172,6 +174,7 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
self._private_key_password: ODVInput[bytes] = DEFAULT_NONE
self._defaults: ODVInput["Defaults"] = DEFAULT_NONE
self._arbitrary_callback_data: DVInput[Union[bool, int]] = DEFAULT_FALSE
self._local_mode: DVInput[bool] = DEFAULT_FALSE
self._bot: DVInput[Bot] = DEFAULT_NONE
self._update_queue: DVInput[Queue] = DefaultValue(Queue())
self._job_queue: ODVInput["JobQueue"] = DefaultValue(JobQueue())
@ -232,8 +235,17 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
request=self._build_request(get_updates=False),
get_updates_request=self._build_request(get_updates=True),
rate_limiter=DefaultValue.get_value(self._rate_limiter),
local_mode=DefaultValue.get_value(self._local_mode),
)
def _bot_check(self, name: str) -> None:
if self._bot is not DEFAULT_NONE:
raise RuntimeError(_TWO_ARGS_REQ.format(name, "bot instance"))
def _updater_check(self, name: str) -> None:
if self._updater not in (DEFAULT_NONE, None):
raise RuntimeError(_TWO_ARGS_REQ.format(name, "updater"))
def build(
self: "ApplicationBuilder[BT, CCT, UD, CD, BD, JQ]",
) -> Application[BT, CCT, UD, CD, BD, JQ]:
@ -326,10 +338,8 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
Returns:
:class:`ApplicationBuilder`: The same builder with the updated argument.
"""
if self._bot is not DEFAULT_NONE:
raise RuntimeError(_TWO_ARGS_REQ.format("token", "bot instance"))
if self._updater not in (DEFAULT_NONE, None):
raise RuntimeError(_TWO_ARGS_REQ.format("token", "updater"))
self._bot_check("token")
self._updater_check("token")
self._token = token
return self
@ -347,10 +357,8 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
Returns:
:class:`ApplicationBuilder`: The same builder with the updated argument.
"""
if self._bot is not DEFAULT_NONE:
raise RuntimeError(_TWO_ARGS_REQ.format("base_url", "bot instance"))
if self._updater not in (DEFAULT_NONE, None):
raise RuntimeError(_TWO_ARGS_REQ.format("base_url", "updater"))
self._bot_check("base_url")
self._updater_check("base_url")
self._base_url = base_url
return self
@ -368,10 +376,8 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
Returns:
:class:`ApplicationBuilder`: The same builder with the updated argument.
"""
if self._bot is not DEFAULT_NONE:
raise RuntimeError(_TWO_ARGS_REQ.format("base_file_url", "bot instance"))
if self._updater not in (DEFAULT_NONE, None):
raise RuntimeError(_TWO_ARGS_REQ.format("base_file_url", "updater"))
self._bot_check("base_file_url")
self._updater_check("base_file_url")
self._base_file_url = base_file_url
return self
@ -388,8 +394,7 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
raise RuntimeError(_TWO_ARGS_REQ.format(name, "connection_pool_size"))
if not isinstance(getattr(self, f"_{prefix}proxy_url"), DefaultValue):
raise RuntimeError(_TWO_ARGS_REQ.format(name, "proxy_url"))
if self._bot is not DEFAULT_NONE:
raise RuntimeError(_TWO_ARGS_REQ.format(name, "bot instance"))
self._bot_check(name)
if self._updater not in (DEFAULT_NONE, None):
raise RuntimeError(_TWO_ARGS_REQ.format(name, "updater instance"))
@ -670,10 +675,8 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
Returns:
:class:`ApplicationBuilder`: The same builder with the updated argument.
"""
if self._bot is not DEFAULT_NONE:
raise RuntimeError(_TWO_ARGS_REQ.format("private_key", "bot instance"))
if self._updater not in (DEFAULT_NONE, None):
raise RuntimeError(_TWO_ARGS_REQ.format("private_key", "updater"))
self._bot_check("private_key")
self._updater_check("private_key")
self._private_key = (
private_key if isinstance(private_key, bytes) else Path(private_key).read_bytes()
@ -698,10 +701,8 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
Returns:
:class:`ApplicationBuilder`: The same builder with the updated argument.
"""
if self._bot is not DEFAULT_NONE:
raise RuntimeError(_TWO_ARGS_REQ.format("defaults", "bot instance"))
if self._updater not in (DEFAULT_NONE, None):
raise RuntimeError(_TWO_ARGS_REQ.format("defaults", "updater"))
self._bot_check("defaults")
self._updater_check("defaults")
self._defaults = defaults
return self
@ -725,13 +726,30 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
Returns:
:class:`ApplicationBuilder`: The same builder with the updated argument.
"""
if self._bot is not DEFAULT_NONE:
raise RuntimeError(_TWO_ARGS_REQ.format("arbitrary_callback_data", "bot instance"))
if self._updater not in (DEFAULT_NONE, None):
raise RuntimeError(_TWO_ARGS_REQ.format("arbitrary_callback_data", "updater"))
self._bot_check("arbitrary_callback_data")
self._updater_check("arbitrary_callback_data")
self._arbitrary_callback_data = arbitrary_callback_data
return self
def local_mode(self: BuilderType, local_mode: bool) -> BuilderType:
"""Specifies the value for :paramref:`~telegram.Bot.local_mode` for the
:attr:`telegram.ext.Application.bot`.
If not called, will default to :obj:`False`.
.. seealso:: `Local Bot API Server <https://github.com/python-telegram-bot/\
python-telegram-bot/wiki/Local-Bot-API-Server>`_,
Args:
local_mode (:obj:`bool`): Whether the bot should run in local mode.
Returns:
:class:`ApplicationBuilder`: The same builder with the updated argument.
"""
self._bot_check("local_mode")
self._updater_check("local_mode")
self._local_mode = local_mode
return self
def bot(
self: "ApplicationBuilder[BT, CCT, UD, CD, BD, JQ]",
bot: InBT,
@ -746,8 +764,7 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
Returns:
:class:`ApplicationBuilder`: The same builder with the updated argument.
"""
if self._updater not in (DEFAULT_NONE, None):
raise RuntimeError(_TWO_ARGS_REQ.format("bot", "updater"))
self._updater_check("bot")
for attr, error in _BOT_CHECKS:
if not isinstance(getattr(self, f"_{attr}"), DefaultValue):
raise RuntimeError(_TWO_ARGS_REQ.format("bot", error))
@ -992,10 +1009,8 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
Returns:
:class:`ApplicationBuilder`: The same builder with the updated argument.
"""
if self._bot is not DEFAULT_NONE:
raise RuntimeError(_TWO_ARGS_REQ.format("rate_limiter", "bot instance"))
if self._updater not in (DEFAULT_NONE, None):
raise RuntimeError(_TWO_ARGS_REQ.format("rate_limiter", "updater"))
self._bot_check("rate_limiter")
self._updater_check("rate_limiter")
self._rate_limiter = rate_limiter
return self # type: ignore[return-value]

View file

@ -163,6 +163,7 @@ class ExtBot(Bot, Generic[RLARGS]):
private_key_password: bytes = None,
defaults: "Defaults" = None,
arbitrary_callback_data: Union[bool, int] = False,
local_mode: bool = False,
):
...
@ -178,6 +179,7 @@ class ExtBot(Bot, Generic[RLARGS]):
private_key_password: bytes = None,
defaults: "Defaults" = None,
arbitrary_callback_data: Union[bool, int] = False,
local_mode: bool = False,
rate_limiter: "BaseRateLimiter[RLARGS]" = None,
):
...
@ -193,6 +195,7 @@ class ExtBot(Bot, Generic[RLARGS]):
private_key_password: bytes = None,
defaults: "Defaults" = None,
arbitrary_callback_data: Union[bool, int] = False,
local_mode: bool = False,
rate_limiter: "BaseRateLimiter" = None,
):
super().__init__(
@ -203,6 +206,7 @@ class ExtBot(Bot, Generic[RLARGS]):
get_updates_request=get_updates_request,
private_key=private_key,
private_key_password=private_key_password,
local_mode=local_mode,
)
self._defaults = defaults
self._rate_limiter = rate_limiter

View file

@ -22,7 +22,7 @@ from pathlib import Path
import pytest
from flaky import flaky
from telegram import Animation, Bot, MessageEntity, PhotoSize, Voice
from telegram import Animation, Bot, InputFile, MessageEntity, PhotoSize, Voice
from telegram.error import BadRequest, TelegramError
from telegram.helpers import escape_markdown
from telegram.request import RequestData
@ -202,20 +202,30 @@ class TestAnimation:
assert message.caption == test_markdown_string
assert message.caption_markdown == escape_markdown(test_markdown_string)
async def test_send_animation_local_files(self, monkeypatch, bot, chat_id):
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
@pytest.mark.parametrize("local_mode", [True, False])
async def test_send_animation_local_files(self, monkeypatch, bot, chat_id, local_mode):
try:
bot._local_mode = local_mode
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
test_flag = data.get("animation") == expected and data.get("thumb") == expected
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
if local_mode:
test_flag = data.get("animation") == expected and data.get("thumb") == expected
else:
test_flag = isinstance(data.get("animation"), InputFile) and isinstance(
data.get("thumb"), InputFile
)
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.send_animation(chat_id, file, thumb=file)
assert test_flag
monkeypatch.delattr(bot, "_post")
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.send_animation(chat_id, file, thumb=file)
assert test_flag
monkeypatch.delattr(bot, "_post")
finally:
bot._local_mode = False
@flaky(3, 1)
@pytest.mark.parametrize(

View file

@ -84,6 +84,7 @@ class TestApplicationBuilder:
assert app.bot.arbitrary_callback_data is False
assert app.bot.defaults is None
assert app.bot.rate_limiter is None
assert app.bot.local_mode is False
get_updates_client = app.bot._request[0]._client
assert get_updates_client.limits == httpx.Limits(
@ -257,6 +258,8 @@ class TestApplicationBuilder:
get_updates_request
).rate_limiter(
rate_limiter
).local_mode(
True
)
built_bot = builder.build().bot
@ -273,6 +276,7 @@ class TestApplicationBuilder:
assert built_bot.callback_data_cache.maxsize == 42
assert built_bot.private_key
assert built_bot.rate_limiter is rate_limiter
assert built_bot.local_mode is True
@dataclass
class Client:

View file

@ -22,7 +22,7 @@ from pathlib import Path
import pytest
from flaky import flaky
from telegram import Audio, Bot, MessageEntity, Voice
from telegram import Audio, Bot, InputFile, MessageEntity, Voice
from telegram.error import TelegramError
from telegram.helpers import escape_markdown
from telegram.request import RequestData
@ -234,20 +234,30 @@ class TestAudio:
unprotected = await default_bot.send_audio(chat_id, audio, protect_content=False)
assert not unprotected.has_protected_content
async def test_send_audio_local_files(self, monkeypatch, bot, chat_id):
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
@pytest.mark.parametrize("local_mode", [True, False])
async def test_send_audio_local_files(self, monkeypatch, bot, chat_id, local_mode):
try:
bot._local_mode = local_mode
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
test_flag = data.get("audio") == expected and data.get("thumb") == expected
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
if local_mode:
test_flag = data.get("audio") == expected and data.get("thumb") == expected
else:
test_flag = isinstance(data.get("audio"), InputFile) and isinstance(
data.get("thumb"), InputFile
)
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.send_audio(chat_id, file, thumb=file)
assert test_flag
monkeypatch.delattr(bot, "_post")
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.send_audio(chat_id, file, thumb=file)
assert test_flag
monkeypatch.delattr(bot, "_post")
finally:
bot._local_mode = False
def test_de_json(self, bot, audio):
json_dict = {

View file

@ -44,6 +44,7 @@ from telegram import (
InlineQueryResultArticle,
InlineQueryResultDocument,
InlineQueryResultVoice,
InputFile,
InputMedia,
InputTextMessageContent,
LabeledPrice,
@ -1578,18 +1579,25 @@ class TestBot:
@flaky(3, 1)
@pytest.mark.parametrize("use_ip", [True, False])
async def test_set_webhook_get_webhook_info_and_delete_webhook(self, bot, use_ip):
# local file path as file_input is tested below in test_set_webhook_params
@pytest.mark.parametrize("file_input", ["bytes", "file_handle"])
async def test_set_webhook_get_webhook_info_and_delete_webhook(self, bot, use_ip, file_input):
url = "https://python-telegram-bot.org/test/webhook"
# Get the ip address of the website - dynamically just in case it ever changes
ip = socket.gethostbyname("python-telegram-bot.org")
max_connections = 7
allowed_updates = ["message"]
file_input = (
data_file("sslcert.pem").read_bytes()
if file_input == "bytes"
else data_file("sslcert.pem").open("rb")
)
await bot.set_webhook(
url,
max_connections=max_connections,
allowed_updates=allowed_updates,
ip_address=ip if use_ip else None,
certificate=data_file("sslcert.pem").read_bytes() if use_ip else None,
certificate=file_input if use_ip else None,
)
await asyncio.sleep(1)
@ -1620,16 +1628,25 @@ class TestBot:
assert await bot.set_webhook("", drop_pending_updates=drop_pending_updates)
assert await bot.delete_webhook(drop_pending_updates=drop_pending_updates)
async def test_set_webhook_params(self, bot, monkeypatch):
@pytest.mark.parametrize("local_file", ["str", "Path", False])
async def test_set_webhook_params(self, bot, monkeypatch, local_file):
# actually making calls to TG is done in
# test_set_webhook_get_webhook_info_and_delete_webhook. Sadly secret_token can't be tested
# there so we have this function \o/
async def make_assertion(*args, **_):
kwargs = args[1]
if local_file is False:
cert_assertion = (
kwargs["certificate"].input_file_content
== data_file("sslcert.pem").read_bytes()
)
else:
cert_assertion = data_file("sslcert.pem").as_uri()
return (
kwargs["url"] == "example.com"
and kwargs["certificate"].input_file_content
== data_file("sslcert.pem").read_bytes()
and cert_assertion
and kwargs["max_connections"] == 7
and kwargs["allowed_updates"] == ["messages"]
and kwargs["ip_address"] == "127.0.0.1"
@ -1639,9 +1656,17 @@ class TestBot:
monkeypatch.setattr(bot, "_post", make_assertion)
cert_path = data_file("sslcert.pem")
if local_file == "str":
certificate = str(cert_path)
elif local_file == "Path":
certificate = cert_path
else:
certificate = cert_path.read_bytes()
assert await bot.set_webhook(
"example.com",
data_file("sslcert.pem").read_bytes(),
certificate,
7,
["messages"],
"127.0.0.1",
@ -2163,19 +2188,27 @@ class TestBot:
func, "Type of file mismatch", "Telegram did not accept the file."
)
async def test_set_chat_photo_local_files(self, monkeypatch, bot, chat_id):
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
@pytest.mark.parametrize("local_mode", [True, False])
async def test_set_chat_photo_local_files(self, monkeypatch, bot, chat_id, local_mode):
try:
bot._local_mode = local_mode
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
test_flag = data.get("photo") == expected
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
if local_mode:
test_flag = data.get("photo") == expected
else:
test_flag = isinstance(data.get("photo"), InputFile)
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.set_chat_photo(chat_id, file)
assert test_flag
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.set_chat_photo(chat_id, file)
assert test_flag
finally:
bot._local_mode = False
@flaky(3, 1)
async def test_delete_chat_photo(self, bot, channel_id):

View file

@ -22,7 +22,7 @@ from pathlib import Path
import pytest
from flaky import flaky
from telegram import Bot, Document, MessageEntity, PhotoSize, Voice
from telegram import Bot, Document, InputFile, MessageEntity, PhotoSize, Voice
from telegram.error import BadRequest, TelegramError
from telegram.helpers import escape_markdown
from telegram.request import RequestData
@ -255,19 +255,29 @@ class TestDocument:
unprotected = await default_bot.send_document(chat_id, document, protect_content=False)
assert not unprotected.has_protected_content
async def test_send_document_local_files(self, monkeypatch, bot, chat_id):
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
@pytest.mark.parametrize("local_mode", [True, False])
async def test_send_document_local_files(self, monkeypatch, bot, chat_id, local_mode):
try:
bot._local_mode = local_mode
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
test_flag = data.get("document") == expected and data.get("thumb") == expected
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
if local_mode:
test_flag = data.get("document") == expected and data.get("thumb") == expected
else:
test_flag = isinstance(data.get("document"), InputFile) and isinstance(
data.get("thumb"), InputFile
)
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.send_document(chat_id, file, thumb=file)
assert test_flag
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.send_document(chat_id, file, thumb=file)
assert test_flag
finally:
bot._local_mode = False
def test_de_json(self, bot, document):
json_dict = {

View file

@ -16,6 +16,9 @@
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import subprocess
import sys
from pathlib import Path
import pytest
@ -43,23 +46,43 @@ class TestFiles:
assert telegram._utils.files.is_local_file(string) == expected
@pytest.mark.parametrize(
"string,expected",
"string,expected_local,expected_non_local",
[
(data_file("game.gif"), data_file("game.gif").as_uri()),
(TEST_DATA_PATH, TEST_DATA_PATH),
("file://foobar", "file://foobar"),
(str(data_file("game.gif")), data_file("game.gif").as_uri()),
(str(TEST_DATA_PATH), str(TEST_DATA_PATH)),
(data_file("game.gif"), data_file("game.gif").as_uri()),
(TEST_DATA_PATH, TEST_DATA_PATH),
(data_file("game.gif"), data_file("game.gif").as_uri(), InputFile),
(TEST_DATA_PATH, TEST_DATA_PATH, TEST_DATA_PATH),
("file://foobar", "file://foobar", ValueError),
(str(data_file("game.gif")), data_file("game.gif").as_uri(), InputFile),
(str(TEST_DATA_PATH), str(TEST_DATA_PATH), str(TEST_DATA_PATH)),
(
"https:/api.org/file/botTOKEN/document/file_3",
"https:/api.org/file/botTOKEN/document/file_3",
"https:/api.org/file/botTOKEN/document/file_3",
),
],
ids=[
"Path(local_file)",
"Path(directory)",
"file_uri",
"str-path local file",
"str-path directory",
"URL",
],
)
def test_parse_file_input_string(self, string, expected):
assert telegram._utils.files.parse_file_input(string) == expected
def test_parse_file_input_string(self, string, expected_local, expected_non_local):
assert telegram._utils.files.parse_file_input(string, local_mode=True) == expected_local
if expected_non_local is InputFile:
assert isinstance(
telegram._utils.files.parse_file_input(string, local_mode=False), InputFile
)
elif expected_non_local is ValueError:
with pytest.raises(ValueError):
telegram._utils.files.parse_file_input(string, local_mode=False)
else:
assert (
telegram._utils.files.parse_file_input(string, local_mode=False)
== expected_non_local
)
def test_parse_file_input_file_like(self):
source_file = data_file("game.gif")
@ -105,3 +128,34 @@ class TestFiles:
assert isinstance(parsed, InputFile)
assert bool(parsed.attach_name) is attach
def test_load_file_none(self):
assert telegram._utils.files.load_file(None) == (None, None)
@pytest.mark.parametrize("arg", [b"bytes", "string", InputFile(b"content"), Path("file/path")])
def test_load_file_no_file(self, arg):
out = telegram._utils.files.load_file(arg)
assert out[0] is None
assert out[1] is arg
def test_load_file_file_handle(self):
out = telegram._utils.files.load_file(data_file("telegram.gif").open("rb"))
assert out[0] == "telegram.gif"
assert out[1] == data_file("telegram.gif").read_bytes()
def test_load_file_subprocess_pipe(self):
png_file = data_file("telegram.png")
cmd_str = "type" if sys.platform == "win32" else "cat"
cmd = [cmd_str, str(png_file)]
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=(sys.platform == "win32"))
out = telegram._utils.files.load_file(proc.stdout)
assert out[0] is None
assert out[1] == png_file.read_bytes()
try:
proc.kill()
except ProcessLookupError:
# This exception may be thrown if the process has finished before we had the chance
# to kill it.
pass

View file

@ -236,19 +236,27 @@ class TestPhoto:
unprotected = await default_bot.send_photo(chat_id, photo, protect_content=False)
assert not unprotected.has_protected_content
async def test_send_photo_local_files(self, monkeypatch, bot, chat_id):
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
@pytest.mark.parametrize("local_mode", [True, False])
async def test_send_photo_local_files(self, monkeypatch, bot, chat_id, local_mode):
try:
bot._local_mode = local_mode
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
test_flag = data.get("photo") == expected
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
if local_mode:
test_flag = data.get("photo") == expected
else:
test_flag = isinstance(data.get("photo"), InputFile)
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.send_photo(chat_id, file)
assert test_flag
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.send_photo(chat_id, file)
assert test_flag
finally:
bot._local_mode = False
@flaky(3, 1)
@pytest.mark.parametrize(

View file

@ -23,7 +23,7 @@ from pathlib import Path
import pytest
from flaky import flaky
from telegram import Audio, Bot, File, MaskPosition, PhotoSize, Sticker, StickerSet
from telegram import Audio, Bot, File, InputFile, MaskPosition, PhotoSize, Sticker, StickerSet
from telegram.error import BadRequest, TelegramError
from telegram.request import RequestData
from tests.conftest import (
@ -250,20 +250,28 @@ class TestSticker:
message = await bot.send_sticker(sticker=sticker, chat_id=chat_id)
assert message
async def test_send_sticker_local_files(self, monkeypatch, bot, chat_id):
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
@pytest.mark.parametrize("local_mode", [True, False])
async def test_send_sticker_local_files(self, monkeypatch, bot, chat_id, local_mode):
try:
bot._local_mode = local_mode
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
test_flag = data.get("sticker") == expected
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
if local_mode:
test_flag = data.get("sticker") == expected
else:
test_flag = isinstance(data.get("sticker"), InputFile)
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.send_sticker(chat_id, file)
assert test_flag
monkeypatch.delattr(bot, "_post")
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.send_sticker(chat_id, file)
assert test_flag
monkeypatch.delattr(bot, "_post")
finally:
bot._local_mode = False
@flaky(3, 1)
@pytest.mark.parametrize(
@ -645,47 +653,67 @@ class TestStickerSet:
file_id = video_sticker_set.stickers[-1].file_id
assert await bot.delete_sticker_from_set(file_id)
async def test_upload_sticker_file_local_files(self, monkeypatch, bot, chat_id):
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
@pytest.mark.parametrize("local_mode", [True, False])
async def test_upload_sticker_file_local_files(self, monkeypatch, bot, chat_id, local_mode):
try:
bot._local_mode = local_mode
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
test_flag = data.get("png_sticker") == expected
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
if local_mode:
test_flag = data.get("png_sticker") == expected
else:
test_flag = isinstance(data.get("png_sticker"), InputFile)
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.upload_sticker_file(chat_id, file)
assert test_flag
monkeypatch.delattr(bot, "_post")
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.upload_sticker_file(chat_id, file)
assert test_flag
monkeypatch.delattr(bot, "_post")
finally:
bot._local_mode = False
async def test_create_new_sticker_set_local_files(self, monkeypatch, bot, chat_id):
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
@pytest.mark.parametrize("local_mode", [True, False])
async def test_create_new_sticker_set_local_files(self, monkeypatch, bot, chat_id, local_mode):
try:
bot._local_mode = local_mode
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
test_flag = (
data.get("png_sticker") == expected
and data.get("tgs_sticker") == expected
and data.get("webm_sticker") == expected
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
if local_mode:
test_flag = (
data.get("png_sticker") == expected
and data.get("tgs_sticker") == expected
and data.get("webm_sticker") == expected
)
else:
test_flag = (
isinstance(data.get("png_sticker"), InputFile)
and isinstance(data.get("tgs_sticker"), InputFile)
and isinstance(data.get("webm_sticker"), InputFile)
)
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.create_new_sticker_set(
chat_id,
"name",
"title",
"emoji",
png_sticker=file,
tgs_sticker=file,
webm_sticker=file,
)
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.create_new_sticker_set(
chat_id,
"name",
"title",
"emoji",
png_sticker=file,
tgs_sticker=file,
webm_sticker=file,
)
assert test_flag
monkeypatch.delattr(bot, "_post")
assert test_flag
monkeypatch.delattr(bot, "_post")
finally:
bot._local_mode = False
async def test_create_new_sticker_all_params(self, monkeypatch, bot, chat_id, mask_position):
async def make_assertion(_, data, *args, **kwargs):
@ -713,35 +741,57 @@ class TestStickerSet:
)
monkeypatch.delattr(bot, "_post")
async def test_add_sticker_to_set_local_files(self, monkeypatch, bot, chat_id):
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
@pytest.mark.parametrize("local_mode", [True, False])
async def test_add_sticker_to_set_local_files(self, monkeypatch, bot, chat_id, local_mode):
try:
bot._local_mode = local_mode
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
test_flag = data.get("png_sticker") == expected and data.get("tgs_sticker") == expected
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
if local_mode:
test_flag = (
data.get("png_sticker") == expected and data.get("tgs_sticker") == expected
)
else:
test_flag = isinstance(data.get("png_sticker"), InputFile) and isinstance(
data.get("tgs_sticker"), InputFile
)
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.add_sticker_to_set(chat_id, "name", "emoji", png_sticker=file, tgs_sticker=file)
assert test_flag
monkeypatch.delattr(bot, "_post")
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.add_sticker_to_set(
chat_id, "name", "emoji", png_sticker=file, tgs_sticker=file
)
assert test_flag
monkeypatch.delattr(bot, "_post")
finally:
bot._local_mode = False
async def test_set_sticker_set_thumb_local_files(self, monkeypatch, bot, chat_id):
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
@pytest.mark.parametrize("local_mode", [True, False])
async def test_set_sticker_set_thumb_local_files(self, monkeypatch, bot, chat_id, local_mode):
try:
bot._local_mode = local_mode
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
test_flag = data.get("thumb") == expected
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
if local_mode:
test_flag = data.get("thumb") == expected
else:
test_flag = isinstance(data.get("thumb"), InputFile)
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.set_sticker_set_thumb("name", chat_id, thumb=file)
assert test_flag
monkeypatch.delattr(bot, "_post")
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.set_sticker_set_thumb("name", chat_id, thumb=file)
assert test_flag
monkeypatch.delattr(bot, "_post")
finally:
bot._local_mode = False
async def test_get_file_instance_method(self, monkeypatch, sticker):
async def make_assertion(*_, **kwargs):

View file

@ -22,7 +22,7 @@ from pathlib import Path
import pytest
from flaky import flaky
from telegram import Bot, MessageEntity, PhotoSize, Video, Voice
from telegram import Bot, InputFile, MessageEntity, PhotoSize, Video, Voice
from telegram.error import BadRequest, TelegramError
from telegram.helpers import escape_markdown
from telegram.request import RequestData
@ -247,19 +247,29 @@ class TestVideo:
unprotected = await default_bot.send_video(chat_id, video, protect_content=False)
assert not unprotected.has_protected_content
async def test_send_video_local_files(self, monkeypatch, bot, chat_id):
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
@pytest.mark.parametrize("local_mode", [True, False])
async def test_send_video_local_files(self, monkeypatch, bot, chat_id, local_mode):
try:
bot._local_mode = local_mode
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
test_flag = data.get("video") == expected and data.get("thumb") == expected
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
if local_mode:
test_flag = data.get("video") == expected and data.get("thumb") == expected
else:
test_flag = isinstance(data.get("video"), InputFile) and isinstance(
data.get("thumb"), InputFile
)
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.send_video(chat_id, file, thumb=file)
assert test_flag
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.send_video(chat_id, file, thumb=file)
assert test_flag
finally:
bot._local_mode = False
@flaky(3, 1)
@pytest.mark.parametrize(

View file

@ -22,7 +22,7 @@ from pathlib import Path
import pytest
from flaky import flaky
from telegram import Bot, PhotoSize, VideoNote, Voice
from telegram import Bot, InputFile, PhotoSize, VideoNote, Voice
from telegram.error import BadRequest, TelegramError
from telegram.request import RequestData
from tests.conftest import (
@ -177,19 +177,31 @@ class TestVideoNote:
assert video_note_dict["duration"] == video_note.duration
assert video_note_dict["file_size"] == video_note.file_size
async def test_send_video_note_local_files(self, monkeypatch, bot, chat_id):
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
@pytest.mark.parametrize("local_mode", [True, False])
async def test_send_video_note_local_files(self, monkeypatch, bot, chat_id, local_mode):
try:
bot._local_mode = local_mode
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
test_flag = data.get("video_note") == expected and data.get("thumb") == expected
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
if local_mode:
test_flag = (
data.get("video_note") == expected and data.get("thumb") == expected
)
else:
test_flag = isinstance(data.get("video_note"), InputFile) and isinstance(
data.get("thumb"), InputFile
)
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.send_video_note(chat_id, file, thumb=file)
assert test_flag
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.send_video_note(chat_id, file, thumb=file)
assert test_flag
finally:
bot._local_mode = False
@flaky(3, 1)
@pytest.mark.parametrize(

View file

@ -22,7 +22,7 @@ from pathlib import Path
import pytest
from flaky import flaky
from telegram import Audio, Bot, MessageEntity, Voice
from telegram import Audio, Bot, InputFile, MessageEntity, Voice
from telegram.error import BadRequest, TelegramError
from telegram.helpers import escape_markdown
from telegram.request import RequestData
@ -207,19 +207,27 @@ class TestVoice:
unprotected = await default_bot.send_voice(chat_id, voice, protect_content=False)
assert not unprotected.has_protected_content
async def test_send_voice_local_files(self, monkeypatch, bot, chat_id):
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
@pytest.mark.parametrize("local_mode", [True, False])
async def test_send_voice_local_files(self, monkeypatch, bot, chat_id, local_mode):
try:
bot._local_mode = local_mode
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
test_flag = data.get("voice") == expected
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
if local_mode:
test_flag = data.get("voice") == expected
else:
test_flag = isinstance(data.get("voice"), InputFile)
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.send_voice(chat_id, file)
assert test_flag
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.send_voice(chat_id, file)
assert test_flag
finally:
bot._local_mode = False
@flaky(3, 1)
@pytest.mark.parametrize(