Skip to content

Commit 3e1218d

Browse files
committed
feat: Add XDG base directory support for cache path - Default cache dir now respects - Falls back to ~/.cachier/ if not set - Updated Params and set_global_params to support dynamic cache_dir - Added tests for XDG compliance and fallback behavior
1 parent bad25e2 commit 3e1218d

2 files changed

Lines changed: 63 additions & 16 deletions

File tree

src/cachier/config.py

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import os
33
import pickle
44
import threading
5-
from dataclasses import dataclass, replace
5+
from dataclasses import dataclass, field
66
from datetime import datetime, timedelta
77
from typing import Any, Optional, Union
88

@@ -18,6 +18,19 @@ def _default_hash_func(args, kwds):
1818
return hashlib.sha256(serialized).hexdigest()
1919

2020

21+
def _default_cache_dir():
22+
"""Return default cache directory based on XDG specification.
23+
24+
Uses $XDG_CACHE_HOME if defined, otherwise falls back to ~/.cachier/
25+
"""
26+
xdg_cache_home = os.environ.get("XDG_CACHE_HOME")
27+
if xdg_cache_home:
28+
# Use XDG-compliant cache directory
29+
return os.path.join(xdg_cache_home, "cachier")
30+
# Default fallback if XDG is not set
31+
return os.path.expanduser("~/.cachier/")
32+
33+
2134
@dataclass
2235
class Params:
2336
"""Default definition for cachier parameters."""
@@ -28,12 +41,24 @@ class Params:
2841
mongetter: Optional[Mongetter] = None
2942
stale_after: timedelta = timedelta.max
3043
next_time: bool = False
31-
cache_dir: Union[str, os.PathLike] = "~/.cachier/"
44+
_cache_dir: Union[str, os.PathLike] = field(default_factory=_default_cache_dir)
3245
pickle_reload: bool = True
3346
separate_files: bool = False
3447
wait_for_calc_timeout: int = 0
3548
allow_none: bool = False
3649

50+
@property
51+
def cache_dir(self) -> Union[str, os.PathLike]:
52+
"""Dynamically get the cache directory, respecting XDG_CACHE_HOME."""
53+
default_cache = os.path.expanduser("~/.cachier/")
54+
if self._cache_dir == _default_cache_dir or self._cache_dir == default_cache:
55+
return _default_cache_dir()
56+
return self._cache_dir
57+
58+
@cache_dir.setter
59+
def cache_dir(self, value: Union[str, os.PathLike]):
60+
self._cache_dir = value
61+
3762

3863
_global_params = Params()
3964

@@ -91,17 +116,9 @@ def set_global_params(**params: Any) -> None:
91116
only have an effect on decorators applied after this function is run.
92117
93118
"""
94-
import cachier
95-
96-
valid_params = {
97-
k: v
98-
for k, v in params.items()
99-
if hasattr(cachier.config._global_params, k)
100-
}
101-
cachier.config._global_params = replace(
102-
cachier.config._global_params,
103-
**valid_params, # type: ignore[arg-type]
104-
)
119+
valid_params = {k: v for k, v in params.items() if hasattr(_global_params, k)}
120+
for k, v in valid_params.items():
121+
setattr(_global_params, k, v)
105122

106123

107124
def get_default_params() -> Params:

tests/test_defaults.py

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,36 @@ def global_test_2():
9797
assert global_test_2.cache_dpath() == str(tmpdir / "2")
9898

9999

100+
def test_cache_dir_respects_xdg(monkeypatch, tmpdir):
101+
xdg_path = str(tmpdir / "xdg_home")
102+
monkeypatch.setenv("XDG_CACHE_HOME", xdg_path)
103+
104+
expected_path = os.path.join(xdg_path, "cachier")
105+
106+
@cachier.cachier(backend="pickle")
107+
def my_func():
108+
return 123
109+
110+
actual_path = my_func.cache_dpath()
111+
assert str(actual_path) == expected_path
112+
113+
my_func() # Create cache file
114+
assert os.path.exists(expected_path)
115+
files = os.listdir(expected_path) if os.path.exists(expected_path) else []
116+
assert any(os.path.isfile(os.path.join(expected_path, f)) for f in files)
117+
118+
119+
def test_cache_dir_default_fallback(monkeypatch):
120+
monkeypatch.delenv("XDG_CACHE_HOME", raising=False)
121+
122+
@cachier.cachier()
123+
def my_func():
124+
return 123
125+
126+
expected_path = os.path.expanduser("~/.cachier/")
127+
assert my_func.cache_dpath().startswith(expected_path)
128+
129+
100130
def test_separate_files_default_param(tmpdir):
101131
cachier.set_global_params(separate_files=True)
102132

@@ -277,17 +307,17 @@ def dummy_func(a, b=2):
277307
dummy_func.clear_cache()
278308
assert count == 0
279309
with pytest.deprecated_call(
280-
match="`verbose_cache` is deprecated and will be removed"
310+
match="`verbose_cache` is deprecated and will be removed"
281311
):
282312
assert dummy_func(1, verbose_cache=True) == 3
283313
assert count == 1
284314
with pytest.deprecated_call(
285-
match="`ignore_cache` is deprecated and will be removed"
315+
match="`ignore_cache` is deprecated and will be removed"
286316
):
287317
assert dummy_func(1, ignore_cache=True) == 3
288318
assert count == 2
289319
with pytest.deprecated_call(
290-
match="`overwrite_cache` is deprecated and will be removed"
320+
match="`overwrite_cache` is deprecated and will be removed"
291321
):
292322
assert dummy_func(1, overwrite_cache=True) == 3
293323
assert count == 3

0 commit comments

Comments
 (0)