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, replace
from pydocs_mcp.application.branch_search import chunk_filter_on_branch, member_filter_on_branch
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.
``branch`` is the branch the chunk and member reads answer from (spec
§6.4, #313): ``None`` — the default — reads the served default branch's
members and every chunk row, as before; a name pins both to that branch
(dependency rows are every branch's).
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]
branch: str | None = None
[docs]
def on_branch(self, branch: str | None) -> PackageLookup:
"""This lookup reading ``branch``'s rows; ``None`` keeps it as it is."""
return self if branch is None else replace(self, branch=branch)
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=chunk_filter_on_branch(
{ChunkFilterField.PACKAGE.value: package_name}, self.branch
),
limit=10,
)
members = await uow.module_members.list(
filter=member_filter_on_branch(
{ModuleMemberFilterField.PACKAGE.value: package_name}, self.branch
),
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
wanted = {ChunkFilterField.PACKAGE.value: package, ChunkFilterField.MODULE.value: module}
async with self.uow_factory() as uow:
chunks = await uow.chunks.list(
filter=chunk_filter_on_branch(wanted, self.branch), limit=1
)
return bool(chunks)