Source code for pydocs_mcp.application.api_search
"""ApiSearch — thin wrapper around member_pipeline.run (spec §5.1)."""
from __future__ import annotations
from dataclasses import dataclass
from typing import cast
from pydocs_mcp.models import ModuleMemberList, SearchQuery, SearchResponse
from pydocs_mcp.retrieval.pipeline import CodeRetrieverPipeline
[docs]
@dataclass(frozen=True, slots=True)
class ApiSearch:
"""Runs the module-member retrieval pipeline and wraps its state as a SearchResponse.
Mirrors :class:`DocsSearch` but for the module-member pipeline:
deliberately thin and substitutes an empty ``ModuleMemberList`` when
the pipeline returns no result.
NOTE (spec S21): this class and :class:`DocsSearch` are intentionally
near-duplicate thin wrappers — see the longer note on
:class:`DocsSearch` for the rationale. Briefly: the duplication is
grep-friendly and avoids over-parameterization; a third near-identical
service is the trigger to factor out a shared base, not the second.
"""
member_pipeline: CodeRetrieverPipeline
async def search(self, query: SearchQuery) -> SearchResponse:
state = await self.member_pipeline.run(query)
return SearchResponse(
result=state.result or ModuleMemberList(items=()),
query=state.query,
duration_ms=state.duration_ms,
# Ranked member rows ride along for items[] (contract §3.2) — the
# formatter collapses ``result`` to a composite CHUNK, so the
# per-member rows only survive here.
candidates=state.candidates,
)
[docs]
async def ranked(self, query: SearchQuery) -> ModuleMemberList:
"""Return the RANKED candidate members (pre composite collapse).
Mirrors :meth:`DocsSearch.ranked` — multi-repo union needs per-item
members (score + ``package`` / ``module`` / ``name`` metadata) to merge
and dedup across databases.
"""
state = await self.member_pipeline.run(query)
# A member pipeline's candidates / result are always ModuleMemberList; the
# state field is a union across pipeline kinds, so narrow explicitly.
candidates = state.candidates if state.candidates is not None else state.result
return (
cast("ModuleMemberList", candidates)
if candidates is not None
else ModuleMemberList(items=())
)