diff --git a/circuitforge_core/preferences/__init__.py b/circuitforge_core/preferences/__init__.py index 5b3e1f7..113629f 100644 --- a/circuitforge_core/preferences/__init__.py +++ b/circuitforge_core/preferences/__init__.py @@ -1,3 +1,47 @@ +from . import store as store_module from .paths import get_path, set_path +from .store import LocalFileStore, PreferenceStore -__all__ = ["get_path", "set_path"] + +def get_user_preference( + user_id: str | None, + path: str, + default=None, + store: PreferenceStore | None = None, +): + """Read a preference value at dot-separated *path*. + + Args: + user_id: User identifier (passed to store; local store ignores it). + path: Dot-separated preference path, e.g. ``"affiliate.opt_out"``. + default: Returned when the path is not set. + store: Optional store override; defaults to ``LocalFileStore`` at + ``~/.config/circuitforge/preferences.yaml``. + """ + s = store or store_module._DEFAULT_STORE + return s.get(user_id=user_id, path=path, default=default) + + +def set_user_preference( + user_id: str | None, + path: str, + value, + store: PreferenceStore | None = None, +) -> None: + """Write *value* at dot-separated *path*. + + Args: + user_id: User identifier (passed to store; local store ignores it). + path: Dot-separated preference path, e.g. ``"affiliate.byok_ids.ebay"``. + value: Value to persist. + store: Optional store override; defaults to ``LocalFileStore``. + """ + s = store or store_module._DEFAULT_STORE + s.set(user_id=user_id, path=path, value=value) + + +__all__ = [ + "get_path", "set_path", + "get_user_preference", "set_user_preference", + "LocalFileStore", "PreferenceStore", +] diff --git a/circuitforge_core/preferences/store.py b/circuitforge_core/preferences/store.py index 126b99a..7ec8776 100644 --- a/circuitforge_core/preferences/store.py +++ b/circuitforge_core/preferences/store.py @@ -73,3 +73,6 @@ class LocalFileStore: def set(self, user_id: str | None, path: str, value: Any) -> None: # noqa: ARG002 self._save(set_path(self._load(), path, value)) + + +_DEFAULT_STORE: PreferenceStore = LocalFileStore() diff --git a/tests/test_preferences.py b/tests/test_preferences.py index 4e85170..1934536 100644 --- a/tests/test_preferences.py +++ b/tests/test_preferences.py @@ -117,3 +117,32 @@ class TestLocalFileStore: store = self._store(tmp_path) store.set(user_id="u123", path="affiliate.opt_out", value=True) assert store.get(user_id="u456", path="affiliate.opt_out", default=False) is True + + +from circuitforge_core.preferences import get_user_preference, set_user_preference + + +class TestPreferenceHelpers: + def _store(self, tmp_path) -> LocalFileStore: + return LocalFileStore(prefs_path=tmp_path / "preferences.yaml") + + def test_get_returns_default_when_unset(self, tmp_path): + store = self._store(tmp_path) + result = get_user_preference(user_id=None, path="affiliate.opt_out", + default=False, store=store) + assert result is False + + def test_set_then_get(self, tmp_path): + store = self._store(tmp_path) + set_user_preference(user_id=None, path="affiliate.opt_out", value=True, store=store) + result = get_user_preference(user_id=None, path="affiliate.opt_out", + default=False, store=store) + assert result is True + + def test_default_store_is_local(self, tmp_path, monkeypatch): + """When no store is passed, helpers use LocalFileStore at default path.""" + from circuitforge_core.preferences import store as store_module + local = self._store(tmp_path) + monkeypatch.setattr(store_module, "_DEFAULT_STORE", local) + set_user_preference(user_id=None, path="x.y", value=42) + assert get_user_preference(user_id=None, path="x.y") == 42