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
7996APT_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
285313class _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
0 commit comments