Skip to content

ResourceWarning: unclosed file when passing pathlib.Path to send_photo/send_document #4907

@vivodi

Description

@vivodi

Steps to Reproduce

  1. Install python-telegram-bot, pytest and pytest-asyncio.
  2. Prepare a photo (e.g., photo.png).
  3. Use the following minimal pytest test case, which enables ResourceWarning as an error.
from pathlib import Path
import pytest
from telegram.ext import ApplicationBuilder

@pytest.mark.filterwarnings("error::ResourceWarning")
@pytest.mark.asyncio
async def test_send_photo_with_path_generates_warning(tmp_path):
    async with (
        ApplicationBuilder()
                .token("******:************")
                .build()
                .bot as bot
    ):
        await bot.send_photo(chat_id='*********', photo=Path(r"C:\Users\vivodi\Downloads\logo.png"))
  1. Run the test with pytest.

Expected behaviour

The method should internally manage the file handle it creates. It should open the file from the provided path, read its contents, and then immediately close the file handle.

No ResourceWarning should be emitted. The pytest test above should pass without errors.

Actual behaviour

When passing a pathlib.Path object to bot.send_photo() or bot.send_document(), the underlying file is opened but is not subsequently closed. This results in a ResourceWarning when the file object is garbage collected.

This becomes a critical issue in environments that treat warnings as errors, such as in a CI/CD pipeline using pytest with the -W error flag, causing tests to fail.

The file handle created from the pathlib.Path object is not closed. This generates a ResourceWarning.

The pytest test fails with the following traceback, pointing to the unclosed file resource.

E               ResourceWarning: unclosed file <_io.BufferedReader name='...\\photo.png'>
.../venv/lib/site-packages/telegram/_utils/files.py:148: ResourceWarning

=========================== short test summary info ===========================
FAILED test_file_handling.py::test_send_photo_with_path_generates_warning

The issue originates in telegram._utils.files.parse_file_input, where path.open() is called but the returned file object is never closed.

if isinstance(file_input, (str, Path)):
if is_local_file(file_input):
path = Path(file_input)
if local_mode:
return path.absolute().as_uri()
return InputFile(path.open(mode="rb"), filename=filename, attach=attach)

Operating System

Windows 11

Version of Python, python-telegram-bot & dependencies

22.3

Relevant log output

============================= test session starts =============================
platform win32 -- Python 3.14.0rc1, pytest-8.4.1, pluggy-1.6.0 -- C:\Users\vivodi\Projects\Flexget\.venv\Scripts\python.exe
cachedir: .pytest_cache
rootdir: C:\Users\vivodi\Projects\Flexget
configfile: pyproject.toml
plugins: anyio-4.10.0, asyncio-1.1.0, cov-6.2.1, xdist-3.8.0
asyncio: mode=Mode.STRICT, asyncio_default_fixture_loop_scope=None, asyncio_default_test_loop_scope=function
created: 12/12 workers
12 workers [1 item]

scheduling tests via LoadGroupScheduling

tests/test_t.py::test_send_photo_with_path_generates_warning 

================================== FAILURES ===================================
_________________ test_send_photo_with_path_generates_warning _________________
[gw0] win32 -- Python 3.14.0 C:\Users\vivodi\Projects\Flexget\.venv\Scripts\python.exe

