RuntimeError: Event loop is closed Pytest, FastApi, SqlAlchemy, Docker

Без использования pytest все прекрасно работает, да и сам pytest работает, но только один тест, если их больше одного, то вылетает ошибка RuntimeError: Event loop is closed, а именно: AttributeError: 'NoneType' object has no attribute 'send' Вот пример теста:

import pytest
from fastapi.testclient import TestClient
from main import app

client = TestClient(app)

@pytest.mark.parametrize('a, b, code', (
    ('[email protected]', 'qwerty', 200),
    ('[email protected]', 'qwerty', 409),
))
def test1(a, b, code):
    response = client.post(f"/users/create?&email={a}&password={b}")
    assert response.status_code == code

Также вот пример main:

from app.db.importer import *
from app.db.db import *
from fastapi import FastAPI
from app.routes.user import user_router
from app.routes.transaction import transaction_router
from app.routes.auth import auth_router
from app.routes.budget import budget_router
from app.routes.budget_items import budget_items_router
import uvicorn
from base.exception import *

app = FastAPI()

app.include_router(transaction_router)
app.include_router(user_router)
app.include_router(auth_router)
app.include_router(budget_router)
app.include_router(budget_items_router)

app.add_exception_handler(DefaultException, default_exception_handler)
app.add_exception_handler(HTTPException, http_exception_handler)

Ну и собственно полный лог ошибки:

================================================================================ FAILURES =================================================================================
___________________________________________________________________ test1[[email protected]] ____________________________________________________________________

a = '[email protected]', b = 'qwerty', code = 200

    @pytest.mark.parametrize('a, b, code', (
        ('[email protected]', 'qwerty', 200),
    ))
    def test1(a, b, code):
        response = client.post(f"/users/create?&email={a}&password={b}")
>       assert response.status_code == code
E       assert 409 == 200
E        +  where 409 = <Response [409 Conflict]>.status_code

tests\test_app.py:12: AssertionError
___________________________________________________________________ test2[[email protected]] ____________________________________________________________________

self = <ProactorEventLoop running=False closed=True debug=False>
callback = <bound method BaseProtocol._on_waiter_completed of <asyncpg.protocol.protocol.Protocol object at 0x000001761818B740>>
context = <_contextvars.Context object at 0x000001761906A6C0>, args = (<Future finished exception=AttributeError("'NoneType' object has no attribute 'send'")>,)

    def call_soon(self, callback, *args, context=None):
        """Arrange for a callback to be called as soon as possible.

        This operates as a FIFO queue: callbacks are called in the
        order in which they are registered.  Each callback will be
        called exactly once.

        Any positional arguments after the callback will be passed to
        the callback when it is called.
        """
>       self._check_closed()

..\..\AppData\Local\Programs\Python\Python310\lib\asyncio\base_events.py:753:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

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

    def _check_closed(self):
        if self._closed:
>           raise RuntimeError('Event loop is closed')
E           RuntimeError: Event loop is closed

..\..\AppData\Local\Programs\Python\Python310\lib\asyncio\base_events.py:515: RuntimeError

During handling of the above exception, another exception occurred:

a = '[email protected]', b = 'qwerty', code = 409

    @pytest.mark.parametrize('a, b, code', (
        ('[email protected]', 'qwerty', 409),
    ))
    def test2(a, b, code):
>       response = client.post(f"/users/create?&email={a}&password={b}")

