truefoundry 0.11.12__py3-none-any.whl → 0.12.0rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of truefoundry might be problematic. Click here for more details.

@@ -1,67 +1,150 @@
1
- from typing import Dict
1
+ from copy import deepcopy
2
+ from typing import Dict, List, Optional, Set, Union
2
3
 
3
- from mako.template import Template
4
+ from jinja2 import Template
4
5
 
5
6
  from truefoundry.common.constants import ENV_VARS, PythonPackageManager
6
- from truefoundry.deploy._autogen.models import PythonBuild
7
- from truefoundry.deploy.builder.constants import (
8
- PIP_CONF_BUILDKIT_SECRET_MOUNT,
9
- UV_CONF_BUILDKIT_SECRET_MOUNT,
10
- )
7
+ from truefoundry.deploy._autogen.models import UV, Pip, Poetry
11
8
  from truefoundry.deploy.builder.utils import (
12
9
  generate_apt_install_command,
10
+ generate_command_to_install_from_uv_lock,
13
11
  generate_pip_install_command,
12
+ generate_poetry_install_command,
13
+ generate_secret_mounts,
14
14
  generate_uv_pip_install_command,
15
+ get_available_secrets,
15
16
  )
16
17
  from truefoundry.deploy.v2.lib.patched_models import (
17
18
  CUDAVersion,
19
+ PythonBuild,
18
20
  _resolve_requirements_path,
21
+ check_whether_poetry_toml_exists,
19
22
  )
23
+ from truefoundry.pydantic_v1 import BaseModel
24
+
25
+
26
+ class TemplateContext(BaseModel):
27
+ """Pydantic model for template context used in Dockerfile generation."""
28
+
29
+ # Common fields
30
+ python_image_repo: str
31
+ python_version: str
32
+ apt_install_command: Optional[str] = None
33
+ package_manager_config_secret_mount: str = ""
34
+
35
+ # Pip-specific fields
36
+ requirements_path: Optional[str] = None
37
+ requirements_destination_path: Optional[str] = None
38
+ python_packages_install_command: Optional[str] = None
39
+
40
+ # UV-specific fields
41
+ final_uv_sync_command: Optional[str] = None
42
+
43
+ # Poetry-specific fields
44
+ final_poetry_install_command: Optional[str] = None
45
+ poetry_version_expression: Optional[str] = None
46
+ poetry_toml_exists: bool = False
47
+
48
+
49
+ CUDA_BASE_IMAGE_TEMPLATE = """\
50
+ FROM nvidia/cuda:{{ cuda_image_tag }} AS base
51
+ SHELL ["/bin/bash", "-o", "pipefail", "-e", "-u", "-c"]
52
+
53
+ ENV PATH=/virtualenvs/venv/bin:$PATH
54
+ ENV VIRTUAL_ENV=/virtualenvs/venv/
55
+ RUN echo "deb https://ppa.launchpadcontent.net/deadsnakes/ppa/ubuntu $(cat /etc/os-release | grep UBUNTU_CODENAME | cut -d = -f 2) main" >> /etc/apt/sources.list && \\
56
+ echo "deb-src https://ppa.launchpadcontent.net/deadsnakes/ppa/ubuntu $(cat /etc/os-release | grep UBUNTU_CODENAME | cut -d = -f 2) main" >> /etc/apt/sources.list && \\
57
+ apt-key adv --keyserver keyserver.ubuntu.com --recv-keys F23C5A6CF475977595C89F51BA6932366A755776 && \\
58
+ apt update && \\
59
+ DEBIAN_FRONTEND=noninteractive apt install -y --no-install-recommends git python{{ python_version }}-dev python{{ python_version }}-venv && \\
60
+ python{{ python_version }} -m venv /virtualenvs/venv/ && \\
61
+ rm -rf /var/lib/apt/lists/* && \\
62
+ python -m pip install -U pip setuptools wheel
63
+ """
64
+
65
+ STANDARD_BASE_IMAGE_TEMPLATE = """\
66
+ FROM {{ python_image_repo }}:{{ python_version }} AS base
67
+ SHELL ["/bin/bash", "-o", "pipefail", "-e", "-u", "-c"]
68
+
69
+ ENV PATH=/virtualenvs/venv/bin:$PATH
70
+ ENV VIRTUAL_ENV=/virtualenvs/venv/
71
+ RUN apt update && \\
72
+ DEBIAN_FRONTEND=noninteractive apt install -y --no-install-recommends git && \\
73
+ python -m venv /virtualenvs/venv/ && \\
74
+ rm -rf /var/lib/apt/lists/* && \\
75
+ python -m pip install -U pip setuptools wheel
76
+ """
20
77
 
