flyte 2.0.0b17__py3-none-any.whl → 2.0.0b19__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 flyte might be problematic. Click here for more details.
- flyte/_bin/runtime.py +3 -0
- flyte/_debug/vscode.py +4 -2
- flyte/_deploy.py +3 -1
- flyte/_environment.py +15 -6
- flyte/_hash.py +1 -16
- flyte/_image.py +6 -1
- flyte/_initialize.py +15 -16
- flyte/_internal/controllers/__init__.py +4 -5
- flyte/_internal/controllers/_local_controller.py +5 -5
- flyte/_internal/controllers/remote/_controller.py +21 -28
- flyte/_internal/controllers/remote/_core.py +1 -1
- flyte/_internal/imagebuild/docker_builder.py +31 -23
- flyte/_internal/imagebuild/remote_builder.py +37 -10
- flyte/_internal/imagebuild/utils.py +2 -1
- flyte/_internal/runtime/convert.py +69 -2
- flyte/_internal/runtime/taskrunner.py +4 -1
- flyte/_logging.py +110 -26
- flyte/_map.py +90 -12
- flyte/_pod.py +2 -1
- flyte/_run.py +6 -1
- flyte/_task.py +3 -0
- flyte/_task_environment.py +5 -1
- flyte/_trace.py +5 -0
- flyte/_version.py +3 -3
- flyte/cli/_create.py +4 -1
- flyte/cli/_deploy.py +4 -5
- flyte/cli/_params.py +18 -4
- flyte/cli/_run.py +2 -2
- flyte/config/_config.py +2 -2
- flyte/config/_reader.py +14 -8
- flyte/errors.py +3 -1
- flyte/git/__init__.py +3 -0
- flyte/git/_config.py +17 -0
- flyte/io/_dataframe/basic_dfs.py +16 -7
- flyte/io/_dataframe/dataframe.py +84 -123
- flyte/io/_dir.py +35 -4
- flyte/io/_file.py +61 -15
- flyte/io/_hashing_io.py +342 -0
- flyte/models.py +12 -4
- flyte/remote/_action.py +4 -2
- flyte/remote/_task.py +52 -22
- flyte/report/_report.py +1 -1
- flyte/storage/_storage.py +16 -1
- flyte/types/_type_engine.py +1 -51
- {flyte-2.0.0b17.data → flyte-2.0.0b19.data}/scripts/runtime.py +3 -0
- {flyte-2.0.0b17.dist-info → flyte-2.0.0b19.dist-info}/METADATA +1 -1
- {flyte-2.0.0b17.dist-info → flyte-2.0.0b19.dist-info}/RECORD +52 -49
- {flyte-2.0.0b17.data → flyte-2.0.0b19.data}/scripts/debug.py +0 -0
- {flyte-2.0.0b17.dist-info → flyte-2.0.0b19.dist-info}/WHEEL +0 -0
- {flyte-2.0.0b17.dist-info → flyte-2.0.0b19.dist-info}/entry_points.txt +0 -0
- {flyte-2.0.0b17.dist-info → flyte-2.0.0b19.dist-info}/licenses/LICENSE +0 -0
- {flyte-2.0.0b17.dist-info → flyte-2.0.0b19.dist-info}/top_level.txt +0 -0
flyte/_bin/runtime.py
CHANGED
|
@@ -116,6 +116,8 @@ def main(
|
|
|
116
116
|
if name.startswith("{{"):
|
|
117
117
|
name = os.getenv("ACTION_NAME", "")
|
|
118
118
|
|
|
119
|
+
logger.warning(f"Flyte runtime started for action {name} with run name {run_name}")
|
|
120
|
+
|
|
119
121
|
if debug and name == "a0":
|
|
120
122
|
from flyte._debug.vscode import _start_vscode_server
|
|
121
123
|
|
|
@@ -168,6 +170,7 @@ def main(
|
|
|
168
170
|
await controller.stop()
|
|
169
171
|
|
|
170
172
|
asyncio.run(_run_and_stop())
|
|
173
|
+
logger.warning(f"Flyte runtime completed for action {name} with run name {run_name}")
|
|
171
174
|
|
|
172
175
|
|
|
173
176
|
if __name__ == "__main__":
|
flyte/_debug/vscode.py
CHANGED
|
@@ -8,6 +8,7 @@ import subprocess
|
|
|
8
8
|
import sys
|
|
9
9
|
import tarfile
|
|
10
10
|
import time
|
|
11
|
+
from pathlib import Path
|
|
11
12
|
from typing import List
|
|
12
13
|
|
|
13
14
|
import aiofiles
|
|
@@ -173,7 +174,7 @@ def prepare_launch_json(ctx: click.Context, pid: int):
|
|
|
173
174
|
Generate the launch.json and settings.json for users to easily launch interactive debugging and task resumption.
|
|
174
175
|
"""
|
|
175
176
|
|
|
176
|
-
virtual_venv = os.getenv("VIRTUAL_ENV")
|
|
177
|
+
virtual_venv = os.getenv("VIRTUAL_ENV", str(Path(sys.executable).parent.parent))
|
|
177
178
|
if virtual_venv is None:
|
|
178
179
|
raise RuntimeError("VIRTUAL_ENV is not found in environment variables.")
|
|
179
180
|
|
|
@@ -258,11 +259,12 @@ async def _start_vscode_server(ctx: click.Context):
|
|
|
258
259
|
await asyncio.gather(download_tgz(ctx.params["dest"], ctx.params["version"], ctx.params["tgz"]), download_vscode())
|
|
259
260
|
child_process = multiprocessing.Process(
|
|
260
261
|
target=lambda cmd: asyncio.run(asyncio.run(execute_command(cmd))),
|
|
261
|
-
kwargs={"cmd": f"code-server --bind-addr 0.0.0.0:
|
|
262
|
+
kwargs={"cmd": f"code-server --bind-addr 0.0.0.0:6060 --disable-workspace-trust --auth none {os.getcwd()}"},
|
|
262
263
|
)
|
|
263
264
|
child_process.start()
|
|
264
265
|
if child_process.pid is None:
|
|
265
266
|
raise RuntimeError("Failed to start vscode server.")
|
|
267
|
+
|
|
266
268
|
prepare_launch_json(ctx, child_process.pid)
|
|
267
269
|
|
|
268
270
|
start_time = time.time()
|
flyte/_deploy.py
CHANGED
|
@@ -153,7 +153,7 @@ async def _build_images(deployment: DeploymentPlan) -> ImageCache:
|
|
|
153
153
|
|
|
154
154
|
elif env.image == "auto" and "auto" not in image_identifier_map:
|
|
155
155
|
auto_image = Image.from_debian_base()
|
|
156
|
-
|
|
156
|
+
images.append(_build_image_bg(env_name, auto_image))
|
|
157
157
|
final_images = await asyncio.gather(*images)
|
|
158
158
|
|
|
159
159
|
for env_name, image_uri in final_images:
|
|
@@ -161,6 +161,8 @@ async def _build_images(deployment: DeploymentPlan) -> ImageCache:
|
|
|
161
161
|
env = deployment.envs[env_name]
|
|
162
162
|
if isinstance(env.image, Image):
|
|
163
163
|
image_identifier_map[env.image.identifier] = image_uri
|
|
164
|
+
elif env.image == "auto":
|
|
165
|
+
image_identifier_map["auto"] = image_uri
|
|
164
166
|
|
|
165
167
|
return ImageCache(image_lookup=image_identifier_map)
|
|
166
168
|
|
flyte/_environment.py
CHANGED
|
@@ -2,16 +2,14 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
4
|
from dataclasses import dataclass, field
|
|
5
|
-
from typing import
|
|
5
|
+
from typing import Any, Dict, List, Literal, Optional, Union
|
|
6
6
|
|
|
7
7
|
import rich.repr
|
|
8
8
|
|
|
9
9
|
from ._image import Image
|
|
10
|
+
from ._pod import PodTemplate
|
|
10
11
|
from ._resources import Resources
|
|
11
|
-
from ._secret import SecretRequest
|
|
12
|
-
|
|
13
|
-
if TYPE_CHECKING:
|
|
14
|
-
from kubernetes.client import V1PodTemplate
|
|
12
|
+
from ._secret import Secret, SecretRequest
|
|
15
13
|
|
|
16
14
|
# Global registry to track all Environment instances in load order
|
|
17
15
|
_ENVIRONMENT_REGISTRY: List[Environment] = []
|
|
@@ -44,7 +42,7 @@ class Environment:
|
|
|
44
42
|
|
|
45
43
|
name: str
|
|
46
44
|
depends_on: List[Environment] = field(default_factory=list)
|
|
47
|
-
pod_template: Optional[Union[str,
|
|
45
|
+
pod_template: Optional[Union[str, PodTemplate]] = None
|
|
48
46
|
description: Optional[str] = None
|
|
49
47
|
secrets: Optional[SecretRequest] = None
|
|
50
48
|
env_vars: Optional[Dict[str, str]] = None
|
|
@@ -54,6 +52,17 @@ class Environment:
|
|
|
54
52
|
def __post_init__(self):
|
|
55
53
|
if not is_snake_or_kebab_with_numbers(self.name):
|
|
56
54
|
raise ValueError(f"Environment name '{self.name}' must be in snake_case or kebab-case format.")
|
|
55
|
+
if not isinstance(self.image, (Image, str)):
|
|
56
|
+
raise TypeError(f"Expected image to be of type str or Image, got {type(self.image)}")
|
|
57
|
+
if self.secrets and not isinstance(self.secrets, (str, Secret, List)):
|
|
58
|
+
raise TypeError(f"Expected secrets to be of type SecretRequest, got {type(self.secrets)}")
|
|
59
|
+
for dep in self.depends_on:
|
|
60
|
+
if not isinstance(dep, Environment):
|
|
61
|
+
raise TypeError(f"Expected depends_on to be of type List[Environment], got {type(dep)}")
|
|
62
|
+
if self.resources is not None and not isinstance(self.resources, Resources):
|
|
63
|
+
raise TypeError(f"Expected resources to be of type Resources, got {type(self.resources)}")
|
|
64
|
+
if self.env_vars is not None and not isinstance(self.env_vars, dict):
|
|
65
|
+
raise TypeError(f"Expected env_vars to be of type Dict[str, str], got {type(self.env_vars)}")
|
|
57
66
|
# Automatically register this environment instance in load order
|
|
58
67
|
_ENVIRONMENT_REGISTRY.append(self)
|
|
59
68
|
|
flyte/_hash.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import TypeVar
|
|
2
2
|
|
|
3
3
|
T = TypeVar("T")
|
|
4
4
|
|
|
@@ -6,18 +6,3 @@ T = TypeVar("T")
|
|
|
6
6
|
class HashOnReferenceMixin(object):
|
|
7
7
|
def __hash__(self):
|
|
8
8
|
return hash(id(self))
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class HashMethod(Generic[T]):
|
|
12
|
-
"""
|
|
13
|
-
Flyte-specific object used to wrap the hash function for a specific type
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
def __init__(self, function: Callable[[T], str]):
|
|
17
|
-
self._function = function
|
|
18
|
-
|
|
19
|
-
def calculate(self, obj: T) -> str:
|
|
20
|
-
"""
|
|
21
|
-
Calculate hash for `obj`.
|
|
22
|
-
"""
|
|
23
|
-
return self._function(obj)
|
flyte/_image.py
CHANGED
|
@@ -887,7 +887,12 @@ class Image:
|
|
|
887
887
|
Use this method to create a new image with the specified uv.lock file layered on top of the current image
|
|
888
888
|
Must have a corresponding pyproject.toml file in the same directory
|
|
889
889
|
Cannot be used in conjunction with conda
|
|
890
|
-
|
|
890
|
+
|
|
891
|
+
By default, this method copies the entire project into the image,
|
|
892
|
+
including files such as pyproject.toml, uv.lock, and the src/ directory.
|
|
893
|
+
|
|
894
|
+
If you prefer not to install the current project, you can pass the extra argument --no-install-project.
|
|
895
|
+
In this case, the image builder will only copy pyproject.toml and uv.lock into the image.
|
|
891
896
|
|
|
892
897
|
:param pyproject_file: path to the pyproject.toml file, needs to have a corresponding uv.lock file
|
|
893
898
|
:param uvlock: path to the uv.lock file, if not specified, will use the default uv.lock file in the same
|
flyte/_initialize.py
CHANGED
|
@@ -228,7 +228,7 @@ async def init(
|
|
|
228
228
|
|
|
229
229
|
@syncify
|
|
230
230
|
async def init_from_config(
|
|
231
|
-
path_or_config: str | Config | None = None,
|
|
231
|
+
path_or_config: str | Path | Config | None = None,
|
|
232
232
|
root_dir: Path | None = None,
|
|
233
233
|
log_level: int | None = None,
|
|
234
234
|
) -> None:
|
|
@@ -248,23 +248,22 @@ async def init_from_config(
|
|
|
248
248
|
import flyte.config as config
|
|
249
249
|
|
|
250
250
|
cfg: config.Config
|
|
251
|
-
if path_or_config is None
|
|
252
|
-
# If
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
raise InitializationError(
|
|
258
|
-
"ConfigFileNotFoundError",
|
|
259
|
-
"user",
|
|
260
|
-
f"Configuration file '{path_or_config}' does not exist., current working directory is {Path.cwd()}",
|
|
261
|
-
)
|
|
262
|
-
cfg = config.auto(path_or_config)
|
|
251
|
+
if path_or_config is None:
|
|
252
|
+
# If no path is provided, use the default config file
|
|
253
|
+
cfg = config.auto()
|
|
254
|
+
elif isinstance(path_or_config, (str, Path)):
|
|
255
|
+
if root_dir:
|
|
256
|
+
cfg_path = root_dir.expanduser() / path_or_config
|
|
263
257
|
else:
|
|
264
|
-
|
|
265
|
-
|
|
258
|
+
cfg_path = Path(path_or_config).expanduser()
|
|
259
|
+
if not Path(cfg_path).exists():
|
|
260
|
+
raise InitializationError(
|
|
261
|
+
"ConfigFileNotFoundError",
|
|
262
|
+
"user",
|
|
263
|
+
f"Configuration file '{cfg_path}' does not exist., current working directory is {Path.cwd()}",
|
|
264
|
+
)
|
|
265
|
+
cfg = config.auto(cfg_path)
|
|
266
266
|
else:
|
|
267
|
-
# If a Config object is passed, use it directly
|
|
268
267
|
cfg = path_or_config
|
|
269
268
|
|
|
270
269
|
logger.debug(f"Flyte config initialized as {cfg}")
|
|
@@ -5,12 +5,13 @@ from typing import TYPE_CHECKING, Any, Callable, Literal, Optional, Protocol, Tu
|
|
|
5
5
|
from flyte._task import TaskTemplate
|
|
6
6
|
from flyte.models import ActionID, NativeInterface
|
|
7
7
|
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from flyte.remote._task import TaskDetails
|
|
10
|
+
|
|
8
11
|
from ._trace import TraceInfo
|
|
9
12
|
|
|
10
13
|
__all__ = ["Controller", "ControllerType", "TraceInfo", "create_controller", "get_controller"]
|
|
11
14
|
|
|
12
|
-
from ..._protos.workflow import task_definition_pb2
|
|
13
|
-
|
|
14
15
|
if TYPE_CHECKING:
|
|
15
16
|
import concurrent.futures
|
|
16
17
|
|
|
@@ -41,9 +42,7 @@ class Controller(Protocol):
|
|
|
41
42
|
"""
|
|
42
43
|
...
|
|
43
44
|
|
|
44
|
-
async def submit_task_ref(
|
|
45
|
-
self, _task: task_definition_pb2.TaskDetails, max_inline_io_bytes: int, *args, **kwargs
|
|
46
|
-
) -> Any:
|
|
45
|
+
async def submit_task_ref(self, _task: "TaskDetails", *args, **kwargs) -> Any:
|
|
47
46
|
"""
|
|
48
47
|
Submit a task reference to the controller asynchronously and wait for the result. This is async and will block
|
|
49
48
|
the current coroutine until the result is available.
|
|
@@ -11,10 +11,10 @@ from flyte._internal.controllers import TraceInfo
|
|
|
11
11
|
from flyte._internal.runtime import convert
|
|
12
12
|
from flyte._internal.runtime.entrypoints import direct_dispatch
|
|
13
13
|
from flyte._logging import log, logger
|
|
14
|
-
from flyte._protos.workflow import task_definition_pb2
|
|
15
14
|
from flyte._task import TaskTemplate
|
|
16
15
|
from flyte._utils.helpers import _selector_policy
|
|
17
16
|
from flyte.models import ActionID, NativeInterface
|
|
17
|
+
from flyte.remote._task import TaskDetails
|
|
18
18
|
|
|
19
19
|
R = TypeVar("R")
|
|
20
20
|
|
|
@@ -192,7 +192,7 @@ class LocalController:
|
|
|
192
192
|
assert info.start_time
|
|
193
193
|
assert info.end_time
|
|
194
194
|
|
|
195
|
-
async def submit_task_ref(
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
195
|
+
async def submit_task_ref(self, _task: TaskDetails, max_inline_io_bytes: int, *args, **kwargs) -> Any:
|
|
196
|
+
raise flyte.errors.ReferenceTaskError(
|
|
197
|
+
f"Reference tasks cannot be executed locally, only remotely. Found remote task {_task.name}"
|
|
198
|
+
)
|
|
@@ -12,7 +12,6 @@ from typing import Any, Awaitable, DefaultDict, Tuple, TypeVar
|
|
|
12
12
|
import flyte
|
|
13
13
|
import flyte.errors
|
|
14
14
|
import flyte.storage as storage
|
|
15
|
-
import flyte.types as types
|
|
16
15
|
from flyte._code_bundle import build_pkl_bundle
|
|
17
16
|
from flyte._context import internal_ctx
|
|
18
17
|
from flyte._internal.controllers import TraceInfo
|
|
@@ -24,10 +23,11 @@ from flyte._internal.runtime.task_serde import translate_task_to_wire
|
|
|
24
23
|
from flyte._internal.runtime.types_serde import transform_native_to_typed_interface
|
|
25
24
|
from flyte._logging import logger
|
|
26
25
|
from flyte._protos.common import identifier_pb2
|
|
27
|
-
from flyte._protos.workflow import run_definition_pb2
|
|
26
|
+
from flyte._protos.workflow import run_definition_pb2
|
|
28
27
|
from flyte._task import TaskTemplate
|
|
29
28
|
from flyte._utils.helpers import _selector_policy
|
|
30
29
|
from flyte.models import MAX_INLINE_IO_BYTES, ActionID, NativeInterface, SerializationContext
|
|
30
|
+
from flyte.remote._task import TaskDetails
|
|
31
31
|
|
|
32
32
|
R = TypeVar("R")
|
|
33
33
|
|
|
@@ -413,8 +413,7 @@ class RemoteController(Controller):
|
|
|
413
413
|
else:
|
|
414
414
|
logger.warning(f"Action {prev_action.action_id.name} failed, but no error was found, re-running trace!")
|
|
415
415
|
elif prev_action.realized_outputs_uri is not None:
|
|
416
|
-
|
|
417
|
-
o = await io.load_outputs(outputs_file_path, max_bytes=MAX_TRACE_BYTES)
|
|
416
|
+
o = await io.load_outputs(prev_action.realized_outputs_uri, max_bytes=MAX_TRACE_BYTES)
|
|
418
417
|
outputs = await convert.convert_outputs_to_native(_interface, o)
|
|
419
418
|
return (
|
|
420
419
|
TraceInfo(func_name, sub_action_id, _interface, inputs_uri, output=outputs),
|
|
@@ -439,15 +438,13 @@ class RemoteController(Controller):
|
|
|
439
438
|
outputs_file_path: str = ""
|
|
440
439
|
|
|
441
440
|
if info.interface.has_outputs():
|
|
442
|
-
if info.
|
|
443
|
-
outputs = await convert.convert_from_native_to_outputs(info.output, info.interface)
|
|
444
|
-
outputs_file_path = io.outputs_path(sub_run_output_path)
|
|
445
|
-
await io.upload_outputs(outputs, sub_run_output_path, max_bytes=MAX_TRACE_BYTES)
|
|
446
|
-
elif info.error:
|
|
441
|
+
if info.error:
|
|
447
442
|
err = convert.convert_from_native_to_error(info.error)
|
|
448
443
|
await io.upload_error(err.err, sub_run_output_path)
|
|
449
444
|
else:
|
|
450
|
-
|
|
445
|
+
outputs = await convert.convert_from_native_to_outputs(info.output, info.interface)
|
|
446
|
+
outputs_file_path = io.outputs_path(sub_run_output_path)
|
|
447
|
+
await io.upload_outputs(outputs, sub_run_output_path, max_bytes=MAX_TRACE_BYTES)
|
|
451
448
|
|
|
452
449
|
typed_interface = transform_native_to_typed_interface(info.interface)
|
|
453
450
|
|
|
@@ -485,19 +482,17 @@ class RemoteController(Controller):
|
|
|
485
482
|
# If the action is cancelled, we need to cancel the action on the server as well
|
|
486
483
|
raise
|
|
487
484
|
|
|
488
|
-
async def _submit_task_ref(
|
|
489
|
-
self, invoke_seq_num: int, _task: task_definition_pb2.TaskDetails, max_inline_io_bytes: int, *args, **kwargs
|
|
490
|
-
) -> Any:
|
|
485
|
+
async def _submit_task_ref(self, invoke_seq_num: int, _task: TaskDetails, *args, **kwargs) -> Any:
|
|
491
486
|
ctx = internal_ctx()
|
|
492
487
|
tctx = ctx.data.task_context
|
|
493
488
|
if tctx is None:
|
|
494
489
|
raise flyte.errors.RuntimeSystemError("BadContext", "Task context not initialized")
|
|
495
490
|
current_action_id = tctx.action
|
|
496
|
-
task_name = _task.
|
|
491
|
+
task_name = _task.name
|
|
492
|
+
|
|
493
|
+
native_interface = _task.interface
|
|
494
|
+
pb_interface = _task.pb2.spec.task_template.interface
|
|
497
495
|
|
|
498
|
-
native_interface = types.guess_interface(
|
|
499
|
-
_task.spec.task_template.interface, default_inputs=_task.spec.default_inputs
|
|
500
|
-
)
|
|
501
496
|
inputs = await convert.convert_from_native_to_inputs(native_interface, *args, **kwargs)
|
|
502
497
|
inputs_hash = convert.generate_inputs_hash_from_proto(inputs.proto_inputs)
|
|
503
498
|
sub_action_id, sub_action_output_path = convert.generate_sub_action_id_and_output_path(
|
|
@@ -506,19 +501,19 @@ class RemoteController(Controller):
|
|
|
506
501
|
|
|
507
502
|
serialized_inputs = inputs.proto_inputs.SerializeToString(deterministic=True)
|
|
508
503
|
inputs_uri = io.inputs_path(sub_action_output_path)
|
|
509
|
-
await upload_inputs_with_retry(serialized_inputs, inputs_uri, max_inline_io_bytes)
|
|
504
|
+
await upload_inputs_with_retry(serialized_inputs, inputs_uri, _task.max_inline_io_bytes)
|
|
510
505
|
# cache key - task name, task signature, inputs, cache version
|
|
511
506
|
cache_key = None
|
|
512
|
-
md = _task.spec.task_template.metadata
|
|
507
|
+
md = _task.pb2.spec.task_template.metadata
|
|
513
508
|
ignored_input_vars = []
|
|
514
509
|
if len(md.cache_ignore_input_vars) > 0:
|
|
515
510
|
ignored_input_vars = list(md.cache_ignore_input_vars)
|
|
516
|
-
if
|
|
517
|
-
discovery_version =
|
|
511
|
+
if md and md.discoverable:
|
|
512
|
+
discovery_version = md.discovery_version
|
|
518
513
|
cache_key = convert.generate_cache_key_hash(
|
|
519
514
|
task_name,
|
|
520
515
|
inputs_hash,
|
|
521
|
-
|
|
516
|
+
pb_interface,
|
|
522
517
|
discovery_version,
|
|
523
518
|
ignored_input_vars,
|
|
524
519
|
inputs.proto_inputs,
|
|
@@ -540,7 +535,7 @@ class RemoteController(Controller):
|
|
|
540
535
|
),
|
|
541
536
|
parent_action_name=current_action_id.name,
|
|
542
537
|
group_data=tctx.group_data,
|
|
543
|
-
task_spec=_task.spec,
|
|
538
|
+
task_spec=_task.pb2.spec,
|
|
544
539
|
inputs_uri=inputs_uri,
|
|
545
540
|
run_output_base=tctx.run_base_dir,
|
|
546
541
|
cache_key=cache_key,
|
|
@@ -569,12 +564,10 @@ class RemoteController(Controller):
|
|
|
569
564
|
"RuntimeError",
|
|
570
565
|
f"Task {n.action_id.name} did not return an output path, but the task has outputs defined.",
|
|
571
566
|
)
|
|
572
|
-
return await load_and_convert_outputs(native_interface, n.realized_outputs_uri, max_inline_io_bytes)
|
|
567
|
+
return await load_and_convert_outputs(native_interface, n.realized_outputs_uri, _task.max_inline_io_bytes)
|
|
573
568
|
return None
|
|
574
569
|
|
|
575
|
-
async def submit_task_ref(
|
|
576
|
-
self, _task: task_definition_pb2.TaskDetails, max_inline_io_bytes: int, *args, **kwargs
|
|
577
|
-
) -> Any:
|
|
570
|
+
async def submit_task_ref(self, _task: TaskDetails, *args, **kwargs) -> Any:
|
|
578
571
|
ctx = internal_ctx()
|
|
579
572
|
tctx = ctx.data.task_context
|
|
580
573
|
if tctx is None:
|
|
@@ -582,4 +575,4 @@ class RemoteController(Controller):
|
|
|
582
575
|
current_action_id = tctx.action
|
|
583
576
|
task_call_seq = self.generate_task_call_sequence(_task, current_action_id)
|
|
584
577
|
async with self._parent_action_semaphore[unique_action_name(current_action_id)]:
|
|
585
|
-
return await self._submit_task_ref(task_call_seq, _task,
|
|
578
|
+
return await self._submit_task_ref(task_call_seq, _task, *args, **kwargs)
|
|
@@ -158,8 +158,8 @@ class Controller:
|
|
|
158
158
|
self._thread.start()
|
|
159
159
|
|
|
160
160
|
# Wait for the thread to be ready
|
|
161
|
-
logger.info("Waiting for controller thread to be ready...")
|
|
162
161
|
if not self._thread_ready.wait(timeout=self._thread_wait_timeout):
|
|
162
|
+
logger.warning("Controller thread did not finish within timeout")
|
|
163
163
|
raise TimeoutError("Controller thread failed to start in time")
|
|
164
164
|
|
|
165
165
|
if self._get_exception():
|
|
@@ -44,19 +44,19 @@ _F_IMG_ID = "_F_IMG_ID"
|
|
|
44
44
|
FLYTE_DOCKER_BUILDER_CACHE_FROM = "FLYTE_DOCKER_BUILDER_CACHE_FROM"
|
|
45
45
|
FLYTE_DOCKER_BUILDER_CACHE_TO = "FLYTE_DOCKER_BUILDER_CACHE_TO"
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
WORKDIR /root
|
|
47
|
+
UV_LOCK_WITHOUT_PROJECT_INSTALL_TEMPLATE = Template("""\
|
|
49
48
|
RUN --mount=type=cache,sharing=locked,mode=0777,target=/root/.cache/uv,id=uv \
|
|
50
|
-
--mount=type=bind,target=uv.lock,src
|
|
51
|
-
--mount=type=bind,target=pyproject.toml,src
|
|
49
|
+
--mount=type=bind,target=uv.lock,src=$UV_LOCK_PATH \
|
|
50
|
+
--mount=type=bind,target=pyproject.toml,src=$PYPROJECT_PATH \
|
|
52
51
|
$SECRET_MOUNT \
|
|
53
|
-
uv sync $PIP_INSTALL_ARGS
|
|
54
|
-
|
|
52
|
+
uv sync --active $PIP_INSTALL_ARGS
|
|
53
|
+
""")
|
|
55
54
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
UV_LOCK_INSTALL_TEMPLATE = Template("""\
|
|
56
|
+
COPY $PYPROJECT_PATH $PYPROJECT_PATH
|
|
57
|
+
RUN --mount=type=cache,sharing=locked,mode=0777,target=/root/.cache/uv,id=uv \
|
|
58
|
+
$SECRET_MOUNT \
|
|
59
|
+
uv sync --active $PIP_INSTALL_ARGS --project $PYPROJECT_PATH
|
|
60
60
|
""")
|
|
61
61
|
|
|
62
62
|
UV_PACKAGE_INSTALL_COMMAND_TEMPLATE = Template("""\
|
|
@@ -90,7 +90,7 @@ RUN --mount=type=cache,sharing=locked,mode=0777,target=/root/.cache/uv,id=uv \
|
|
|
90
90
|
# new template
|
|
91
91
|
DOCKER_FILE_UV_BASE_TEMPLATE = Template("""\
|
|
92
92
|
# syntax=docker/dockerfile:1.10
|
|
93
|
-
FROM ghcr.io/astral-sh/uv:0.
|
|
93
|
+
FROM ghcr.io/astral-sh/uv:0.8.13 AS uv
|
|
94
94
|
FROM $BASE_IMAGE
|
|
95
95
|
|
|
96
96
|
USER root
|
|
@@ -177,6 +177,7 @@ class PythonWheelHandler:
|
|
|
177
177
|
"/dist",
|
|
178
178
|
"--no-deps",
|
|
179
179
|
"--no-index",
|
|
180
|
+
"--reinstall",
|
|
180
181
|
layer.package_name,
|
|
181
182
|
],
|
|
182
183
|
]
|
|
@@ -230,20 +231,27 @@ class AptPackagesHandler:
|
|
|
230
231
|
class UVProjectHandler:
|
|
231
232
|
@staticmethod
|
|
232
233
|
async def handle(layer: UVProject, context_path: Path, dockerfile: str) -> str:
|
|
233
|
-
# copy the two files
|
|
234
|
-
shutil.copy(layer.pyproject, context_path)
|
|
235
|
-
shutil.copy(layer.uvlock, context_path)
|
|
236
|
-
|
|
237
|
-
# --locked: Assert that the `uv.lock` will remain unchanged
|
|
238
|
-
# --no-dev: Omit the development dependency group
|
|
239
|
-
# --no-install-project: Do not install the current project
|
|
240
|
-
additional_pip_install_args = ["--locked", "--no-dev", "--no-install-project"]
|
|
241
234
|
secret_mounts = _get_secret_mounts_layer(layer.secret_mounts)
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
235
|
+
if layer.extra_index_urls and "--no-install-project" in layer.extra_index_urls:
|
|
236
|
+
# Only Copy pyproject.yaml and uv.lock.
|
|
237
|
+
pyproject_dst = copy_files_to_context(layer.pyproject, context_path)
|
|
238
|
+
uvlock_dst = copy_files_to_context(layer.uvlock, context_path)
|
|
239
|
+
delta = UV_LOCK_WITHOUT_PROJECT_INSTALL_TEMPLATE.substitute(
|
|
240
|
+
UV_LOCK_PATH=uvlock_dst.relative_to(context_path),
|
|
241
|
+
PYPROJECT_PATH=pyproject_dst.relative_to(context_path),
|
|
242
|
+
PIP_INSTALL_ARGS=" ".join(layer.get_pip_install_args()),
|
|
243
|
+
SECRET_MOUNT=secret_mounts,
|
|
244
|
+
)
|
|
245
|
+
else:
|
|
246
|
+
# Copy the entire project.
|
|
247
|
+
pyproject_dst = copy_files_to_context(layer.pyproject.parent, context_path)
|
|
248
|
+
delta = UV_LOCK_INSTALL_TEMPLATE.substitute(
|
|
249
|
+
PYPROJECT_PATH=pyproject_dst.relative_to(context_path),
|
|
250
|
+
PIP_INSTALL_ARGS=" ".join(layer.get_pip_install_args()),
|
|
251
|
+
SECRET_MOUNT=secret_mounts,
|
|
252
|
+
)
|
|
246
253
|
|
|
254
|
+
dockerfile += delta
|
|
247
255
|
return dockerfile
|
|
248
256
|
|
|
249
257
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import gzip
|
|
1
2
|
import os
|
|
2
3
|
import shutil
|
|
4
|
+
import tarfile
|
|
3
5
|
import tempfile
|
|
4
6
|
import typing
|
|
5
7
|
from datetime import datetime, timezone
|
|
@@ -7,11 +9,13 @@ from pathlib import Path
|
|
|
7
9
|
from typing import TYPE_CHECKING, Optional, Tuple, cast
|
|
8
10
|
from uuid import uuid4
|
|
9
11
|
|
|
12
|
+
import aiofiles
|
|
10
13
|
import click
|
|
11
14
|
|
|
12
15
|
import flyte
|
|
13
16
|
import flyte.errors
|
|
14
17
|
from flyte import Image, remote
|
|
18
|
+
from flyte._code_bundle._utils import tar_strip_file_attributes
|
|
15
19
|
from flyte._image import (
|
|
16
20
|
AptPackages,
|
|
17
21
|
Architecture,
|
|
@@ -130,7 +134,7 @@ class RemoteImageBuilder(ImageBuilder):
|
|
|
130
134
|
auto_version="latest",
|
|
131
135
|
)
|
|
132
136
|
await flyte.with_runcontext(project=IMAGE_TASK_PROJECT, domain=IMAGE_TASK_DOMAIN).run.aio(
|
|
133
|
-
entity,
|
|
137
|
+
entity, target_image=image_name
|
|
134
138
|
)
|
|
135
139
|
except Exception as e:
|
|
136
140
|
# Ignore the error if optimize is not enabled in the backend.
|
|
@@ -160,17 +164,32 @@ async def _validate_configuration(image: Image) -> Tuple[str, Optional[str]]:
|
|
|
160
164
|
|
|
161
165
|
if any(context_path.iterdir()):
|
|
162
166
|
# If there are files in the context directory, upload it
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
167
|
+
tar_path = tmp_path / "context.tar"
|
|
168
|
+
with tarfile.open(tar_path, "w", dereference=False) as tar:
|
|
169
|
+
files: typing.List[str] = os.listdir(context_path)
|
|
170
|
+
for ws_file in files:
|
|
171
|
+
tar.add(
|
|
172
|
+
os.path.join(context_path, ws_file),
|
|
173
|
+
recursive=True,
|
|
174
|
+
arcname=ws_file,
|
|
175
|
+
filter=tar_strip_file_attributes,
|
|
176
|
+
)
|
|
177
|
+
context_dst = Path(f"{tar_path!s}.gz")
|
|
178
|
+
with gzip.GzipFile(filename=context_dst, mode="wb", mtime=0) as gzipped:
|
|
179
|
+
async with aiofiles.open(tar_path, "rb") as tar_file:
|
|
180
|
+
content = await tar_file.read()
|
|
181
|
+
gzipped.write(content)
|
|
182
|
+
|
|
183
|
+
context_size = tar_path.stat().st_size
|
|
184
|
+
if context_size > 5 * 1024 * 1024:
|
|
166
185
|
logger.warning(
|
|
167
186
|
click.style(
|
|
168
|
-
f"Context size is {
|
|
187
|
+
f"Context size is {context_size / (1024 * 1024):.2f} MB, which is larger than 5 MB. "
|
|
169
188
|
"Upload and build speed will be impacted.",
|
|
170
189
|
fg="yellow",
|
|
171
190
|
)
|
|
172
191
|
)
|
|
173
|
-
_, context_url = await remote.upload_file.aio(
|
|
192
|
+
_, context_url = await remote.upload_file.aio(context_dst)
|
|
174
193
|
else:
|
|
175
194
|
context_url = ""
|
|
176
195
|
|
|
@@ -248,12 +267,20 @@ def _get_layers_proto(image: Image, context_path: Path) -> "image_definition_pb2
|
|
|
248
267
|
for line in layer.pyproject.read_text().splitlines():
|
|
249
268
|
if "tool.uv.index" in line:
|
|
250
269
|
raise ValueError("External sources are not supported in pyproject.toml")
|
|
251
|
-
|
|
270
|
+
|
|
271
|
+
if layer.extra_index_urls and "--no-install-project" in layer.extra_index_urls:
|
|
272
|
+
# Copy pyproject itself
|
|
273
|
+
pyproject_dst = copy_files_to_context(layer.pyproject, context_path)
|
|
274
|
+
else:
|
|
275
|
+
# Copy the entire project
|
|
276
|
+
pyproject_dst = copy_files_to_context(layer.pyproject.parent, context_path)
|
|
252
277
|
|
|
253
278
|
uv_layer = image_definition_pb2.Layer(
|
|
254
279
|
uv_project=image_definition_pb2.UVProject(
|
|
255
|
-
pyproject=str(
|
|
256
|
-
uvlock=str(layer.uvlock.
|
|
280
|
+
pyproject=str(pyproject_dst.relative_to(context_path)),
|
|
281
|
+
uvlock=str(copy_files_to_context(layer.uvlock, context_path).relative_to(context_path)),
|
|
282
|
+
options=pip_options,
|
|
283
|
+
secret_mounts=secret_mounts,
|
|
257
284
|
)
|
|
258
285
|
)
|
|
259
286
|
layers.append(uv_layer)
|
|
@@ -303,7 +330,7 @@ def _get_fully_qualified_image_name(outputs: ActionOutputs) -> str:
|
|
|
303
330
|
|
|
304
331
|
def _get_build_secrets_from_image(image: Image) -> Optional[typing.List[Secret]]:
|
|
305
332
|
secrets = []
|
|
306
|
-
DEFAULT_SECRET_DIR = Path("etc/flyte/secrets")
|
|
333
|
+
DEFAULT_SECRET_DIR = Path("/etc/flyte/secrets")
|
|
307
334
|
for layer in image._layers:
|
|
308
335
|
if isinstance(layer, (PipOption, Commands, AptPackages)) and layer.secret_mounts is not None:
|
|
309
336
|
for secret_mount in layer.secret_mounts:
|
|
@@ -23,7 +23,8 @@ def copy_files_to_context(src: Path, context_path: Path) -> Path:
|
|
|
23
23
|
dst_path = context_path / src
|
|
24
24
|
dst_path.parent.mkdir(parents=True, exist_ok=True)
|
|
25
25
|
if src.is_dir():
|
|
26
|
-
|
|
26
|
+
# TODO: Add support dockerignore
|
|
27
|
+
shutil.copytree(src, dst_path, dirs_exist_ok=True, ignore=shutil.ignore_patterns(".idea", ".venv"))
|
|
27
28
|
else:
|
|
28
29
|
shutil.copy(src, dst_path)
|
|
29
30
|
return dst_path
|