Using starlette TestClient causes an AttributeError : '_UnixSelectorEventLoop' object has no attribute '_compute_internal_coro'
Asked Answered
F

1

9

Using FastAPI : 0.101.1

I run this test_read_aynsc and it pass.

# app.py
from fastapi import FastAPI


app =  FastAPI()
app.get("/")
def read_root():
    return {"Hello": "World"}

# conftest.py

import pytest
from typing import Generator
from fastapi.testclient import TestClient

from server import app
@pytest.fixture(scope="session")
def client() -> Generator:
    with TestClient(app) as c:
        yield c

# test_root.py

def test_read_aynsc(client):
    response = client.get("/item")

However, executing this test in DEBUG mode (in pycharm) will cause an error. Here is the Traceback :

test setup failed
cls = <class 'anyio._backends._asyncio.AsyncIOBackend'>
func = <function start_blocking_portal.<locals>.run_portal at 0x1555c51b0>
args = (), kwargs = {}, options = {}

    @classmethod
    def run(
        cls,
        func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]],
        args: tuple[Unpack[PosArgsT]],
        kwargs: dict[str, Any],
        options: dict[str, Any],
    ) -> T_Retval:
        @wraps(func)
        async def wrapper() -> T_Retval:
            task = cast(asyncio.Task, current_task())
            task.set_name(get_callable_name(func))
            _task_states[task] = TaskState(None, None)
    
            try:
                return await func(*args)
            finally:
                del _task_states[task]
    
        debug = options.get("debug", False)
        loop_factory = options.get("loop_factory", None)
        if loop_factory is None and options.get("use_uvloop", False):
            import uvloop
    
            loop_factory = uvloop.new_event_loop
    
        with Runner(debug=debug, loop_factory=loop_factory) as runner:
>           return runner.run(wrapper())

../../../Library/Caches/pypoetry/virtualenvs/kms-backend-F9vGicV3-py3.10/lib/python3.10/site-packages/anyio/_backends/_asyncio.py:1991: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../Library/Caches/pypoetry/virtualenvs/kms-backend-F9vGicV3-py3.10/lib/python3.10/site-packages/anyio/_backends/_asyncio.py:193: in run
    return self._loop.run_until_complete(task)
../../../Library/Application Support/JetBrains/Toolbox/apps/PyCharm-P/ch-0/233.13763.11/PyCharm.app/Contents/plugins/python/helpers-pro/pydevd_asyncio/pydevd_nest_asyncio.py:202: in run_until_complete
    self._run_once()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <_UnixSelectorEventLoop running=False closed=True debug=False>

    def _run_once(self):
        """
        Simplified re-implementation of asyncio's _run_once that
        runs handles as they become ready.
        """
        ready = self._ready
        scheduled = self._scheduled
        while scheduled and scheduled[0]._cancelled:
            heappop(scheduled)
    
        timeout = (
            0 if ready or self._stopping
            else min(max(
                scheduled[0]._when - self.time(), 0), 86400) if scheduled
            else None)
        event_list = self._selector.select(timeout)
        self._process_events(event_list)
    
        end_time = self.time() + self._clock_resolution
        while scheduled and scheduled[0]._when < end_time:
            handle = heappop(scheduled)
            ready.append(handle)
    
>       if self._compute_internal_coro:
E       AttributeError: '_UnixSelectorEventLoop' object has no attribute '_compute_internal_coro'

../../../Library/Application Support/JetBrains/Toolbox/apps/PyCharm-P/ch-0/233.13763.11/PyCharm.app/Contents/plugins/python/helpers-pro/pydevd_asyncio/pydevd_nest_asyncio.py:236: AttributeError

During handling of the above exception, another exception occurred:

    @pytest.fixture(scope="session")
    def client() -> Generator:
>       with TestClient(app) as c:

