-
Notifications
You must be signed in to change notification settings - Fork 162
Open
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?
Metadata
Metadata
Assignees
Labels
No labels