From ff38c3e6a408f9598c3e65b9425e2efa362d5930 Mon Sep 17 00:00:00 2001 From: Spoofiecus Date: Tue, 15 Sep 2026 12:11:02 +0200 Subject: [PATCH] fix(cookbook): discover macOS HuggingFace cache location (#5978) (cherry picked from commit 204d0227f4daa7fe0b9aba6c7b5a8732de0f9b7c) --- routes/cookbook_helpers.py | 20 +++++++++- tests/test_cookbook_helpers.py | 67 +++++++++++++++++++++++++++++++++- 2 files changed, 84 insertions(+), 3 deletions(-) diff --git a/routes/cookbook_helpers.py b/routes/cookbook_helpers.py index 73157ff8e7..b7487898f8 100644 --- a/routes/cookbook_helpers.py +++ b/routes/cookbook_helpers.py @@ -377,7 +377,7 @@ def _cached_model_scan_script(model_dirs: list[str] | None = None, add_hf_cache: Allows for an additional HuggingFace cache path to be scanned (i.e. Windows HF cache for local WSL envs.) """ lines = [ - "import json, os, re, shutil, subprocess, urllib.request", + "import json, os, re, shutil, subprocess, sys, urllib.request", "models = []", "seen = set()", "BLOCKED_ROOTS = ('/sys', '/proc', '/dev', '/run', '/var/run')", @@ -488,7 +488,23 @@ def _cached_model_scan_script(model_dirs: list[str] | None = None, add_hf_cache: " add(os.environ.get('HUGGINGFACE_HUB_CACHE'))", " hf_home = os.environ.get('HF_HOME')", " if hf_home: add(os.path.join(hf_home, 'hub'))", - " add('~/.cache/huggingface/hub')", + " # Best-effort: ask huggingface_hub for its own resolved cache.", + " # Its defaults are platform-aware - macOS uses ~/Library/Caches/huggingface", + " # (NOT ~/.cache/...), which is what issue #5978 reported as missing. Falls", + " # back silently so the scanner still runs on remote hosts without the lib.", + " try:", + " import huggingface_hub", + " _c = getattr(huggingface_hub, 'constants', None)", + " _hc = getattr(_c, 'HF_HUB_CACHE', None) or getattr(_c, 'hf_hub_cache', None)", + " if _hc: add(_hc)", + " except Exception:", + " pass", + " # Platform-aware hard-coded fallback (covers hosts lacking huggingface_hub", + " # so the cache is still discoverable on macOS even without the library).", + " if sys.platform == 'darwin':", + " add('~/Library/Caches/huggingface/hub')", + " else:", + " add('~/.cache/huggingface/hub')", " # Docker images mount ./data/huggingface at /app/.cache/huggingface.", " # When HOME is /root, expanduser() misses that persisted cache.", " add('/app/.cache/huggingface/hub')", diff --git a/tests/test_cookbook_helpers.py b/tests/test_cookbook_helpers.py index fdac772cdc..57bbe8c215 100644 --- a/tests/test_cookbook_helpers.py +++ b/tests/test_cookbook_helpers.py @@ -1022,7 +1022,72 @@ def test_validate_serve_cmd_rejects_unrelated_subshell_pipelines(): ( "llama-server --model model.gguf " "--mmproj \"$(find '/app/models' -iname '*.gguf' 2>/dev/null | sort | head -1)\"" - ), + ), ]: with pytest.raises(HTTPException): _validate_serve_cmd(cmd) + + +# ----- Issue #5978: macOS HF cache discovery ----- +# huggingface_hub stores downloads under ~/Library/Caches/huggingface/hub on +# macOS, but the scanner only knew ~/.cache/... (Linux) and never consulted the +# library, so macOS caches were invisible unless the user manually exported +# HF_HOME. The two tests below pin the fix. + + +def _exec_hf_cache_paths_def(): + """Recover the shipped `hf_cache_paths()` def out of the generated scanner + + and return it as a callable for behavioral testing. + + Why exec-and-call instead of reaching the GET /api/model/cached route: + `_cached_model_scan_script` emits a standalone source string intended to + run via `python -` / `ssh host "python -"`; exercising it through the route + would reconstruct a Request + ServeRequest instead of testing the real + emission point. Extracting the one def (by stable delimiters, no regex) + and calling it pins exactly what ships (narrow, documented exception per + tests/TESTING_STANDARD.md). + """ + script = _cached_model_scan_script() + start = script.index("def hf_cache_paths():") + end = script.index(" return candidates", start) + len(" return candidates") + ns: dict = {"os": os, "sys": sys} + exec(script[start:end] + "\n", ns) + return ns["hf_cache_paths"] + + +def test_hf_cache_paths_includes_macos_library_caches_under_darwin(monkeypatch): + fn = _exec_hf_cache_paths_def() + monkeypatch.setattr("sys.platform", "darwin") + cands = fn() + assert any("Library/Caches/huggingface" in c for c in cands), cands + + +def test_hf_cache_paths_includes_linux_dotcache_under_linux(monkeypatch): + fn = _exec_hf_cache_paths_def() + monkeypatch.setattr("sys.platform", "linux") + cands = fn() + assert any(".cache/huggingface/hub" in c for c in cands), cands + + +def test_cached_model_scan_finds_model_at_platform_default_cache(tmp_path): + """End-to-end: the platform-default cache location (Linux ~/.cache here) must + be scanned with no model_dir / HF_HOME override — the #5978 scenario.""" + cache = tmp_path / "home" / "tester" / ".cache" / "huggingface" / "hub" + snap = cache / "models--acme--cached-7b" / "snapshots" / "sha1" + snap.mkdir(parents=True) + (snap / "model-q4.gguf").write_bytes(b"gguf") + + env = {k: v for k, v in os.environ.items() + if k not in ("HUGGINGFACE_HUB_CACHE", "HF_HOME")} + env["HOME"] = str(tmp_path / "home" / "tester") + + scan_py = tmp_path / "scan_cache.py" + scan_py.write_text(_cached_model_scan_script(), encoding="utf-8") + proc = subprocess.run( + [sys.executable, str(scan_py)], + env=env, capture_output=True, text=True, timeout=30, + ) + assert proc.returncode == 0, proc.stderr + models = json.loads(proc.stdout) + assert "acme/cached-7b" in {m["repo_id"] for m in models}, models