使用 starlette TestClient 会导致 AttributeError :“_UnixSelectorEventLoop”对象没有属性“_compute_internal_coro”

jos*_*faz 6 python pycharm python-asyncio starlette fastapi

使用 FastAPI :0.101.1

我运行这个test_read_aynsc并且它通过了。

# 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")
Run Code Online (Sandbox Code Playgroud)

但是,在 DEBUG 模式(在 pycharm 中)执行此测试会导致错误。这是回溯:

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'

Run Code Online (Sandbox Code Playgroud)

我不确定导致错误的原因,因为我可以看到_UnixSelectorEventLoop,所以我需要准确地说我的操作系统是 MacOS M1。

aho*_*off 6

为了支持异步调试,PyCharm 使用自定义包装函数修补了一堆异步 API。值得注意的是,它修补asyncio.new_event_loop()~/Applications/PyCharm Professional Edition.app/Contents/plugins/python/helpers-pro/pydevd_asyncio/pydevd_nest_asyncio.py:169.

Starlette 使用 anyio,默认使用 asyncio 后端。asyncio.events.new_event_loop()Anyio 最终将尝试从未修补的事件循环中获取事件循环。对修补的 asyncio API 的后续调用将抛出错误,因为它们假定修补的事件循环。

在正确修复之前,您可以通过强制 anyio 使用修补程序来解决此new_event_loop问题

TestClient(app, backend_options={'loop_factory': asyncio.new_event_loop})
Run Code Online (Sandbox Code Playgroud)

更新

JetBrains 开发人员表示修复正在进行中,请参阅https://youtrack.jetbrains.com/issue/PY-70245。在那之前,他们建议python.debug.asyncio.repl帮助|中禁用。查找操作 | 登记处