Coverage for src/lilbee/server/routes/analyze.py: 100%

28 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-11-06 08:17 +0000

1"""Analyze routes: the streamed run, the tip state, and hiding the tip.""" 

2 

3from __future__ import annotations 

4 

5import asyncio 

6 

7from litestar import Router, get, post 

8from litestar.exceptions import ValidationException 

9from litestar.openapi.datastructures import ResponseSpec 

10from litestar.response import Stream 

11from litestar.status_codes import HTTP_200_OK 

12 

13from lilbee.app.analyze import AnalyzeRequest, server_directory, validate_request 

14from lilbee.server.handlers import analyze as handlers 

15from lilbee.server.handlers.sse import SSE_MEDIA_TYPE 

16from lilbee.server.models import AnalyzeRequestBody, AnalyzeResponse, AnalyzeStateResponse 

17 

18 

19def _request(data: AnalyzeRequestBody) -> AnalyzeRequest: 

20 return AnalyzeRequest( 

21 directory=server_directory(data.directory), 

22 apply=data.apply, 

23 save=data.save, 

24 target=data.target, 

25 ) 

26 

27 

28@post( 

29 "/api/analyze", 

30 media_type=SSE_MEDIA_TYPE, 

31 status_code=HTTP_200_OK, 

32 responses={ 

33 HTTP_200_OK: ResponseSpec( 

34 data_container=AnalyzeResponse, 

35 media_type=SSE_MEDIA_TYPE, 

36 generate_examples=False, 

37 description="Progress events, then a done event whose data is this report.", 

38 ) 

39 }, 

40) 

41async def analyze_route(data: AnalyzeRequestBody | None = None) -> Stream: 

42 """Read the corpus or a folder and recommend a profile, streaming ``analyze`` progress. 

43 

44 The ``done`` event carries the report; a refused request answers 400 before any file 

45 is read. A client that disconnects before the save starts cancels the run; nothing is saved. 

46 """ 

47 try: 

48 request = _request(data if data is not None else AnalyzeRequestBody()) 

49 await asyncio.to_thread(validate_request, request) 

50 except ValueError as exc: 

51 raise ValidationException(str(exc)) from exc 

52 return Stream(handlers.analyze_stream(request), media_type=SSE_MEDIA_TYPE) 

53 

54 

55@get("/api/analyze/state") 

56async def analyze_state_route() -> AnalyzeStateResponse: 

57 """Whether the project was analyzed, hid the tip, and would see the tip now.""" 

58 return await handlers.analyze_state() 

59 

60 

61@post("/api/analyze/dismiss", status_code=HTTP_200_OK) 

62async def analyze_dismiss_route() -> AnalyzeStateResponse: 

63 """Hide the analyze tip for this project.""" 

64 return await handlers.dismiss_tip() 

65 

66 

67analyze_router = Router( 

68 path="/", 

69 route_handlers=[analyze_route, analyze_state_route, analyze_dismiss_route], 

70)