from collections.abc import AsyncIterator, Sequence from dataclasses import dataclass from anyio import CapacityLimiter, Semaphore from fastapi import Request from minio import Minio from qdrant_client import AsyncQdrantClient from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker from src.application.ports.embedding import DenseEmbedder, SparseEmbedder from src.application.ports.object_storage import ObjectStorage from src.application.ports.point_repository import PointRepository from src.application.ports.point_storage import PointStorage from src.config import Settings @dataclass class AppResources: settings: Settings db_engine: AsyncEngine db_sessionmaker: async_sessionmaker[AsyncSession] minio_client: Minio qdrant_client: AsyncQdrantClient object_storage: ObjectStorage point_storage: PointStorage point_repository: PointRepository ingestion_limiter: CapacityLimiter dense_embedders: Sequence[DenseEmbedder] sparse_embedder: SparseEmbedder ingestion_concurrency_limiter: Semaphore def _resources(request: Request) -> AppResources: return request.app.state.resources def get_settings(request: Request) -> Settings: return _resources(request).settings def get_minio_client(request: Request) -> Minio: return _resources(request).minio_client def get_qdrant_client(request: Request) -> AsyncQdrantClient: return _resources(request).qdrant_client def get_object_storage(request: Request) -> ObjectStorage: return _resources(request).object_storage def get_point_storage(request: Request) -> PointStorage: return _resources(request).point_storage def get_point_repository(request: Request) -> PointRepository: return _resources(request).point_repository def get_ingestion_limiter(request: Request) -> CapacityLimiter: return _resources(request).ingestion_limiter def get_dense_embedders(request: Request) -> Sequence[DenseEmbedder]: return _resources(request).dense_embedders def get_sparse_embedder(request: Request) -> SparseEmbedder: return _resources(request).sparse_embedder def get_ingestion_concurrency_limiter(request: Request) -> Semaphore: return _resources(request).ingestion_concurrency_limiter def get_sessionmaker(request: Request) -> async_sessionmaker[AsyncSession]: """The session *factory*, not a request-scoped session. Application services that own more than one transaction in a single request (ADR-0017's two-phase upload) need to open and close sessions themselves rather than borrow one request-scoped session that would otherwise stay open across the whole request. """ return _resources(request).db_sessionmaker async def get_db_session(request: Request) -> AsyncIterator[AsyncSession]: sessionmaker = _resources(request).db_sessionmaker async with sessionmaker() as session: try: yield session except Exception: await session.rollback() raise