"""Per-invocation async session factory for Celery task modules. Async engine connections are bound to the event loop. Each Celery task runs its async body under a fresh ``asyncio.run()`` loop, so it needs its own engine created (and disposed) within that loop — a process-wide async engine would reuse loop-bound connections across tasks and raise "attached to a different loop". So unlike the process-wide sync engine in ``_sync_engine.py``, this returns a fresh engine per call; the caller disposes it (``await engine.dispose()``) when its loop ends. """ from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine from ..config import get_config def async_session_factory(): """Return ``(sessionmaker, engine)`` bound to a fresh async engine.""" cfg = get_config() engine = create_async_engine(cfg.database_url, future=True, pool_pre_ping=True) return async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False), engine