Source code for pydocs_mcp.application.package_lookup

"""PackageLookup — list + get_package_doc via UoW (spec §5.1, post-#5a-2)."""

from __future__ import annotations

from collections.abc import Callable
from dataclasses import dataclass

from pydocs_mcp.models import (
    ChunkFilterField,
    ModuleMemberFilterField,
    Package,
    PackageDoc,
)
from pydocs_mcp.storage.protocols import UnitOfWork


[docs] @dataclass(frozen=True, slots=True) class PackageLookup: """Composes the three domain stores (via UoW) into a read-only package view. Post-#5a-2: depends only on ``uow_factory``. Each public method opens a fresh UoW; reads run inside ``async with`` and exit without committing. Note: ``get_package_doc`` no longer uses ``asyncio.gather`` for the two list reads — both go through the same held connection inside the UoW, and concurrent access would race ``_sqlite_transaction``'s lock. Per spec §7.2, the two ~1ms SELECTs run sequentially. """ uow_factory: Callable[[], UnitOfWork] async def list_packages(self) -> tuple[Package, ...]: async with self.uow_factory() as uow: return tuple(await uow.packages.list(limit=200)) async def get_package_doc(self, package_name: str) -> PackageDoc | None: async with self.uow_factory() as uow: pkg = await uow.packages.get(package_name) if pkg is None: return None chunks = await uow.chunks.list( filter={ChunkFilterField.PACKAGE.value: package_name}, limit=10, ) members = await uow.module_members.list( filter={ModuleMemberFilterField.PACKAGE.value: package_name}, limit=30, ) return PackageDoc(package=pkg, chunks=tuple(chunks), members=tuple(members)) async def find_module(self, package: str, module: str) -> bool: if not package or not module: return False async with self.uow_factory() as uow: chunks = await uow.chunks.list( filter={ ChunkFilterField.PACKAGE.value: package, ChunkFilterField.MODULE.value: module, }, limit=1, ) return bool(chunks)