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.
- truefoundry/common/constants.py +8 -1
- truefoundry/deploy/__init__.py +3 -0
- truefoundry/deploy/_autogen/models.py +191 -144
- truefoundry/deploy/builder/__init__.py +1 -0
- truefoundry/deploy/builder/builders/tfy_python_buildpack/__init__.py +3 -6
- truefoundry/deploy/builder/builders/tfy_python_buildpack/dockerfile_template.py +347 -84
- truefoundry/deploy/builder/builders/tfy_spark_buildpack/__init__.py +3 -7
- truefoundry/deploy/builder/builders/tfy_spark_buildpack/dockerfile_template.py +18 -18
- truefoundry/deploy/builder/builders/tfy_task_pyspark_buildpack/__init__.py +3 -7
- truefoundry/deploy/builder/builders/tfy_task_pyspark_buildpack/dockerfile_template.py +18 -18
- truefoundry/deploy/builder/constants.py +12 -0
- truefoundry/deploy/builder/utils.py +210 -21
- truefoundry/deploy/lib/util.py +35 -0
- truefoundry/deploy/v2/lib/deploy.py +3 -1
- truefoundry/deploy/v2/lib/patched_models.py +76 -3
- truefoundry/deploy/v2/lib/source.py +4 -0
- {truefoundry-0.11.10.dist-info → truefoundry-0.11.11rc1.dist-info}/METADATA +2 -1
- {truefoundry-0.11.10.dist-info → truefoundry-0.11.11rc1.dist-info}/RECORD +20 -20
- {truefoundry-0.11.10.dist-info → truefoundry-0.11.11rc1.dist-info}/WHEEL +0 -0
- {truefoundry-0.11.10.dist-info → truefoundry-0.11.11rc1.dist-info}/entry_points.txt +0 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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(
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
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
|
-
|
|
190
|
+
available_secrets: Optional[Set[str]] = None,
|
|
77
191
|
) -> Optional[str]:
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
86
|
-
|
|
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 =
|
|
103
|
-
|
|
104
|
-
|
|
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
|
truefoundry/deploy/lib/util.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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.
|
|
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
|