truefoundry 0.11.9rc1__py3-none-any.whl → 0.11.11rc1__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,15 +1,15 @@
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 TaskPySparkBuild
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_apt_install_command,
11
9
  generate_pip_install_command,
10
+ generate_secret_mounts,
12
11
  generate_uv_pip_install_command,
12
+ get_available_secrets,
13
13
  )
14
14
  from truefoundry.deploy.v2.lib.patched_models import (
15
15
  _resolve_requirements_path,
@@ -57,8 +57,13 @@ def get_additional_pip_packages(build_configuration: TaskPySparkBuild):
57
57
  def generate_dockerfile_content(
58
58
  build_configuration: TaskPySparkBuild,
59
59
  package_manager: str = ENV_VARS.TFY_PYTHON_BUILD_PACKAGE_MANAGER,
60
- mount_python_package_manager_conf_secret: bool = False,
60
+ docker_build_extra_args: Optional[List[str]] = None,
61
61
  ) -> str:
62
+ # Get available secrets from docker build extra args
63
+ available_secrets = set()
64
+ if docker_build_extra_args:
65
+ available_secrets = get_available_secrets(docker_build_extra_args)
66
+
62
67
  # TODO (chiragjn): Handle recursive references to other requirements files e.g. `-r requirements-gpu.txt`
63
68
  requirements_path = _resolve_requirements_path(
64
69
  build_context_path="",
@@ -78,13 +83,13 @@ def generate_dockerfile_content(
78
83
  python_packages_install_command = generate_pip_install_command(
79
84
  requirements_path=requirements_destination_path,
80
85
  pip_packages=pip_packages,
81
- mount_pip_conf_secret=mount_python_package_manager_conf_secret,
86
+ available_secrets=available_secrets,
82
87
  )
83
88
  elif package_manager == PythonPackageManager.UV.value:
84
89
  python_packages_install_command = generate_uv_pip_install_command(
85
90
  requirements_path=requirements_destination_path,
86
91
  pip_packages=pip_packages,
87
- mount_uv_conf_secret=mount_python_package_manager_conf_secret,
92
+ available_secrets=available_secrets,
88
93
  )
89
94
  else:
90
95
  raise ValueError(f"Unsupported package manager: {package_manager}")
@@ -101,17 +106,12 @@ def generate_dockerfile_content(
101
106
  "python_packages_install_command": python_packages_install_command,
102
107
  }
103
108
 
104
- if mount_python_package_manager_conf_secret:
105
- if package_manager == PythonPackageManager.PIP.value:
106
- template_args["package_manager_config_secret_mount"] = (
107
- PIP_CONF_BUILDKIT_SECRET_MOUNT
108
- )
109
- elif package_manager == PythonPackageManager.UV.value:
110
- template_args["package_manager_config_secret_mount"] = (
111
- UV_CONF_BUILDKIT_SECRET_MOUNT
112
- )
113
- else:
114
- raise ValueError(f"Unsupported package manager: {package_manager}")
109
+ if available_secrets:
110
+ template_args["package_manager_config_secret_mount"] = generate_secret_mounts(
111
+ available_secrets=available_secrets,
112
+ python_dependencies_type="pip",
113
+ package_manager=package_manager,
114
+ )
115
115
  else:
116
116
  template_args["package_manager_config_secret_mount"] = ""
117
117
 
@@ -13,3 +13,15 @@ UV_CONF_BUILDKIT_SECRET_MOUNT = (
13
13
  UV_CONF_SECRET_MOUNT_AS_ENV = (
14
14
  f"UV_CONFIG_FILE=/run/secrets/{BUILDKIT_SECRET_MOUNT_UV_CONF_ID}"
15
15
  )
16
+
17
+ BUILDKIT_SECRET_MOUNT_UV_ENV_ID = "uv.env"
18
+ UV_ENV_BUILDKIT_SECRET_MOUNT = (
19
+ f"--mount=type=secret,id={BUILDKIT_SECRET_MOUNT_UV_ENV_ID}"
20
+ )
21
+ UV_ENV_SECRET_MOUNT_AS_ENV = f". /run/secrets/{BUILDKIT_SECRET_MOUNT_UV_ENV_ID}"
22
+
23
+ BUILDKIT_SECRET_MOUNT_POETRY_ENV_ID = "poetry.env"
24
+ POETRY_ENV_BUILDKIT_SECRET_MOUNT = (
25
+ f"--mount=type=secret,id={BUILDKIT_SECRET_MOUNT_POETRY_ENV_ID}"
26
+ )
27
+ POETRY_ENV_SECRET_MOUNT_AS_ENV = f". /run/secrets/{BUILDKIT_SECRET_MOUNT_POETRY_ENV_ID}"
@@ -1,12 +1,20 @@
1
1
  import shlex
2
- from typing import List, Optional
2
+ from typing import List, Optional, Set
3
3
 
4
4
  from truefoundry.common.constants import ENV_VARS
5
5
  from truefoundry.deploy.builder.constants import (
6
6
  BUILDKIT_SECRET_MOUNT_PIP_CONF_ID,
7
+ BUILDKIT_SECRET_MOUNT_POETRY_ENV_ID,
7
8
  BUILDKIT_SECRET_MOUNT_UV_CONF_ID,
9
+ BUILDKIT_SECRET_MOUNT_UV_ENV_ID,
10
+ PIP_CONF_BUILDKIT_SECRET_MOUNT,
8
11
  PIP_CONF_SECRET_MOUNT_AS_ENV,
12
+ POETRY_ENV_BUILDKIT_SECRET_MOUNT,
13
+ POETRY_ENV_SECRET_MOUNT_AS_ENV,
14
+ UV_CONF_BUILDKIT_SECRET_MOUNT,
9
15
  UV_CONF_SECRET_MOUNT_AS_ENV,
16
+ UV_ENV_BUILDKIT_SECRET_MOUNT,
17
+ UV_ENV_SECRET_MOUNT_AS_ENV,
10
18
  )
11
19
 
12
20
 
@@ -28,29 +36,129 @@ def _get_id_from_buildkit_secret_value(value: str) -> Optional[str]:
28
36
 
29
37
  def has_python_package_manager_conf_secret(docker_build_extra_args: List[str]) -> bool:
30
38
  args = [arg.strip() for arg in docker_build_extra_args]
31
- for i, arg in enumerate(docker_build_extra_args):
39
+ for i, arg in enumerate(args):
32
40
  if (
33
41
  arg == "--secret"
34
42
  and i + 1 < len(args)
35
43
  and (
36
44
  _get_id_from_buildkit_secret_value(args[i + 1])
37
- in (BUILDKIT_SECRET_MOUNT_PIP_CONF_ID, BUILDKIT_SECRET_MOUNT_UV_CONF_ID)
45
+ in (
46
+ BUILDKIT_SECRET_MOUNT_PIP_CONF_ID,
47
+ BUILDKIT_SECRET_MOUNT_UV_CONF_ID,
48
+ BUILDKIT_SECRET_MOUNT_UV_ENV_ID,
49
+ BUILDKIT_SECRET_MOUNT_POETRY_ENV_ID,
50
+ )
38
51
  )
39
52
  ):
40
53
  return True
41
54
  return False
42
55
 
43
56
 
57
+ def get_available_secrets(docker_build_extra_args: List[str]) -> Set[str]:
58
+ available_secrets = set()
59
+ args = [arg.strip() for arg in docker_build_extra_args]
60
+ for i, arg in enumerate(args):
61
+ if arg == "--secret" and i + 1 < len(args):
62
+ secret_id = _get_id_from_buildkit_secret_value(args[i + 1])
63
+ if secret_id:
64
+ available_secrets.add(secret_id)
65
+ return available_secrets
66
+
67
+
68
+ def generate_secret_mounts(
69
+ available_secrets: Set[str], python_dependencies_type: str, package_manager: str
70
+ ) -> str:
71
+ mounts = []
72
+
73
+ if python_dependencies_type == "pip":
74
+ if (
75
+ package_manager == "pip"
76
+ and BUILDKIT_SECRET_MOUNT_PIP_CONF_ID in available_secrets
77
+ ):
78
+ mounts.append(PIP_CONF_BUILDKIT_SECRET_MOUNT)
79
+ elif (
80
+ package_manager == "uv"
81
+ and BUILDKIT_SECRET_MOUNT_UV_CONF_ID in available_secrets
82
+ ):
83
+ mounts.append(UV_CONF_BUILDKIT_SECRET_MOUNT)
84
+ elif python_dependencies_type == "uv":
85
+ if BUILDKIT_SECRET_MOUNT_UV_CONF_ID in available_secrets:
86
+ mounts.append(UV_CONF_BUILDKIT_SECRET_MOUNT)
87
+ if BUILDKIT_SECRET_MOUNT_UV_ENV_ID in available_secrets:
88
+ mounts.append(UV_ENV_BUILDKIT_SECRET_MOUNT)
89
+ elif python_dependencies_type == "poetry":
90
+ if BUILDKIT_SECRET_MOUNT_POETRY_ENV_ID in available_secrets:
91
+ mounts.append(POETRY_ENV_BUILDKIT_SECRET_MOUNT)
92
+
93
+ return " ".join(mounts)
94
+
95
+
96
+ def generate_secret_env_commands(
97
+ available_secrets: Set[str], python_dependencies_type: str, package_manager: str
98
+ ) -> List[str]:
99
+ env_commands = []
100
+
101
+ if python_dependencies_type == "pip":
102
+ if (
103
+ package_manager == "pip"
104
+ and BUILDKIT_SECRET_MOUNT_PIP_CONF_ID in available_secrets
105
+ ):
106
+ env_commands.append(PIP_CONF_SECRET_MOUNT_AS_ENV)
107
+ elif (
108
+ package_manager == "uv"
109
+ and BUILDKIT_SECRET_MOUNT_UV_CONF_ID in available_secrets
110
+ ):
111
+ env_commands.append(UV_CONF_SECRET_MOUNT_AS_ENV)
112
+ elif python_dependencies_type == "uv":
113
+ if BUILDKIT_SECRET_MOUNT_UV_CONF_ID in available_secrets:
114
+ env_commands.append(UV_CONF_SECRET_MOUNT_AS_ENV)
115
+ if BUILDKIT_SECRET_MOUNT_UV_ENV_ID in available_secrets:
116
+ env_commands.append(UV_ENV_SECRET_MOUNT_AS_ENV)
117
+ elif python_dependencies_type == "poetry":
118
+ if BUILDKIT_SECRET_MOUNT_POETRY_ENV_ID in available_secrets:
119
+ env_commands.append(POETRY_ENV_SECRET_MOUNT_AS_ENV)
120
+
121
+ return env_commands
122
+
123
+
124
+ def generate_shell_command_with_secrets(
125
+ env_commands: List[str], command: List[str]
126
+ ) -> str:
127
+ """Generate a shell command that properly handles source commands and environment variables."""
128
+ if not env_commands:
129
+ return shlex.join(command)
130
+
131
+ # Separate source commands from env var assignments
132
+ source_commands = [
133
+ cmd for cmd in env_commands if cmd.startswith(". ") or cmd.startswith("source ")
134
+ ]
135
+ env_assignments = [
136
+ cmd
137
+ for cmd in env_commands
138
+ if not cmd.startswith(". ") and not cmd.startswith("source ")
139
+ ]
140
+
141
+ if source_commands:
142
+ # If we have source commands, join them with the command
143
+ shell_command_parts = source_commands.copy()
144
+
145
+ # Add environment variable assignments and command together
146
+ if env_assignments:
147
+ final_command = shlex.join(env_assignments + command)
148
+ else:
149
+ final_command = shlex.join(command)
150
+
151
+ shell_command_parts.append(final_command)
152
+ return " && ".join(shell_command_parts)
153
+
154
+ return shlex.join(env_assignments + command)
155
+
156
+
44
157
  def generate_pip_install_command(
45
158
  requirements_path: Optional[str],
46
159
  pip_packages: Optional[List[str]],
47
- mount_pip_conf_secret: bool = False,
160
+ available_secrets: Optional[Set[str]] = None,
48
161
  ) -> Optional[str]:
49
- upgrade_pip_command = "python -m pip install -U pip setuptools wheel"
50
- envs = []
51
- if mount_pip_conf_secret:
52
- envs.append(PIP_CONF_SECRET_MOUNT_AS_ENV)
53
-
54
162
  command = ["python", "-m", "pip", "install", "--use-pep517", "--no-cache-dir"]
55
163
  args = []
56
164
  if requirements_path:
@@ -63,27 +171,37 @@ def generate_pip_install_command(
63
171
  if not args:
64
172
  return None
65
173
 
66
- final_pip_install_command = shlex.join(envs + command + args)
67
- final_docker_run_command = " && ".join(
68
- [upgrade_pip_command, final_pip_install_command]
174
+ secret_env_commands = []
175
+ if available_secrets:
176
+ secret_env_commands = generate_secret_env_commands(
177
+ available_secrets, python_dependencies_type="pip", package_manager="pip"
178
+ )
179
+
180
+ final_pip_install_command = generate_shell_command_with_secrets(
181
+ secret_env_commands, command + args
69
182
  )
70
- return final_docker_run_command
183
+
184
+ return final_pip_install_command
71
185
 
72
186
 
73
187
  def generate_uv_pip_install_command(
74
188
  requirements_path: Optional[str],
75
189
  pip_packages: Optional[List[str]],
76
- mount_uv_conf_secret: bool = False,
190
+ available_secrets: Optional[Set[str]] = None,
77
191
  ) -> Optional[str]:
78
- upgrade_pip_command = "python -m pip install -U pip setuptools wheel"
79
- uv_mount = f"--mount=from={ENV_VARS.TFY_PYTHON_BUILD_UV_IMAGE_URI},source=/uv,target=/usr/local/bin/uv"
192
+ uv_mount = f"--mount=from={ENV_VARS.TFY_PYTHON_BUILD_UV_IMAGE_REPO}:{ENV_VARS.TFY_PYTHON_BUILD_UV_IMAGE_TAG},source=/uv,target=/usr/local/bin/uv"
193
+
80
194
  envs = [
81
195
  "UV_LINK_MODE=copy",
82
196
  "UV_PYTHON_DOWNLOADS=never",
83
197
  "UV_INDEX_STRATEGY=unsafe-best-match",
84
198
  ]
85
- if mount_uv_conf_secret:
86
- envs.append(UV_CONF_SECRET_MOUNT_AS_ENV)
199
+
200
+ secret_env_commands = []
201
+ if available_secrets:
202
+ secret_env_commands = generate_secret_env_commands(
203
+ available_secrets, python_dependencies_type="pip", package_manager="uv"
204
+ )
87
205
 
88
206
  command = ["uv", "pip", "install", "--no-cache-dir"]
89
207
 
@@ -99,9 +217,10 @@ def generate_uv_pip_install_command(
99
217
  if not args:
100
218
  return None
101
219
 
102
- uv_pip_install_command = shlex.join(envs + command + args)
103
- shell_commands = " && ".join([upgrade_pip_command, uv_pip_install_command])
104
- final_docker_run_command = " ".join([uv_mount, shell_commands])
220
+ uv_pip_install_command = generate_shell_command_with_secrets(
221
+ secret_env_commands, envs + command + args
222
+ )
223
+ final_docker_run_command = " ".join([uv_mount, uv_pip_install_command])
105
224
 
106
225
  return final_docker_run_command
107
226
 
@@ -118,3 +237,73 @@ def generate_apt_install_command(apt_packages: Optional[List[str]]) -> Optional[
118
237
  return " && ".join(
119
238
  [apt_update_command, apt_install_command, clear_apt_lists_command]
120
239
  )
240
+
241
+
242
+ def generate_command_to_install_from_uv_lock(
243
+ sync_options: Optional[str],
244
+ uv_version: Optional[str],
245
+ install_project: bool = False,
246
+ available_secrets: Optional[Set[str]] = None,
247
+ ):
248
+ uv_image_uri = f"{ENV_VARS.TFY_PYTHON_BUILD_UV_IMAGE_REPO}:{uv_version if uv_version is not None else ENV_VARS.TFY_PYTHON_BUILD_UV_IMAGE_TAG}"
249
+ uv_mount = f"--mount=from={uv_image_uri},source=/uv,target=/usr/local/bin/uv"
250
+
251
+ envs = [
252
+ "UV_LINK_MODE=copy",
253
+ "UV_PYTHON_DOWNLOADS=never",
254
+ "UV_INDEX_STRATEGY=unsafe-best-match",
255
+ ]
256
+
257
+ secret_env_commands = []
258
+ if available_secrets:
259
+ secret_env_commands = generate_secret_env_commands(
260
+ available_secrets, python_dependencies_type="uv", package_manager="uv"
261
+ )
262
+
263
+ command = ["uv", "sync"]
264
+ sync_options_list = shlex.split(sync_options or "")
265
+ if "--active" not in sync_options_list:
266
+ sync_options_list.append("--active")
267
+
268
+ if not install_project and "--no-install-project" not in sync_options_list:
269
+ sync_options_list.append("--no-install-project")
270
+
271
+ command.extend(sync_options_list)
272
+
273
+ uv_sync_install_command = generate_shell_command_with_secrets(
274
+ secret_env_commands, envs + command
275
+ )
276
+ final_docker_run_command = " ".join([uv_mount, uv_sync_install_command])
277
+
278
+ return final_docker_run_command
279
+
280
+
281
+ def generate_poetry_install_command(
282
+ install_options: Optional[str],
283
+ install_project: bool = False,
284
+ available_secrets: Optional[Set[str]] = None,
285
+ ) -> Optional[str]:
286
+ command = ["poetry", "install"]
287
+ install_options_list = shlex.split(install_options or "")
288
+
289
+ if "--no-interaction" not in install_options_list:
290
+ command.append("--no-interaction")
291
+
292
+ if not install_project and "--no-root" not in install_options_list:
293
+ command.append("--no-root")
294
+
295
+ command.extend(install_options_list)
296
+
297
+ secret_env_commands = []
298
+ if available_secrets:
299
+ secret_env_commands = generate_secret_env_commands(
300
+ available_secrets=available_secrets,
301
+ python_dependencies_type="poetry",
302
+ package_manager="poetry",
303
+ )
304
+
305
+ poetry_install_cmd = generate_shell_command_with_secrets(
306
+ secret_env_commands, command
307
+ )
308
+
309
+ return poetry_install_cmd
@@ -58,9 +58,19 @@ def patch_command(
58
58
  ]
59
59
  if output_file:
60
60
  with open(output_file, "w") as fd:
61
- subprocess.run(yq_command, stdout=fd)
61
+ p = subprocess.run(
62
+ yq_command, stdout=fd, stderr=subprocess.PIPE, check=False
63
+ )
64
+ if p.returncode != 0:
65
+ stderr = p.stderr.decode("UTF-8")
66
+ raise Exception(f"Failed to patch yaml file: {stderr}")
62
67
  else:
63
- p = subprocess.run(yq_command, stdout=subprocess.PIPE)
68
+ p = subprocess.run(
69
+ yq_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False
70
+ )
71
+ if p.returncode != 0:
72
+ stderr = p.stderr.decode("UTF-8")
73
+ raise Exception(f"Failed to patch yaml file: {stderr}")
64
74
  output = p.stdout.decode("UTF-8")
65
75
  callback = RichOutputCallBack()
66
76
  callback.print_line(output)
@@ -1,6 +1,11 @@
1
+ import os
1
2
  import re
2
3
  from typing import Union
3
4
 
5
+ from truefoundry.deploy._autogen.models import (
6
+ PythonBuild,
7
+ )
8
+
4
9
 
5
10
  def get_application_fqn_from_deployment_fqn(deployment_fqn: str) -> str:
6
11
  if not re.search(r":\d+$", deployment_fqn):
@@ -29,3 +34,33 @@ def find_list_paths(data, parent_key="", sep="."):
29
34
  new_key = f"{parent_key}[{i}]"
30
35
  list_paths.extend(find_list_paths(value, new_key, sep))
31
36
  return list_paths
37
+
38
+
39
+ def _validate_file_path(project_root_path: str, file_path: str):
40
+ file_absolute_path = os.path.join(project_root_path, file_path)
41
+ if not os.path.exists(file_absolute_path):
42
+ raise FileNotFoundError(
43
+ f"file {file_path} not found. file path should be relative to your project root path: {project_root_path}."
44
+ )
45
+
46
+
47
+ def validate_paths(python_build: PythonBuild, source_dir: str):
48
+ if not python_build.python_dependencies:
49
+ return
50
+
51
+ if not os.path.exists(source_dir):
52
+ raise ValueError(f"project root path {source_dir!r} of does not exist")
53
+ if (
54
+ python_build.python_dependencies.type == "pip"
55
+ and python_build.python_dependencies.requirements_path
56
+ ):
57
+ _validate_file_path(
58
+ source_dir,
59
+ python_build.python_dependencies.requirements_path,
60
+ )
61
+ if python_build.python_dependencies.type == "uv":
62
+ _validate_file_path(source_dir, "uv.lock")
63
+ _validate_file_path(source_dir, "pyproject.toml")
64
+ if python_build.python_dependencies.type == "poetry":
65
+ _validate_file_path(source_dir, "pyproject.toml")
66
+ _validate_file_path(source_dir, "poetry.lock")
@@ -13,7 +13,9 @@ from truefoundry.deploy.lib.clients.servicefoundry_client import (
13
13
  )
14
14
  from truefoundry.deploy.lib.dao.workspace import get_workspace_by_fqn
15
15
  from truefoundry.deploy.lib.model.entity import Deployment, DeploymentTransitionStatus
16
- from truefoundry.deploy.lib.util import get_application_fqn_from_deployment_fqn
16
+ from truefoundry.deploy.lib.util import (
17
+ get_application_fqn_from_deployment_fqn,
18
+ )
17
19
  from truefoundry.deploy.v2.lib.models import BuildResponse
18
20
  from truefoundry.deploy.v2.lib.source import (
19
21
  local_source_to_image,
@@ -24,7 +24,7 @@ build=Build(
24
24
  ...
25
25
  build_spec=PythonBuild(
26
26
  ...
27
- requirements_path={requirements_txt_path!r}
27
+ python_dependencies=Pip(requirements_path={requirements_txt_path!r})
28
28
  )
29
29
  )
30
30
  ```
@@ -36,12 +36,44 @@ build:
36
36
  type: build
37
37
  build_spec:
38
38
  type: tfy-python-buildpack
39
- requirements_path: {requirements_txt_path!r}
39
+ python_dependencies:
40
+ type: pip
41
+ requirements_path: {requirements_txt_path!r}
40
42
  ...
41
43
  ...
42
44
  ```
43
45
 
44
- or set it to None if you don't want use any requirements file.
46
+ or set it to None if you don't want to use any requirements file.
47
+ """
48
+
49
+ SPECS_UPGRADE_WARNING_MESSAGE_TEMPLATE = """\
50
+ The `requirements_path` and `pip_packages` fields are deprecated and will be removed in a future release.
51
+ Please use the `python_dependencies` field instead. Please use the following format:
52
+
53
+ ```python
54
+ build=Build(
55
+ ...
56
+ build_spec=PythonBuild(
57
+ ...
58
+ python_dependencies=Pip(requirements_path={requirements_txt_path!r}, pip_packages={pip_packages!r})
59
+ )
60
+ )
61
+ ```
62
+
63
+ OR
64
+
65
+ ```yaml
66
+ build:
67
+ type: build
68
+ build_spec:
69
+ type: tfy-python-buildpack
70
+ python_dependencies:
71
+ type: pip
72
+ requirements_path: {requirements_txt_path!r}
73
+ pip_packages: {pip_packages!r}
74
+ ...
75
+ ...
76
+ ```
45
77
  """
46
78
 
47
79
 
@@ -74,6 +106,18 @@ def _resolve_requirements_path(
74
106
  return None
75
107
 
76
108
 
109
+ def check_whether_poetry_toml_exists(
110
+ build_context_path: str,
111
+ ) -> bool:
112
+ required_filename = "poetry.toml"
113
+ possible_path = os.path.join(build_context_path, required_filename)
114
+
115
+ if os.path.isfile(possible_path):
116
+ return True
117
+
118
+ return False
119
+
120
+
77
121
  class CUDAVersion(str, enum.Enum):
78
122
  CUDA_11_0_CUDNN8 = "11.0-cudnn8"
79
123
  CUDA_11_1_CUDNN8 = "11.1-cudnn8"
@@ -91,6 +135,8 @@ class CUDAVersion(str, enum.Enum):
91
135
  CUDA_12_4_CUDNN9 = "12.4-cudnn9"
92
136
  CUDA_12_5_CUDNN9 = "12.5-cudnn9"
93
137
  CUDA_12_6_CUDNN9 = "12.6-cudnn9"
138
+ CUDA_12_8_CUDNN9 = "12.8-cudnn9"
139
+ CUDA_12_9_CUDNN9 = "12.9-cudnn9"
94
140
 
95
141
 
96
142
  class GPUType(str, enum.Enum):
@@ -99,12 +145,19 @@ class GPUType(str, enum.Enum):
99
145
  V100 = "V100"
100
146
  T4 = "T4"
101
147
  A10G = "A10G"
148
+ A10_4GB = "A10_4GB"
149
+ A10_8GB = "A10_8GB"
150
+ A10_12GB = "A10_12GB"
151
+ A10_24GB = "A10_24GB"
102
152
  A100_40GB = "A100_40GB"
103
153
  A100_80GB = "A100_80GB"
104
154
  L4 = "L4"
155
+ L40S = "L40S"
105
156
  H100_80GB = "H100_80GB"
106
157
  H100_94GB = "H100_94GB"
158
+ H100_96GB = "H100_96GB"
107
159
  H200 = "H200"
160
+ B200 = "B200"
108
161
 
109
162
 
110
163
  class TPUType(str, enum.Enum):
@@ -156,6 +209,23 @@ class PythonBuild(models.PythonBuild, PatchedModelBase):
156
209
  build_context_path=values.get("build_context_path") or "./",
157
210
  requirements_path=values.get("requirements_path"),
158
211
  )
212
+
213
+ if not values.get("python_dependencies"):
214
+ warnings.warn(
215
+ SPECS_UPGRADE_WARNING_MESSAGE_TEMPLATE.format(
216
+ requirements_txt_path=values.get("requirements_path"),
217
+ pip_packages=values.get("pip_packages"),
218
+ ),
219
+ category=TrueFoundryDeprecationWarning,
220
+ stacklevel=2,
221
+ )
222
+ values["python_dependencies"] = Pip(
223
+ type="pip",
224
+ requirements_path=values.get("requirements_path"),
225
+ pip_packages=values.get("pip_packages"),
226
+ )
227
+ values.pop("pip_packages", None)
228
+ values.pop("requirements_path", None)
159
229
  return values
160
230
 
161
231
 
@@ -565,3 +635,15 @@ class SparkExecutorDynamicScaling(models.SparkExecutorDynamicScaling, PatchedMod
565
635
 
566
636
  class TaskPySparkBuild(models.TaskPySparkBuild, PatchedModelBase):
567
637
  type: Literal["task-pyspark-build"] = "task-pyspark-build"
638
+
639
+
640
+ class Pip(models.Pip, PatchedModelBase):
641
+ type: Literal["pip"] = "pip"
642
+
643
+
644
+ class UV(models.UV, PatchedModelBase):
645
+ type: Literal["uv"] = "uv"
646
+
647
+
648
+ class Poetry(models.Poetry, PatchedModelBase):
649
+ type: Literal["poetry"] = "poetry"
@@ -21,6 +21,7 @@ from truefoundry.deploy.lib.clients.servicefoundry_client import (
21
21
  ServiceFoundryServiceClient,
22
22
  )
23
23
  from truefoundry.deploy.lib.dao import workspace as workspace_lib
24
+ from truefoundry.deploy.lib.util import validate_paths
24
25
  from truefoundry.deploy.v2.lib.patched_models import Image, RemoteSource
25
26
  from truefoundry.logger import logger
26
27
 
@@ -209,6 +210,9 @@ def local_source_to_image(
209
210
  f"of component {component_name!r} does not exist"
210
211
  )
211
212
 
213
+ if isinstance(build.build_spec, models.PythonBuild):
214
+ validate_paths(build.build_spec, source_dir)
215
+
212
216
  client = ServiceFoundryServiceClient()
213
217
 
214
218
  workspace = workspace_lib.get_workspace_by_fqn(workspace_fqn=workspace_fqn)
@@ -371,7 +371,7 @@ class MlFoundryArtifactsRepository:
371
371
  is MultiPartUploadStorageProvider.AZURE_BLOB
372
372
  ):
373
373
  azure_multi_part_upload(
374
- multipart_upload=MultiPartUpload.parse_obj(multipart_upload.to_dict()),
374
+ multipart_upload=MultiPartUpload.parse_obj(multipart_upload.dict()),
375
375
  local_file=local_file,
376
376
  executor=executor,
377
377
  multipart_info=multipart_info,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: truefoundry
3
- Version: 0.11.9rc1
3
+ Version: 0.11.11rc1
4
4
  Summary: TrueFoundry CLI
5
5
  Author-email: TrueFoundry Team <abhishek@truefoundry.com>
6
6
  Requires-Python: <3.14,>=3.8.1
@@ -13,6 +13,7 @@ Requires-Dist: gitignorefile<2.0.0,>=1.1.2
13
13
  Requires-Dist: gitpython<4.0.0,>=3.1.43
14
14
  Requires-Dist: importlib-metadata<9.0.0,>=4.11.3
15
15
  Requires-Dist: importlib-resources<7.0.0,>=5.2.0
16
+ Requires-Dist: jinja2<4.0.0,>=3.1.6
16
17
  Requires-Dist: mako<2.0.0,>=1.1.6
17
18
  Requires-Dist: numpy<3.0.0,>=1.23.0
18
19
  Requires-Dist: openai<2.0.0,>=1.16.2