fix(engines): pin uv pip install to the running interpreter (#529/#527) (#559)
The engine Install chip (deep_translator / openai / argostranslate) 500'd with "No virtual environment found". run_pip shells out to bare `uv pip install`, which discovers its target venv from VIRTUAL_ENV / a .venv in CWD — NOT from the running interpreter. The desktop spawns `<venv>/bin/python -m uvicorn` without exporting VIRTUAL_ENV and CWDs outside the venv, so uv finds nothing. The existing `--system` fallback never fires because it's gated on _in_virtualenv() being False, but the running interpreter genuinely IS in a venv (it just can't be auto-discovered) — the heuristic answers the wrong question. Pass `--python sys.executable` for uv install/uninstall: targets the same interpreter _probe()/is_installed() import from, fixing the whole class (spawned-venv, system, conda). It takes precedence when both flags are present, so the Docker `--system` path is untouched. (#527's openai is already bundled by #484; this hardens the chip for the remaining runtime-installed engines.) Test: run_pip's spawned argv contains `--python <sys.executable>` after install/uninstall. 2 passed. Co-authored-by: mergetest <test@local> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
mergetest
Claude Opus 4.8
parent
cb8ff16d86
commit
3f23a77eab
@@ -177,6 +177,16 @@ async def run_pip(args: list[str], timeout: float = 600.0) -> tuple[int, str]:
|
||||
"""
|
||||
base = _installer_cmd()
|
||||
using_uv = base[:1] == ["uv"]
|
||||
# Pin `uv pip` to the interpreter the backend ACTUALLY runs under. The desktop
|
||||
# spawns `<venv>/bin/python -m uvicorn` WITHOUT exporting VIRTUAL_ENV, so bare
|
||||
# `uv pip install` finds no venv and 500s with "No virtual environment found"
|
||||
# (#529/#527) — and the `--system` branch below never fires, because the
|
||||
# running interpreter genuinely IS in a venv (uv just can't auto-discover it).
|
||||
# `--python sys.executable` targets the same interpreter _probe()/is_installed()
|
||||
# import from, and takes precedence when both flags are present, so the Docker
|
||||
# `--system` path is unaffected.
|
||||
if using_uv and args and args[0] in ("install", "uninstall") and "--python" not in args:
|
||||
args = [args[0], "--python", sys.executable, *args[1:]]
|
||||
if using_uv and not _in_virtualenv() and args and args[0] in ("install", "uninstall") and "--system" not in args:
|
||||
args = [args[0], "--system", *args[1:]]
|
||||
cmd = base + args
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
"""The engine Install chip must target the interpreter the backend runs under.
|
||||
|
||||
#529/#527: the desktop spawns `<venv>/bin/python -m uvicorn` WITHOUT exporting
|
||||
VIRTUAL_ENV, so bare `uv pip install` finds no venv and 500s with "No virtual
|
||||
environment found". run_pip must pass `--python sys.executable`.
|
||||
"""
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
from services import translation_engines as te
|
||||
|
||||
|
||||
class _FakeProc:
|
||||
returncode = 0
|
||||
|
||||
async def communicate(self):
|
||||
return (b"ok", b"")
|
||||
|
||||
|
||||
def _run_capturing(monkeypatch, args):
|
||||
"""Force the uv branch + capture the spawned argv; return (rc, argv)."""
|
||||
captured = {}
|
||||
monkeypatch.setattr(te.shutil, "which", lambda name: "/usr/bin/uv" if name == "uv" else None)
|
||||
|
||||
async def fake_exec(*argv, **kwargs):
|
||||
captured["argv"] = list(argv)
|
||||
return _FakeProc()
|
||||
|
||||
monkeypatch.setattr(te.asyncio, "create_subprocess_exec", fake_exec)
|
||||
rc, _out = asyncio.run(te.run_pip(args))
|
||||
return rc, captured.get("argv", [])
|
||||
|
||||
|
||||
def test_run_pip_pins_uv_install_to_sys_executable(monkeypatch):
|
||||
rc, argv = _run_capturing(monkeypatch, ["install", "deep_translator"])
|
||||
assert rc == 0
|
||||
assert argv[:3] == ["uv", "pip", "install"], argv
|
||||
assert "--python" in argv, argv
|
||||
assert argv[argv.index("--python") + 1] == sys.executable
|
||||
|
||||
|
||||
def test_run_pip_pins_uv_uninstall_to_sys_executable(monkeypatch):
|
||||
_rc, argv = _run_capturing(monkeypatch, ["uninstall", "deep_translator"])
|
||||
assert "--python" in argv, argv
|
||||
assert argv[argv.index("--python") + 1] == sys.executable
|
||||
Reference in New Issue
Block a user