truefoundry 0.11.10__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
@@ -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"
@@ -165,6 +209,23 @@ class PythonBuild(models.PythonBuild, PatchedModelBase):
165
209
  build_context_path=values.get("build_context_path") or "./",
166
210
  requirements_path=values.get("requirements_path"),
167
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)
168
229
  return values
169
230
 
170
231
 
@@ -574,3 +635,15 @@ class SparkExecutorDynamicScaling(models.SparkExecutorDynamicScaling, PatchedMod
574
635
 
575
636
  class TaskPySparkBuild(models.TaskPySparkBuild, PatchedModelBase):
576
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: truefoundry
3
- Version: 0.11.10
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