From 44da57d084af3169faa36d0778ad120759c7765a Mon Sep 17 00:00:00 2001 From: Username Date: Tue, 24 Feb 2026 16:23:04 +0100 Subject: [PATCH] config: filter unknown toml keys before dataclass init Prevents opaque TypeError on typos in config.toml; unknown keys are logged as warnings and silently dropped. --- src/tuimble/config.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/tuimble/config.py b/src/tuimble/config.py index 14af7c4..5c2361e 100644 --- a/src/tuimble/config.py +++ b/src/tuimble/config.py @@ -1,8 +1,12 @@ """Configuration management.""" +import dataclasses +import logging from dataclasses import dataclass, field from pathlib import Path +log = logging.getLogger(__name__) + CONFIG_DIR = Path.home() / ".config" / "tuimble" CONFIG_FILE = CONFIG_DIR / "config.toml" @@ -60,9 +64,18 @@ def load_config(path: Path | None = None) -> Config: cfg = Config() if "server" in data: - cfg.server = ServerConfig(**data["server"]) + cfg.server = _load_section(ServerConfig, data["server"]) if "audio" in data: - cfg.audio = AudioConfig(**data["audio"]) + cfg.audio = _load_section(AudioConfig, data["audio"]) if "ptt" in data: - cfg.ptt = PttConfig(**data["ptt"]) + cfg.ptt = _load_section(PttConfig, data["ptt"]) return cfg + + +def _load_section(cls, raw: dict): + """Instantiate a dataclass, silently dropping unknown keys.""" + valid = {f.name for f in dataclasses.fields(cls)} + unknown = set(raw) - valid + if unknown: + log.warning("ignoring unknown config keys: %s", ", ".join(sorted(unknown))) + return cls(**{k: v for k, v in raw.items() if k in valid})