Coverage for src / lilbee / settings.py: 100%
43 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-04-29 19:16 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-04-29 19:16 +0000
1"""Persistent settings stored in config.toml alongside the data directory."""
3import sys
4import threading
5import tomllib
6from pathlib import Path
8_settings_lock = threading.Lock()
11def _config_path(data_root: Path) -> Path:
12 return data_root / "config.toml"
15def _escape_toml_string(s: str) -> str:
16 """Escape a string for embedding in a TOML double-quoted value."""
17 return (
18 s.replace("\\", "\\\\")
19 .replace('"', '\\"')
20 .replace("\n", "\\n")
21 .replace("\r", "\\r")
22 .replace("\t", "\\t")
23 .replace("\b", "\\b")
24 .replace("\f", "\\f")
25 )
28def load(data_root: Path) -> dict[str, str]:
29 """Read all settings from config.toml. Returns {} if file is missing."""
30 path = _config_path(data_root)
31 if not path.exists():
32 return {}
33 with path.open("rb") as f:
34 return {k: str(v) for k, v in tomllib.load(f).items()}
37def save(data_root: Path, settings: dict[str, str]) -> None:
38 """Write settings dict as simple TOML key-value pairs."""
39 path = _config_path(data_root)
40 path.parent.mkdir(parents=True, exist_ok=True)
41 lines = [f'{k} = "{_escape_toml_string(v)}"\n' for k, v in sorted(settings.items())]
42 path.write_text("".join(lines))
43 if sys.platform != "win32":
44 path.chmod(0o600)
47def get(data_root: Path, key: str) -> str | None:
48 """Look up a single key from config.toml."""
49 return load(data_root).get(key)
52def set_value(data_root: Path, key: str, value: str) -> None:
53 """Read-modify-write a single key in config.toml (thread-safe)."""
54 with _settings_lock:
55 current = load(data_root)
56 current[key] = value
57 save(data_root, current)
60def delete_value(data_root: Path, key: str) -> None:
61 """Remove a key from config.toml. No-op if key doesn't exist."""
62 with _settings_lock:
63 current = load(data_root)
64 current.pop(key, None)
65 save(data_root, current)
68def update_values(data_root: Path, updates: dict[str, str]) -> None:
69 """Batch update multiple keys in config.toml (single write)."""
70 with _settings_lock:
71 current = load(data_root)
72 current.update(updates)
73 save(data_root, current)
76def delete_values(data_root: Path, keys: list[str]) -> None:
77 """Batch delete multiple keys from config.toml (single write)."""
78 with _settings_lock:
79 current = load(data_root)
80 for key in keys:
81 current.pop(key, None)
82 save(data_root, current)