-
Couldn't load subscription status.
- Fork 165
Description
Hi, I'm using versions:
Python 3.12
"pytest>=8.4.1",
"pytest-asyncio>=1.1.0",
I'm trying to create a fixture for TortoiseORM to upgrade the database before running the tests and downgrade it after the test through aerich, but it seems to me that I'm facing a problem due to incorrectly loop closed.
the boilerplate code looks like this:
@pytest.fixture(scope="session")
async def sanic_app():
from aerich import Command
from social_backend.app.db_session import TORTOISE_CONFIG, close_db, init_db
await init_db()
async with Command(tortoise_config=TORTOISE_CONFIG, app="models") as command:
await command.upgrade()
from social_backend.service_core import app
yield app
async with Command(tortoise_config=TORTOISE_CONFIG, app="models") as command:
await command.downgrade(-1, False)
await close_db()
I've debugged, and the command.downgrade only issues an "drop table" command to the postgres connector asyncpg>=0.30.0.
There are two situations:
- Endpoint doesn't do anything DB related, then this setup works perfectly, the downgrade also is issued correctly
- Endpoint does anything DB related, like creating an user, the downgrade fails
Here's the full stacktrace of the error:
=================================== ERRORS ====================================
________________ ERROR at teardown of test_register_and_login _________________
self = <asyncpg.pool.Pool object at 0x00000265225CA800>
async def close(self):
"""Attempt to gracefully close all connections in the pool.
Wait until all pool connections are released, close them and
shut down the pool. If any error (including cancellation) occurs
in ``close()`` the pool will terminate by calling
:meth:`Pool.terminate() <pool.Pool.terminate>`.
It is advisable to use :func:`python:asyncio.wait_for` to set
a timeout.
.. versionchanged:: 0.16.0
``close()`` now waits until all pool connections are released
before closing them and the pool. Errors raised in ``close()``
will cause immediate pool termination.
"""
if self._closed:
return
self._check_init()
self._closing = True
warning_callback = None
try:
> warning_callback = self._loop.call_later(
60, self._warn_on_long_close)
.venv\Lib\site-packages\asyncpg\pool.py:931:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
..\..\..\AppData\Roaming\uv\python\cpython-3.12.9-windows-x86_64-none\Lib\asyncio\base_events.py:765: in call_later
timer = self.call_at(self.time() + delay, callback, *args,
..\..\..\AppData\Roaming\uv\python\cpython-3.12.9-windows-x86_64-none\Lib\asyncio\base_events.py:778: in call_at
self._check_closed()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
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\Roaming\uv\python\cpython-3.12.9-windows-x86_64-none\Lib\asyncio\base_events.py:545: RuntimeError
During handling of the above exception, another exception occurred:
def finalizer() -> None:
"""Yield again, to finalize."""
async def async_finalizer() -> None:
try:
await gen_obj.__anext__() # type: ignore[union-attr]
except StopAsyncIteration:
pass
else:
msg = "Async generator fixture didn't stop."
msg += "Yield only once."
raise ValueError(msg)
> runner.run(async_finalizer(), context=context)
.venv\Lib\site-packages\pytest_asyncio\plugin.py:289:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
..\..\..\AppData\Roaming\uv\python\cpython-3.12.9-windows-x86_64-none\Lib\asyncio\runners.py:118: in run
return self._loop.run_until_complete(task)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
..\..\..\AppData\Roaming\uv\python\cpython-3.12.9-windows-x86_64-none\Lib\asyncio\base_events.py:691: in run_until_complete
return future.result()
^^^^^^^^^^^^^^^
.venv\Lib\site-packages\pytest_asyncio\plugin.py:281: in async_finalizer
await gen_obj.__anext__() # type: ignore[union-attr]
^^^^^^^^^^^^^^^^^^^^^^^^^
social-tests\src\conftest.py:47: in sanic_app
await all_exec()
social-tests\src\conftest.py:43: in all_exec
await downgrade()
social-tests\src\conftest.py:37: in downgrade
async with Command(tortoise_config=TORTOISE_CONFIG, app="models") as command:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv\Lib\site-packages\aerich\__init__.py:163: in __aenter__
await self.init()
.venv\Lib\site-packages\aerich\__init__.py:160: in init
await Migrate.init(self.tortoise_config, self.app, self.location)
.venv\Lib\site-packages\aerich\migrate.py:108: in init
await Tortoise.init(config=config)
.venv\Lib\site-packages\tortoise\__init__.py:472: in init
await connections.close_all(discard=True)
.venv\Lib\site-packages\tortoise\connection.py:197: in close_all
await asyncio.gather(*tasks)
.venv\Lib\site-packages\tortoise\backends\base_postgres\client.py:114: in close
await self._close()
.venv\Lib\site-packages\tortoise\backends\asyncpg\client.py:81: in _close
await asyncio.wait_for(self._pool.close(), 10)
..\..\..\AppData\Roaming\uv\python\cpython-3.12.9-windows-x86_64-none\Lib\asyncio\tasks.py:520: in wait_for
return await fut
^^^^^^^^^
.venv\Lib\site-packages\asyncpg\pool.py:943: in close
self.terminate()
.venv\Lib\site-packages\asyncpg\pool.py:964: in terminate
ch.terminate()
.venv\Lib\site-packages\asyncpg\pool.py:253: in terminate
self._con.terminate()
.venv\Lib\site-packages\asyncpg\connection.py:1515: in terminate
self._abort()
.venv\Lib\site-packages\asyncpg\connection.py:1567: in _abort
self._protocol.abort()
asyncpg\\protocol\\protocol.pyx:607: in asyncpg.protocol.protocol.BaseProtocol.abort
???
asyncpg\\protocol\\coreproto.pyx:1205: in asyncpg.protocol.protocol.CoreProtocol._terminate
???
asyncpg\\protocol\\protocol.pyx:967: in asyncpg.protocol.protocol.BaseProtocol._write
???
..\..\..\AppData\Roaming\uv\python\cpython-3.12.9-windows-x86_64-none\Lib\asyncio\proactor_events.py:366: in write
self._loop_writing(data=bytes(data))
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <_ProactorSocketTransport fd=1484 read=<_OverlappedFuture cancelled>>
f = None, data = b'X\x00\x00\x00\x04'
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\Roaming\uv\python\cpython-3.12.9-windows-x86_64-none\Lib\asyncio\proactor_events.py:402: AttributeError
It appears to me that the issue revolves around the loop being closed ahead of time, any idea on how to tackle this problem?