"""Асинхронный доступ к БД для Celery-задач: сессия на один вызов задачи. Celery-воркер (`celery ... -B`) работает в prefork-пуле — глобальный async engine backend (`core.db.engine`), созданный один раз в родительском процессе, не переживает fork (сетевые соединения, открытые до fork, в дочернем процессе ведут к зависаниям/битым event loop'ам asyncpg). Поэтому каждый вызов задачи создаёт свой временный engine и гарантированно уничтожает его по завершении. """ import asyncio from collections.abc import AsyncGenerator, Callable, Coroutine from contextlib import asynccontextmanager from typing import Any, TypeVar from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine from core.config import get_settings T = TypeVar("T") @asynccontextmanager async def open_session() -> AsyncGenerator[AsyncSession, None]: """Открыть сессию БД с короткоживущим engine (закрывается по выходу из контекста).""" settings = get_settings() engine = create_async_engine(settings.database_url, pool_pre_ping=True) session_maker = async_sessionmaker(engine, expire_on_commit=False) try: async with session_maker() as session: yield session finally: await engine.dispose() def run_async(factory: Callable[[], Coroutine[Any, Any, T]]) -> T: """Выполнить async-корутину в новом event loop — обёртка для sync Celery-задач.""" return asyncio.run(factory())