Coverage for src/lilbee/app/reset.py: 100%
34 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-14 11:46 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-14 11:46 +0000
1"""Knowledge base reset (delete all documents and data)."""
3from __future__ import annotations
5import logging
6import shutil
7from pathlib import Path
9from pydantic import BaseModel
11from lilbee.core.config import cfg
14class ResetResult(BaseModel):
15 """Result of a full knowledge base reset."""
17 command: str = "reset"
18 deleted_docs: int
19 deleted_data: int
20 skipped: list[str] = []
21 documents_dir: str
22 data_dir: str
25def _clear_dir(base_dir: Path, skipped: list[str]) -> int:
26 """Delete all items in *base_dir*, appending undeletable paths to *skipped*."""
27 log = logging.getLogger(__name__)
28 deleted = 0
29 if not base_dir.exists():
30 return deleted
31 for item in list(base_dir.iterdir()):
32 try:
33 # iterdir yields direct children only, so the entry is within
34 # base_dir by construction. Registered source roots live outside this
35 # dir (only their config entry is here), so reset never reaches a
36 # user's corpus: it deletes owned files, and un-registering roots is
37 # done by clearing config.toml under data_dir.
38 if item.is_dir():
39 shutil.rmtree(item)
40 else:
41 item.unlink()
42 except OSError as exc:
43 # best-effort: reset is "delete as much as you can", and the
44 # caller surfaces the skipped list to the user verbatim.
45 log.warning("Could not delete %s: %s", item, exc)
46 skipped.append(str(item))
47 continue
48 deleted += 1
49 return deleted
52def perform_reset() -> ResetResult:
53 """Delete all documents and data. Returns summary of what was deleted."""
54 skipped: list[str] = []
55 deleted_docs = _clear_dir(cfg.documents_dir, skipped)
56 deleted_data = _clear_dir(cfg.data_dir, skipped)
58 return ResetResult(
59 deleted_docs=deleted_docs,
60 deleted_data=deleted_data,
61 skipped=skipped,
62 documents_dir=str(cfg.documents_dir),
63 data_dir=str(cfg.data_dir),
64 )