diff --git a/backend/app/db.py b/backend/app/db.py index fa51df5..e44dd35 100644 --- a/backend/app/db.py +++ b/backend/app/db.py @@ -1,9 +1,23 @@ +import sys + from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine from sqlalchemy.orm import DeclarativeBase +from sqlalchemy.pool import NullPool from app.config import settings -engine = create_async_engine(settings.database_url, echo=False) +# pytest-asyncio gives each test function its own event loop by default, but +# this engine (and its connection pool) is a module-level singleton created +# once and reused across every test. asyncpg connections are bound to the +# event loop they were opened on, so pooling them across per-test loops +# raises "got Future attached to a different loop". NullPool sidesteps this +# by opening a fresh connection per checkout instead of reusing one from a +# prior loop. Only applied under pytest; production keeps normal pooling. +_engine_kwargs = {"echo": False} +if "pytest" in sys.modules: + _engine_kwargs["poolclass"] = NullPool + +engine = create_async_engine(settings.database_url, **_engine_kwargs) async_session_maker = async_sessionmaker(engine, expire_on_commit=False)