Thanks to visit codestin.com
Credit goes to github.com

Skip to content

⬆️ Add support for Python 3.13#13274

Merged
tiangolo merged 18 commits into
masterfrom
py313
Jan 29, 2025
Merged

⬆️ Add support for Python 3.13#13274
tiangolo merged 18 commits into
masterfrom
py313

Conversation

@tiangolo

@tiangolo tiangolo commented Jan 28, 2025

Copy link
Copy Markdown
Member

⬆️ Add support for Python 3.13


This has been triggering a hard to debug error on Python 3.13 with Pydantic v1. It only happens there, it only happens the first time the test is run. When I connect to the remote GitHub Actions runner and run it once, it shows the error, when I run it again, it disappears. It only shows up when running the entire test suite, running the exact file where the error normally shows up, passes the test "without errors".

ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f1a705f2980>

It seems it only happens when running the tests with coverage, as in:

coverage run -m pytest tests

But running pytest directly doesn't show the error.

On that first run, it always shows on the same test, even though it seems it's not completely related to that test.

First it would show up in tests/test_tutorial/test_websockets/test_tutorial002.py:

tests/test_tutorial/test_testing/test_tutorial003.py .                                                                                                                                 [ 95%]
tests/test_tutorial/test_testing_dependencies/test_tutorial001.py ..........                                                                                                           [ 95%]
tests/test_tutorial/test_testing_dependencies/test_tutorial001_an.py ..........                                                                                                        [ 96%]
tests/test_tutorial/test_testing_dependencies/test_tutorial001_an_py310.py .......                                                                                                     [ 96%]
tests/test_tutorial/test_testing_dependencies/test_tutorial001_an_py39.py .......                                                                                                      [ 96%]
tests/test_tutorial/test_testing_dependencies/test_tutorial001_py310.py .......                                                                                                        [ 96%]
tests/test_tutorial/test_websockets/test_tutorial001.py ..                                                                                                                             [ 97%]
tests/test_tutorial/test_websockets/test_tutorial002.py ...........F..................                                                                                                 [ 98%]
tests/test_tutorial/test_websockets/test_tutorial003.py ..                                                                                                                             [ 98%]
tests/test_tutorial/test_websockets/test_tutorial003_py39.py ..                                                                                                                        [ 98%]
tests/test_tutorial/test_wsgi/test_tutorial001.py ..                                                                                                                                   [ 98%]
tests/test_typing_python39.py .                                                                                                                                                        [ 98%]
tests/test_union_body.py ...                                                                                                                                                           [ 98%]
tests/test_union_inherited_body.py ...                                                                                                                                                 [ 98%]
tests/test_validate_response.py ......                                                                                                                                                 [ 99%]
tests/test_validate_response_dataclass.py ...                                                                                                                                          [ 99%]
tests/test_validate_response_recursive/test_validate_response_recursive_pv1.py .                                                                                                       [ 99%]
tests/test_validate_response_recursive/test_validate_response_recursive_pv2.py s                                                                                                       [ 99%]
tests/test_webhooks_security.py ..                                                                                                                                                     [ 99%]
tests/test_ws_dependencies.py ...                                                                                                                                                      [ 99%]
tests/test_ws_router.py ............                                                                                                                                                   [100%]

========================================================================================== FAILURES ==========================================================================================
_______________________________________________________________________ test_websocket_with_header[tutorial002_py310] ________________________________________________________________________

cls = <class '_pytest.runner.CallInfo'>, func = <function call_and_report.<locals>.<lambda> at 0x7fb3992ea0c0>, when = 'call'
reraise = (<class '_pytest.outcomes.Exit'>, <class 'KeyboardInterrupt'>)                                                                                                              [46/411]

    @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
        start = timing.time()
        precise_start = timing.perf_counter()
        try:
>           result: TResult | None = func()

