From 35c40bce75e929399342d858aaff92d3da4a4bb3 Mon Sep 17 00:00:00 2001 From: red person Date: Tue, 2 Jun 2026 21:53:05 +0300 Subject: [PATCH] Fall back from invalid settings stores (#1416) --- src/settings.py | 11 ++++++++--- tests/test_settings_store_shape.py | 20 ++++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 tests/test_settings_store_shape.py diff --git a/src/settings.py b/src/settings.py index 374fd28..3907dab 100644 --- a/src/settings.py +++ b/src/settings.py @@ -184,8 +184,10 @@ def load_settings() -> dict: try: with open(SETTINGS_FILE, "r", encoding="utf-8") as f: saved = json.load(f) + if not isinstance(saved, dict): + raise ValueError("settings must be an object") merged = {**DEFAULT_SETTINGS, **saved} - except (FileNotFoundError, json.JSONDecodeError): + except (FileNotFoundError, json.JSONDecodeError, ValueError): merged = dict(DEFAULT_SETTINGS) _settings_cache = (now, merged) return merged @@ -213,7 +215,8 @@ def is_setting_overridden(key: str) -> bool: """ try: with open(SETTINGS_FILE, "r", encoding="utf-8") as f: - return key in json.load(f) + saved = json.load(f) + return isinstance(saved, dict) and key in saved except (FileNotFoundError, json.JSONDecodeError): return False @@ -264,8 +267,10 @@ def load_features() -> dict: try: with open(FEATURES_FILE, "r", encoding="utf-8") as f: saved = json.load(f) + if not isinstance(saved, dict): + raise ValueError("features must be an object") merged = {**DEFAULT_FEATURES, **saved} - except (FileNotFoundError, json.JSONDecodeError): + except (FileNotFoundError, json.JSONDecodeError, ValueError): merged = dict(DEFAULT_FEATURES) _features_cache = (now, merged) return merged diff --git a/tests/test_settings_store_shape.py b/tests/test_settings_store_shape.py new file mode 100644 index 0000000..aa0d00c --- /dev/null +++ b/tests/test_settings_store_shape.py @@ -0,0 +1,20 @@ +from src import settings + + +def test_load_settings_falls_back_for_non_object_json(tmp_path, monkeypatch): + settings_file = tmp_path / "settings.json" + settings_file.write_text("[]", encoding="utf-8") + monkeypatch.setattr(settings, "SETTINGS_FILE", str(settings_file)) + settings._invalidate_caches() + + assert settings.load_settings() == settings.DEFAULT_SETTINGS + assert settings.is_setting_overridden("default_model") is False + + +def test_load_features_falls_back_for_non_object_json(tmp_path, monkeypatch): + features_file = tmp_path / "features.json" + features_file.write_text("[]", encoding="utf-8") + monkeypatch.setattr(settings, "FEATURES_FILE", str(features_file)) + settings._invalidate_caches() + + assert settings.load_features() == settings.DEFAULT_FEATURES