fix(cli): fall back to uv pip when pip is unavailable
When nanobot is installed via uv tool install, sys.executable points to
a Python that does not have pip available as a module. _pip_install_argv
and _pip_uninstall_argv always used [sys.executable, -m, pip, ...]
which fails in that environment.
Add _pip_available() helper that checks importlib.util.find_spec('pip').
When pip is not available and uv is on PATH, fall back to:
uv pip install --python <sys.executable> ...
uv pip uninstall --python <sys.executable> -y ...
If neither pip nor uv is available, raise CliAppError.
Fixes #4158
This commit is contained in:
@@ -777,3 +777,68 @@ def test_run_blocks_working_dir_outside_workspace(tmp_path: Path) -> None:
|
||||
|
||||
with pytest.raises(CliAppError, match="outside the configured workspace"):
|
||||
manager.run("gimp", working_dir="/etc", restrict_to_workspace=True)
|
||||
|
||||
|
||||
def test_install_uses_uv_pip_when_pip_unavailable(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
manager = _manager(tmp_path)
|
||||
_seed_catalog(manager)
|
||||
calls: list[list[str]] = []
|
||||
|
||||
def fake_run(argv: list[str], *, timeout: int) -> subprocess.CompletedProcess[str]:
|
||||
calls.append(argv)
|
||||
return subprocess.CompletedProcess(argv, 0, stdout="ok", stderr="")
|
||||
|
||||
monkeypatch.setattr(CliAppManager, "_pip_available", staticmethod(lambda: False))
|
||||
monkeypatch.setattr(
|
||||
"nanobot.apps.cli.service.shutil.which",
|
||||
lambda command: "/usr/bin/uv" if command == "uv" else None,
|
||||
)
|
||||
monkeypatch.setattr(manager, "_run_argv", fake_run)
|
||||
monkeypatch.setattr(manager, "_fetch_skill_content", lambda app: None)
|
||||
|
||||
manager.install("gimp")
|
||||
|
||||
assert calls[0][:6] == [
|
||||
"uv",
|
||||
"pip",
|
||||
"install",
|
||||
"--python",
|
||||
sys.executable,
|
||||
"cli-anything-gimp",
|
||||
]
|
||||
|
||||
|
||||
def test_uninstall_uses_uv_pip_when_pip_unavailable(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
manager = _manager(tmp_path)
|
||||
_seed_catalog(manager)
|
||||
manager._save_installed({"suno": {"entry_point": "suno"}})
|
||||
calls: list[list[str]] = []
|
||||
|
||||
def fake_run(argv: list[str], *, timeout: int) -> subprocess.CompletedProcess[str]:
|
||||
calls.append(argv)
|
||||
return subprocess.CompletedProcess(argv, 0, stdout="ok", stderr="")
|
||||
|
||||
monkeypatch.setattr(CliAppManager, "_pip_available", staticmethod(lambda: False))
|
||||
monkeypatch.setattr(
|
||||
"nanobot.apps.cli.service.shutil.which",
|
||||
lambda command: "/usr/bin/uv" if command == "uv" else None,
|
||||
)
|
||||
monkeypatch.setattr(manager, "_run_argv", fake_run)
|
||||
|
||||
manager.uninstall("suno")
|
||||
|
||||
assert calls[0] == [
|
||||
"uv",
|
||||
"pip",
|
||||
"uninstall",
|
||||
"--python",
|
||||
sys.executable,
|
||||
"-y",
|
||||
"suno-cli",
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user