/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/site-packages/_pytest/runner.py:341: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/site-packages/_pytest/runner.py:242: in <lambda>
    lambda: runtest_hook(item=item, **kwds), when=when, reraise=reraise
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/site-packages/pluggy/_hooks.py:513: in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/site-packages/pluggy/_manager.py:120: in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/site-packages/_pytest/threadexception.py:92: in pytest_runtest_call
    yield from thread_exception_runtest_hook()
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/site-packages/_pytest/threadexception.py:68: in thread_exception_runtest_hook
    yield
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/site-packages/_pytest/unraisableexception.py:95: in pytest_runtest_call
    yield from unraisable_exception_runtest_hook()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def unraisable_exception_runtest_hook() -> Generator[None]:
        with catch_unraisable_exception() as cm:
            try:
                yield
            finally:
                if cm.unraisable:
                    if cm.unraisable.err_msg is not None:
                        err_msg = cm.unraisable.err_msg
                    else:
                        err_msg = "Exception ignored in"
                    msg = f"{err_msg}: {cm.unraisable.object!r}\n\n"
                    msg += "".join(
                        traceback.format_exception(
                            cm.unraisable.exc_type,
                            cm.unraisable.exc_value,
                            cm.unraisable.exc_traceback,
                        )
                    )
>                   warnings.warn(pytest.PytestUnraisableExceptionWarning(msg))
E                   pytest.PytestUnraisableExceptionWarning: Exception ignored in: <sqlite3.Connection object at 0x7fb3995e66b0>
E                   
E                   Traceback (most recent call last):
E                     File "/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/weakref.py", line 428, in __setitem__
E                       self.data[ref(key, self._remove)] = value
E                                 ~~~^^^^^^^^^^^^^^^^^^^
E                   ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7fb3995e66b0>

/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/site-packages/_pytest/unraisableexception.py:85: PytestUnraisableExceptionWarning
====================================================================================== inline snapshot =======================================================================================
Info: one snapshot changed its representation (--inline-snapshot=update)

You can also use --inline-snapshot=review to approve the changes interactively
=========================== short test summary info ============================
FAILED tests/test_tutorial/test_websockets/test_tutorial002.py::test_websocket_with_header[tutorial002_py310] - pytest.PytestUnraisableExceptionWarning: Exception ignored in: <sqlite3.Connection object at 0x7fb3995e66b0>

Traceback (most recent call last):
  File "/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/weakref.py", line 428, in __setitem__
    self.data[ref(key, self._remove)] = value
              ~~~^^^^^^^^^^^^^^^^^^^
ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7fb3995e66b0>
================= 1 failed, 2261 passed, 147 skipped in 50.58s =================
Exception ignored in: <sqlite3.Connection object at 0x7fb39a04f6a0>
ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7fb39a04f6a0>
Exception ignored in: <sqlite3.Connection object at 0x7fb399d2a3e0>
ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7fb399d2a3e0>
Exception ignored in: <sqlite3.Connection object at 0x7fb3993cfe20>
ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7fb3993cfe20>
Exception ignored in: <sqlite3.Connection object at 0x7fb39944fe20>
ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7fb39944fe20>
runner@fv-az792-523:~/work/fastapi/fastapi$

If I remove that file, then it shows up in another file, in this case in tests/test_validate_response_recursive/test_validate_response_recursive_pv1.py:

tests/test_tutorial/test_wsgi/test_tutorial001.py ..                                                                                                                                   [ 98%]
tests/test_typing_python39.py .                                                                                                                                                        [ 98%]
tests/test_union_body.py ...                                                                                                                                                           [ 98%]
tests/test_union_inherited_body.py ...                                                                                                                                                 [ 98%]
tests/test_validate_response.py ......                                                                                                                                                 [ 99%]
tests/test_validate_response_dataclass.py ...                                                                                                                                          [ 99%]
tests/test_validate_response_recursive/test_validate_response_recursive_pv1.py F                                                                                                       [ 99%]
tests/test_validate_response_recursive/test_validate_response_recursive_pv2.py s                                                                                                       [ 99%]
tests/test_webhooks_security.py ..                                                                                                                                                     [ 99%]
tests/test_ws_dependencies.py ...                                                                                                                                                      [ 99%]
tests/test_ws_router.py ............                                                                                                                                                   [100%]

========================================================================================== FAILURES ==========================================================================================
_______________________________________________________________________________________ test_recursive _______________________________________________________________________________________

cls = <class '_pytest.runner.CallInfo'>, func = <function call_and_report.<locals>.<lambda> at 0x7f1a702618a0>, 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
        start = timing.time()
        precise_start = timing.perf_counter()
        try:
>           result: TResult | None = func()

