diff --git a/src/cachier/config.py b/src/cachier/config.py index 003f6b0b..cb908a4a 100644 --- a/src/cachier/config.py +++ b/src/cachier/config.py @@ -2,7 +2,7 @@ import os import pickle import threading -from dataclasses import dataclass, replace +from dataclasses import dataclass, field, replace from datetime import datetime, timedelta from typing import Any, Optional, Union @@ -18,6 +18,36 @@ def _default_hash_func(args, kwds): return hashlib.sha256(serialized).hexdigest() +def _default_cache_dir(): + """Return default cache directory based on XDG specification. + + Uses $XDG_CACHE_HOME if defined, otherwise falls back to ~/.cachier/ + + """ + xdg_cache_home = os.environ.get("XDG_CACHE_HOME") + if xdg_cache_home: + # Use XDG-compliant cache directory + return os.path.join(xdg_cache_home, "cachier") + # Default fallback if XDG is not set + return os.path.expanduser("~/.cachier/") + + +class LazyCacheDir: + """Lazily resolve the default cache directory using $XDG_CACHE_HOME.""" + + def __str__(self): + """Return the resolved cache directory path as a string.""" + return _default_cache_dir() + + def __fspath__(self): + """Return the path for filesystem operations.""" + return self.__str__() + + def __eq__(self, other): + """Compare the resolved path to another path.""" + return str(self) == str(other) + + @dataclass class Params: """Default definition for cachier parameters.""" @@ -28,7 +58,7 @@ class Params: mongetter: Optional[Mongetter] = None stale_after: timedelta = timedelta.max next_time: bool = False - cache_dir: Union[str, os.PathLike] = "~/.cachier/" + cache_dir: Union[str, os.PathLike] = field(default_factory=LazyCacheDir) pickle_reload: bool = True separate_files: bool = False wait_for_calc_timeout: int = 0 diff --git a/tests/test_defaults.py b/tests/test_defaults.py index 374ebd82..012fb1b8 100644 --- a/tests/test_defaults.py +++ b/tests/test_defaults.py @@ -97,6 +97,43 @@ def global_test_2(): assert global_test_2.cache_dpath() == str(tmpdir / "2") +def test_cache_dir_respects_xdg(monkeypatch, tmpdir): + xdg_path = str(tmpdir / "xdg_home") + monkeypatch.setenv("XDG_CACHE_HOME", xdg_path) + + expected_path = os.path.join(xdg_path, "cachier") + + @cachier.cachier(backend="pickle") + def my_func(): + return 123 + + actual_path = my_func.cache_dpath() + assert str(actual_path) == expected_path + + my_func() # Create cache file + assert os.path.exists(expected_path) + files = os.listdir(expected_path) if os.path.exists(expected_path) else [] + assert any(os.path.isfile(os.path.join(expected_path, f)) for f in files) + + +def test_cache_dir_default_fallback(monkeypatch): + monkeypatch.delenv("XDG_CACHE_HOME", raising=False) + + @cachier.cachier() + def my_func(): + return 123 + + expected_path = os.path.expanduser("~/.cachier/") + assert my_func.cache_dpath().startswith(expected_path) + + +def test_lazy_cache_dir_eq_triggered(): + default_dir = cachier.get_global_params().cache_dir + + assert default_dir == str(default_dir) + assert default_dir != "/some/random/path" + + def test_separate_files_default_param(tmpdir): cachier.set_global_params(separate_files=True)