21
- # TODO (chiragjn): Switch to a non-root user inside the container
22
78
 
23
- DEFAULT_PYTHON_IMAGE_REPO = "public.ecr.aws/docker/library/python"
79
+ APT_PACKAGES_TEMPLATE = """
80
+ {% if apt_install_command %}
81
+ RUN {{ apt_install_command }}
82
+ {% endif %}
83
+ """
84
+
85
+
86
+ PIP_DEPENDENCIES_TEMPLATE = """\
87
+ {% if requirements_path %}
88
+ COPY {{ requirements_path }} {{ requirements_destination_path }}
89
+ {% endif %}
90
+
91
+ {% if python_packages_install_command %}
92
+ RUN {{ package_manager_config_secret_mount }} {{ python_packages_install_command }}
93
+ {% endif %}
24
94
 
25
- _POST_PYTHON_INSTALL_TEMPLATE = """
26
- % if apt_install_command is not None:
27
- RUN ${apt_install_command}
28
- % endif
29
- % if requirements_path is not None:
30
- COPY ${requirements_path} ${requirements_destination_path}
31
- % endif
32
- % if python_packages_install_command is not None:
33
- RUN ${package_manager_config_secret_mount} ${python_packages_install_command}
34
- % endif
35
95
  COPY . /app
36
96
  WORKDIR /app
