- 313 new markdown files created - 30 relationships embedded - 313 entries indexed - State initialized with usage data
51 lines
1.9 KiB
Markdown
51 lines
1.9 KiB
Markdown
---
|
|
id: fecad34a-4cc1-4f00-878c-393a9ecb5df0
|
|
type: solution
|
|
title: "pytest-asyncio SQLAlchemy async DB test fixture pattern"
|
|
tags: [mantimon-tcg, pytest, pytest-asyncio, sqlalchemy, async, testing, pattern]
|
|
importance: 0.8
|
|
confidence: 0.8
|
|
created: "2026-01-27T15:50:36.216118+00:00"
|
|
updated: "2026-01-27T15:50:36.216118+00:00"
|
|
---
|
|
|
|
When testing SQLAlchemy async with pytest-asyncio, the fixture teardown runs in a DIFFERENT event loop than the test body, causing 'Future attached to different loop' errors on async cleanup.
|
|
|
|
SOLUTION:
|
|
1. Use sync psycopg2 for fixture setup/teardown operations (migrations, truncate)
|
|
2. Create fresh NullPool engine per test (no connection reuse)
|
|
3. Use TRUNCATE via sync psycopg2 in teardown instead of async rollback
|
|
4. Suppress warnings for connection cleanup (harmless GC noise)
|
|
|
|
KEY CODE (conftest.py):
|
|
```python
|
|
def truncate_all_tables():
|
|
conn = psycopg2.connect(**DB_PARAMS)
|
|
try:
|
|
conn.autocommit = True
|
|
with conn.cursor() as cur:
|
|
for table in TABLES_TO_TRUNCATE:
|
|
cur.execute(f'TRUNCATE TABLE {table} CASCADE')
|
|
finally:
|
|
conn.close()
|
|
|
|
@pytest_asyncio.fixture
|
|
async def db_session():
|
|
engine = create_async_engine(URL, poolclass=pool.NullPool)
|
|
session_factory = async_sessionmaker(engine, expire_on_commit=False)
|
|
session = session_factory()
|
|
try:
|
|
yield session
|
|
finally:
|
|
try:
|
|
await session.close()
|
|
except RuntimeError:
|
|
pass # Ignore event loop errors
|
|
truncate_all_tables() # SYNC cleanup always works
|
|
```
|
|
|
|
ADDITIONAL NOTES:
|
|
- When testing relationship loading (selectinload), must expire() the parent object first if children were added after initial load
|
|
- ON DELETE SET NULL requires passive_deletes=True on relationship to let DB handle it
|
|
- Suppress warnings in pyproject.toml: 'ignore:The garbage collector is trying to clean up:sqlalchemy.exc.SAWarning'
|