Skip to content

Commit acb8d58

Browse files
Add utilities for installing kwave binaries (#377)
To check if kwave binaries are present, one can now do this: ``` all(p.exists() for p,_ in get_kwave_paths()) ``` And then if they are not present then one can ``` download_and_install_kwave_assets() ``` if there is an internet connection, otherwise us `install_kwave_asset_from_file` on individual needed files. The names of the needed files are ``` [p.name for p,_ in get_kwave_paths()] ```
1 parent 7f7f375 commit acb8d58

2 files changed

Lines changed: 78 additions & 4 deletions

File tree

src/openlifu/util/assets.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from openlifu.util.types import PathLike
1616

1717

18-
def install_asset(destination:PathLike, path_to_asset:PathLike|None, url_to_asset:str|None) -> None:
18+
def install_asset(destination:PathLike, path_to_asset:PathLike|None=None, url_to_asset:str|None=None) -> None:
1919
"""Install a file to a location if it isn't already there.
2020
2121
Downloads if a `url_to_asset` is provided, and copies if a local `path_to_asset` is provided.
@@ -136,9 +136,9 @@ def _import_kwave_inertly() -> ModuleType:
136136
return _import_without_calls("kwave", banned_calls=["install_binaries"])
137137

138138
def get_kwave_paths() -> list[tuple[Path, str]]:
139-
"""Get a list of paths and urls to kwave binaries.
139+
"""Get a list of paths and urls to kwave binaries for this platform.
140140
141-
Each item in the list is a pair consisting of the install of a needed binary, followed by a download url for that binary.
141+
Each item in the list is a pair consisting of the install path of a needed binary, followed by a download url for that binary.
142142
"""
143143
kwave = _import_kwave_inertly()
144144
paths : list[tuple[str, str]] = []
@@ -147,3 +147,24 @@ def get_kwave_paths() -> list[tuple[Path, str]]:
147147
_, filename = url.split("/")[-2:]
148148
paths.append((Path(kwave.BINARY_PATH) / filename, url))
149149
return paths
150+
151+
def download_and_install_kwave_assets() -> None:
152+
"""Download and install the binaries needed by kwave for this platform"""
153+
for install_path, url in get_kwave_paths():
154+
install_asset(destination=install_path, url_to_asset=url)
155+
156+
def install_kwave_asset_from_file(path_to_kwave_binary:PathLike) -> Path:
157+
"""Copy kwave binary file to the appropriate place for kwave to use it.
158+
The filename is used to identify which binary it is.
159+
Returns the path to the installed (i.e. copied) binary.
160+
"""
161+
path_to_kwave_binary = Path(path_to_kwave_binary)
162+
kwave_paths = get_kwave_paths()
163+
for install_path, _ in kwave_paths:
164+
if path_to_kwave_binary.name == install_path.name:
165+
install_asset(destination=install_path, path_to_asset=path_to_kwave_binary)
166+
return install_path
167+
raise ValueError(
168+
f"The filename {path_to_kwave_binary.name} was not recognized as one of the binaries kwave is looking for: "
169+
+ ", ".join([str(install_path) for install_path, _ in kwave_paths])
170+
)

tests/test_assets.py

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@
77
import pytest
88
import requests
99

10-
from openlifu.util.assets import get_kwave_paths, install_asset
10+
from openlifu.util.assets import (
11+
download_and_install_kwave_assets,
12+
get_kwave_paths,
13+
install_asset,
14+
install_kwave_asset_from_file,
15+
)
1116

1217

1318
def test_destination_already_exists(tmp_path, mocker):
@@ -106,3 +111,51 @@ def test_get_kwave_paths():
106111
assert isinstance(p, Path)
107112
assert isinstance(url, str)
108113
assert len(url) > 0
114+
115+
def test_download_and_install_kwave_assets(tmp_path, mocker):
116+
"""Verify kwave assets are downloaded by calling install_asset for each binary."""
117+
fake_paths = [
118+
(tmp_path / "bin1", "http://example.com/bin1"),
119+
(tmp_path / "bin2", "http://example.com/bin2"),
120+
]
121+
mocker.patch("openlifu.util.assets.get_kwave_paths", return_value=fake_paths)
122+
mock_install_asset = mocker.patch("openlifu.util.assets.install_asset")
123+
124+
download_and_install_kwave_assets()
125+
126+
assert mock_install_asset.call_count == 2
127+
# Check that install_asset was called for each item in our fake list
128+
mock_install_asset.assert_any_call(destination=fake_paths[0][0], url_to_asset=fake_paths[0][1])
129+
mock_install_asset.assert_any_call(destination=fake_paths[1][0], url_to_asset=fake_paths[1][1])
130+
131+
132+
def test_install_kwave_asset_from_file_succeeds(tmp_path, mocker):
133+
"""Verify a kwave binary is installed from a file with a matching name."""
134+
install_path1 = tmp_path / "kwave_binary_1"
135+
install_path2 = tmp_path / "another_binary"
136+
fake_paths = [(install_path1, "url1"), (install_path2, "url2")]
137+
mocker.patch("openlifu.util.assets.get_kwave_paths", return_value=fake_paths)
138+
mock_install_asset = mocker.patch("openlifu.util.assets.install_asset")
139+
140+
# The source file can be in any directory, as long as its name matches.
141+
source_file = tmp_path / "some_dir" / "kwave_binary_1"
142+
143+
result = install_kwave_asset_from_file(source_file)
144+
145+
mock_install_asset.assert_called_once_with(destination=install_path1, path_to_asset=source_file)
146+
assert result == install_path1
147+
148+
149+
def test_install_kwave_asset_from_file_fails_on_unrecognized_name(tmp_path, mocker):
150+
"""Verify a ValueError is raised for a kwave binary with a non-matching name."""
151+
fake_paths = [(tmp_path / "kwave_binary_1", "url1")]
152+
mocker.patch("openlifu.util.assets.get_kwave_paths", return_value=fake_paths)
153+
mock_install_asset = mocker.patch("openlifu.util.assets.install_asset")
154+
155+
unrecognized_file = tmp_path / "unrecognized_file.exe"
156+
157+
with pytest.raises(ValueError, match="was not recognized as one of the binaries"):
158+
install_kwave_asset_from_file(unrecognized_file)
159+
160+
# Ensure we didn't accidentally try to install anything
161+
mock_install_asset.assert_not_called()

0 commit comments

Comments
 (0)