tests\test_app.py:18:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\starlette\testclient.py:531: in post
    return super().post(
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\httpx\_client.py:1144: in post
    return self.request(
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\starlette\testclient.py:430: in request
    return super().request(
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\httpx\_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\httpx\_client.py:914: in send
    response = self._send_handling_auth(
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\httpx\_client.py:942: in _send_handling_auth
    response = self._send_handling_redirects(
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\httpx\_client.py:979: in _send_handling_redirects
    response = self._send_single_request(request)
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\httpx\_client.py:1014: in _send_single_request
    response = transport.handle_request(request)
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\starlette\testclient.py:339: in handle_request
    raise exc
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\starlette\testclient.py:336: in handle_request
    portal.call(self.app, scope, receive, send)
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\anyio\from_thread.py:277: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
..\..\AppData\Local\Programs\Python\Python310\lib\concurrent\futures\_base.py:458: in result
    return self.__get_result()
..\..\AppData\Local\Programs\Python\Python310\lib\concurrent\futures\_base.py:403: in __get_result
    raise self._exception
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\anyio\from_thread.py:217: in _call_func
    retval = await retval
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\fastapi\applications.py:1054: in __call__
    await super().__call__(scope, receive, send)
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\starlette\applications.py:112: in __call__
    await self.middleware_stack(scope, receive, send)
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\starlette\middleware\errors.py:187: in __call__
    raise exc
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\starlette\middleware\errors.py:165: in __call__
    await self.app(scope, receive, _send)
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\starlette\middleware\exceptions.py:62: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\starlette\_exception_handler.py:53: in wrapped_app
    raise exc
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\starlette\_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\starlette\routing.py:715: in __call__
    await self.middleware_stack(scope, receive, send)
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\starlette\routing.py:735: in app
    await route.handle(scope, receive, send)
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\starlette\routing.py:288: in handle
    await self.app(scope, receive, send)
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\starlette\routing.py:76: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\starlette\_exception_handler.py:53: in wrapped_app
    raise exc
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\starlette\_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\starlette\routing.py:73: in app
    response = await f(request)
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\fastapi\routing.py:301: in app
    raw_response = await run_endpoint_function(
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\fastapi\routing.py:212: in run_endpoint_function
    return await dependant.call(**values)
app\routes\user.py:88: in create_user
    user : UserDTO = await user_r.filter(UserFilterDTO(email=data.email))
app\repositories\user.py:68: in filter
    user = await self.conn.execute(query)
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\sqlalchemy\ext\asyncio\session.py:463: in execute
    result = await greenlet_spawn(
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\sqlalchemy\util\_concurrency_py3k.py:201: in greenlet_spawn
    result = context.throw(*sys.exc_info())
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\sqlalchemy\orm\session.py:2365: in execute
    return self._execute_internal(
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\sqlalchemy\orm\session.py:2251: in _execute_internal
    result: Result[Any] = compile_state_cls.orm_execute_statement(
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\sqlalchemy\orm\context.py:305: in orm_execute_statement
    result = conn.execute(
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\sqlalchemy\engine\base.py:1416: in execute
    return meth(
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\sqlalchemy\sql\elements.py:515: in _execute_on_connection
    return connection._execute_clauseelement(
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\sqlalchemy\engine\base.py:1638: in _execute_clauseelement
    ret = self._execute_context(
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\sqlalchemy\engine\base.py:1843: in _execute_context
    return self._exec_single_context(
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\sqlalchemy\engine\base.py:1983: in _exec_single_context
    self._handle_dbapi_exception(
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\sqlalchemy\engine\base.py:2355: in _handle_dbapi_exception
    raise exc_info[1].with_traceback(exc_info[2])
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\sqlalchemy\engine\base.py:1964: in _exec_single_context
    self.dialect.do_execute(
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\sqlalchemy\engine\default.py:942: in do_execute
    cursor.execute(statement, parameters)
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\sqlalchemy\dialects\postgresql\asyncpg.py:580: in execute
    self._adapt_connection.await_(
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\sqlalchemy\util\_concurrency_py3k.py:132: in await_only
    return current.parent.switch(awaitable)  # type: ignore[no-any-return,attr-defined] # noqa: E501
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\sqlalchemy\util\_concurrency_py3k.py:196: in greenlet_spawn
    value = await result
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\sqlalchemy\dialects\postgresql\asyncpg.py:515: in _prepare_and_execute
    await adapt_connection._start_transaction()
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\sqlalchemy\dialects\postgresql\asyncpg.py:845: in _start_transaction
    self._handle_exception(error)
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\sqlalchemy\dialects\postgresql\asyncpg.py:794: in _handle_exception
    raise error
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\sqlalchemy\dialects\postgresql\asyncpg.py:843: in _start_transaction
    await self._transaction.start()
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\asyncpg\transaction.py:146: in start
    await self._connection.execute(query)
..\..\AppData\Local\Programs\Python\Python310\lib\site-packages\asyncpg\connection.py:349: in execute
    result = await self._protocol.query(query, timeout)
asyncpg\\protocol\\protocol.pyx:375: in query
    ???
asyncpg\\protocol\\protocol.pyx:368: in asyncpg.protocol.protocol.BaseProtocol.query
    ???
asyncpg\\protocol\\coreproto.pyx:1174: in asyncpg.protocol.protocol.CoreProtocol._simple_query
    ???
asyncpg\\protocol\\protocol.pyx:967: in asyncpg.protocol.protocol.BaseProtocol._write
    ???
..\..\AppData\Local\Programs\Python\Python310\lib\asyncio\proactor_events.py:365: in write
    self._loop_writing(data=bytes(data))
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <_ProactorSocketTransport fd=676 read=<_OverlappedFuture cancelled>>, f = None, data = b'Q\x00\x00\x00*BEGIN ISOLATION LEVEL READ COMMITTED;\x00'

    def _loop_writing(self, f=None, data=None):
        try:
            if f is not None and self._write_fut is None and self._closing:
                # XXX most likely self._force_close() has been called, and
                # it has set self._write_fut to None.
                return
            assert f is self._write_fut
            self._write_fut = None
            self._pending_write = 0
            if f:
                f.result()
            if data is None:
                data = self._buffer
                self._buffer = None
            if not data:
                if self._closing:
                    self._loop.call_soon(self._call_connection_lost, None)
                if self._eof_written:
                    self._sock.shutdown(socket.SHUT_WR)
                # Now that we've reduced the buffer size, tell the
                # protocol to resume writing if it was paused.  Note that
                # we do this last since the callback is called immediately
                # and it may add more data to the buffer (even causing the
                # protocol to be paused again).
                self._maybe_resume_protocol()
            else:
>               self._write_fut = self._loop._proactor.send(self._sock, data)
E               AttributeError: 'NoneType' object has no attribute 'send'

..\..\AppData\Local\Programs\Python\Python310\lib\asyncio\proactor_events.py:401: AttributeError
============================================================================ warnings summary =============================================================================
tests/test_app.py::test1[[email protected]]
tests/test_app.py::test2[[email protected]]
  C:\Users\XCompleX\Desktop\fintrack\base\shemas.py:20: PydanticDeprecatedSince20: The `dict` method is deprecated; use `model_dump` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.10/migration/
    for attr, value in self.dict(exclude_unset=True).items():

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
========================================================================= short test summary info =========================================================================
FAILED tests/test_app.py::test1[[email protected]] - assert 409 == 200
FAILED tests/test_app.py::test2[[email protected]] - AttributeError: 'NoneType' object has no attribute 'send'
====================================================================== 2 failed, 2 warnings in 6.21s ======================================================================

на 409 можно не обращать внимание, так пока и должно быть, заметил еще что если первый сработает на 422 ошибку, то 2 тест проходит нормально.


Ответы (1 шт):

Автор решения: Максим Брагин

Не понимаю каким образом, я пробовал этот вариант, но в итоге он же и заработал, просто добавь фикстуру

import pytest
from fastapi.testclient import TestClient
from app.schemas.user import UserCreateDTO
from main import app

@pytest.fixture(scope="module")
def client():
    with TestClient(app) as c:
        yield c

@pytest.mark.parametrize('a, b, code', (
    ('[email protected]', 'qwerty', 201),
    ('[email protected]', 'qwerty', 409),
))
def test1(client, a, b, code):
    response = client.post(f"/users/create?&email={a}&password={b}")
    assert response.status_code == code
→ Ссылка