tests/fixtures/common/http_client_app.py:10: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../Library/Caches/pypoetry/virtualenvs/kms-backend-F9vGicV3-py3.10/lib/python3.10/site-packages/starlette/testclient.py:730: in __enter__
    self.portal = portal = stack.enter_context(
../../../.pyenv/versions/3.10.12/lib/python3.10/contextlib.py:492: in enter_context
    result = _cm_type.__enter__(cm)
../../../.pyenv/versions/3.10.12/lib/python3.10/contextlib.py:135: in __enter__
    return next(self.gen)
../../../Library/Caches/pypoetry/virtualenvs/kms-backend-F9vGicV3-py3.10/lib/python3.10/site-packages/anyio/from_thread.py:454: in start_blocking_portal
    run_future.result()
../../../.pyenv/versions/3.10.12/lib/python3.10/concurrent/futures/_base.py:451: in result
    return self.__get_result()
../../../.pyenv/versions/3.10.12/lib/python3.10/concurrent/futures/_base.py:403: in __get_result
    raise self._exception
../../../.pyenv/versions/3.10.12/lib/python3.10/concurrent/futures/thread.py:58: in run
    result = self.fn(*self.args, **self.kwargs)
../../../Library/Caches/pypoetry/virtualenvs/kms-backend-F9vGicV3-py3.10/lib/python3.10/site-packages/anyio/_core/_eventloop.py:73: in run
    return async_backend.run(func, args, {}, backend_options)
../../../Library/Caches/pypoetry/virtualenvs/kms-backend-F9vGicV3-py3.10/lib/python3.10/site-packages/anyio/_backends/_asyncio.py:1990: in run
    with Runner(debug=debug, loop_factory=loop_factory) as runner:
../../../Library/Caches/pypoetry/virtualenvs/kms-backend-F9vGicV3-py3.10/lib/python3.10/site-packages/anyio/_backends/_asyncio.py:133: in __exit__
    self.close()
../../../Library/Caches/pypoetry/virtualenvs/kms-backend-F9vGicV3-py3.10/lib/python3.10/site-packages/anyio/_backends/_asyncio.py:141: in close
    _cancel_all_tasks(loop)
../../../Library/Caches/pypoetry/virtualenvs/kms-backend-F9vGicV3-py3.10/lib/python3.10/site-packages/anyio/_backends/_asyncio.py:243: in _cancel_all_tasks
    loop.run_until_complete(tasks.gather(*to_cancel, return_exceptions=True))
../../../Library/Application Support/JetBrains/Toolbox/apps/PyCharm-P/ch-0/233.13763.11/PyCharm.app/Contents/plugins/python/helpers-pro/pydevd_asyncio/pydevd_nest_asyncio.py:202: in run_until_complete
    self._run_once()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <_UnixSelectorEventLoop running=False closed=True debug=False>

    def _run_once(self):
        """
        Simplified re-implementation of asyncio's _run_once that
        runs handles as they become ready.
        """
        ready = self._ready
        scheduled = self._scheduled
        while scheduled and scheduled[0]._cancelled:
            heappop(scheduled)
    
        timeout = (
            0 if ready or self._stopping
            else min(max(
                scheduled[0]._when - self.time(), 0), 86400) if scheduled
            else None)
        event_list = self._selector.select(timeout)
        self._process_events(event_list)
    
        end_time = self.time() + self._clock_resolution
        while scheduled and scheduled[0]._when < end_time:
            handle = heappop(scheduled)
            ready.append(handle)
    
>       if self._compute_internal_coro:
E       AttributeError: '_UnixSelectorEventLoop' object has no attribute '_compute_internal_coro'

I am not sure to understand what causes the error Since I can see the _UnixSelectorEventLoop, I need to precise that my operating system is MacOS M1.

Fear answered 1/2 at 12:18 Comment(0)
L
10

To support async debugging, PyCharm patches a bunch of asyncio APIs with custom wrapped functions. Notably, it patches asyncio.new_event_loop() in ~/Applications/PyCharm Professional Edition.app/Contents/plugins/python/helpers-pro/pydevd_asyncio/pydevd_nest_asyncio.py:169.

Starlette uses anyio, with the asyncio backend by default. Anyio is eventually gonna try to get its event loop from asyncio.events.new_event_loop(), which is unpatched. Subsequent calls to patched asyncio APIs are gonna throw errors because they assume a patched event loop.

Until it's fixed properly, you can resolve this issue by forcing anyio to use the patched new_event_loop with

TestClient(app, backend_options={'loop_factory': asyncio.new_event_loop})

Update

JetBrains dev says the fix is on the way, see https://youtrack.jetbrains.com/issue/PY-70245. Until then, they recommend disabling python.debug.asyncio.repl in Help | Find Actions | Registry

Leroi answered 2/2 at 11:7 Comment(1)
In version 2023.3.4 fix was appliedNeiman

© 2022 - 2024 — McMap. All rights reserved.