37
97
  """
38
98
 
39
- DOCKERFILE_TEMPLATE = Template(
40
- """
41
- FROM ${python_image_repo}:${python_version}
42
- ENV PATH=/virtualenvs/venv/bin:$PATH
43
- RUN apt update && \
44
- DEBIAN_FRONTEND=noninteractive apt install -y --no-install-recommends git && \
45
- python -m venv /virtualenvs/venv/ && \
46
- rm -rf /var/lib/apt/lists/*
99
+ UV_DEPENDENCIES_TEMPLATE = """\
100
+ # Set up UV environment
101
+ WORKDIR /app
102
+ COPY pyproject.toml uv.lock .
103
+ {% if python_packages_install_command %}
104
+ RUN {{ package_manager_config_secret_mount }} {{ python_packages_install_command }}
105
+ {% endif %}
106
+
107
+ COPY ./ .
108
+ {% if final_uv_sync_command %}
109
+ RUN {{ package_manager_config_secret_mount }} {{ final_uv_sync_command }}
110
+ {% endif %}
47
111
  """
48
- + _POST_PYTHON_INSTALL_TEMPLATE
49
- )
50
112
 
51
- CUDA_DOCKERFILE_TEMPLATE = Template(
52
- """
53
- FROM nvidia/cuda:${nvidia_cuda_image_tag}
54
- ENV PATH=/virtualenvs/venv/bin:$PATH
55
- RUN echo "deb https://ppa.launchpadcontent.net/deadsnakes/ppa/ubuntu $(cat /etc/os-release | grep UBUNTU_CODENAME | cut -d = -f 2) main" >> /etc/apt/sources.list && \
56
- echo "deb-src https://ppa.launchpadcontent.net/deadsnakes/ppa/ubuntu $(cat /etc/os-release | grep UBUNTU_CODENAME | cut -d = -f 2) main" >> /etc/apt/sources.list && \
57
- apt-key adv --keyserver keyserver.ubuntu.com --recv-keys F23C5A6CF475977595C89F51BA6932366A755776 && \
58
- apt update && \
59
- DEBIAN_FRONTEND=noninteractive apt install -y --no-install-recommends git python${python_version}-dev python${python_version}-venv && \
60
- python${python_version} -m venv /virtualenvs/venv/ && \
61
- rm -rf /var/lib/apt/lists/*
113
+ POETRY_DEPENDENCIES_TEMPLATE = """\
114
+ RUN python -m venv /opt/poetry
115
+ ENV PATH="/opt/poetry/bin:$PATH"
116
+ RUN pip install -q --no-cache-dir -U "poetry{{ poetry_version_expression }}"
117
+
118
+ WORKDIR /app
119
+ COPY pyproject.toml poetry.lock .
120
+ {% if poetry_toml_exists %}
121
+ COPY poetry.toml .
122
+ {%- endif %}
123
+ RUN poetry config virtualenvs.create false --local \\
124
+ && poetry config virtualenvs.in-project false --local
125
+
126
+ ENV PATH="/virtualenvs/venv/bin:$PATH"
127
+ ENV VIRTUAL_ENV="/virtualenvs/venv/"
128
+
129
+ {% if python_packages_install_command %}
130
+ RUN {{ package_manager_config_secret_mount }} {{ python_packages_install_command }}
131
+ {% endif %}
132
+
133
+ COPY ./ .
134
+
135
+ {% if final_poetry_install_command %}
136
+ RUN {{ package_manager_config_secret_mount }} {{ final_poetry_install_command }}
137
+ {% endif %}
138
+
139
+ RUN rm -rf /opt/poetry
140
+ """
141
+
142
+ DOCKERFILE_TEMPLATE = """\
143
+ {{ base_image_setup }}
144
+ {{ apt_packages_section }}
145
+ {{ python_dependencies_section }}
62
146
  """
63
- + _POST_PYTHON_INSTALL_TEMPLATE
64
- )
147
+
65
148
 
66
149
  CUDA_VERSION_TO_IMAGE_TAG: Dict[str, str] = {
67
150
  CUDAVersion.CUDA_11_0_CUDNN8.value: "11.0.3-cudnn8-runtime-ubuntu20.04",
@@ -89,68 +172,248 @@ CUDA_VERSION_TO_IMAGE_TAG: Dict[str, str] = {
89
172
  def generate_dockerfile_content(
90
173
  build_configuration: PythonBuild,
91
174
  package_manager: str = ENV_VARS.TFY_PYTHON_BUILD_PACKAGE_MANAGER,
92
- mount_python_package_manager_conf_secret: bool = False,
175
+ docker_build_extra_args: Optional[List[str]] = None,
93
176
  ) -> str:
177
+ if isinstance(build_configuration, dict):
178
+ build_configuration = PythonBuild(**build_configuration)
179
+ if not build_configuration.python_version:
180
+ raise ValueError(
181
+ "`python_version` is required for `tfy-python-buildpack` builder"
182
+ )
183
+
184
+ # Set up Python dependencies
185
+ python_dependencies = deepcopy(build_configuration.python_dependencies)
186
+ if not python_dependencies:
187
+ python_dependencies = Pip(
188
+ type="pip",
189
+ requirements_path=build_configuration.requirements_path,
190
+ pip_packages=build_configuration.pip_packages,
191
+ )
192
+
193
+ # Get available secrets from docker build extra args
194
+ available_secrets = set()
195
+ if docker_build_extra_args:
196
+ available_secrets = get_available_secrets(docker_build_extra_args)
197
+
198
+ # Prepare template context
199
+ context = _build_template_context(
200
+ build_configuration=build_configuration,
201
+ python_dependencies=python_dependencies,
202
+ package_manager=package_manager,
203
+ available_secrets=available_secrets,
204
+ )
205
+
206
+ # Build sections
207
+ base_image_setup = _build_base_image_section(build_configuration)
208
+ apt_packages_section = _build_apt_packages_section(context.apt_install_command)
209
+ python_dependencies_section = _build_python_dependencies_section(
210
+ python_dependencies.type, context
211
+ )
212
+
213
+ template = Template(DOCKERFILE_TEMPLATE)
214
+ return template.render(
215
+ base_image_setup=base_image_setup,
216
+ apt_packages_section=apt_packages_section,
217
+ python_dependencies_section=python_dependencies_section,
218
+ )
219
+
220
+
221
+ def _build_template_context(
222
+ build_configuration: PythonBuild,
223
+ python_dependencies: Union[Pip, UV, Poetry],
224
+ package_manager: str,
225
+ available_secrets: Set[str],
226
+ ) -> TemplateContext:
227
+ # Set up package manager config secret mount
228
+ python_dependencies_type = python_dependencies.type
229
+ package_manager_config_secret_mount = ""
230
+ if available_secrets:
231
+ package_manager_config_secret_mount = generate_secret_mounts(
232
+ available_secrets=available_secrets,
233
+ python_dependencies_type=python_dependencies_type,
234
+ package_manager=package_manager,
235
+ )
236
+
237
+ # Configure dependencies based on type
238
+ if python_dependencies_type == "uv":
239
+ uv_context = _build_uv_context(
240
+ python_dependencies,
241
+ available_secrets,
242
+ )
243
+ return TemplateContext(
244
+ python_image_repo=ENV_VARS.TFY_PYTHONBUILD_PYTHON_IMAGE_REPO,
245
+ python_version=build_configuration.python_version,
246
+ apt_install_command=generate_apt_install_command(
247
+ apt_packages=build_configuration.apt_packages
248
+ ),
249
+ package_manager_config_secret_mount=package_manager_config_secret_mount,
250
+ python_packages_install_command=uv_context[
251
+ "python_packages_install_command"
252
+ ],
253
+ final_uv_sync_command=uv_context["final_uv_sync_command"],
254
+ )
255
+ elif python_dependencies_type == "poetry":
256
+ poetry_toml_exists = check_whether_poetry_toml_exists(
257
+ build_context_path=build_configuration.build_context_path,
258
+ )
259
+ poetry_context = _build_poetry_context(python_dependencies, available_secrets)
260
+ return TemplateContext(
261
+ python_image_repo=ENV_VARS.TFY_PYTHONBUILD_PYTHON_IMAGE_REPO,
262
+ python_version=build_configuration.python_version,
263
+ apt_install_command=generate_apt_install_command(
264
+ apt_packages=build_configuration.apt_packages
265
+ ),
266
+ package_manager_config_secret_mount=package_manager_config_secret_mount,
267
+ python_packages_install_command=poetry_context[
268
+ "python_packages_install_command"
269
+ ],
270
+ final_poetry_install_command=poetry_context["final_poetry_install_command"],
271
+ poetry_version_expression=poetry_context["poetry_version_expression"],
272
+ poetry_toml_exists=poetry_toml_exists,
273
+ )
274
+ elif python_dependencies_type == "pip":
275
+ pip_context = _build_pip_context(
276
+ build_configuration,
277
+ python_dependencies,
278
+ package_manager,
279
+ available_secrets,
280
+ )
281
+ return TemplateContext(
282
+ python_image_repo=ENV_VARS.TFY_PYTHONBUILD_PYTHON_IMAGE_REPO,
283
+ python_version=build_configuration.python_version,
284
+ apt_install_command=generate_apt_install_command(
285
+ apt_packages=build_configuration.apt_packages
286
+ ),
287
+ package_manager_config_secret_mount=package_manager_config_secret_mount,
288
+ requirements_path=pip_context["requirements_path"],
289
+ requirements_destination_path=pip_context["requirements_destination_path"],
290
+ python_packages_install_command=pip_context[
291
+ "python_packages_install_command"
292
+ ],
293
+ )
294
+ else:
295
+ raise ValueError(f"Unsupported dependency type: {python_dependencies_type}")
296
+
297
+
298
+ def _build_base_image_section(build_configuration: PythonBuild) -> str:
299
+ if build_configuration.cuda_version:
300
+ cuda_image_tag = CUDA_VERSION_TO_IMAGE_TAG.get(
301
+ build_configuration.cuda_version, build_configuration.cuda_version
302
+ )
303
+ template = Template(CUDA_BASE_IMAGE_TEMPLATE)
304
+ return template.render(
305
+ cuda_image_tag=cuda_image_tag,
306
+ python_version=build_configuration.python_version,
307
+ )
308
+ else:
309
+ template = Template(STANDARD_BASE_IMAGE_TEMPLATE)
310
+ return template.render(
311
+ python_image_repo=ENV_VARS.TFY_PYTHONBUILD_PYTHON_IMAGE_REPO,
312
+ python_version=build_configuration.python_version,
313
+ )
314
+
315
+
316
+ def _build_apt_packages_section(apt_install_command: Optional[str]) -> str:
317
+ template = Template(APT_PACKAGES_TEMPLATE)
318
+ return template.render(apt_install_command=apt_install_command)
319
+
320
+
321
+ def _build_python_dependencies_section(
322
+ python_dependencies_type: str, context: TemplateContext
323
+ ) -> str:
324
+ if python_dependencies_type == "pip":
325
+ template = Template(PIP_DEPENDENCIES_TEMPLATE)
326
+ return template.render(**context.dict())
327
+ elif python_dependencies_type == "uv":
328
+ template = Template(UV_DEPENDENCIES_TEMPLATE)
329
+ return template.render(**context.dict())
330
+ elif python_dependencies_type == "poetry":
331
+ template = Template(POETRY_DEPENDENCIES_TEMPLATE)
332
+ return template.render(**context.dict())
333
+ else:
334
+ raise ValueError(f"Unsupported dependency type: {python_dependencies_type}")
335
+
336
+
337
+ def _build_uv_context(python_dependencies: UV, available_secrets: Set[str]) -> Dict:
338
+ python_packages_install_command = generate_command_to_install_from_uv_lock(
339
+ sync_options=python_dependencies.sync_options,
340
+ uv_version=python_dependencies.uv_version,
341
+ available_secrets=available_secrets,
342
+ )
343
+ final_uv_sync_command = generate_command_to_install_from_uv_lock(
344
+ sync_options=python_dependencies.sync_options,
345
+ uv_version=python_dependencies.uv_version,
346
+ install_project=True,
347
+ available_secrets=available_secrets,
348
+ )
349
+
350
+ return {
351
+ "python_packages_install_command": python_packages_install_command,
352
+ "final_uv_sync_command": final_uv_sync_command,
353
+ }
354
+
355
+
356
+ def _build_poetry_context(
357
+ python_dependencies: Poetry, available_secrets: Set[str]
358
+ ) -> Dict:
359
+ python_packages_install_command = generate_poetry_install_command(
360
+ install_options=python_dependencies.install_options,
361
+ available_secrets=available_secrets,
362
+ )
363
+ final_poetry_install_command = generate_poetry_install_command(
364
+ install_options=python_dependencies.install_options,
365
+ install_project=True,
366
+ available_secrets=available_secrets,
367
+ )
368
+
369
+ poetry_version = python_dependencies.poetry_version
370
+ # Handle "latest" poetry version by using major version constraint
371
+ if poetry_version == "latest":
372
+ major_version = ENV_VARS.TFY_PYTHON_BUILD_LATEST_POETRY_MAJOR_VERSION
373
+ poetry_version_expression = f">={major_version},<{major_version + 1}"
374
+ else:
375
+ # For regular versions, use ~= operator for compatibility
376
+ poetry_version_expression = f"~={poetry_version}"
377
+
378
+ return {
379
+ "python_packages_install_command": python_packages_install_command,
380
+ "final_poetry_install_command": final_poetry_install_command,
381
+ "poetry_version_expression": poetry_version_expression,
382
+ }
383
+
384
+
385
+ def _build_pip_context(
386
+ build_configuration: PythonBuild,
387
+ python_dependencies: Pip,
388
+ package_manager: str,
389
+ available_secrets: Set[str],
390
+ ) -> Dict:
94
391
  # TODO (chiragjn): Handle recursive references to other requirements files e.g. `-r requirements-gpu.txt`
95
392
  requirements_path = _resolve_requirements_path(
96
393
  build_context_path=build_configuration.build_context_path,
97
- requirements_path=build_configuration.requirements_path,
394
+ requirements_path=python_dependencies.requirements_path,
98
395
  )
99
396
  requirements_destination_path = (
100
397
  "/tmp/requirements.txt" if requirements_path else None
101
398
  )
102
- if not build_configuration.python_version:
103
- raise ValueError(
104
- "`python_version` is required for `tfy-python-buildpack` builder"
105
- )
399
+
106
400
  if package_manager == PythonPackageManager.PIP.value:
107
401
  python_packages_install_command = generate_pip_install_command(
108
402
  requirements_path=requirements_destination_path,
109
- pip_packages=build_configuration.pip_packages,
110
- mount_pip_conf_secret=mount_python_package_manager_conf_secret,
403
+ pip_packages=python_dependencies.pip_packages,
404
+ available_secrets=available_secrets,
111
405
  )
112
406
  elif package_manager == PythonPackageManager.UV.value:
113
407
  python_packages_install_command = generate_uv_pip_install_command(
114
408
  requirements_path=requirements_destination_path,
115
- pip_packages=build_configuration.pip_packages,
116
- mount_uv_conf_secret=mount_python_package_manager_conf_secret,
409
+ pip_packages=python_dependencies.pip_packages,
410
+ available_secrets=available_secrets,
117
411
  )
118
412
  else:
119
413
  raise ValueError(f"Unsupported package manager: {package_manager}")
120
414
 
121
- apt_install_command = generate_apt_install_command(
122
- apt_packages=build_configuration.apt_packages
123
- )
124
- template_args = {
125
- "python_image_repo": ENV_VARS.TFY_PYTHONBUILD_PYTHON_IMAGE_REPO,
126
- "python_version": build_configuration.python_version,
127
- "apt_install_command": apt_install_command,
415
+ return {
128
416
  "requirements_path": requirements_path,
129
417
  "requirements_destination_path": requirements_destination_path,
130
418
  "python_packages_install_command": python_packages_install_command,
131
419
  }
132
-
133
- if mount_python_package_manager_conf_secret:
134
- if package_manager == PythonPackageManager.PIP.value:
135
- template_args["package_manager_config_secret_mount"] = (
136
- PIP_CONF_BUILDKIT_SECRET_MOUNT
137
- )
138
- elif package_manager == PythonPackageManager.UV.value:
139
- template_args["package_manager_config_secret_mount"] = (
140
- UV_CONF_BUILDKIT_SECRET_MOUNT
141
- )
142
- else:
143
- raise ValueError(f"Unsupported package manager: {package_manager}")
144
- else:
145
- template_args["package_manager_config_secret_mount"] = ""
146
-
147
- if build_configuration.cuda_version:
148
- template = CUDA_DOCKERFILE_TEMPLATE
149
- template_args["nvidia_cuda_image_tag"] = CUDA_VERSION_TO_IMAGE_TAG.get(
150
- build_configuration.cuda_version, build_configuration.cuda_version
151
- )
152
- else:
153
- template = DOCKERFILE_TEMPLATE
154
-
155
- dockerfile_content = template.render(**template_args)
156
- return dockerfile_content
@@ -17,12 +17,12 @@ __all__ = ["generate_dockerfile_content", "build"]
17
17
  def _convert_to_dockerfile_build_config(
18
18
  build_configuration: SparkBuild,
19
19
  dockerfile_path: str,
20
- mount_python_package_manager_conf_secret: bool = False,
20
+ extra_opts: Optional[List[str]] = None,
21
21
  ) -> DockerFileBuild:
22
22
  dockerfile_content = generate_dockerfile_content(
23
23
  build_configuration=build_configuration,
24
- mount_python_package_manager_conf_secret=mount_python_package_manager_conf_secret,
25
24
  package_manager=PythonPackageManager.PIP.value,
25
+ docker_build_extra_args=extra_opts,
26
26
  )
27
27
  with open(dockerfile_path, "w", encoding="utf8") as fp:
28
28
  fp.write(dockerfile_content)
@@ -39,10 +39,6 @@ def build(
39
39
  build_configuration: SparkBuild,
40
40
  extra_opts: Optional[List[str]] = None,
41
41
  ):
42
- mount_python_package_manager_conf_secret = (
43
- has_python_package_manager_conf_secret(extra_opts) if extra_opts else False
44
- )
45
-
46
42
  # Copy tfy_execute_notebook.py to the build context
47
43
  execute_notebook_src = os.path.join(
48
44
  os.path.dirname(__file__), "tfy_execute_notebook.py"
@@ -63,7 +59,7 @@ def build(
63
59
  docker_build_configuration = _convert_to_dockerfile_build_config(
64
60
  build_configuration,
65
61
  dockerfile_path=os.path.join(local_dir, "Dockerfile"),
66
- mount_python_package_manager_conf_secret=mount_python_package_manager_conf_secret,
62
+ extra_opts=extra_opts,
67
63
  )
68
64
  dockerfile.build(
69
65
  tag=tag,
@@ -1,14 +1,14 @@
1
+ from typing import List, Optional
2
+
1
3
  from mako.template import Template
2
4
 
3
5
  from truefoundry.common.constants import ENV_VARS, PythonPackageManager
4
6
  from truefoundry.deploy._autogen.models import SparkBuild
5
- from truefoundry.deploy.builder.constants import (
6
- PIP_CONF_BUILDKIT_SECRET_MOUNT,
7
- UV_CONF_BUILDKIT_SECRET_MOUNT,
8
- )
9
7
  from truefoundry.deploy.builder.utils import (
10
8
  generate_pip_install_command,
9
+ generate_secret_mounts,
11
10
  generate_uv_pip_install_command,
11
+ get_available_secrets,
12
12
  )
13
13
  from truefoundry.deploy.v2.lib.patched_models import (
14
14
  _resolve_requirements_path,
@@ -71,8 +71,13 @@ ADDITIONAL_PIP_PACKAGES = [
71
71
  def generate_dockerfile_content(
72
72
  build_configuration: SparkBuild,
73
73
  package_manager: str = ENV_VARS.TFY_PYTHON_BUILD_PACKAGE_MANAGER,
74
- mount_python_package_manager_conf_secret: bool = False,
74
+ docker_build_extra_args: Optional[List[str]] = None,
75
75
  ) -> str:
76
+ # Get available secrets from docker build extra args
77
+ available_secrets = set()
78
+ if docker_build_extra_args:
79
+ available_secrets = get_available_secrets(docker_build_extra_args)
80
+
76
81
  # TODO (chiragjn): Handle recursive references to other requirements files e.g. `-r requirements-gpu.txt`
77
82
  requirements_path = _resolve_requirements_path(
78
83
  build_context_path=build_configuration.build_context_path,
@@ -90,13 +95,13 @@ def generate_dockerfile_content(
90
95
  python_packages_install_command = generate_pip_install_command(
91
96
  requirements_path=requirements_destination_path,
92
97
  pip_packages=ADDITIONAL_PIP_PACKAGES,
93
- mount_pip_conf_secret=mount_python_package_manager_conf_secret,
98
+ available_secrets=available_secrets,
94
99
  )
95
100
  elif package_manager == PythonPackageManager.UV.value:
96
101
  python_packages_install_command = generate_uv_pip_install_command(
97
102
  requirements_path=requirements_destination_path,
98
103
  pip_packages=ADDITIONAL_PIP_PACKAGES,
99
- mount_uv_conf_secret=mount_python_package_manager_conf_secret,
104
+ available_secrets=available_secrets,
100
105
  )
101
106
  else:
102
107
  raise ValueError(f"Unsupported package manager: {package_manager}")
@@ -109,17 +114,12 @@ def generate_dockerfile_content(
109
114
  "python_packages_install_command": python_packages_install_command,
110
115
  }
111
116
 
112
- if mount_python_package_manager_conf_secret:
113
- if package_manager == PythonPackageManager.PIP.value:
114
- template_args["package_manager_config_secret_mount"] = (
115
- PIP_CONF_BUILDKIT_SECRET_MOUNT
116
- )
117
- elif package_manager == PythonPackageManager.UV.value:
118
- template_args["package_manager_config_secret_mount"] = (
119
- UV_CONF_BUILDKIT_SECRET_MOUNT
120
- )
121
- else:
122
- raise ValueError(f"Unsupported package manager: {package_manager}")
117
+ if available_secrets:
118
+ template_args["package_manager_config_secret_mount"] = generate_secret_mounts(
119
+ available_secrets=available_secrets,
120
+ python_dependencies_type="pip",
121
+ package_manager=package_manager,
122
+ )
123
123
  else:
124
124
  template_args["package_manager_config_secret_mount"] = ""
125
125
 
@@ -15,11 +15,11 @@ __all__ = ["generate_dockerfile_content", "build"]
15
15
  def _convert_to_dockerfile_build_config(
16
16
  build_configuration: TaskPySparkBuild,
17
17
  dockerfile_path: str,
18
- mount_python_package_manager_conf_secret: bool = False,
18
+ extra_opts: Optional[List[str]] = None,
19
19
  ) -> DockerFileBuild:
20
20
  dockerfile_content = generate_dockerfile_content(
21
21
  build_configuration=build_configuration,
22
- mount_python_package_manager_conf_secret=mount_python_package_manager_conf_secret,
22
+ docker_build_extra_args=extra_opts,
23
23
  )
24
24
  with open(dockerfile_path, "w", encoding="utf8") as fp:
25
25
  fp.write(dockerfile_content)
@@ -35,15 +35,11 @@ def build(
35
35
  build_configuration: TaskPySparkBuild,
36
36
  extra_opts: Optional[List[str]] = None,
37
37
  ):
38
- mount_python_package_manager_conf_secret = (
39
- has_python_package_manager_conf_secret(extra_opts) if extra_opts else False
40
- )
41
-
42
38
  with TemporaryDirectory() as local_dir:
43
39
  docker_build_configuration = _convert_to_dockerfile_build_config(
44
40
  build_configuration,
45
41
  dockerfile_path=os.path.join(local_dir, "Dockerfile"),
46
- mount_python_package_manager_conf_secret=mount_python_package_manager_conf_secret,
42
+ extra_opts=extra_opts,
47
43
  )
48
44
  dockerfile.build(
49
45
  tag=tag,