Skip to content

Commit c164f35

Browse files
cosmicBboypingsutw
andauthored
add support for installing system-level venv in ImageSpec (#3400)
Signed-off-by: Niels Bantilan <niels.bantilan@gmail.com> Co-authored-by: Kevin Su <pingsutw@apache.org>
1 parent 71194a4 commit c164f35

File tree

2 files changed

+44
-4
lines changed

2 files changed

+44
-4
lines changed

flytekit/image_spec/default_builder.py

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,23 @@
7575
"""
7676
)
7777

78+
# When python_exec points to system Python (e.g. on NVIDIA base images), PEP 668 prevents
79+
# installing packages directly. We create a venv using the base Python and install into it.
80+
# --system-site-packages lets the venv see base image packages (e.g. NVIDIA TensorRT).
81+
UV_PYTHON_VENV_INSTALL_COMMAND_TEMPLATE = Template(
82+
"""\
83+
WORKDIR /root
84+
RUN --mount=type=cache,sharing=locked,mode=0777,target=/root/.cache/uv,id=uv \
85+
--mount=from=uv,source=/uv,target=/usr/bin/uv \
86+
--mount=type=bind,target=requirements_uv.txt,src=requirements_uv.txt \
87+
$PIP_SECRET_MOUNT \
88+
uv venv --python $BASE_PYTHON_EXEC --system-site-packages /root/.venv && \
89+
uv pip install --python /root/.venv/bin/python $PIP_INSTALL_ARGS && \
90+
chown -R flytekit /root/.venv
91+
WORKDIR /
92+
"""
93+
)
94+
7895

7996
APT_INSTALL_COMMAND_TEMPLATE = Template("""\
8097
RUN --mount=type=cache,sharing=locked,mode=0777,target=/var/cache/apt,id=apt \
@@ -262,7 +279,14 @@ def prepare_python_install(image_spec: ImageSpec, tmp_dir: Path) -> str:
262279
requirements.extend([line.strip() for line in f.readlines()])
263280

264281
if template is None:
265-
template = UV_PYTHON_INSTALL_COMMAND_TEMPLATE
282+
if image_spec.python_exec:
283+
# Use venv when python_exec is set: base images with PEP 668 (externally managed
284+
# Python) like NVIDIA TensorRT prevent direct system installs. Creating a venv
285+
# preserves base image packages while allowing flytekit deps to be installed.
286+
template = UV_PYTHON_VENV_INSTALL_COMMAND_TEMPLATE
287+
else:
288+
template = UV_PYTHON_INSTALL_COMMAND_TEMPLATE
289+
266290
if image_spec.packages:
267291
requirements.extend(image_spec.packages)
268292

@@ -276,10 +300,14 @@ def prepare_python_install(image_spec: ImageSpec, tmp_dir: Path) -> str:
276300

277301
pip_install_args = " ".join(pip_install_args)
278302

279-
return template.substitute(
303+
substitute_kwargs = dict(
280304
PIP_INSTALL_ARGS=pip_install_args,
281305
PIP_SECRET_MOUNT=pip_secret_mount,
282306
)
307+
if image_spec.python_exec and template == UV_PYTHON_VENV_INSTALL_COMMAND_TEMPLATE:
308+
substitute_kwargs["BASE_PYTHON_EXEC"] = image_spec.python_exec
309+
310+
return template.substitute(**substitute_kwargs)
283311

284312

285313
class _PythonInstallTemplate(NamedTuple):
@@ -294,7 +322,13 @@ def prepare_python_executable(image_spec: ImageSpec) -> _PythonInstallTemplate:
294322
raise ValueError("conda_channels is not supported with python_exec")
295323
if image_spec.conda_packages:
296324
raise ValueError("conda_packages is not supported with python_exec")
297-
return _PythonInstallTemplate(python_exec=image_spec.python_exec, template="", extra_path="")
325+
# Packages are installed into /root/.venv (see UV_PYTHON_VENV_INSTALL_COMMAND_TEMPLATE)
326+
# so runtime must use the venv interpreter
327+
return _PythonInstallTemplate(
328+
python_exec="/root/.venv/bin/python",
329+
template="",
330+
extra_path="/root/.venv/bin",
331+
)
298332

299333
conda_packages = image_spec.conda_packages or []
300334
conda_channels = image_spec.conda_channels or []
@@ -478,6 +512,7 @@ class DefaultImageBuilder(ImageSpecBuilder):
478512
# "registry_config",
479513
"commands",
480514
"copy",
515+
"python_exec",
481516
"builder_config",
482517
}
483518

tests/flytekit/unit/core/image_spec/test_default_builder.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ def test_create_poetry_lock(tmp_path):
338338

339339

340340
def test_python_exec(tmp_path):
341+
"""python_exec uses venv to avoid PEP 668 (externally managed Python) errors on NVIDIA etc."""
341342
docker_context_path = tmp_path / "builder_root"
342343
docker_context_path.mkdir()
343344
base_image = "ghcr.io/flyteorg/flytekit:py3.11-1.14.4"
@@ -355,7 +356,11 @@ def test_python_exec(tmp_path):
355356
assert dockerfile_path.exists()
356357
dockerfile_content = dockerfile_path.read_text()
357358

358-
assert f"UV_PYTHON={python_exec}" in dockerfile_content
359+
# Packages install into venv; runtime uses venv Python (fixes PEP 668 on NVIDIA etc.)
360+
assert "uv venv --python /usr/local/bin/python --system-site-packages /root/.venv" in dockerfile_content
361+
assert "uv pip install --python /root/.venv/bin/python" in dockerfile_content
362+
assert "UV_PYTHON=/root/.venv/bin/python" in dockerfile_content
363+
assert "/root/.venv/bin" in dockerfile_content
359364

360365

361366
@pytest.mark.parametrize("key, value", [("conda_packages", ["ruff"]), ("conda_channels", ["bioconda"])])

0 commit comments

Comments
 (0)