/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/site-packages/_pytest/runner.py:341: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/site-packages/_pytest/runner.py:242: in <lambda>
    lambda: runtest_hook(item=item, **kwds), when=when, reraise=reraise
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/site-packages/pluggy/_hooks.py:513: in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/site-packages/pluggy/_manager.py:120: in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/site-packages/_pytest/threadexception.py:92: in pytest_runtest_call
    yield from thread_exception_runtest_hook()
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/site-packages/_pytest/threadexception.py:68: in thread_exception_runtest_hook
    yield
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/site-packages/_pytest/unraisableexception.py:95: in pytest_runtest_call
    yield from unraisable_exception_runtest_hook()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def unraisable_exception_runtest_hook() -> Generator[None]:
        with catch_unraisable_exception() as cm:
            try:
                yield
            finally:                                                                                                                                                                   [0/410]
                if cm.unraisable:
                    if cm.unraisable.err_msg is not None:
                        err_msg = cm.unraisable.err_msg
                    else:
                        err_msg = "Exception ignored in"
                    msg = f"{err_msg}: {cm.unraisable.object!r}\n\n"
                    msg += "".join(
                        traceback.format_exception(
                            cm.unraisable.exc_type,
                            cm.unraisable.exc_value,
                            cm.unraisable.exc_traceback,
                        )
                    )
>                   warnings.warn(pytest.PytestUnraisableExceptionWarning(msg))
E                   pytest.PytestUnraisableExceptionWarning: Exception ignored in: <sqlite3.Connection object at 0x7f1a705f2980>
E                   
E                   Traceback (most recent call last):
E                     File "/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/site-packages/starlette/routing.py", line 259, in matches
E                       path_params = dict(scope.get("path_params", {}))
E                                          ~~~~~~~~~^^^^^^^^^^^^^^^^^^^
E                   ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f1a705f2980>

/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/site-packages/_pytest/unraisableexception.py:85: PytestUnraisableExceptionWarning
====================================================================================== inline snapshot =======================================================================================
Info: one snapshot changed its representation (--inline-snapshot=update)

You can also use --inline-snapshot=review to approve the changes interactively
=========================== short test summary info ============================
FAILED tests/test_validate_response_recursive/test_validate_response_recursive_pv1.py::test_recursive - pytest.PytestUnraisableExceptionWarning: Exception ignored in: <sqlite3.Connection obj
ect at 0x7f1a705f2980>

Traceback (most recent call last):
  File "/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/site-packages/starlette/routing.py", line 259, in matches
    path_params = dict(scope.get("path_params", {}))
                       ~~~~~~~~~^^^^^^^^^^^^^^^^^^^
ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f1a705f2980>
================= 1 failed, 2231 passed, 147 skipped in 54.45s =================
Exception ignored in: <sqlite3.Connection object at 0x7f1a70d69210>
ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f1a70d69210>
Exception ignored in: <sqlite3.Connection object at 0x7f1a7036f010>
ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f1a7036f010>
Exception ignored in: <sqlite3.Connection object at 0x7f1a70ed2200>
ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f1a70ed2200>
Exception ignored in: <sqlite3.Connection object at 0x7f1a70437010>
ResourceWarning: unclosed database in <sqlite3.Connection object at 0x7f1a70437010>
runner@fv-az1335-855:~/work/fastapi/fastapi$ 

The traceback doesn't give much information. I temporarily updated the tests in the file to alter the order of the context managers and then the traceback would point to another line. Not to the internal weakref but to Starlette's datastructures.MutableHeaders.__delitem__, in the line with:

for idx, (item_key, item_value) in enumerate(self._list):

Showing the error in enumerate(self._list). But that doesn't make much sense.

Using a context manager for the TestClient would then show the traceback somewhere at Starlette's middleware.

Then changing it more would show the traceback in coverage's should_start_context_test_function.

The current theory is that coverage stores data in SQLite files and some of its internal parts are at some point not closing a DB. I would imagine that's not shown in the traceback maybe because of the ways it injects its logic into the executing runtime Python.


As this seems to be a corner case only related to the testing setup with coverage, the current workaround is just a warning filter in the config.

@tiangolo tiangolo marked this pull request as ready for review January 29, 2025 17:58
@tiangolo tiangolo enabled auto-merge (squash) January 29, 2025 17:58
@tiangolo tiangolo merged commit bd106fc into master Jan 29, 2025
@tiangolo tiangolo deleted the py313 branch January 29, 2025 18:02
@elikoga

elikoga commented Jan 31, 2025

Copy link
Copy Markdown

Didn't know it was not supported till now

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants