mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2024-12-22 06:25:12 +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:
|
||||
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
|
||||
photo (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
||||
:class:`telegram.PhotoSize`): Photo to send.
|
||||
photo (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` \
|
||||
| :class:`pathlib.Path` | :class:`telegram.PhotoSize`): Photo to send.
|
||||
|fileinput|
|
||||
Lastly you can pass an existing :class:`telegram.PhotoSize` object to send.
|
||||
|
||||
|
@ -1465,9 +1465,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
|
|||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
|
||||
audio (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
||||
:class:`telegram.Audio`): Audio file to send.
|
||||
|fileinput|
|
||||
audio (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | \
|
||||
:obj:`bytes` | :class:`pathlib.Path` | :class:`telegram.Audio`): Audio file to
|
||||
send. |fileinput|
|
||||
Lastly you can pass an existing :class:`telegram.Audio` object to send.
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
|
@ -1617,8 +1617,8 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
|
|||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
|
||||
document (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
||||
:class:`telegram.Document`): File to send.
|
||||
document (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | \
|
||||
:obj:`bytes` | :class:`pathlib.Path` | :class:`telegram.Document`): File to send.
|
||||
|fileinput|
|
||||
Lastly you can pass an existing :class:`telegram.Document` object to send.
|
||||
|
||||
|
@ -1755,8 +1755,8 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
|
|||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
|
||||
sticker (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
||||
:class:`telegram.Sticker`): Sticker to send.
|
||||
sticker (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | \
|
||||
: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
|
||||
stickers can't be sent via an HTTP URL.
|
||||
|
||||
|
@ -1895,8 +1895,8 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
|
|||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
|
||||
video (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
||||
:class:`telegram.Video`): Video file to send.
|
||||
video (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` \
|
||||
| :class:`pathlib.Path` | :class:`telegram.Video`): Video file to send.
|
||||
|fileinput|
|
||||
Lastly you can pass an existing :class:`telegram.Video` object to send.
|
||||
|
||||
|
@ -2059,8 +2059,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
|
|||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
|
||||
video_note (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
||||
:class:`telegram.VideoNote`): Video note to send.
|
||||
video_note (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | \
|
||||
: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.
|
||||
|uploadinput|
|
||||
|
@ -2209,9 +2210,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
|
|||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
|
||||
animation (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
||||
:class:`telegram.Animation`): Animation to send.
|
||||
|fileinput|
|
||||
animation (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | \
|
||||
:obj:`bytes` | :class:`pathlib.Path` | :class:`telegram.Animation`): Animation to
|
||||
send. |fileinput|
|
||||
Lastly you can pass an existing :class:`telegram.Animation` object to send.
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
|
@ -2371,8 +2372,8 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
|
|||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
|
||||
voice (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
||||
:class:`telegram.Voice`): Voice file to send.
|
||||
voice (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` \
|
||||
| :class:`pathlib.Path` | :class:`telegram.Voice`): Voice file to send.
|
||||
|fileinput|
|
||||
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:
|
||||
user_id (:obj:`int`): User identifier of sticker file owner.
|
||||
sticker (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path`):
|
||||
A file with the sticker in the ``".WEBP"``, ``".PNG"``, ``".TGS"`` or ``".WEBM"``
|
||||
sticker (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | \
|
||||
: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
|
||||
. |uploadinput|
|
||||
|
||||
|
@ -6695,8 +6697,9 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
|
|||
|
||||
.. versionadded:: 21.1
|
||||
|
||||
thumbnail (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path`, \
|
||||
optional): A **.WEBP** or **.PNG** image with the thumbnail, must
|
||||
thumbnail (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | \
|
||||
: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`
|
||||
kilobytes in size and have width and height of exactly
|
||||
:tg-const:`telegram.constants.StickerSetLimit.STATIC_THUMB_DIMENSIONS` px, or a
|
||||
|
|
|
@ -22,7 +22,7 @@ import mimetypes
|
|||
from typing import IO, Optional, Union
|
||||
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.types import FieldTuple
|
||||
|
||||
|
@ -53,9 +53,36 @@ class InputFile:
|
|||
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`.
|
||||
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:
|
||||
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
|
||||
the request to Telegram should point to the multipart data via a an URI of the form
|
||||
``attach://<attach_name>`` URI.
|
||||
|
@ -71,14 +98,18 @@ class InputFile:
|
|||
obj: Union[IO[bytes], bytes, str],
|
||||
filename: Optional[str] = None,
|
||||
attach: bool = False,
|
||||
read_file_handle: bool = True,
|
||||
):
|
||||
if isinstance(obj, bytes):
|
||||
self.input_file_content: bytes = obj
|
||||
self.input_file_content: Union[bytes, IO[bytes]] = obj
|
||||
elif isinstance(obj, str):
|
||||
self.input_file_content = obj.encode(TextEncoding.UTF_8)
|
||||
else:
|
||||
elif read_file_handle:
|
||||
reported_filename, self.input_file_content = load_file(obj)
|
||||
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
|
||||
|
||||
|
@ -95,8 +126,11 @@ class InputFile:
|
|||
def field_tuple(self) -> FieldTuple:
|
||||
"""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:
|
||||
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
|
||||
|
||||
|
|
|
@ -50,8 +50,8 @@ class InputMedia(TelegramObject):
|
|||
|
||||
Args:
|
||||
media_type (:obj:`str`): Type of media that the instance represents.
|
||||
media (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
||||
:class:`telegram.Animation` | :class:`telegram.Audio` | \
|
||||
media (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` | \
|
||||
:class:`pathlib.Path` | :class:`telegram.Animation` | :class:`telegram.Audio` | \
|
||||
:class:`telegram.Document` | :class:`telegram.PhotoSize` | \
|
||||
:class:`telegram.Video`): File to send.
|
||||
|fileinputnopath|
|
||||
|
@ -128,8 +128,9 @@ class InputPaidMedia(TelegramObject):
|
|||
|
||||
Args:
|
||||
type (:obj:`str`): Type of media that the instance represents.
|
||||
media (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
||||
:class:`telegram.PhotoSize` | :class:`telegram.Video`): File to send. |fileinputnopath|
|
||||
media (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` | \
|
||||
: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
|
||||
to send.
|
||||
|
||||
|
@ -167,8 +168,8 @@ class InputPaidMediaPhoto(InputPaidMedia):
|
|||
.. versionadded:: 21.4
|
||||
|
||||
Args:
|
||||
media (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
||||
:class:`telegram.PhotoSize`): File to send. |fileinputnopath|
|
||||
media (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` | \
|
||||
:class:`pathlib.Path` | :class:`telegram.PhotoSize`): File to send. |fileinputnopath|
|
||||
Lastly you can pass an existing :class:`telegram.PhotoSize` object to send.
|
||||
|
||||
Attributes:
|
||||
|
@ -207,8 +208,8 @@ class InputPaidMediaVideo(InputPaidMedia):
|
|||
changed by Telegram.
|
||||
|
||||
Args:
|
||||
media (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
||||
:class:`telegram.Video`): File to send. |fileinputnopath|
|
||||
media (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` | \
|
||||
:class:`pathlib.Path` | :class:`telegram.Video`): File to send. |fileinputnopath|
|
||||
Lastly you can pass an existing :class:`telegram.Video` object to send.
|
||||
thumbnail (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \
|
||||
optional): |thumbdocstringnopath|
|
||||
|
@ -278,8 +279,8 @@ class InputMediaAnimation(InputMedia):
|
|||
|removed_thumb_note|
|
||||
|
||||
Args:
|
||||
media (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
||||
:class:`telegram.Animation`): File to send. |fileinputnopath|
|
||||
media (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` | \
|
||||
:class:`pathlib.Path` | :class:`telegram.Animation`): File to send. |fileinputnopath|
|
||||
Lastly you can pass an existing :class:`telegram.Animation` object to send.
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
|
@ -401,8 +402,8 @@ class InputMediaPhoto(InputMedia):
|
|||
.. seealso:: :wiki:`Working with Files and Media <Working-with-Files-and-Media>`
|
||||
|
||||
Args:
|
||||
media (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
||||
:class:`telegram.PhotoSize`): File to send. |fileinputnopath|
|
||||
media (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` | \
|
||||
:class:`pathlib.Path` | :class:`telegram.PhotoSize`): File to send. |fileinputnopath|
|
||||
Lastly you can pass an existing :class:`telegram.PhotoSize` object to send.
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
|
@ -501,8 +502,8 @@ class InputMediaVideo(InputMedia):
|
|||
|removed_thumb_note|
|
||||
|
||||
Args:
|
||||
media (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
||||
:class:`telegram.Video`): File to send. |fileinputnopath|
|
||||
media (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` | \
|
||||
:class:`pathlib.Path` | :class:`telegram.Video`): File to send. |fileinputnopath|
|
||||
Lastly you can pass an existing :class:`telegram.Video` object to send.
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
|
@ -639,8 +640,8 @@ class InputMediaAudio(InputMedia):
|
|||
|removed_thumb_note|
|
||||
|
||||
Args:
|
||||
media (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
||||
:class:`telegram.Audio`): File to send. |fileinputnopath|
|
||||
media (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` | \
|
||||
:class:`pathlib.Path` | :class:`telegram.Audio`): File to send. |fileinputnopath|
|
||||
Lastly you can pass an existing :class:`telegram.Audio` object to send.
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
|
@ -743,8 +744,8 @@ class InputMediaDocument(InputMedia):
|
|||
|removed_thumb_note|
|
||||
|
||||
Args:
|
||||
media (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
|
||||
:class:`telegram.Document`): File to send. |fileinputnopath|
|
||||
media (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` \
|
||||
| :class:`pathlib.Path` | :class:`telegram.Document`): File to send. |fileinputnopath|
|
||||
Lastly you can pass an existing :class:`telegram.Document` object to send.
|
||||
|
||||
.. versionchanged:: 13.2
|
||||
|
|
|
@ -41,7 +41,8 @@ class InputSticker(TelegramObject):
|
|||
order of the arguments has changed.
|
||||
|
||||
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
|
||||
HTTP URL.
|
||||
emoji_list (Sequence[:obj:`str`]): Sequence of
|
||||
|
|
|
@ -61,14 +61,21 @@ def load_file(
|
|||
except AttributeError:
|
||||
return None, cast(Union[bytes, "InputFile", str, Path], obj)
|
||||
|
||||
if hasattr(obj, "name") and not isinstance(obj.name, int):
|
||||
filename = Path(obj.name).name
|
||||
else:
|
||||
filename = None
|
||||
filename = guess_file_name(obj)
|
||||
|
||||
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:
|
||||
"""
|
||||
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.
|
||||
|
||||
Args:
|
||||
file_input (:obj:`str` | :obj:`bytes` | :term:`file object` | Telegram media object): The
|
||||
input to parse.
|
||||
file_input (:obj:`str` | :obj:`bytes` | :term:`file object` | :class:`~telegram.InputFile`\
|
||||
| Telegram media object): The input to parse.
|
||||
tg_type (:obj:`type`, optional): The Telegram media type the input can be. E.g.
|
||||
:class:`telegram.Animation`.
|
||||
filename (:obj:`str`, optional): The filename. Only relevant in case an
|
||||
|
|
|
@ -82,7 +82,7 @@ ReplyMarkup = Union[
|
|||
.. versionadded:: 20.0
|
||||
"""
|
||||
|
||||
FieldTuple = Tuple[str, bytes, str]
|
||||
FieldTuple = Tuple[str, Union[bytes, IO[bytes]], str]
|
||||
"""Alias for return type of `InputFile.field_tuple`."""
|
||||
UploadFileDict = Dict[str, FieldTuple]
|
||||
"""Dictionary containing file data to be uploaded to the API."""
|
||||
|
|
|
@ -129,7 +129,11 @@ class RequestData:
|
|||
|
||||
@property
|
||||
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 = {}
|
||||
for param in self._parameters:
|
||||
m_data = param.multipart_data
|
||||
|
|
|
@ -77,7 +77,11 @@ class RequestParameter:
|
|||
|
||||
@property
|
||||
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:
|
||||
return None
|
||||
return {
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
import contextlib
|
||||
import subprocess
|
||||
import sys
|
||||
from io import BytesIO
|
||||
from io import BufferedReader, BytesIO
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -66,21 +66,45 @@ class TestInputFileWithoutRequest:
|
|||
assert input_file.attach_name 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
|
||||
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
|
||||
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",
|
||||
"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
|
||||
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/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
|
||||
assert InputFile(BytesIO(b"blah"), filename="tg.jpg").mimetype == "image/jpeg"
|
||||
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"
|
||||
|
||||
# Test string file
|
||||
assert InputFile(data_file("text_file.txt").open()).mimetype == "text/plain"
|
||||
|
||||
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"
|
||||
@pytest.mark.parametrize("read_file_handle", [True, False])
|
||||
def test_filenames(self, read_file_handle):
|
||||
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"
|
||||
)
|
||||
assert InputFile(data_file("telegram").open("rb")).filename == "telegram"
|
||||
assert InputFile(data_file("telegram").open("rb"), filename="blah").filename == "blah"
|
||||
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:
|
||||
|
@ -140,6 +193,19 @@ class TestInputFileWithoutRequest:
|
|||
== "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:
|
||||
async def test_send_bytes(self, bot, chat_id):
|
||||
|
|
|
@ -30,6 +30,7 @@ import httpx
|
|||
import pytest
|
||||
from httpx import AsyncHTTPTransport
|
||||
|
||||
from telegram import InputFile
|
||||
from telegram._utils.defaultvalue import DEFAULT_NONE
|
||||
from telegram._utils.strings import TextEncoding
|
||||
from telegram.error import (
|
||||
|
@ -48,6 +49,7 @@ from telegram.request._httpxrequest import HTTPXRequest
|
|||
from telegram.request._requestparameter import RequestParameter
|
||||
from telegram.warnings import PTBDeprecationWarning
|
||||
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.slots import mro_slots
|
||||
|
||||
|
@ -821,3 +823,15 @@ class TestHTTPXRequestWithRequest:
|
|||
task_2.exception()
|
||||
except (asyncio.CancelledError, asyncio.InvalidStateError):
|
||||
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"},
|
||||
"InputMedia": {"caption", "caption_entities", "media", "media_type", "parse_mode"},
|
||||
"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
|
||||
"ChatBoostSource": {"source"}, # attributes common to all subclasses
|
||||
"MessageOrigin": {"type", "date"}, # attributes common to all subclasses
|
||||
|
|
Loading…
Reference in a new issue