file_input = WindowsPath('C:/Users/vivodi/Downloads/logo.png')
tg_type = <class 'telegram._files.photosize.PhotoSize'>, filename = None
attach = False, local_mode = False

    def parse_file_input(  # pylint: disable=too-many-return-statements
        file_input: Union[FileInput, "TelegramObject"],
        tg_type: Optional[type["TelegramObject"]] = None,
        filename: Optional[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:
    
            * 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``
          attribute.
    
        Args:
            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
                :class:`telegram.InputFile` is returned.
            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
            :attr:`file_input`, in case it's no valid file input.
        """
        # Importing on file-level yields cyclic Import Errors
        from telegram import InputFile  # pylint: disable=import-outside-toplevel  # noqa: PLC0415
    
        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):
                path = Path(file_input)
                if local_mode:
                    return path.absolute().as_uri()
>               return InputFile(path.open(mode="rb"), filename=filename, attach=attach)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E               ResourceWarning: unclosed file <_io.BufferedReader name='C:\\Users\\vivodi\\Downloads\\logo.png'>

C:\Users\vivodi\Projects\Flexget\.venv\Lib\site-packages\telegram\_utils\files.py:148: ResourceWarning

The above exception was the direct cause of the following exception:

cls = <class '_pytest.runner.CallInfo'>
func = <function call_and_report.<locals>.<lambda> at 0x0000014645816C40>
when = 'call'
reraise = (<class '_pytest.outcomes.Exit'>, <class 'KeyboardInterrupt'>)

    @classmethod
    def from_call(
        cls,
        func: Callable[[], TResult],
        when: Literal["collect", "setup", "call", "teardown"],
        reraise: type[BaseException] | tuple[type[BaseException], ...] | None = None,
    ) -> CallInfo[TResult]:
        """Call func, wrapping the result in a CallInfo.
    
        :param func:
            The function to call. Called without arguments.
        :type func: Callable[[], _pytest.runner.TResult]
        :param when:
            The phase in which the function is called.
        :param reraise:
            Exception or exceptions that shall propagate if raised by the
            function, instead of being wrapped in the CallInfo.
        """
        excinfo = None
        instant = timing.Instant()
        try:
>           result: TResult | None = func()
                                     ^^^^^^

C:\Users\vivodi\Projects\Flexget\.venv\Lib\site-packages\_pytest\runner.py:344: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
C:\Users\vivodi\Projects\Flexget\.venv\Lib\site-packages\_pytest\runner.py:246: in <lambda>
    lambda: runtest_hook(item=item, **kwds), when=when, reraise=reraise
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
C:\Users\vivodi\Projects\Flexget\.venv\Lib\site-packages\pluggy\_hooks.py:512: in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
C:\Users\vivodi\Projects\Flexget\.venv\Lib\site-packages\pluggy\_manager.py:120: in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
C:\Users\vivodi\Projects\Flexget\.venv\Lib\site-packages\_pytest\logging.py:850: in pytest_runtest_call
    yield
C:\Users\vivodi\Projects\Flexget\.venv\Lib\site-packages\_pytest\capture.py:900: in pytest_runtest_call
    return (yield)
            ^^^^^
C:\Users\vivodi\Projects\Flexget\.venv\Lib\site-packages\_pytest\skipping.py:263: in pytest_runtest_call
    return (yield)
            ^^^^^
C:\Users\vivodi\Projects\Flexget\.venv\Lib\site-packages\_pytest\unraisableexception.py:158: in pytest_runtest_call
    collect_unraisable(item.config)
C:\Users\vivodi\Projects\Flexget\.venv\Lib\site-packages\_pytest\unraisableexception.py:79: in collect_unraisable
    raise errors[0]
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

config = <_pytest.config.Config object at 0x000001463C1C2120>

    def collect_unraisable(config: Config) -> None:
        pop_unraisable = config.stash[unraisable_exceptions].pop
        errors: list[pytest.PytestUnraisableExceptionWarning | RuntimeError] = []
        meta = None
        hook_error = None
        try:
            while True:
                try:
                    meta = pop_unraisable()
                except IndexError:
                    break
    
                if isinstance(meta, BaseException):
                    hook_error = RuntimeError("Failed to process unraisable exception")
                    hook_error.__cause__ = meta
                    errors.append(hook_error)
                    continue
    
                msg = meta.msg
                try:
>                   warnings.warn(pytest.PytestUnraisableExceptionWarning(msg))
E                   pytest.PytestUnraisableExceptionWarning: Exception ignored while finalizing file <_io.FileIO name='C:\\Users\\vivodi\\Downloads\\logo.png' mode='rb' closefd=True>: None

C:\Users\vivodi\Projects\Flexget\.venv\Lib\site-packages\_pytest\unraisableexception.py:67: PytestUnraisableExceptionWarning
----------------------------- Captured log setup ------------------------------
DEBUG    asyncio:proactor_events.py:631 Using proactor: IocpProactor
------------------------------ Captured log call ------------------------------
DEBUG    telegram.ext.ExtBot:_bot.py:333 Set Bot API URL: https://api.telegram.org/bot************:************************
DEBUG    telegram.ext.ExtBot:_bot.py:334 Set Bot API File URL: https://api.telegram.org/file/bot************:************************
DEBUG    telegram.ext.ExtBot:_bot.py:726 Calling Bot API endpoint `getMe` with parameters `{}`
DEBUG    httpcore.connection:_trace.py:87 connect_tcp.started host='api.telegram.org' port=443 local_address=None timeout=5.0 socket_options=None
DEBUG    httpcore.connection:_trace.py:87 connect_tcp.complete return_value=<httpcore._backends.anyio.AnyIOStream object at 0x0000014645C1E900>
DEBUG    httpcore.connection:_trace.py:87 start_tls.started ssl_context=<ssl.SSLContext object at 0x0000014645A13110> server_hostname='api.telegram.org' timeout=5.0
DEBUG    httpcore.connection:_trace.py:87 start_tls.complete return_value=<httpcore._backends.anyio.AnyIOStream object at 0x0000014645C225D0>
DEBUG    httpcore.http11:_trace.py:87 send_request_headers.started request=<Request [b'POST']>
DEBUG    httpcore.http11:_trace.py:87 send_request_headers.complete
DEBUG    httpcore.http11:_trace.py:87 send_request_body.started request=<Request [b'POST']>
DEBUG    httpcore.http11:_trace.py:87 send_request_body.complete
DEBUG    httpcore.http11:_trace.py:87 receive_response_headers.started request=<Request [b'POST']>
DEBUG    httpcore.http11:_trace.py:87 receive_response_headers.complete return_value=(b'HTTP/1.1', 200, b'OK', [(b'Server', b'nginx/1.18.0'), (b'Date', b'Wed, 13 Aug 2025 12:33:36 GMT'), (b'Content-Type', b'application/json'), (b'Content-Length', b'251'), (b'Connection', b'keep-alive'), (b'Strict-Transport-Security', b'max-age=31536000; includeSubDomains; preload'), (b'Access-Control-Allow-Origin', b'*'), (b'Access-Control-Allow-Methods', b'GET, POST, OPTIONS'), (b'Access-Control-Expose-Headers', b'Content-Length,Content-Type,Date,Server,Connection')])
INFO     httpx:_client.py:1740 HTTP Request: POST https://api.telegram.org/bot************:************************/getMe "HTTP/1.1 200 OK"
DEBUG    httpcore.http11:_trace.py:87 receive_response_body.started request=<Request [b'POST']>
DEBUG    httpcore.http11:_trace.py:87 receive_response_body.complete
DEBUG    httpcore.http11:_trace.py:87 response_closed.started
DEBUG    httpcore.http11:_trace.py:87 response_closed.complete
DEBUG    telegram.ext.ExtBot:_bot.py:735 Call to Bot API endpoint `getMe` finished with return value `{'id': 5187592617, 'is_bot': True, 'first_name': 'oneofptbots', 'username': 'oneofptbot', 'can_join_groups': True, 'can_read_all_group_messages': False, 'supports_inline_queries': False, 'can_connect_to_business': False, 'has_main_web_app': False}`
DEBUG    telegram.ext.ExtBot:_bot.py:726 Calling Bot API endpoint `sendPhoto` with parameters `{'chat_id': '12345678', 'photo': <telegram._files.inputfile.InputFile object at 0x0000014645C13B00>}`
DEBUG    httpcore.http11:_trace.py:87 send_request_headers.started request=<Request [b'POST']>
DEBUG    httpcore.http11:_trace.py:87 send_request_headers.complete
DEBUG    httpcore.http11:_trace.py:87 send_request_body.started request=<Request [b'POST']>
DEBUG    httpcore.http11:_trace.py:87 send_request_body.complete
DEBUG    httpcore.http11:_trace.py:87 receive_response_headers.started request=<Request [b'POST']>
DEBUG    httpcore.http11:_trace.py:87 receive_response_headers.complete return_value=(b'HTTP/1.1', 200, b'OK', [(b'Server', b'nginx/1.18.0'), (b'Date', b'Wed, 13 Aug 2025 12:33:37 GMT'), (b'Content-Type', b'application/json'), (b'Content-Length', b'778'), (b'Connection', b'keep-alive'), (b'Strict-Transport-Security', b'max-age=31536000; includeSubDomains; preload'), (b'Access-Control-Allow-Origin', b'*'), (b'Access-Control-Allow-Methods', b'GET, POST, OPTIONS'), (b'Access-Control-Expose-Headers', b'Content-Length,Content-Type,Date,Server,Connection')])
INFO     httpx:_client.py:1740 HTTP Request: POST https://api.telegram.org/bot************:************************/sendPhoto "HTTP/1.1 200 OK"
DEBUG    httpcore.http11:_trace.py:87 receive_response_body.started request=<Request [b'POST']>
DEBUG    httpcore.http11:_trace.py:87 receive_response_body.complete
DEBUG    httpcore.http11:_trace.py:87 response_closed.started
DEBUG    httpcore.http11:_trace.py:87 response_closed.complete
DEBUG    telegram.ext.ExtBot:_bot.py:735 Call to Bot API endpoint `sendPhoto` finished with return value `{'message_id': 3095, 'from': {'id': 5187592617, 'is_bot': True, 'first_name': 'oneofptbots', 'username': 'oneofptbot'}, 'chat': {'id': 12345678, 'first_name': 'o', 'last_name': '0', 'username': 'chehxlpp', 'type': 'private'}, 'date': 1755088417, 'photo': [{'file_id': 'AgACAgUAAxkDAAIMFGichK1MJVfAlG8CFpMCpXAcaJF2AAIPyjEbvBroVBfWvmfZKAfVAQADAgADcwADNgQ', 'file_unique_id': 'AQADD8oxG7wa6FR4', 'file_size': 780, 'width': 90, 'height': 18}, {'file_id': 'AgACAgUAAxkDAAIMFGichK1MJVfAlG8CFpMCpXAcaJF2AAIPyjEbvBroVBfWvmfZKAfVAQADAgADbQADNgQ', 'file_unique_id': 'AQADD8oxG7wa6FRy', 'file_size': 5446, 'width': 320, 'height': 65}, {'file_id': 'AgACAgUAAxkDAAIMFGichK1MJVfAlG8CFpMCpXAcaJF2AAIPyjEbvBroVBfWvmfZKAfVAQADAgADeAADNgQ', 'file_unique_id': 'AQADD8oxG7wa6FR9', 'file_size': 5767, 'width': 331, 'height': 67}]}`
DEBUG    httpcore.connection:_trace.py:87 close.started
DEBUG    httpcore.connection:_trace.py:87 close.complete
=========================== short test summary info ===========================
FAILED tests/test_t.py::test_send_photo_with_path_generates_warning - pytest.PytestUnraisableExceptionWarning: Exception ignored while finalizing file <_io.FileIO name='C:\\Users\\vivodi\\Downloads\\logo.png' mode='rb' closefd=True>: None
============================= 1 failed in 13.26s ==============================

[gw0] [100%] FAILED tests/test_t.py::test_send_photo_with_path_generates_warning 
tests\test_t.py:7 (test_send_photo_with_path_generates_warning)
file_input = WindowsPath('C:/Users/vivodi/Downloads/logo.png')
tg_type = <class 'telegram._files.photosize.PhotoSize'>, filename = None
attach = False, local_mode = False

    def parse_file_input(  # pylint: disable=too-many-return-statements
        file_input: Union[FileInput, "TelegramObject"],
        tg_type: Optional[type["TelegramObject"]] = None,
        filename: Optional[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:
    
            * 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``
          attribute.
    
        Args:
            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
                :class:`telegram.InputFile` is returned.
            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
            :attr:`file_input`, in case it's no valid file input.
        """
        # Importing on file-level yields cyclic Import Errors
        from telegram import InputFile  # pylint: disable=import-outside-toplevel  # noqa: PLC0415
    
        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):
                path = Path(file_input)
                if local_mode:
                    return path.absolute().as_uri()
>               return InputFile(path.open(mode="rb"), filename=filename, attach=attach)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E               ResourceWarning: unclosed file <_io.BufferedReader name='C:\\Users\\vivodi\\Downloads\\logo.png'>

C:\Users\vivodi\Projects\Flexget\.venv\Lib\site-packages\telegram\_utils\files.py:148: ResourceWarning

The above exception was the direct cause of the following exception:

cls = <class '_pytest.runner.CallInfo'>
func = <function call_and_report.<locals>.<lambda> at 0x0000014645816C40>
when = 'call'
reraise = (<class '_pytest.outcomes.Exit'>, <class 'KeyboardInterrupt'>)

    @classmethod
    def from_call(
        cls,
        func: Callable[[], TResult],
        when: Literal["collect", "setup", "call", "teardown"],
        reraise: type[BaseException] | tuple[type[BaseException], ...] | None = None,
    ) -> CallInfo[TResult]:
        """Call func, wrapping the result in a CallInfo.
    
        :param func:
            The function to call. Called without arguments.
        :type func: Callable[[], _pytest.runner.TResult]
        :param when:
            The phase in which the function is called.
        :param reraise:
            Exception or exceptions that shall propagate if raised by the
            function, instead of being wrapped in the CallInfo.
        """
        excinfo = None
        instant = timing.Instant()
        try:
>           result: TResult | None = func()
                                     ^^^^^^

C:\Users\vivodi\Projects\Flexget\.venv\Lib\site-packages\_pytest\runner.py:344: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
C:\Users\vivodi\Projects\Flexget\.venv\Lib\site-packages\_pytest\runner.py:246: in <lambda>
    lambda: runtest_hook(item=item, **kwds), when=when, reraise=reraise
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
C:\Users\vivodi\Projects\Flexget\.venv\Lib\site-packages\pluggy\_hooks.py:512: in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
C:\Users\vivodi\Projects\Flexget\.venv\Lib\site-packages\pluggy\_manager.py:120: in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
C:\Users\vivodi\Projects\Flexget\.venv\Lib\site-packages\_pytest\logging.py:850: in pytest_runtest_call
    yield
C:\Users\vivodi\Projects\Flexget\.venv\Lib\site-packages\_pytest\capture.py:900: in pytest_runtest_call
    return (yield)
            ^^^^^
C:\Users\vivodi\Projects\Flexget\.venv\Lib\site-packages\_pytest\skipping.py:263: in pytest_runtest_call
    return (yield)
            ^^^^^
C:\Users\vivodi\Projects\Flexget\.venv\Lib\site-packages\_pytest\unraisableexception.py:158: in pytest_runtest_call
    collect_unraisable(item.config)
C:\Users\vivodi\Projects\Flexget\.venv\Lib\site-packages\_pytest\unraisableexception.py:79: in collect_unraisable
    raise errors[0]
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

config = <_pytest.config.Config object at 0x000001463C1C2120>

    def collect_unraisable(config: Config) -> None:
        pop_unraisable = config.stash[unraisable_exceptions].pop
        errors: list[pytest.PytestUnraisableExceptionWarning | RuntimeError] = []
        meta = None
        hook_error = None
        try:
            while True:
                try:
                    meta = pop_unraisable()
                except IndexError:
                    break
    
                if isinstance(meta, BaseException):
                    hook_error = RuntimeError("Failed to process unraisable exception")
                    hook_error.__cause__ = meta
                    errors.append(hook_error)
                    continue
    
                msg = meta.msg
                try:
>                   warnings.warn(pytest.PytestUnraisableExceptionWarning(msg))
E                   pytest.PytestUnraisableExceptionWarning: Exception ignored while finalizing file <_io.FileIO name='C:\\Users\\vivodi\\Downloads\\logo.png' mode='rb' closefd=True>: None

C:\Users\vivodi\Projects\Flexget\.venv\Lib\site-packages\_pytest\unraisableexception.py:67: PytestUnraisableExceptionWarning

Additional Context

Workaround

The current workaround is to manually manage the file handle in the user's code, which prevents the library from opening the file itself.

# This works correctly and does not generate a warning
image_path = Path("photo.png")
with open(image_path, "rb") as f:
    await bot.send_photo(chat_id=chat_id, photo=f)

While this workaround is effective, the library should ideally handle this gracefully when given a path.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions