diff --git a/headroom/cache/backends/__init__.py b/headroom/cache/backends/__init__.py index 8dd19ac3d..85adeff08 100644 --- a/headroom/cache/backends/__init__.py +++ b/headroom/cache/backends/__init__.py @@ -13,7 +13,7 @@ Usage: from headroom.cache.backends import SQLiteBackend, CompressionStoreBackend from headroom.cache.compression_store import CompressionStore, get_compression_store - # Env-driven default (SQLite at ~/.headroom/ccr_store.db) + # Env-driven default (SQLite at workspace_dir()/ccr_store.db) store = get_compression_store() # Direct construction defaults to in-memory; pass a backend for persistence diff --git a/headroom/cache/backends/sqlite.py b/headroom/cache/backends/sqlite.py index 641ae369b..35d9cacb5 100644 --- a/headroom/cache/backends/sqlite.py +++ b/headroom/cache/backends/sqlite.py @@ -13,7 +13,7 @@ cannot provide, both load-bearing for the no-accuracy-loss guarantee: Set ``HEADROOM_CCR_BACKEND=memory`` to opt back into the in-memory backend, or ``HEADROOM_CCR_SQLITE_PATH`` to relocate the database file -(default ``~/.headroom/ccr_store.db``). +(default ``workspace_dir()/ccr_store.db``). """ from __future__ import annotations @@ -49,11 +49,13 @@ _PURGE_INTERVAL = 60.0 def default_db_path() -> Path: - """Resolve the database path (env override or ~/.headroom/).""" + """Resolve the database path (env override, else workspace root).""" env = os.environ.get("HEADROOM_CCR_SQLITE_PATH", "").strip() if env: return Path(env).expanduser() - return Path.home() / ".headroom" / "ccr_store.db" + from ...paths import workspace_dir + + return workspace_dir() / "ccr_store.db" class SQLiteBackend: diff --git a/headroom/cache/compression_store.py b/headroom/cache/compression_store.py index 69f3610cf..84d3e2cf6 100644 --- a/headroom/cache/compression_store.py +++ b/headroom/cache/compression_store.py @@ -946,9 +946,9 @@ def clear_request_compression_store() -> None: def _create_default_ccr_backend() -> CompressionStoreBackend | None: """Create a CCR backend from env (e.g. HEADROOM_CCR_BACKEND=redis). - Default (env unset or "sqlite"): SQLiteBackend at - ~/.headroom/ccr_store.db — restart-safe and shared across worker - processes, which the session-scale 30-minute TTL assumes. + Default (env unset or "sqlite"): SQLiteBackend at workspace_dir()/ccr_store.db + — restart-safe and shared across worker processes, which the + session-scale 30-minute TTL assumes. "memory" opts back into the in-process dict. Other values load adapters via setuptools entry point 'headroom.ccr_backend'. Returns None to use InMemoryBackend. diff --git a/tests/test_ccr_sqlite_backend.py b/tests/test_ccr_sqlite_backend.py index 3c6a88550..fbdd87ef7 100644 --- a/tests/test_ccr_sqlite_backend.py +++ b/tests/test_ccr_sqlite_backend.py @@ -212,6 +212,56 @@ class TestDefaults: assert backend is not None assert backend.get_stats()["backend_type"] == "sqlite" + def test_workspace_dir(self, monkeypatch, tmp_path): + from headroom.cache.compression_store import _create_default_ccr_backend + + workspace = tmp_path / "workspace" + fake_home = tmp_path / "fake_home" + + monkeypatch.delenv("HEADROOM_CCR_BACKEND", raising=False) + monkeypatch.delenv("HEADROOM_CCR_SQLITE_PATH", raising=False) + monkeypatch.setenv("HEADROOM_WORKSPACE_DIR", str(workspace)) + monkeypatch.setenv("HOME", str(fake_home)) + monkeypatch.setenv("USERPROFILE", str(fake_home)) + + backend = _create_default_ccr_backend() + assert backend is not None + assert str(backend._path) == str(workspace / "ccr_store.db") + + def test_sqlite_path_env_wins(self, monkeypatch, tmp_path): + from headroom.cache.compression_store import _create_default_ccr_backend + + workspace = tmp_path / "workspace" + sqlite_path = tmp_path / "sqlite_override.db" + + monkeypatch.delenv("HEADROOM_CCR_BACKEND", raising=False) + monkeypatch.setenv("HEADROOM_WORKSPACE_DIR", str(workspace)) + monkeypatch.setenv("HEADROOM_CCR_SQLITE_PATH", str(sqlite_path)) + backend = _create_default_ccr_backend() + + assert backend is not None + assert str(backend._path) == str(sqlite_path) + + def test_home_fallback(self, monkeypatch, tmp_path): + from headroom.cache.compression_store import _create_default_ccr_backend + + fake_home = tmp_path / "fake_home" + + monkeypatch.delenv("HEADROOM_CCR_BACKEND", raising=False) + monkeypatch.delenv("HEADROOM_CCR_SQLITE_PATH", raising=False) + monkeypatch.delenv("HEADROOM_WORKSPACE_DIR", raising=False) + monkeypatch.setenv("HOME", str(fake_home)) + monkeypatch.setenv("USERPROFILE", str(fake_home)) + + backend = _create_default_ccr_backend() + assert backend is not None + assert str(backend._path) == str(fake_home / ".headroom" / "ccr_store.db") + + def test_explicit_db_path(self, tmp_path): + explicit = tmp_path / "explicit.db" + backend = SQLiteBackend(explicit) + assert backend._path == explicit + def test_memory_opt_out(self, monkeypatch): from headroom.cache.compression_store import _create_default_ccr_backend