Coverage for src/local_deep_research/exceptions.py: 100%

6 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-06-03 23:15 +0000

1"""Project-wide exception classes.""" 

2 

3 

4class ResearchTerminatedException(BaseException): 

5 """Raised when a user cancels an in-progress research process. 

6 

7 Inherits from BaseException (not Exception) so that ``except Exception`` 

8 blocks throughout the strategy code naturally let it propagate -- the same 

9 pattern Python's stdlib uses for asyncio.CancelledError (since 3.9), 

10 KeyboardInterrupt, and SystemExit. 

11 """ 

12 

13 pass 

14 

15 

16class SystemAtCapacityError(Exception): 

17 """Raised when ``start_research_process`` cannot acquire the global 

18 concurrency semaphore. 

19 

20 Previously the semaphore was acquired *inside* the worker thread, so a 

21 full system would silently park the thread after the HTTP route had 

22 already returned 200 — the user saw a thinking spinner that never 

23 advanced, and the partial unique in-progress index blocked retries on 

24 the same chat session. 

25 

26 Acquiring synchronously in the caller and surfacing this exception lets 

27 routes return HTTP 429 (or queue/retry, depending on caller) before any 

28 ``ResearchHistory`` row is committed. 

29 """ 

30 

31 pass 

32 

33 

34class DuplicateResearchError(Exception): 

35 """Raised when a research should not be (re-)spawned. 

36 

37 Two triggering cases, both handled identically by callers: 

38 

39 1. A live thread already exists in the active-research dict for this 

40 ``research_id`` — typically a retry after a prior attempt's 

41 post-spawn ``UserActiveResearch`` commit failed. 

42 2. The ``ResearchHistory.status`` is non-QUEUED (``IN_PROGRESS`` from 

43 a prior attempt's pre-spawn commit that succeeded; terminal 

44 ``COMPLETED`` / ``FAILED`` / ``SUSPENDED`` from a thread that 

45 already finished and cleaned itself out of ``_active_research``). 

46 Re-spawning would either contradict the live thread or re-run a 

47 finished research. 

48 

49 Callers that wrap the spawn in ``except Exception`` to clean up orphan 

50 state on spawn failure MUST catch ``DuplicateResearchError`` separately 

51 *before* that generic branch and re-raise / return without mutating the 

52 research's status or deleting the ``UserActiveResearch`` row — those 

53 rows belong to the live thread, and marking them FAILED terminates a 

54 running thread from the user's perspective while it keeps executing. 

55 """ 

56 

57 pass