mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2024-11-21 14:46:29 +01:00
Add Parameter read_file_handle
to InputFile
(#4388)
This commit is contained in:
parent
e637d1733c
commit
3a49372591
11 changed files with 206 additions and 72 deletions
|
@ -1305,8 +1305,8 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
|
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
|
||||||
photo (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
photo (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` \
|
||||||
:class:`telegram.PhotoSize`): Photo to send.
|
| :class:`pathlib.Path` | :class:`telegram.PhotoSize`): Photo to send.
|
||||||
|fileinput|
|
|fileinput|
|
||||||
Lastly you can pass an existing :class:`telegram.PhotoSize` object to send.
|
Lastly you can pass an existing :class:`telegram.PhotoSize` object to send.
|
||||||
|
|
||||||
|
@ -1465,9 +1465,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
|
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
|
||||||
audio (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
audio (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | \
|
||||||
:class:`telegram.Audio`): Audio file to send.
|
:obj:`bytes` | :class:`pathlib.Path` | :class:`telegram.Audio`): Audio file to
|
||||||
|fileinput|
|
send. |fileinput|
|
||||||
Lastly you can pass an existing :class:`telegram.Audio` object to send.
|
Lastly you can pass an existing :class:`telegram.Audio` object to send.
|
||||||
|
|
||||||
.. versionchanged:: 13.2
|
.. versionchanged:: 13.2
|
||||||
|
@ -1617,8 +1617,8 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
|
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
|
||||||
document (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
document (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | \
|
||||||
:class:`telegram.Document`): File to send.
|
:obj:`bytes` | :class:`pathlib.Path` | :class:`telegram.Document`): File to send.
|
||||||
|fileinput|
|
|fileinput|
|
||||||
Lastly you can pass an existing :class:`telegram.Document` object to send.
|
Lastly you can pass an existing :class:`telegram.Document` object to send.
|
||||||
|
|
||||||
|
@ -1755,8 +1755,8 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
|
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
|
||||||
sticker (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
sticker (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | \
|
||||||
:class:`telegram.Sticker`): Sticker to send.
|
:obj:`bytes` | :class:`pathlib.Path` | :class:`telegram.Sticker`): Sticker to send.
|
||||||
|fileinput| Video stickers can only be sent by a ``file_id``. Video and animated
|
|fileinput| Video stickers can only be sent by a ``file_id``. Video and animated
|
||||||
stickers can't be sent via an HTTP URL.
|
stickers can't be sent via an HTTP URL.
|
||||||
|
|
||||||
|
@ -1895,8 +1895,8 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
|
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
|
||||||
video (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
video (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` \
|
||||||
:class:`telegram.Video`): Video file to send.
|
| :class:`pathlib.Path` | :class:`telegram.Video`): Video file to send.
|
||||||
|fileinput|
|
|fileinput|
|
||||||
Lastly you can pass an existing :class:`telegram.Video` object to send.
|
Lastly you can pass an existing :class:`telegram.Video` object to send.
|
||||||
|
|
||||||
|
@ -2059,8 +2059,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
|
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
|
||||||
video_note (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
video_note (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | \
|
||||||
:class:`telegram.VideoNote`): Video note to send.
|
: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
|
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.
|
servers (recommended) or upload a new video using multipart/form-data.
|
||||||
|uploadinput|
|
|uploadinput|
|
||||||
|
@ -2209,9 +2210,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
|
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
|
||||||
animation (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
animation (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | \
|
||||||
:class:`telegram.Animation`): Animation to send.
|
:obj:`bytes` | :class:`pathlib.Path` | :class:`telegram.Animation`): Animation to
|
||||||
|fileinput|
|
send. |fileinput|
|
||||||
Lastly you can pass an existing :class:`telegram.Animation` object to send.
|
Lastly you can pass an existing :class:`telegram.Animation` object to send.
|
||||||
|
|
||||||
.. versionchanged:: 13.2
|
.. versionchanged:: 13.2
|
||||||
|
@ -2371,8 +2372,8 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
|
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
|
||||||
voice (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
voice (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` \
|
||||||
:class:`telegram.Voice`): Voice file to send.
|
| :class:`pathlib.Path` | :class:`telegram.Voice`): Voice file to send.
|
||||||
|fileinput|
|
|fileinput|
|
||||||
Lastly you can pass an existing :class:`telegram.Voice` object to send.
|
Lastly you can pass an existing :class:`telegram.Voice` object to send.
|
||||||
|
|
||||||
|
@ -6370,8 +6371,9 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
user_id (:obj:`int`): User identifier of sticker file owner.
|
user_id (:obj:`int`): User identifier of sticker file owner.
|
||||||
sticker (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path`):
|
sticker (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | \
|
||||||
A file with the sticker in the ``".WEBP"``, ``".PNG"``, ``".TGS"`` or ``".WEBM"``
|
:obj:`bytes` | :class:`pathlib.Path`): A file with the sticker in the
|
||||||
|
``".WEBP"``, ``".PNG"``, ``".TGS"`` or ``".WEBM"``
|
||||||
format. See `here <https://core.telegram.org/stickers>`_ for technical requirements
|
format. See `here <https://core.telegram.org/stickers>`_ for technical requirements
|
||||||
. |uploadinput|
|
. |uploadinput|
|
||||||
|
|
||||||
|
@ -6695,8 +6697,9 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
|
||||||
|
|
||||||
.. versionadded:: 21.1
|
.. versionadded:: 21.1
|
||||||
|
|
||||||
thumbnail (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path`, \
|
thumbnail (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | \
|
||||||
optional): A **.WEBP** or **.PNG** image with the thumbnail, must
|
:obj:`bytes` | :class:`pathlib.Path`, optional): A **.WEBP** or **.PNG** image
|
||||||
|
with the thumbnail, must
|
||||||
be up to :tg-const:`telegram.constants.StickerSetLimit.MAX_STATIC_THUMBNAIL_SIZE`
|
be up to :tg-const:`telegram.constants.StickerSetLimit.MAX_STATIC_THUMBNAIL_SIZE`
|
||||||
kilobytes in size and have width and height of exactly
|
kilobytes in size and have width and height of exactly
|
||||||
:tg-const:`telegram.constants.StickerSetLimit.STATIC_THUMB_DIMENSIONS` px, or a
|
:tg-const:`telegram.constants.StickerSetLimit.STATIC_THUMB_DIMENSIONS` px, or a
|
||||||
|
|
|
@ -22,7 +22,7 @@ import mimetypes
|
||||||
from typing import IO, Optional, Union
|
from typing import IO, Optional, Union
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from telegram._utils.files import load_file
|
from telegram._utils.files import guess_file_name, load_file
|
||||||
from telegram._utils.strings import TextEncoding
|
from telegram._utils.strings import TextEncoding
|
||||||
from telegram._utils.types import FieldTuple
|
from telegram._utils.types import FieldTuple
|
||||||
|
|
||||||
|
@ -53,9 +53,36 @@ class InputFile:
|
||||||
attach (:obj:`bool`, optional): Pass :obj:`True` if the parameter this file belongs to in
|
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.
|
the request to Telegram should point to the multipart data via an ``attach://`` URI.
|
||||||
Defaults to `False`.
|
Defaults to `False`.
|
||||||
|
read_file_handle (:obj:`bool`, optional): If :obj:`True` and :paramref:`obj` is a file
|
||||||
|
handle, the data will be read from the file handle on initialization of this object.
|
||||||
|
If :obj:`False`, the file handle will be passed on to the
|
||||||
|
`networking backend <telegram.request.BaseRequest.do_request>`_ which will have to
|
||||||
|
handle the reading. Defaults to :obj:`True`.
|
||||||
|
|
||||||
|
Tip:
|
||||||
|
If you upload extremely large files, you may want to set this to :obj:`False` to
|
||||||
|
avoid reading the complete file into memory. Additionally, this may be supported
|
||||||
|
better by the networking backend (in particular it is handled better by
|
||||||
|
the default :class:`~telegram.request.HTTPXRequest`).
|
||||||
|
|
||||||
|
Important:
|
||||||
|
If you set this to :obj:`False`, you have to ensure that the file handle is still
|
||||||
|
open when the request is made. In particular, the following snippet can *not* work
|
||||||
|
as expected.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
with open('file.txt', 'rb') as file:
|
||||||
|
input_file = InputFile(file, read_file_handle=False)
|
||||||
|
|
||||||
|
# here the file handle is already closed and the upload will fail
|
||||||
|
await bot.send_document(chat_id, input_file)
|
||||||
|
|
||||||
|
.. versionadded:: NEXT.VERSION
|
||||||
|
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
input_file_content (:obj:`bytes`): The binary content of the file to send.
|
input_file_content (:obj:`bytes` | :class:`IO`): The binary content of the file to send.
|
||||||
attach_name (:obj:`str`): Optional. If present, the parameter this file belongs to in
|
attach_name (:obj:`str`): Optional. If present, the parameter this file belongs to in
|
||||||
the request to Telegram should point to the multipart data via a an URI of the form
|
the request to Telegram should point to the multipart data via a an URI of the form
|
||||||
``attach://<attach_name>`` URI.
|
``attach://<attach_name>`` URI.
|
||||||
|
@ -71,14 +98,18 @@ class InputFile:
|
||||||
obj: Union[IO[bytes], bytes, str],
|
obj: Union[IO[bytes], bytes, str],
|
||||||
filename: Optional[str] = None,
|
filename: Optional[str] = None,
|
||||||
attach: bool = False,
|
attach: bool = False,
|
||||||
|
read_file_handle: bool = True,
|
||||||
):
|
):
|
||||||
if isinstance(obj, bytes):
|
if isinstance(obj, bytes):
|
||||||
self.input_file_content: bytes = obj
|
self.input_file_content: Union[bytes, IO[bytes]] = obj
|
||||||
elif isinstance(obj, str):
|
elif isinstance(obj, str):
|
||||||
self.input_file_content = obj.encode(TextEncoding.UTF_8)
|
self.input_file_content = obj.encode(TextEncoding.UTF_8)
|
||||||
else:
|
elif read_file_handle:
|
||||||
reported_filename, self.input_file_content = load_file(obj)
|
reported_filename, self.input_file_content = load_file(obj)
|
||||||
filename = filename or reported_filename
|
filename = filename or reported_filename
|
||||||
|
else:
|
||||||
|
self.input_file_content = obj
|
||||||
|
filename = filename or guess_file_name(obj)
|
||||||
|
|
||||||
self.attach_name: Optional[str] = "attached" + uuid4().hex if attach else None
|
self.attach_name: Optional[str] = "attached" + uuid4().hex if attach else None
|
||||||
|
|
||||||
|
@ -95,8 +126,11 @@ class InputFile:
|
||||||
def field_tuple(self) -> FieldTuple:
|
def field_tuple(self) -> FieldTuple:
|
||||||
"""Field tuple representing the contents of the file for upload to the Telegram servers.
|
"""Field tuple representing the contents of the file for upload to the Telegram servers.
|
||||||
|
|
||||||
|
.. versionchanged:: NEXT.VERSION
|
||||||
|
Content may now be a file handle.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple[:obj:`str`, :obj:`bytes`, :obj:`str`]:
|
Tuple[:obj:`str`, :obj:`bytes` | :class:`IO`, :obj:`str`]:
|
||||||
"""
|
"""
|
||||||
return self.filename, self.input_file_content, self.mimetype
|
return self.filename, self.input_file_content, self.mimetype
|
||||||
|
|
||||||
|
|
|
@ -50,8 +50,8 @@ class InputMedia(TelegramObject):
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
media_type (:obj:`str`): Type of media that the instance represents.
|
media_type (:obj:`str`): Type of media that the instance represents.
|
||||||
media (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
media (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` | \
|
||||||
:class:`telegram.Animation` | :class:`telegram.Audio` | \
|
:class:`pathlib.Path` | :class:`telegram.Animation` | :class:`telegram.Audio` | \
|
||||||
:class:`telegram.Document` | :class:`telegram.PhotoSize` | \
|
:class:`telegram.Document` | :class:`telegram.PhotoSize` | \
|
||||||
:class:`telegram.Video`): File to send.
|
:class:`telegram.Video`): File to send.
|
||||||
|fileinputnopath|
|
|fileinputnopath|
|
||||||
|
@ -128,8 +128,9 @@ class InputPaidMedia(TelegramObject):
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
type (:obj:`str`): Type of media that the instance represents.
|
type (:obj:`str`): Type of media that the instance represents.
|
||||||
media (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
media (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` | \
|
||||||
:class:`telegram.PhotoSize` | :class:`telegram.Video`): File to send. |fileinputnopath|
|
:class:`pathlib.Path` | :class:`telegram.PhotoSize` | :class:`telegram.Video`): File
|
||||||
|
to send. |fileinputnopath|
|
||||||
Lastly you can pass an existing telegram media object of the corresponding type
|
Lastly you can pass an existing telegram media object of the corresponding type
|
||||||
to send.
|
to send.
|
||||||
|
|
||||||
|
@ -167,8 +168,8 @@ class InputPaidMediaPhoto(InputPaidMedia):
|
||||||
.. versionadded:: 21.4
|
.. versionadded:: 21.4
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
media (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
media (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` | \
|
||||||
:class:`telegram.PhotoSize`): File to send. |fileinputnopath|
|
:class:`pathlib.Path` | :class:`telegram.PhotoSize`): File to send. |fileinputnopath|
|
||||||
Lastly you can pass an existing :class:`telegram.PhotoSize` object to send.
|
Lastly you can pass an existing :class:`telegram.PhotoSize` object to send.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
|
@ -207,8 +208,8 @@ class InputPaidMediaVideo(InputPaidMedia):
|
||||||
changed by Telegram.
|
changed by Telegram.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
media (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
media (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` | \
|
||||||
:class:`telegram.Video`): File to send. |fileinputnopath|
|
:class:`pathlib.Path` | :class:`telegram.Video`): File to send. |fileinputnopath|
|
||||||
Lastly you can pass an existing :class:`telegram.Video` object to send.
|
Lastly you can pass an existing :class:`telegram.Video` object to send.
|
||||||
thumbnail (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \
|
thumbnail (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \
|
||||||
optional): |thumbdocstringnopath|
|
optional): |thumbdocstringnopath|
|
||||||
|
@ -278,8 +279,8 @@ class InputMediaAnimation(InputMedia):
|
||||||
|removed_thumb_note|
|
|removed_thumb_note|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
media (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
media (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` | \
|
||||||
:class:`telegram.Animation`): File to send. |fileinputnopath|
|
:class:`pathlib.Path` | :class:`telegram.Animation`): File to send. |fileinputnopath|
|
||||||
Lastly you can pass an existing :class:`telegram.Animation` object to send.
|
Lastly you can pass an existing :class:`telegram.Animation` object to send.
|
||||||
|
|
||||||
.. versionchanged:: 13.2
|
.. versionchanged:: 13.2
|
||||||
|
@ -401,8 +402,8 @@ class InputMediaPhoto(InputMedia):
|
||||||
.. seealso:: :wiki:`Working with Files and Media <Working-with-Files-and-Media>`
|
.. seealso:: :wiki:`Working with Files and Media <Working-with-Files-and-Media>`
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
media (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
media (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` | \
|
||||||
:class:`telegram.PhotoSize`): File to send. |fileinputnopath|
|
:class:`pathlib.Path` | :class:`telegram.PhotoSize`): File to send. |fileinputnopath|
|
||||||
Lastly you can pass an existing :class:`telegram.PhotoSize` object to send.
|
Lastly you can pass an existing :class:`telegram.PhotoSize` object to send.
|
||||||
|
|
||||||
.. versionchanged:: 13.2
|
.. versionchanged:: 13.2
|
||||||
|
@ -501,8 +502,8 @@ class InputMediaVideo(InputMedia):
|
||||||
|removed_thumb_note|
|
|removed_thumb_note|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
media (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
media (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` | \
|
||||||
:class:`telegram.Video`): File to send. |fileinputnopath|
|
:class:`pathlib.Path` | :class:`telegram.Video`): File to send. |fileinputnopath|
|
||||||
Lastly you can pass an existing :class:`telegram.Video` object to send.
|
Lastly you can pass an existing :class:`telegram.Video` object to send.
|
||||||
|
|
||||||
.. versionchanged:: 13.2
|
.. versionchanged:: 13.2
|
||||||
|
@ -639,8 +640,8 @@ class InputMediaAudio(InputMedia):
|
||||||
|removed_thumb_note|
|
|removed_thumb_note|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
media (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
media (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` | \
|
||||||
:class:`telegram.Audio`): File to send. |fileinputnopath|
|
:class:`pathlib.Path` | :class:`telegram.Audio`): File to send. |fileinputnopath|
|
||||||
Lastly you can pass an existing :class:`telegram.Audio` object to send.
|
Lastly you can pass an existing :class:`telegram.Audio` object to send.
|
||||||
|
|
||||||
.. versionchanged:: 13.2
|
.. versionchanged:: 13.2
|
||||||
|
@ -743,8 +744,8 @@ class InputMediaDocument(InputMedia):
|
||||||
|removed_thumb_note|
|
|removed_thumb_note|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
media (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
media (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` \
|
||||||
:class:`telegram.Document`): File to send. |fileinputnopath|
|
| :class:`pathlib.Path` | :class:`telegram.Document`): File to send. |fileinputnopath|
|
||||||
Lastly you can pass an existing :class:`telegram.Document` object to send.
|
Lastly you can pass an existing :class:`telegram.Document` object to send.
|
||||||
|
|
||||||
.. versionchanged:: 13.2
|
.. versionchanged:: 13.2
|
||||||
|
|
|
@ -41,7 +41,8 @@ class InputSticker(TelegramObject):
|
||||||
order of the arguments has changed.
|
order of the arguments has changed.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
sticker (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path`): The
|
sticker (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` \
|
||||||
|
| :class:`pathlib.Path`): The
|
||||||
added sticker. |uploadinputnopath| Animated and video stickers can't be uploaded via
|
added sticker. |uploadinputnopath| Animated and video stickers can't be uploaded via
|
||||||
HTTP URL.
|
HTTP URL.
|
||||||
emoji_list (Sequence[:obj:`str`]): Sequence of
|
emoji_list (Sequence[:obj:`str`]): Sequence of
|
||||||
|
|
|
@ -61,14 +61,21 @@ def load_file(
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return None, cast(Union[bytes, "InputFile", str, Path], obj)
|
return None, cast(Union[bytes, "InputFile", str, Path], obj)
|
||||||
|
|
||||||
if hasattr(obj, "name") and not isinstance(obj.name, int):
|
filename = guess_file_name(obj)
|
||||||
filename = Path(obj.name).name
|
|
||||||
else:
|
|
||||||
filename = None
|
|
||||||
|
|
||||||
return filename, contents
|
return filename, contents
|
||||||
|
|
||||||
|
|
||||||
|
def guess_file_name(obj: FileInput) -> Optional[str]:
|
||||||
|
"""If the input is a file handle, read name and return it. Otherwise, return
|
||||||
|
the input unchanged.
|
||||||
|
"""
|
||||||
|
if hasattr(obj, "name") and not isinstance(obj.name, int):
|
||||||
|
return Path(obj.name).name
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def is_local_file(obj: Optional[FilePathInput]) -> bool:
|
def is_local_file(obj: Optional[FilePathInput]) -> bool:
|
||||||
"""
|
"""
|
||||||
Checks if a given string is a file on local system.
|
Checks if a given string is a file on local system.
|
||||||
|
@ -110,8 +117,8 @@ def parse_file_input( # pylint: disable=too-many-return-statements
|
||||||
attribute.
|
attribute.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
file_input (:obj:`str` | :obj:`bytes` | :term:`file object` | Telegram media object): The
|
file_input (:obj:`str` | :obj:`bytes` | :term:`file object` | :class:`~telegram.InputFile`\
|
||||||
input to parse.
|
| Telegram media object): The input to parse.
|
||||||
tg_type (:obj:`type`, optional): The Telegram media type the input can be. E.g.
|
tg_type (:obj:`type`, optional): The Telegram media type the input can be. E.g.
|
||||||
:class:`telegram.Animation`.
|
:class:`telegram.Animation`.
|
||||||
filename (:obj:`str`, optional): The filename. Only relevant in case an
|
filename (:obj:`str`, optional): The filename. Only relevant in case an
|
||||||
|
|
|
@ -82,7 +82,7 @@ ReplyMarkup = Union[
|
||||||
.. versionadded:: 20.0
|
.. versionadded:: 20.0
|
||||||
"""
|
"""
|
||||||
|
|
||||||
FieldTuple = Tuple[str, bytes, str]
|
FieldTuple = Tuple[str, Union[bytes, IO[bytes]], str]
|
||||||
"""Alias for return type of `InputFile.field_tuple`."""
|
"""Alias for return type of `InputFile.field_tuple`."""
|
||||||
UploadFileDict = Dict[str, FieldTuple]
|
UploadFileDict = Dict[str, FieldTuple]
|
||||||
"""Dictionary containing file data to be uploaded to the API."""
|
"""Dictionary containing file data to be uploaded to the API."""
|
||||||
|
|
|
@ -129,7 +129,11 @@ class RequestData:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def multipart_data(self) -> UploadFileDict:
|
def multipart_data(self) -> UploadFileDict:
|
||||||
"""Gives the files contained in this object as mapping of part name to encoded content."""
|
"""Gives the files contained in this object as mapping of part name to encoded content.
|
||||||
|
|
||||||
|
.. versionchanged:: NEXT.VERSION
|
||||||
|
Content may now be a file handle.
|
||||||
|
"""
|
||||||
multipart_data: UploadFileDict = {}
|
multipart_data: UploadFileDict = {}
|
||||||
for param in self._parameters:
|
for param in self._parameters:
|
||||||
m_data = param.multipart_data
|
m_data = param.multipart_data
|
||||||
|
|
|
@ -77,7 +77,11 @@ class RequestParameter:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def multipart_data(self) -> Optional[UploadFileDict]:
|
def multipart_data(self) -> Optional[UploadFileDict]:
|
||||||
"""A dict with the file data to upload, if any."""
|
"""A dict with the file data to upload, if any.
|
||||||
|
|
||||||
|
.. versionchanged:: NEXT.VERSION
|
||||||
|
Content may now be a file handle.
|
||||||
|
"""
|
||||||
if not self.input_files:
|
if not self.input_files:
|
||||||
return None
|
return None
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
import contextlib
|
import contextlib
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from io import BytesIO
|
from io import BufferedReader, BytesIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -66,21 +66,45 @@ class TestInputFileWithoutRequest:
|
||||||
assert input_file.attach_name is None
|
assert input_file.attach_name is None
|
||||||
assert input_file.attach_uri is None
|
assert input_file.attach_uri is None
|
||||||
|
|
||||||
def test_mimetypes(self):
|
@pytest.mark.parametrize("read_file_handle", [True, False])
|
||||||
|
def test_mimetypes_file_handle(self, read_file_handle):
|
||||||
# Only test a few to make sure logic works okay
|
# Only test a few to make sure logic works okay
|
||||||
assert InputFile(data_file("telegram.jpg").open("rb")).mimetype == "image/jpeg"
|
assert (
|
||||||
|
InputFile(
|
||||||
|
data_file("telegram.jpg").open("rb"), read_file_handle=read_file_handle
|
||||||
|
).mimetype
|
||||||
|
== "image/jpeg"
|
||||||
|
)
|
||||||
# For some reason python can guess the type on macOS
|
# For some reason python can guess the type on macOS
|
||||||
assert InputFile(data_file("telegram.webp").open("rb")).mimetype in [
|
assert InputFile(
|
||||||
|
data_file("telegram.webp").open("rb"), read_file_handle=read_file_handle
|
||||||
|
).mimetype in [
|
||||||
"application/octet-stream",
|
"application/octet-stream",
|
||||||
"image/webp",
|
"image/webp",
|
||||||
]
|
]
|
||||||
assert InputFile(data_file("telegram.mp3").open("rb")).mimetype == "audio/mpeg"
|
assert (
|
||||||
|
InputFile(
|
||||||
|
data_file("telegram.mp3").open("rb"), read_file_handle=read_file_handle
|
||||||
|
).mimetype
|
||||||
|
== "audio/mpeg"
|
||||||
|
)
|
||||||
# For some reason windows drops the trailing i
|
# For some reason windows drops the trailing i
|
||||||
assert InputFile(data_file("telegram.midi").open("rb")).mimetype in [
|
assert InputFile(
|
||||||
|
data_file("telegram.midi").open("rb"), read_file_handle=read_file_handle
|
||||||
|
).mimetype in [
|
||||||
"audio/mid",
|
"audio/mid",
|
||||||
"audio/midi",
|
"audio/midi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Test string file
|
||||||
|
assert (
|
||||||
|
InputFile(
|
||||||
|
data_file("text_file.txt").open("rb"), read_file_handle=read_file_handle
|
||||||
|
).mimetype
|
||||||
|
== "text/plain"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_mimetypes_other(self):
|
||||||
# Test guess from file
|
# Test guess from file
|
||||||
assert InputFile(BytesIO(b"blah"), filename="tg.jpg").mimetype == "image/jpeg"
|
assert InputFile(BytesIO(b"blah"), filename="tg.jpg").mimetype == "image/jpeg"
|
||||||
assert InputFile(BytesIO(b"blah"), filename="tg.mp3").mimetype == "audio/mpeg"
|
assert InputFile(BytesIO(b"blah"), filename="tg.mp3").mimetype == "audio/mpeg"
|
||||||
|
@ -92,20 +116,49 @@ class TestInputFileWithoutRequest:
|
||||||
)
|
)
|
||||||
assert InputFile(BytesIO(b"blah")).mimetype == "application/octet-stream"
|
assert InputFile(BytesIO(b"blah")).mimetype == "application/octet-stream"
|
||||||
|
|
||||||
# Test string file
|
@pytest.mark.parametrize("read_file_handle", [True, False])
|
||||||
assert InputFile(data_file("text_file.txt").open()).mimetype == "text/plain"
|
def test_filenames(self, read_file_handle):
|
||||||
|
|
||||||
def test_filenames(self):
|
|
||||||
assert InputFile(data_file("telegram.jpg").open("rb")).filename == "telegram.jpg"
|
|
||||||
assert InputFile(data_file("telegram.jpg").open("rb"), filename="blah").filename == "blah"
|
|
||||||
assert (
|
assert (
|
||||||
InputFile(data_file("telegram.jpg").open("rb"), filename="blah.jpg").filename
|
InputFile(
|
||||||
|
data_file("telegram.jpg").open("rb"), read_file_handle=read_file_handle
|
||||||
|
).filename
|
||||||
|
== "telegram.jpg"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
InputFile(
|
||||||
|
data_file("telegram.jpg").open("rb"),
|
||||||
|
filename="blah",
|
||||||
|
read_file_handle=read_file_handle,
|
||||||
|
).filename
|
||||||
|
== "blah"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
InputFile(
|
||||||
|
data_file("telegram.jpg").open("rb"),
|
||||||
|
filename="blah.jpg",
|
||||||
|
read_file_handle=read_file_handle,
|
||||||
|
).filename
|
||||||
== "blah.jpg"
|
== "blah.jpg"
|
||||||
)
|
)
|
||||||
assert InputFile(data_file("telegram").open("rb")).filename == "telegram"
|
|
||||||
assert InputFile(data_file("telegram").open("rb"), filename="blah").filename == "blah"
|
|
||||||
assert (
|
assert (
|
||||||
InputFile(data_file("telegram").open("rb"), filename="blah.jpg").filename == "blah.jpg"
|
InputFile(data_file("telegram").open("rb"), read_file_handle=read_file_handle).filename
|
||||||
|
== "telegram"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
InputFile(
|
||||||
|
data_file("telegram").open("rb"),
|
||||||
|
filename="blah",
|
||||||
|
read_file_handle=read_file_handle,
|
||||||
|
).filename
|
||||||
|
== "blah"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
InputFile(
|
||||||
|
data_file("telegram").open("rb"),
|
||||||
|
filename="blah.jpg",
|
||||||
|
read_file_handle=read_file_handle,
|
||||||
|
).filename
|
||||||
|
== "blah.jpg"
|
||||||
)
|
)
|
||||||
|
|
||||||
class MockedFileobject:
|
class MockedFileobject:
|
||||||
|
@ -140,6 +193,19 @@ class TestInputFileWithoutRequest:
|
||||||
== "blah.jpg"
|
== "blah.jpg"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("read_file_handle", [True, False])
|
||||||
|
def test_read_file_handle(self, read_file_handle):
|
||||||
|
input_file = InputFile(
|
||||||
|
data_file("telegram.jpg").open("rb"), read_file_handle=read_file_handle
|
||||||
|
)
|
||||||
|
content = input_file.field_tuple[1]
|
||||||
|
if read_file_handle:
|
||||||
|
assert isinstance(content, bytes)
|
||||||
|
assert content == data_file("telegram.jpg").read_bytes()
|
||||||
|
else:
|
||||||
|
assert isinstance(content, BufferedReader)
|
||||||
|
assert content.read() == data_file("telegram.jpg").read_bytes()
|
||||||
|
|
||||||
|
|
||||||
class TestInputFileWithRequest:
|
class TestInputFileWithRequest:
|
||||||
async def test_send_bytes(self, bot, chat_id):
|
async def test_send_bytes(self, bot, chat_id):
|
||||||
|
|
|
@ -30,6 +30,7 @@ import httpx
|
||||||
import pytest
|
import pytest
|
||||||
from httpx import AsyncHTTPTransport
|
from httpx import AsyncHTTPTransport
|
||||||
|
|
||||||
|
from telegram import InputFile
|
||||||
from telegram._utils.defaultvalue import DEFAULT_NONE
|
from telegram._utils.defaultvalue import DEFAULT_NONE
|
||||||
from telegram._utils.strings import TextEncoding
|
from telegram._utils.strings import TextEncoding
|
||||||
from telegram.error import (
|
from telegram.error import (
|
||||||
|
@ -48,6 +49,7 @@ from telegram.request._httpxrequest import HTTPXRequest
|
||||||
from telegram.request._requestparameter import RequestParameter
|
from telegram.request._requestparameter import RequestParameter
|
||||||
from telegram.warnings import PTBDeprecationWarning
|
from telegram.warnings import PTBDeprecationWarning
|
||||||
from tests.auxil.envvars import TEST_WITH_OPT_DEPS
|
from tests.auxil.envvars import TEST_WITH_OPT_DEPS
|
||||||
|
from tests.auxil.files import data_file
|
||||||
from tests.auxil.networking import NonchalantHttpxRequest
|
from tests.auxil.networking import NonchalantHttpxRequest
|
||||||
from tests.auxil.slots import mro_slots
|
from tests.auxil.slots import mro_slots
|
||||||
|
|
||||||
|
@ -821,3 +823,15 @@ class TestHTTPXRequestWithRequest:
|
||||||
task_2.exception()
|
task_2.exception()
|
||||||
except (asyncio.CancelledError, asyncio.InvalidStateError):
|
except (asyncio.CancelledError, asyncio.InvalidStateError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
async def test_input_file_postponed_read(self, bot, chat_id):
|
||||||
|
"""Here we test that `read_file_handle=False` is correctly handled by HTTPXRequest.
|
||||||
|
Since manually building the RequestData object has no real benefit, we simply use the Bot
|
||||||
|
for that.
|
||||||
|
"""
|
||||||
|
message = await bot.send_document(
|
||||||
|
document=InputFile(data_file("telegram.jpg").open("rb"), read_file_handle=False),
|
||||||
|
chat_id=chat_id,
|
||||||
|
)
|
||||||
|
assert message.document
|
||||||
|
assert message.document.file_name == "telegram.jpg"
|
||||||
|
|
|
@ -117,7 +117,7 @@ PTB_EXTRA_PARAMS = {
|
||||||
"PassportElementError": {"source", "type", "message"},
|
"PassportElementError": {"source", "type", "message"},
|
||||||
"InputMedia": {"caption", "caption_entities", "media", "media_type", "parse_mode"},
|
"InputMedia": {"caption", "caption_entities", "media", "media_type", "parse_mode"},
|
||||||
"InputMedia(Animation|Audio|Document|Photo|Video|VideoNote|Voice)": {"filename"},
|
"InputMedia(Animation|Audio|Document|Photo|Video|VideoNote|Voice)": {"filename"},
|
||||||
"InputFile": {"attach", "filename", "obj"},
|
"InputFile": {"attach", "filename", "obj", "read_file_handle"},
|
||||||
"MaybeInaccessibleMessage": {"date", "message_id", "chat"}, # attributes common to all subcls
|
"MaybeInaccessibleMessage": {"date", "message_id", "chat"}, # attributes common to all subcls
|
||||||
"ChatBoostSource": {"source"}, # attributes common to all subclasses
|
"ChatBoostSource": {"source"}, # attributes common to all subclasses
|
||||||
"MessageOrigin": {"type", "date"}, # attributes common to all subclasses
|
"MessageOrigin": {"type", "date"}, # attributes common to all subclasses
|
||||||
|
|
Loading…
Reference in a new issue