Skip to content

Commit b8e84ae

Browse files
committed
fix: detect Ollama models directory using platform-specific defaults
Previously, get_ollama_models_dir() only checked ~/.ollama/models, which doesn't exist on Linux systems using the systemd-managed Ollama install (which stores models in /usr/share/ollama/.ollama/models). Now checks in order: 1. OLLAMA_MODELS environment variable 2. Platform-specific directories: - macOS: ~/.ollama/models - Linux: /usr/share/ollama/.ollama/models (systemd), ~/.ollama/models (user) - Windows: ~/.ollama/models Updated tests to mock get_ollama_models_dir to avoid using real system models.
1 parent 9791d76 commit b8e84ae

File tree

4 files changed

+108
-8
lines changed

4 files changed

+108
-8
lines changed

merle/model_split.py

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import os
1515
import shutil
1616
import subprocess
17+
import sys
1718
from pathlib import Path
1819

1920
import boto3
@@ -34,10 +35,61 @@
3435
SPLIT_METADATA_FILE = "split_metadata.json"
3536

3637

38+
def _get_platform_models_dirs() -> list[Path]:
39+
"""
40+
Get platform-specific Ollama models directories to search.
41+
42+
Returns directories in priority order:
43+
- macOS: ~/.ollama/models
44+
- Linux: /usr/share/ollama/.ollama/models (systemd), ~/.ollama/models (user)
45+
- Windows: ~/.ollama/models
46+
"""
47+
if sys.platform == "darwin":
48+
# macOS: ~/.ollama/models
49+
return [Path.home() / ".ollama" / "models"]
50+
51+
if sys.platform == "win32":
52+
# Windows: C:\Users\%username%\.ollama\models
53+
return [Path.home() / ".ollama" / "models"]
54+
55+
# Linux: check systemd install first, then user install
56+
return [
57+
Path("/usr/share/ollama/.ollama/models"), # systemd install
58+
Path.home() / ".ollama" / "models", # user install
59+
]
60+
61+
3762
def get_ollama_models_dir() -> Path:
38-
"""Get the default Ollama models directory."""
39-
home = Path.home()
40-
return home / ".ollama" / "models"
63+
"""
64+
Get the Ollama models directory.
65+
66+
Checks in order:
67+
1. OLLAMA_MODELS environment variable (if set)
68+
2. Platform-specific directories (first existing one):
69+
- macOS: ~/.ollama/models
70+
- Linux: /usr/share/ollama/.ollama/models (systemd) or ~/.ollama/models (user)
71+
- Windows: ~/.ollama/models
72+
73+
Returns:
74+
Path to the Ollama models directory
75+
"""
76+
# Check environment variable first
77+
env_models_dir = os.environ.get("OLLAMA_MODELS")
78+
if env_models_dir:
79+
models_path = Path(env_models_dir)
80+
logger.debug(f"Using OLLAMA_MODELS from environment: {models_path}")
81+
return models_path
82+
83+
# Search platform-specific directories
84+
for models_dir in _get_platform_models_dirs():
85+
if models_dir.exists():
86+
logger.debug(f"Found models directory: {models_dir}")
87+
return models_dir
88+
89+
# Fall back to first platform default (even if it doesn't exist yet)
90+
default_dir = _get_platform_models_dirs()[0]
91+
logger.debug(f"Using default models directory: {default_dir}")
92+
return default_dir
4193

4294

4395
def calculate_directory_size(path: Path) -> int:

tests/conftest.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,25 @@ def temp_cache_dir(tmp_path: Path) -> Path:
2121
return cache_dir
2222

2323

24+
@pytest.fixture
25+
def mock_models_dir(tmp_path: Path) -> Path:
26+
"""
27+
Create a mock Ollama models directory for testing.
28+
29+
Creates an empty models directory structure to avoid tests
30+
using the real system Ollama models.
31+
32+
Args:
33+
tmp_path: pytest tmp_path fixture
34+
35+
Returns:
36+
Path to mock models directory
37+
"""
38+
models_dir = tmp_path / ".ollama" / "models"
39+
models_dir.mkdir(parents=True, exist_ok=True)
40+
return models_dir
41+
42+
2443
@pytest.fixture
2544
def sample_config() -> dict:
2645
"""

tests/test_deployment_completeness.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,21 @@
1010
class TestDeploymentCompleteness:
1111
"""Test that verifies what's missing for successful deployment."""
1212

