Coverage for src/lilbee/core/config/parsing.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"""Parsing helpers shared by the Config field validators and the resolver's soft checks."""
3from collections.abc import Mapping
4from types import MappingProxyType
6# Matches what pydantic itself accepts for a bool field. Every other bool on
7# Config is coerced by pydantic, so a narrower vocabulary here would make the
8# same env spelling mean different things on different fields of one object.
9_BOOL_TRUE = frozenset({"true", "t", "yes", "y", "on", "1"})
10_BOOL_FALSE = frozenset({"false", "f", "no", "n", "off", "0"})
11_AUTO_ALIASES = frozenset({"", "auto", "none"})
14def parse_bool(raw: str) -> bool:
15 """Parse a boolean env string; raises ValueError on anything else."""
16 normalized = raw.strip().lower()
17 if normalized in _BOOL_TRUE:
18 return True
19 if normalized in _BOOL_FALSE:
20 return False
21 raise ValueError(f"Invalid boolean: {raw!r}")
24def parse_tristate_bool(raw: str) -> bool | None:
25 """Parse an auto/on/off string: blank, ``auto`` or ``none`` means auto-detect."""
26 if raw.strip().lower() in _AUTO_ALIASES:
27 return None
28 return parse_bool(raw)
31def parse_optional_int(
32 raw: str, *, aliases: Mapping[str, int] = MappingProxyType({})
33) -> int | None:
34 """Parse an optional integer: blank/``auto``/``none`` is None; *aliases* map other labels."""
35 label = raw.strip().lower()
36 if label in _AUTO_ALIASES:
37 return None
38 if label in aliases:
39 return aliases[label]
40 return int(label)
43def parse_gpu_device_list(raw: str) -> str | None:
44 """Parse a comma-separated device index list; blank/auto/all/none means every device."""
45 label = raw.strip().lower()
46 if label in _AUTO_ALIASES or label == "all":
47 return None
48 parts = [p.strip() for p in raw.split(",") if p.strip()]
49 if not parts:
50 return None
51 for part in parts:
52 if not part.lstrip("-").isdigit():
53 raise ValueError(f"Invalid device index: {part!r}")
54 return ",".join(parts)