feat: preferences public helpers — get_user_preference / set_user_preference (closes #22 self-hosted)

This commit is contained in:
pyr0ball 2026-04-04 18:10:24 -07:00
parent 0d9d030320
commit d719ea2309
3 changed files with 77 additions and 1 deletions

View file

@ -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",
]

View file

@ -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()

View file

@ -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