Run node code on the venv Fluksio was installed into
The workflow this serves: make a venv, install what you work with, then `pip install fluksio` into the same one. Building a second environment beside it was exactly wrong — the packages the nodes need are already here, and the Modules screen was asking for them a second time. `NODE_VENV=auto` (the default) adopts that venv. It declines in the three cases where adopting would be wrong: `managed` says otherwise, a managed venv already exists and may hold packages somebody installed on purpose, or the engine is not running from a venv at all. The images set `managed`, since the venv in them holds the app and nothing of anybody else's. An adopted venv is never written to. `uv pip sync` makes a venv hold exactly the manifest, so pointed at somebody's own environment it uninstalls their work and the engine with it — `sync()` refuses outright and `reconcile()` returns before it can be called at startup, which is where that would have happened first. The Modules screen lists what is installed and drops its editor; `pip` is how that environment changes. `fluksio serve` now names the interpreter node code runs on, which is the thing a data scientist most needs to know at that moment. `fluksio-worker` already defaulted `--python` to its own interpreter, so a GPU box works the same way — that was only ever undocumented. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ue1tkFWB1bcGy3aWhCKpU
This commit is contained in:
@@ -75,3 +75,90 @@ def test_a_manifest_that_does_not_resolve_leaves_the_venv_alone(venv: Path):
|
||||
assert output
|
||||
# The marker still describes the manifest that actually installed.
|
||||
assert (venv / ".applied").read_text() == modules._digest("")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Adopting the venv Fluksio was installed into
|
||||
#
|
||||
# The workflow this exists for: make a venv, install what you work with, then
|
||||
# `pip install fluksio` into the same one. Building a second environment beside
|
||||
# it would leave node code unable to import the packages that are the point.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def their_venv(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
||||
"""Pretend the engine is running from a venv somebody else made."""
|
||||
directory = tmp_path / "research"
|
||||
(directory / "bin").mkdir(parents=True)
|
||||
(directory / "bin" / "python").write_text("")
|
||||
monkeypatch.setattr(modules.sys, "prefix", str(directory))
|
||||
monkeypatch.setattr(modules.sys, "base_prefix", str(tmp_path / "usr"))
|
||||
# Undoes the suite-wide "managed" in tests/__init__.py.
|
||||
monkeypatch.setattr(modules.settings, "NODE_VENV", "auto")
|
||||
return directory
|
||||
|
||||
|
||||
def test_a_venv_of_your_own_is_adopted(venv: Path, their_venv: Path):
|
||||
assert modules.adopted() == their_venv
|
||||
assert modules.venv_python() == str(their_venv / "bin" / "python")
|
||||
|
||||
|
||||
def test_an_adopted_venv_is_never_synced(venv: Path, their_venv: Path):
|
||||
"""The whole hazard: sync means "hold exactly this", so it would empty it."""
|
||||
ok, output = modules.sync("numpy>=2")
|
||||
|
||||
assert ok is False
|
||||
assert "not Fluksio's to install" in output
|
||||
# Nothing was built beside it either.
|
||||
assert not venv.exists()
|
||||
|
||||
|
||||
def test_reconcile_leaves_an_adopted_venv_alone(
|
||||
venv: Path, their_venv: Path, tmp_path: Path
|
||||
):
|
||||
store = FlowStore(tmp_path / "flows")
|
||||
store.write_requirements("numpy>=2\n")
|
||||
|
||||
modules.reconcile(store)
|
||||
|
||||
assert not venv.exists()
|
||||
assert modules.venv_python() == str(their_venv / "bin" / "python")
|
||||
|
||||
|
||||
def test_a_venv_the_engine_already_owns_is_kept(venv: Path, their_venv: Path):
|
||||
"""An upgrade must not take away packages somebody installed on purpose."""
|
||||
(venv / "bin").mkdir(parents=True)
|
||||
(venv / "bin" / "python").write_text("")
|
||||
|
||||
assert modules.adopted() is None
|
||||
assert modules.venv_python() == str(venv / "bin" / "python")
|
||||
|
||||
|
||||
def test_managed_is_what_a_container_asks_for(
|
||||
venv: Path, their_venv: Path, monkeypatch: pytest.MonkeyPatch
|
||||
):
|
||||
monkeypatch.setattr(modules.settings, "NODE_VENV", "managed")
|
||||
|
||||
assert modules.adopted() is None
|
||||
|
||||
|
||||
def test_an_interpreter_can_be_named_outright(
|
||||
venv: Path, their_venv: Path, monkeypatch: pytest.MonkeyPatch
|
||||
):
|
||||
monkeypatch.setattr(modules.settings, "NODE_VENV", "/opt/conda/bin/python")
|
||||
|
||||
assert modules.venv_python() == "/opt/conda/bin/python"
|
||||
|
||||
|
||||
def test_info_describes_an_adopted_venv(venv: Path, their_venv: Path, tmp_path: Path):
|
||||
store = FlowStore(tmp_path / "flows")
|
||||
store.write_requirements("numpy>=2\n")
|
||||
|
||||
information = modules.info(store)
|
||||
|
||||
assert information.adopted is True
|
||||
assert information.venv_path == str(their_venv)
|
||||
assert information.python_version
|
||||
# The manifest does not describe it, so it cannot be out of step with it.
|
||||
assert information.applied is True
|
||||
|
||||
Reference in New Issue
Block a user