13+
@patch("merle.model_split.get_ollama_models_dir")
1314
@patch("merle.functions.validate_ollama_model")
1415
@patch("merle.cli.get_default_project_name")
1516
def test_prepare_creates_expected_files(
16-
self, mock_get_default_project: MagicMock, mock_validate: MagicMock, temp_cache_dir: Path
17+
self,
18+
mock_get_default_project: MagicMock,
19+
mock_validate: MagicMock,
20+
mock_models_dir_fn: MagicMock,
21+
temp_cache_dir: Path,
22+
mock_models_dir: Path,
1723
) -> None:
1824
"""Test that prepare command creates expected deployment files."""
1925
mock_validate.return_value = True
2026
mock_get_default_project.return_value = "testproject"
27+
mock_models_dir_fn.return_value = mock_models_dir
2128

2229
# Prepare deployment files
2330
args = argparse.Namespace(
@@ -53,14 +60,21 @@ def test_prepare_creates_expected_files(
5360
config_path = temp_cache_dir / "testproject" / "config.json"
5461
assert config_path.exists(), "config.json should be created"
5562

63+
@patch("merle.model_split.get_ollama_models_dir")
5664
@patch("merle.functions.validate_ollama_model")
5765
@patch("merle.cli.get_default_project_name")
5866
def test_deployment_is_now_complete( # noqa: PLR0915, PLR0912
59-
self, mock_get_default_project: MagicMock, mock_validate: MagicMock, temp_cache_dir: Path
67+
self,
68+
mock_get_default_project: MagicMock,
69+
mock_validate: MagicMock,
70+
mock_models_dir_fn: MagicMock,
71+
temp_cache_dir: Path,
72+
mock_models_dir: Path,
6073
) -> None:
6174
"""Verify that deployment is now complete with all required components."""
6275
mock_validate.return_value = True
6376
mock_get_default_project.return_value = "testproject"
77+
mock_models_dir_fn.return_value = mock_models_dir
6478

6579
# Prepare deployment files
6680
args = argparse.Namespace(

tests/test_functions.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -394,10 +394,14 @@ def test_generate_zappa_settings_with_tags(self, tmp_path: Path, sample_tags: di
394394
class TestPrepareDeploymentFiles:
395395
"""Tests for prepare_deployment_files function."""
396396

397+
@patch("merle.model_split.get_ollama_models_dir")
397398
@patch("merle.functions.validate_ollama_model")
398-
def test_prepare_deployment_files_basic(self, mock_validate: MagicMock, temp_cache_dir: Path):
399+
def test_prepare_deployment_files_basic(
400+
self, mock_validate: MagicMock, mock_models_dir_fn: MagicMock, temp_cache_dir: Path, mock_models_dir: Path
401+
):
399402
"""Test preparing deployment files with basic parameters."""
400403
mock_validate.return_value = True
404+
mock_models_dir_fn.return_value = mock_models_dir
401405

402406
# Create template directory with mock templates
403407
template_dir = Path(__file__).parent.parent / "merle" / "templates"
@@ -426,12 +430,19 @@ def test_prepare_deployment_files_basic(self, mock_validate: MagicMock, temp_cac
426430
assert config["models"]["llama2"]["dev"]["auth_token"] == "test-token"
427431
assert config["models"]["llama2"]["dev"]["region"] == "us-east-1"
428432

433+
@patch("merle.model_split.get_ollama_models_dir")
429434
@patch("merle.functions.validate_ollama_model")
430435
def test_prepare_deployment_files_with_tags(
431-
self, mock_validate: MagicMock, temp_cache_dir: Path, sample_tags: dict
436+
self,
437+
mock_validate: MagicMock,
438+
mock_models_dir_fn: MagicMock,
439+
temp_cache_dir: Path,
440+
sample_tags: dict,
441+
mock_models_dir: Path,
432442
):
433443
"""Test preparing deployment files with tags."""
434444
mock_validate.return_value = True
445+
mock_models_dir_fn.return_value = mock_models_dir
435446

436447
model_cache_dir = prepare_deployment_files(
437448
model_name="llama2",
@@ -454,10 +465,14 @@ def test_prepare_deployment_files_with_tags(
454465
config = load_config(temp_cache_dir)
455466
assert config["models"]["llama2"]["dev"]["tags"] == sample_tags
456467

468+
@patch("merle.model_split.get_ollama_models_dir")
457469
@patch("merle.functions.validate_ollama_model")
458-
def test_prepare_deployment_files_without_tags(self, mock_validate: MagicMock, temp_cache_dir: Path):
470+
def test_prepare_deployment_files_without_tags(
471+
self, mock_validate: MagicMock, mock_models_dir_fn: MagicMock, temp_cache_dir: Path, mock_models_dir: Path
472+
):
459473
"""Test preparing deployment files without tags."""
460474
mock_validate.return_value = True
475+
mock_models_dir_fn.return_value = mock_models_dir
461476

462477
model_cache_dir = prepare_deployment_files(
463478
model_name="llama2",

0 commit comments

Comments
 (0)