Coverage for src/lilbee/server/handlers/analyze.py: 100%
34 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-11-06 08:17 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-11-06 08:17 +0000
1"""Analyze handlers: the SSE-streamed run, and the analyze tip state."""
3from __future__ import annotations
5import asyncio
6from collections.abc import AsyncGenerator
8from litestar.exceptions import HTTPException
9from litestar.status_codes import HTTP_503_SERVICE_UNAVAILABLE
11from lilbee.app.analyze import AnalyzeReport, AnalyzeRequest, hide_tip, run_analysis, tip_state
12from lilbee.app.profiles import file_failure_message
13from lilbee.app.services import get_services
14from lilbee.core.config import cfg
15from lilbee.server.handlers.sse import SseStream
16from lilbee.server.models import AnalyzeResponse, AnalyzeStateResponse
19async def _run(sse: SseStream, request: AnalyzeRequest) -> AnalyzeReport:
20 """Run analyze with the stream's progress and cancel; the drain ends when this task does."""
21 try:
22 return await run_analysis(
23 get_services().profile_store, request, on_progress=sse.callback, cancel=sse.cancel
24 )
25 except OSError as exc:
26 raise RuntimeError(file_failure_message(exc)) from exc
29def _done_payload(report: AnalyzeReport) -> dict[str, object]:
30 return AnalyzeResponse.from_report(report).model_dump(mode="json")
33async def analyze_stream(request: AnalyzeRequest) -> AsyncGenerator[str, None]:
34 """Yield ``analyze`` progress events, then ``done`` with the report or ``error``."""
35 sse = SseStream()
36 task = asyncio.create_task(_run(sse, request))
37 async for event in sse.drain(task, "Analyze stream"):
38 yield event
39 frame = sse.terminal_frame(task, _done_payload)
40 if frame is not None:
41 yield frame
44async def analyze_state() -> AnalyzeStateResponse:
45 """The project's analyze tip state."""
46 return AnalyzeStateResponse.from_state(await asyncio.to_thread(tip_state, cfg.data_root))
49async def dismiss_tip() -> AnalyzeStateResponse:
50 """Hide the analyze tip for the project and return the new state; a failed write is 503."""
51 try:
52 await asyncio.to_thread(hide_tip, cfg.data_root)
53 except OSError as exc:
54 raise HTTPException(
55 status_code=HTTP_503_SERVICE_UNAVAILABLE, detail=file_failure_message(exc)
56 ) from exc
57 return await analyze_state()