flyte 2.0.0b2__py3-none-any.whl → 2.0.0b4__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/_image.py +54 -13
- flyte/_internal/controllers/__init__.py +3 -1
- flyte/_internal/controllers/_local_controller.py +3 -1
- flyte/_internal/controllers/remote/_controller.py +53 -32
- flyte/_internal/controllers/remote/_core.py +1 -4
- flyte/_internal/imagebuild/docker_builder.py +78 -12
- flyte/_internal/runtime/io.py +48 -9
- flyte/_secret.py +3 -2
- flyte/_task.py +8 -2
- flyte/_task_environment.py +5 -1
- flyte/_version.py +2 -2
- flyte/errors.py +10 -0
- flyte/extras/_container.py +3 -1
- flyte/models.py +3 -0
- flyte/remote/_task.py +3 -1
- flyte/report/_report.py +5 -1
- {flyte-2.0.0b2.dist-info → flyte-2.0.0b4.dist-info}/METADATA +3 -3
- {flyte-2.0.0b2.dist-info → flyte-2.0.0b4.dist-info}/RECORD +23 -23
- {flyte-2.0.0b2.data → flyte-2.0.0b4.data}/scripts/runtime.py +0 -0
- {flyte-2.0.0b2.dist-info → flyte-2.0.0b4.dist-info}/WHEEL +0 -0
- {flyte-2.0.0b2.dist-info → flyte-2.0.0b4.dist-info}/entry_points.txt +0 -0
- {flyte-2.0.0b2.dist-info → flyte-2.0.0b4.dist-info}/licenses/LICENSE +0 -0
- {flyte-2.0.0b2.dist-info → flyte-2.0.0b4.dist-info}/top_level.txt +0 -0
flyte/_image.py
CHANGED
|
@@ -8,11 +8,14 @@ from abc import abstractmethod
|
|
|
8
8
|
from dataclasses import asdict, dataclass, field
|
|
9
9
|
from functools import cached_property
|
|
10
10
|
from pathlib import Path
|
|
11
|
-
from typing import Callable, ClassVar, Dict, List, Literal, Optional, Tuple, TypeVar, Union
|
|
11
|
+
from typing import TYPE_CHECKING, Callable, ClassVar, Dict, List, Literal, Optional, Tuple, TypeVar, Union
|
|
12
12
|
|
|
13
13
|
import rich.repr
|
|
14
14
|
from packaging.version import Version
|
|
15
15
|
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from flyte import Secret, SecretRequest
|
|
18
|
+
|
|
16
19
|
# Supported Python versions
|
|
17
20
|
PYTHON_3_10 = (3, 10)
|
|
18
21
|
PYTHON_3_11 = (3, 11)
|
|
@@ -71,6 +74,7 @@ class PipOption:
|
|
|
71
74
|
extra_index_urls: Optional[Tuple[str] | Tuple[str, ...] | List[str]] = None
|
|
72
75
|
pre: bool = False
|
|
73
76
|
extra_args: Optional[str] = None
|
|
77
|
+
secret_mounts: Optional[Tuple[str | Secret, ...]] = None
|
|
74
78
|
|
|
75
79
|
def get_pip_install_args(self) -> List[str]:
|
|
76
80
|
pip_install_args = []
|
|
@@ -101,6 +105,9 @@ class PipOption:
|
|
|
101
105
|
hash_input += str(self.pre)
|
|
102
106
|
if self.extra_args:
|
|
103
107
|
hash_input += self.extra_args
|
|
108
|
+
if self.secret_mounts:
|
|
109
|
+
for secret_mount in self.secret_mounts:
|
|
110
|
+
hash_input += str(secret_mount)
|
|
104
111
|
|
|
105
112
|
hasher.update(hash_input.encode("utf-8"))
|
|
106
113
|
|
|
@@ -110,9 +117,6 @@ class PipOption:
|
|
|
110
117
|
class PipPackages(PipOption, Layer):
|
|
111
118
|
packages: Optional[Tuple[str, ...]] = None
|
|
112
119
|
|
|
113
|
-
# todo: to be implemented
|
|
114
|
-
# secret_mounts: Optional[List[Tuple[str, str]]] = None
|
|
115
|
-
|
|
116
120
|
def update_hash(self, hasher: hashlib._Hash):
|
|
117
121
|
"""
|
|
118
122
|
Update the hash with the pip packages
|
|
@@ -172,9 +176,15 @@ class UVProject(PipOption, Layer):
|
|
|
172
176
|
@dataclass(frozen=True, repr=True)
|
|
173
177
|
class AptPackages(Layer):
|
|
174
178
|
packages: Tuple[str, ...]
|
|
179
|
+
secret_mounts: Optional[Tuple[str | Secret, ...]] = None
|
|
175
180
|
|
|
176
181
|
def update_hash(self, hasher: hashlib._Hash):
|
|
177
|
-
|
|
182
|
+
hash_input = "".join(self.packages)
|
|
183
|
+
|
|
184
|
+
if self.secret_mounts:
|
|
185
|
+
for secret_mount in self.secret_mounts:
|
|
186
|
+
hash_input += str(secret_mount)
|
|
187
|
+
hasher.update(hash_input.encode("utf-8"))
|
|
178
188
|
|
|
179
189
|
|
|
180
190
|
@rich.repr.auto
|
|
@@ -690,19 +700,26 @@ class Image:
|
|
|
690
700
|
new_image = self.clone(addl_layer=WorkDir(workdir=workdir))
|
|
691
701
|
return new_image
|
|
692
702
|
|
|
693
|
-
def with_requirements(
|
|
703
|
+
def with_requirements(
|
|
704
|
+
self,
|
|
705
|
+
file: str | Path,
|
|
706
|
+
secret_mounts: Optional[SecretRequest] = None,
|
|
707
|
+
) -> Image:
|
|
694
708
|
"""
|
|
695
709
|
Use this method to create a new image with the specified requirements file layered on top of the current image
|
|
696
710
|
Cannot be used in conjunction with conda
|
|
697
711
|
|
|
698
712
|
:param file: path to the requirements file, must be a .txt file
|
|
713
|
+
:param secret_mounts: list of secret to mount for the build process.
|
|
699
714
|
:return:
|
|
700
715
|
"""
|
|
701
716
|
if isinstance(file, str):
|
|
702
717
|
file = Path(file)
|
|
703
718
|
if file.suffix != ".txt":
|
|
704
719
|
raise ValueError(f"Requirements file {file} must have a .txt extension")
|
|
705
|
-
new_image = self.clone(
|
|
720
|
+
new_image = self.clone(
|
|
721
|
+
addl_layer=Requirements(file=file, secret_mounts=_ensure_tuple(secret_mounts) if secret_mounts else None)
|
|
722
|
+
)
|
|
706
723
|
return new_image
|
|
707
724
|
|
|
708
725
|
def with_pip_packages(
|
|
@@ -712,6 +729,7 @@ class Image:
|
|
|
712
729
|
extra_index_urls: Union[str, List[str], Tuple[str, ...], None] = None,
|
|
713
730
|
pre: bool = False,
|
|
714
731
|
extra_args: Optional[str] = None,
|
|
732
|
+
secret_mounts: Optional[SecretRequest] = None,
|
|
715
733
|
) -> Image:
|
|
716
734
|
"""
|
|
717
735
|
Use this method to create a new image with the specified pip packages layered on top of the current image
|
|
@@ -732,8 +750,8 @@ class Image:
|
|
|
732
750
|
:param extra_index_urls: extra index urls to use for pip install, default is None
|
|
733
751
|
:param pre: whether to allow pre-release versions, default is False
|
|
734
752
|
:param extra_args: extra arguments to pass to pip install, default is None
|
|
735
|
-
# :param secret_mounts: todo
|
|
736
753
|
:param extra_args: extra arguments to pass to pip install, default is None
|
|
754
|
+
:param secret_mounts: list of secret to mount for the build process.
|
|
737
755
|
:return: Image
|
|
738
756
|
"""
|
|
739
757
|
new_packages: Optional[Tuple] = packages or None
|
|
@@ -745,6 +763,7 @@ class Image:
|
|
|
745
763
|
extra_index_urls=new_extra_index_urls,
|
|
746
764
|
pre=pre,
|
|
747
765
|
extra_args=extra_args,
|
|
766
|
+
secret_mounts=_ensure_tuple(secret_mounts) if secret_mounts else None,
|
|
748
767
|
)
|
|
749
768
|
new_image = self.clone(addl_layer=ll)
|
|
750
769
|
return new_image
|
|
@@ -792,9 +811,10 @@ class Image:
|
|
|
792
811
|
self,
|
|
793
812
|
pyproject_file: Path,
|
|
794
813
|
index_url: Optional[str] = None,
|
|
795
|
-
extra_index_urls: Union[
|
|
814
|
+
extra_index_urls: Union[List[str], Tuple[str, ...], None] = None,
|
|
796
815
|
pre: bool = False,
|
|
797
816
|
extra_args: Optional[str] = None,
|
|
817
|
+
secret_mounts: Optional[SecretRequest] = None,
|
|
798
818
|
) -> Image:
|
|
799
819
|
"""
|
|
800
820
|
Use this method to create a new image with the specified uv.lock file layered on top of the current image
|
|
@@ -803,7 +823,12 @@ class Image:
|
|
|
803
823
|
In the Union builders, using this will change the virtual env to /root/.venv
|
|
804
824
|
|
|
805
825
|
:param pyproject_file: path to the pyproject.toml file, needs to have a corresponding uv.lock file
|
|
806
|
-
:
|
|
826
|
+
:param index_url: index url to use for pip install, default is None
|
|
827
|
+
:param extra_index_urls: extra index urls to use for pip install, default is None
|
|
828
|
+
:param pre: whether to allow pre-release versions, default is False
|
|
829
|
+
:param extra_args: extra arguments to pass to pip install, default is None
|
|
830
|
+
:param secret_mounts: list of secret mounts to use for the build process.
|
|
831
|
+
:return: Image
|
|
807
832
|
"""
|
|
808
833
|
if not pyproject_file.exists():
|
|
809
834
|
raise FileNotFoundError(f"UVLock file {pyproject_file} does not exist")
|
|
@@ -812,17 +837,33 @@ class Image:
|
|
|
812
837
|
lock = pyproject_file.parent / "uv.lock"
|
|
813
838
|
if not lock.exists():
|
|
814
839
|
raise ValueError(f"UVLock file {lock} does not exist")
|
|
815
|
-
new_image = self.clone(
|
|
840
|
+
new_image = self.clone(
|
|
841
|
+
addl_layer=UVProject(
|
|
842
|
+
pyproject=pyproject_file,
|
|
843
|
+
uvlock=lock,
|
|
844
|
+
index_url=index_url,
|
|
845
|
+
extra_index_urls=extra_index_urls,
|
|
846
|
+
pre=pre,
|
|
847
|
+
extra_args=extra_args,
|
|
848
|
+
secret_mounts=_ensure_tuple(secret_mounts) if secret_mounts else None,
|
|
849
|
+
)
|
|
850
|
+
)
|
|
816
851
|
return new_image
|
|
817
852
|
|
|
818
|
-
def with_apt_packages(self, *packages: str) -> Image:
|
|
853
|
+
def with_apt_packages(self, *packages: str, secret_mounts: Optional[SecretRequest] = None) -> Image:
|
|
819
854
|
"""
|
|
820
855
|
Use this method to create a new image with the specified apt packages layered on top of the current image
|
|
821
856
|
|
|
822
857
|
:param packages: list of apt packages to install
|
|
858
|
+
:param secret_mounts: list of secret mounts to use for the build process.
|
|
823
859
|
:return: Image
|
|
824
860
|
"""
|
|
825
|
-
new_image = self.clone(
|
|
861
|
+
new_image = self.clone(
|
|
862
|
+
addl_layer=AptPackages(
|
|
863
|
+
packages=packages,
|
|
864
|
+
secret_mounts=_ensure_tuple(secret_mounts) if secret_mounts else None,
|
|
865
|
+
)
|
|
866
|
+
)
|
|
826
867
|
return new_image
|
|
827
868
|
|
|
828
869
|
def with_commands(self, commands: List[str]) -> Image:
|
|
@@ -41,7 +41,9 @@ class Controller(Protocol):
|
|
|
41
41
|
"""
|
|
42
42
|
...
|
|
43
43
|
|
|
44
|
-
async def submit_task_ref(
|
|
44
|
+
async def submit_task_ref(
|
|
45
|
+
self, _task: task_definition_pb2.TaskDetails, max_inline_io_bytes: int, *args, **kwargs
|
|
46
|
+
) -> Any:
|
|
45
47
|
"""
|
|
46
48
|
Submit a task reference to the controller asynchronously and wait for the result. This is async and will block
|
|
47
49
|
the current coroutine until the result is available.
|
|
@@ -192,5 +192,7 @@ class LocalController:
|
|
|
192
192
|
assert info.start_time
|
|
193
193
|
assert info.end_time
|
|
194
194
|
|
|
195
|
-
async def submit_task_ref(
|
|
195
|
+
async def submit_task_ref(
|
|
196
|
+
self, _task: task_definition_pb2.TaskDetails, max_inline_io_bytes: int, *args, **kwargs
|
|
197
|
+
) -> Any:
|
|
196
198
|
raise flyte.errors.ReferenceTaskError("Reference tasks cannot be executed locally, only remotely.")
|
|
@@ -7,7 +7,7 @@ import threading
|
|
|
7
7
|
from collections import defaultdict
|
|
8
8
|
from collections.abc import Callable
|
|
9
9
|
from pathlib import Path
|
|
10
|
-
from typing import Any,
|
|
10
|
+
from typing import Any, Awaitable, DefaultDict, Tuple, TypeVar
|
|
11
11
|
|
|
12
12
|
import flyte
|
|
13
13
|
import flyte.errors
|
|
@@ -27,22 +27,30 @@ from flyte._protos.common import identifier_pb2
|
|
|
27
27
|
from flyte._protos.workflow import run_definition_pb2, task_definition_pb2
|
|
28
28
|
from flyte._task import TaskTemplate
|
|
29
29
|
from flyte._utils.helpers import _selector_policy
|
|
30
|
-
from flyte.models import ActionID, NativeInterface, SerializationContext
|
|
30
|
+
from flyte.models import MAX_INLINE_IO_BYTES, ActionID, NativeInterface, SerializationContext
|
|
31
31
|
|
|
32
32
|
R = TypeVar("R")
|
|
33
33
|
|
|
34
|
+
MAX_TRACE_BYTES = MAX_INLINE_IO_BYTES
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
|
|
37
|
+
async def upload_inputs_with_retry(serialized_inputs: bytes, inputs_uri: str, max_bytes: int) -> None:
|
|
36
38
|
"""
|
|
37
39
|
Upload inputs to the specified URI with error handling.
|
|
38
40
|
|
|
39
41
|
Args:
|
|
40
42
|
serialized_inputs: The serialized inputs to upload
|
|
41
43
|
inputs_uri: The destination URI
|
|
44
|
+
max_bytes: Maximum number of bytes to read from the input stream
|
|
42
45
|
|
|
43
46
|
Raises:
|
|
44
47
|
RuntimeSystemError: If the upload fails
|
|
45
48
|
"""
|
|
49
|
+
if len(serialized_inputs) > max_bytes:
|
|
50
|
+
raise flyte.errors.InlineIOMaxBytesBreached(
|
|
51
|
+
f"Inputs exceed max_bytes limit of {max_bytes / 1024 / 1024} MB,"
|
|
52
|
+
f" actual size: {len(serialized_inputs) / 1024 / 1024} MB"
|
|
53
|
+
)
|
|
46
54
|
try:
|
|
47
55
|
# TODO Add retry decorator to this
|
|
48
56
|
await storage.put_stream(serialized_inputs, to_path=inputs_uri)
|
|
@@ -80,19 +88,20 @@ async def handle_action_failure(action: Action, task_name: str) -> Exception:
|
|
|
80
88
|
return exc
|
|
81
89
|
|
|
82
90
|
|
|
83
|
-
async def load_and_convert_outputs(iface: NativeInterface, realized_outputs_uri: str) -> Any:
|
|
91
|
+
async def load_and_convert_outputs(iface: NativeInterface, realized_outputs_uri: str, max_bytes: int) -> Any:
|
|
84
92
|
"""
|
|
85
93
|
Load outputs from the given URI and convert them to native format.
|
|
86
94
|
|
|
87
95
|
Args:
|
|
88
96
|
iface: The Native interface
|
|
89
97
|
realized_outputs_uri: The URI where outputs are stored
|
|
98
|
+
max_bytes: Maximum number of bytes to read from the output file
|
|
90
99
|
|
|
91
100
|
Returns:
|
|
92
101
|
The converted native outputs
|
|
93
102
|
"""
|
|
94
103
|
outputs_file_path = io.outputs_path(realized_outputs_uri)
|
|
95
|
-
outputs = await io.load_outputs(outputs_file_path)
|
|
104
|
+
outputs = await io.load_outputs(outputs_file_path, max_bytes=max_bytes)
|
|
96
105
|
return await convert.convert_outputs_to_native(iface, outputs)
|
|
97
106
|
|
|
98
107
|
|
|
@@ -144,7 +153,7 @@ class RemoteController(Controller):
|
|
|
144
153
|
name = task_obj.__name__
|
|
145
154
|
elif hasattr(task_obj, "name"):
|
|
146
155
|
name = task_obj.name
|
|
147
|
-
logger.
|
|
156
|
+
logger.info(f"For action {uniq}, task {name} call sequence is {new_seq}")
|
|
148
157
|
return new_seq
|
|
149
158
|
|
|
150
159
|
async def _submit(self, _task_call_seq: int, _task: TaskTemplate, *args, **kwargs) -> Any:
|
|
@@ -187,11 +196,11 @@ class RemoteController(Controller):
|
|
|
187
196
|
sub_action_id, sub_action_output_path = convert.generate_sub_action_id_and_output_path(
|
|
188
197
|
tctx, task_spec, inputs_hash, _task_call_seq
|
|
189
198
|
)
|
|
190
|
-
logger.
|
|
199
|
+
logger.info(f"Sub action {sub_action_id} output path {sub_action_output_path}")
|
|
191
200
|
|
|
192
201
|
serialized_inputs = inputs.proto_inputs.SerializeToString(deterministic=True)
|
|
193
202
|
inputs_uri = io.inputs_path(sub_action_output_path)
|
|
194
|
-
await upload_inputs_with_retry(serialized_inputs, inputs_uri)
|
|
203
|
+
await upload_inputs_with_retry(serialized_inputs, inputs_uri, max_bytes=_task.max_inline_io_bytes)
|
|
195
204
|
|
|
196
205
|
md = task_spec.task_template.metadata
|
|
197
206
|
ignored_input_vars = []
|
|
@@ -254,7 +263,9 @@ class RemoteController(Controller):
|
|
|
254
263
|
"RuntimeError",
|
|
255
264
|
f"Task {n.action_id.name} did not return an output path, but the task has outputs defined.",
|
|
256
265
|
)
|
|
257
|
-
return await load_and_convert_outputs(
|
|
266
|
+
return await load_and_convert_outputs(
|
|
267
|
+
_task.native_interface, n.realized_outputs_uri, max_bytes=_task.max_inline_io_bytes
|
|
268
|
+
)
|
|
258
269
|
return None
|
|
259
270
|
|
|
260
271
|
async def submit(self, _task: TaskTemplate, *args, **kwargs) -> Any:
|
|
@@ -357,7 +368,7 @@ class RemoteController(Controller):
|
|
|
357
368
|
)
|
|
358
369
|
|
|
359
370
|
inputs_uri = io.inputs_path(sub_action_output_path)
|
|
360
|
-
await upload_inputs_with_retry(serialized_inputs, inputs_uri)
|
|
371
|
+
await upload_inputs_with_retry(serialized_inputs, inputs_uri, max_bytes=MAX_TRACE_BYTES)
|
|
361
372
|
# Clear to free memory
|
|
362
373
|
serialized_inputs = None # type: ignore
|
|
363
374
|
|
|
@@ -388,7 +399,7 @@ class RemoteController(Controller):
|
|
|
388
399
|
logger.warning(f"Action {prev_action.action_id.name} failed, but no error was found, re-running trace!")
|
|
389
400
|
elif prev_action.realized_outputs_uri is not None:
|
|
390
401
|
outputs_file_path = io.outputs_path(prev_action.realized_outputs_uri)
|
|
391
|
-
o = await io.load_outputs(outputs_file_path)
|
|
402
|
+
o = await io.load_outputs(outputs_file_path, max_bytes=MAX_TRACE_BYTES)
|
|
392
403
|
outputs = await convert.convert_outputs_to_native(_interface, o)
|
|
393
404
|
return (
|
|
394
405
|
TraceInfo(func_name, sub_action_id, _interface, inputs_uri, output=outputs),
|
|
@@ -410,18 +421,13 @@ class RemoteController(Controller):
|
|
|
410
421
|
|
|
411
422
|
current_action_id = tctx.action
|
|
412
423
|
sub_run_output_path = storage.join(tctx.run_base_dir, info.action.name)
|
|
413
|
-
print(f"Sub run output path for {info.name} is {sub_run_output_path}", flush=True)
|
|
414
424
|
|
|
415
425
|
if info.interface.has_outputs():
|
|
416
426
|
outputs_file_path: str = ""
|
|
417
427
|
if info.output:
|
|
418
428
|
outputs = await convert.convert_from_native_to_outputs(info.output, info.interface)
|
|
419
429
|
outputs_file_path = io.outputs_path(sub_run_output_path)
|
|
420
|
-
|
|
421
|
-
f"Uploading outputs for {info.name} Outputs file path: {outputs_file_path}",
|
|
422
|
-
flush=True,
|
|
423
|
-
)
|
|
424
|
-
await io.upload_outputs(outputs, sub_run_output_path)
|
|
430
|
+
await io.upload_outputs(outputs, sub_run_output_path, max_bytes=MAX_TRACE_BYTES)
|
|
425
431
|
elif info.error:
|
|
426
432
|
err = convert.convert_from_native_to_error(info.error)
|
|
427
433
|
await io.upload_error(err.err, sub_run_output_path)
|
|
@@ -450,18 +456,23 @@ class RemoteController(Controller):
|
|
|
450
456
|
end_time=info.end_time,
|
|
451
457
|
typed_interface=typed_interface if typed_interface else None,
|
|
452
458
|
)
|
|
453
|
-
try:
|
|
454
|
-
logger.info(
|
|
455
|
-
f"Submitting Trace action Run:[{trace_action.run_name}, Parent:[{trace_action.parent_action_name}],"
|
|
456
|
-
f" Trace fn:[{info.name}], action:[{info.action.name}]"
|
|
457
|
-
)
|
|
458
|
-
await self.submit_action(trace_action)
|
|
459
|
-
logger.info(f"Trace Action for [{info.name}] action id: {info.action.name}, completed!")
|
|
460
|
-
except asyncio.CancelledError:
|
|
461
|
-
# If the action is cancelled, we need to cancel the action on the server as well
|
|
462
|
-
raise
|
|
463
459
|
|
|
464
|
-
|
|
460
|
+
async with self._parent_action_semaphore[unique_action_name(current_action_id)]:
|
|
461
|
+
try:
|
|
462
|
+
logger.info(
|
|
463
|
+
f"Submitting Trace action Run:[{trace_action.run_name},"
|
|
464
|
+
f" Parent:[{trace_action.parent_action_name}],"
|
|
465
|
+
f" Trace fn:[{info.name}], action:[{info.action.name}]"
|
|
466
|
+
)
|
|
467
|
+
await self.submit_action(trace_action)
|
|
468
|
+
logger.info(f"Trace Action for [{info.name}] action id: {info.action.name}, completed!")
|
|
469
|
+
except asyncio.CancelledError:
|
|
470
|
+
# If the action is cancelled, we need to cancel the action on the server as well
|
|
471
|
+
raise
|
|
472
|
+
|
|
473
|
+
async def _submit_task_ref(
|
|
474
|
+
self, invoke_seq_num: int, _task: task_definition_pb2.TaskDetails, max_inline_io_bytes: int, *args, **kwargs
|
|
475
|
+
) -> Any:
|
|
465
476
|
ctx = internal_ctx()
|
|
466
477
|
tctx = ctx.data.task_context
|
|
467
478
|
if tctx is None:
|
|
@@ -469,8 +480,6 @@ class RemoteController(Controller):
|
|
|
469
480
|
current_action_id = tctx.action
|
|
470
481
|
task_name = _task.spec.task_template.id.name
|
|
471
482
|
|
|
472
|
-
invoke_seq_num = self.generate_task_call_sequence(_task, current_action_id)
|
|
473
|
-
|
|
474
483
|
native_interface = types.guess_interface(
|
|
475
484
|
_task.spec.task_template.interface, default_inputs=_task.spec.default_inputs
|
|
476
485
|
)
|
|
@@ -482,7 +491,7 @@ class RemoteController(Controller):
|
|
|
482
491
|
|
|
483
492
|
serialized_inputs = inputs.proto_inputs.SerializeToString(deterministic=True)
|
|
484
493
|
inputs_uri = io.inputs_path(sub_action_output_path)
|
|
485
|
-
await upload_inputs_with_retry(serialized_inputs, inputs_uri)
|
|
494
|
+
await upload_inputs_with_retry(serialized_inputs, inputs_uri, max_inline_io_bytes)
|
|
486
495
|
# cache key - task name, task signature, inputs, cache version
|
|
487
496
|
cache_key = None
|
|
488
497
|
md = _task.spec.task_template.metadata
|
|
@@ -545,5 +554,17 @@ class RemoteController(Controller):
|
|
|
545
554
|
"RuntimeError",
|
|
546
555
|
f"Task {n.action_id.name} did not return an output path, but the task has outputs defined.",
|
|
547
556
|
)
|
|
548
|
-
return await load_and_convert_outputs(native_interface, n.realized_outputs_uri)
|
|
557
|
+
return await load_and_convert_outputs(native_interface, n.realized_outputs_uri, max_inline_io_bytes)
|
|
549
558
|
return None
|
|
559
|
+
|
|
560
|
+
async def submit_task_ref(
|
|
561
|
+
self, _task: task_definition_pb2.TaskDetails, max_inline_io_bytes: int, *args, **kwargs
|
|
562
|
+
) -> Any:
|
|
563
|
+
ctx = internal_ctx()
|
|
564
|
+
tctx = ctx.data.task_context
|
|
565
|
+
if tctx is None:
|
|
566
|
+
raise flyte.errors.RuntimeSystemError("BadContext", "Task context not initialized")
|
|
567
|
+
current_action_id = tctx.action
|
|
568
|
+
task_call_seq = self.generate_task_call_sequence(_task, current_action_id)
|
|
569
|
+
async with self._parent_action_semaphore[unique_action_name(current_action_id)]:
|
|
570
|
+
return await self._submit_task_ref(task_call_seq, _task, max_inline_io_bytes, *args, **kwargs)
|
|
@@ -86,10 +86,7 @@ class Controller:
|
|
|
86
86
|
|
|
87
87
|
async def get_action(self, action_id: identifier_pb2.ActionIdentifier, parent_action_name: str) -> Optional[Action]:
|
|
88
88
|
"""Get the action from the informer"""
|
|
89
|
-
|
|
90
|
-
if informer:
|
|
91
|
-
return await informer.get(action_id.name)
|
|
92
|
-
return None
|
|
89
|
+
return await self._run_coroutine_in_controller_thread(self._bg_get_action(action_id, parent_action_name))
|
|
93
90
|
|
|
94
91
|
@log
|
|
95
92
|
async def cancel_action(self, action: Action):
|
|
@@ -11,6 +11,7 @@ from typing import ClassVar, Optional, Protocol, cast
|
|
|
11
11
|
import aiofiles
|
|
12
12
|
import click
|
|
13
13
|
|
|
14
|
+
from flyte import Secret
|
|
14
15
|
from flyte._image import (
|
|
15
16
|
AptPackages,
|
|
16
17
|
Commands,
|
|
@@ -19,6 +20,7 @@ from flyte._image import (
|
|
|
19
20
|
Env,
|
|
20
21
|
Image,
|
|
21
22
|
Layer,
|
|
23
|
+
PipOption,
|
|
22
24
|
PipPackages,
|
|
23
25
|
PythonWheels,
|
|
24
26
|
Requirements,
|
|
@@ -44,6 +46,7 @@ WORKDIR /root
|
|
|
44
46
|
RUN --mount=type=cache,sharing=locked,mode=0777,target=/root/.cache/uv,id=uv \
|
|
45
47
|
--mount=type=bind,target=uv.lock,src=uv.lock \
|
|
46
48
|
--mount=type=bind,target=pyproject.toml,src=pyproject.toml \
|
|
49
|
+
$SECRET_MOUNT \
|
|
47
50
|
uv sync $PIP_INSTALL_ARGS
|
|
48
51
|
WORKDIR /
|
|
49
52
|
|
|
@@ -56,31 +59,35 @@ ENV PATH="/root/.venv/bin:$$PATH" \
|
|
|
56
59
|
UV_PACKAGE_INSTALL_COMMAND_TEMPLATE = Template("""\
|
|
57
60
|
RUN --mount=type=cache,sharing=locked,mode=0777,target=/root/.cache/uv,id=uv \
|
|
58
61
|
--mount=type=bind,target=requirements_uv.txt,src=requirements_uv.txt \
|
|
59
|
-
|
|
62
|
+
$SECRET_MOUNT \
|
|
63
|
+
uv pip install --python $$UV_PYTHON $PIP_INSTALL_ARGS
|
|
60
64
|
""")
|
|
61
65
|
|
|
62
66
|
UV_WHEEL_INSTALL_COMMAND_TEMPLATE = Template("""\
|
|
63
67
|
RUN --mount=type=cache,sharing=locked,mode=0777,target=/root/.cache/uv,id=wheel \
|
|
64
68
|
--mount=source=/dist,target=/dist,type=bind \
|
|
65
|
-
|
|
69
|
+
$SECRET_MOUNT \
|
|
70
|
+
uv pip install --python $$UV_PYTHON $PIP_INSTALL_ARGS
|
|
66
71
|
""")
|
|
67
72
|
|
|
68
73
|
APT_INSTALL_COMMAND_TEMPLATE = Template("""\
|
|
69
74
|
RUN --mount=type=cache,sharing=locked,mode=0777,target=/var/cache/apt,id=apt \
|
|
75
|
+
$SECRET_MOUNT \
|
|
70
76
|
apt-get update && apt-get install -y --no-install-recommends \
|
|
71
77
|
$APT_PACKAGES
|
|
72
78
|
""")
|
|
73
79
|
|
|
74
80
|
UV_PYTHON_INSTALL_COMMAND = Template("""\
|
|
75
81
|
RUN --mount=type=cache,sharing=locked,mode=0777,target=/root/.cache/uv,id=uv \
|
|
82
|
+
$SECRET_MOUNT \
|
|
76
83
|
uv pip install $PIP_INSTALL_ARGS
|
|
77
84
|
""")
|
|
78
85
|
|
|
79
86
|
# uv pip install --python /root/env/bin/python
|
|
80
87
|
# new template
|
|
81
88
|
DOCKER_FILE_UV_BASE_TEMPLATE = Template("""\
|
|
82
|
-
#syntax=docker/dockerfile:1.
|
|
83
|
-
FROM ghcr.io/astral-sh/uv:0.6.12
|
|
89
|
+
# syntax=docker/dockerfile:1.10
|
|
90
|
+
FROM ghcr.io/astral-sh/uv:0.6.12 AS uv
|
|
84
91
|
FROM $BASE_IMAGE
|
|
85
92
|
|
|
86
93
|
USER root
|
|
@@ -139,8 +146,10 @@ class PipAndRequirementsHandler:
|
|
|
139
146
|
|
|
140
147
|
pip_install_args = layer.get_pip_install_args()
|
|
141
148
|
pip_install_args.extend(["--requirement", "requirements_uv.txt"])
|
|
142
|
-
|
|
143
|
-
delta = UV_PACKAGE_INSTALL_COMMAND_TEMPLATE.substitute(
|
|
149
|
+
secret_mounts = _get_secret_mounts_layer(layer.secret_mounts)
|
|
150
|
+
delta = UV_PACKAGE_INSTALL_COMMAND_TEMPLATE.substitute(
|
|
151
|
+
PIP_INSTALL_ARGS=" ".join(pip_install_args), SECRET_MOUNT=secret_mounts
|
|
152
|
+
)
|
|
144
153
|
dockerfile += delta
|
|
145
154
|
|
|
146
155
|
return dockerfile
|
|
@@ -152,8 +161,10 @@ class PythonWheelHandler:
|
|
|
152
161
|
shutil.copytree(layer.wheel_dir, context_path / "dist", dirs_exist_ok=True)
|
|
153
162
|
pip_install_args = layer.get_pip_install_args()
|
|
154
163
|
pip_install_args.extend(["/dist/*.whl"])
|
|
155
|
-
|
|
156
|
-
delta = UV_WHEEL_INSTALL_COMMAND_TEMPLATE.substitute(
|
|
164
|
+
secret_mounts = _get_secret_mounts_layer(layer.secret_mounts)
|
|
165
|
+
delta = UV_WHEEL_INSTALL_COMMAND_TEMPLATE.substitute(
|
|
166
|
+
PIP_INSTALL_ARGS=" ".join(pip_install_args), SECRET_MOUNT=secret_mounts
|
|
167
|
+
)
|
|
157
168
|
dockerfile += delta
|
|
158
169
|
|
|
159
170
|
return dockerfile
|
|
@@ -181,9 +192,10 @@ class EnvHandler:
|
|
|
181
192
|
|
|
182
193
|
class AptPackagesHandler:
|
|
183
194
|
@staticmethod
|
|
184
|
-
async def handle(layer: AptPackages,
|
|
195
|
+
async def handle(layer: AptPackages, _: Path, dockerfile: str) -> str:
|
|
185
196
|
packages = layer.packages
|
|
186
|
-
|
|
197
|
+
secret_mounts = _get_secret_mounts_layer(layer.secret_mounts)
|
|
198
|
+
delta = APT_INSTALL_COMMAND_TEMPLATE.substitute(APT_PACKAGES=" ".join(packages), SECRET_MOUNT=secret_mounts)
|
|
187
199
|
dockerfile += delta
|
|
188
200
|
|
|
189
201
|
return dockerfile
|
|
@@ -200,7 +212,10 @@ class UVProjectHandler:
|
|
|
200
212
|
# --no-dev: Omit the development dependency group
|
|
201
213
|
# --no-install-project: Do not install the current project
|
|
202
214
|
additional_pip_install_args = ["--locked", "--no-dev", "--no-install-project"]
|
|
203
|
-
|
|
215
|
+
secret_mounts = _get_secret_mounts_layer(layer.secret_mounts)
|
|
216
|
+
delta = UV_LOCK_INSTALL_TEMPLATE.substitute(
|
|
217
|
+
PIP_INSTALL_ARGS=" ".join(additional_pip_install_args), SECRET_MOUNT=secret_mounts
|
|
218
|
+
)
|
|
204
219
|
dockerfile += delta
|
|
205
220
|
|
|
206
221
|
return dockerfile
|
|
@@ -250,13 +265,60 @@ class CommandsHandler:
|
|
|
250
265
|
|
|
251
266
|
class WorkDirHandler:
|
|
252
267
|
@staticmethod
|
|
253
|
-
async def handle(layer: WorkDir,
|
|
268
|
+
async def handle(layer: WorkDir, _: Path, dockerfile: str) -> str:
|
|
254
269
|
# cd to the workdir
|
|
255
270
|
dockerfile += f"\nWORKDIR {layer.workdir}\n"
|
|
256
271
|
|
|
257
272
|
return dockerfile
|
|
258
273
|
|
|
259
274
|
|
|
275
|
+
def _get_secret_commands(layers: typing.Tuple[Layer, ...]) -> typing.List[str]:
|
|
276
|
+
commands = []
|
|
277
|
+
|
|
278
|
+
def _get_secret_command(secret: str | Secret) -> typing.List[str]:
|
|
279
|
+
secret_id = hash(secret)
|
|
280
|
+
if isinstance(secret, str):
|
|
281
|
+
if not os.path.exists(secret):
|
|
282
|
+
raise FileNotFoundError(f"Secret file '{secret}' not found")
|
|
283
|
+
return ["--secret", f"id={secret_id},src={secret}"]
|
|
284
|
+
secret_env_key = "_".join([k.upper() for k in filter(None, (secret.group, secret.key))])
|
|
285
|
+
secret_env = os.getenv(secret_env_key)
|
|
286
|
+
if secret_env:
|
|
287
|
+
return ["--secret", f"id={secret_id},env={secret_env}"]
|
|
288
|
+
secret_file_name = "_".join(list(filter(None, (secret.group, secret.key))))
|
|
289
|
+
secret_file_path = f"/etc/secrets/{secret_file_name}"
|
|
290
|
+
if not os.path.exists(secret_file_path):
|
|
291
|
+
raise FileNotFoundError(f"Secret not found in Env Var {secret_env_key} or file {secret_file_path}")
|
|
292
|
+
return ["--secret", f"id={secret_id},src={secret_file_path}"]
|
|
293
|
+
|
|
294
|
+
for layer in layers:
|
|
295
|
+
if isinstance(layer, (PipOption, AptPackages)):
|
|
296
|
+
if layer.secret_mounts:
|
|
297
|
+
for secret_mount in layer.secret_mounts:
|
|
298
|
+
commands.extend(_get_secret_command(secret_mount))
|
|
299
|
+
return commands
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _get_secret_mounts_layer(secrets: typing.Tuple[str | Secret, ...] | None) -> str:
|
|
303
|
+
if secrets is None:
|
|
304
|
+
return ""
|
|
305
|
+
secret_mounts_layer = ""
|
|
306
|
+
for secret in secrets:
|
|
307
|
+
secret_id = hash(secret)
|
|
308
|
+
if isinstance(secret, str):
|
|
309
|
+
secret_mounts_layer += f"--mount=type=secret,id={secret_id},target=/run/secrets/{os.path.basename(secret)}"
|
|
310
|
+
elif isinstance(secret, Secret):
|
|
311
|
+
if secret.mount:
|
|
312
|
+
secret_mounts_layer += f"--mount=type=secret,id={secret_id},target={secret.mount}"
|
|
313
|
+
elif secret.as_env_var:
|
|
314
|
+
secret_mounts_layer += f"--mount=type=secret,id={secret_id},env={secret.as_env_var}"
|
|
315
|
+
else:
|
|
316
|
+
secret_file_name = "_".join(list(filter(None, (secret.group, secret.key))))
|
|
317
|
+
secret_mounts_layer += f"--mount=type=secret,id={secret_id},src=/run/secrets/{secret_file_name}"
|
|
318
|
+
|
|
319
|
+
return secret_mounts_layer
|
|
320
|
+
|
|
321
|
+
|
|
260
322
|
async def _process_layer(layer: Layer, context_path: Path, dockerfile: str) -> str:
|
|
261
323
|
match layer:
|
|
262
324
|
case PythonWheels():
|
|
@@ -357,6 +419,8 @@ class DockerImageBuilder(ImageBuilder):
|
|
|
357
419
|
else:
|
|
358
420
|
command.append("--load")
|
|
359
421
|
|
|
422
|
+
command.extend(_get_secret_commands(layers=image._layers))
|
|
423
|
+
|
|
360
424
|
concat_command = " ".join(command)
|
|
361
425
|
logger.debug(f"Build command: {concat_command}")
|
|
362
426
|
click.secho(f"Run command: {concat_command} ", fg="blue")
|
|
@@ -464,6 +528,8 @@ class DockerImageBuilder(ImageBuilder):
|
|
|
464
528
|
command.append("--push")
|
|
465
529
|
else:
|
|
466
530
|
command.append("--load")
|
|
531
|
+
|
|
532
|
+
command.extend(_get_secret_commands(layers=image._layers))
|
|
467
533
|
command.append(tmp_dir)
|
|
468
534
|
|
|
469
535
|
concat_command = " ".join(command)
|
flyte/_internal/runtime/io.py
CHANGED
|
@@ -5,14 +5,11 @@ It uses the storage module to handle the actual uploading and downloading of fil
|
|
|
5
5
|
TODO: Convert to use streaming apis
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
import logging
|
|
9
|
-
|
|
10
8
|
from flyteidl.core import errors_pb2, execution_pb2
|
|
11
9
|
|
|
12
10
|
import flyte.storage as storage
|
|
13
11
|
from flyte._protos.workflow import run_definition_pb2
|
|
14
12
|
|
|
15
|
-
from ..._logging import log
|
|
16
13
|
from .convert import Inputs, Outputs, _clean_error_code
|
|
17
14
|
|
|
18
15
|
# ------------------------------- CONSTANTS ------------------------------- #
|
|
@@ -55,11 +52,19 @@ async def upload_inputs(inputs: Inputs, input_path: str):
|
|
|
55
52
|
await storage.put_stream(data_iterable=inputs.proto_inputs.SerializeToString(), to_path=input_path)
|
|
56
53
|
|
|
57
54
|
|
|
58
|
-
async def upload_outputs(outputs: Outputs, output_path: str):
|
|
55
|
+
async def upload_outputs(outputs: Outputs, output_path: str, max_bytes: int = -1):
|
|
59
56
|
"""
|
|
60
57
|
:param outputs: Outputs
|
|
61
58
|
:param output_path: The path to upload the output file.
|
|
59
|
+
:param max_bytes: Maximum number of bytes to write to the output file. Default is -1, which means no limit.
|
|
62
60
|
"""
|
|
61
|
+
if max_bytes != -1 and outputs.proto_outputs.ByteSize() > max_bytes:
|
|
62
|
+
import flyte.errors
|
|
63
|
+
|
|
64
|
+
raise flyte.errors.InlineIOMaxBytesBreached(
|
|
65
|
+
f"Output file at {output_path} exceeds max_bytes limit of {max_bytes},"
|
|
66
|
+
f" size: {outputs.proto_outputs.ByteSize()}"
|
|
67
|
+
)
|
|
63
68
|
output_uri = outputs_path(output_path)
|
|
64
69
|
await storage.put_stream(data_iterable=outputs.proto_outputs.SerializeToString(), to_path=output_uri)
|
|
65
70
|
|
|
@@ -85,25 +90,59 @@ async def upload_error(err: execution_pb2.ExecutionError, output_prefix: str):
|
|
|
85
90
|
|
|
86
91
|
|
|
87
92
|
# ------------------------------- DOWNLOAD Methods ------------------------------- #
|
|
88
|
-
|
|
89
|
-
async def load_inputs(path: str) -> Inputs:
|
|
93
|
+
async def load_inputs(path: str, max_bytes: int = -1) -> Inputs:
|
|
90
94
|
"""
|
|
91
95
|
:param path: Input file to be downloaded
|
|
96
|
+
:param max_bytes: Maximum number of bytes to read from the input file. Default is -1, which means no limit.
|
|
92
97
|
:return: Inputs object
|
|
93
98
|
"""
|
|
94
99
|
lm = run_definition_pb2.Inputs()
|
|
95
|
-
|
|
100
|
+
|
|
101
|
+
if max_bytes == -1:
|
|
102
|
+
proto_str = b"".join([c async for c in storage.get_stream(path=path)])
|
|
103
|
+
else:
|
|
104
|
+
proto_bytes = []
|
|
105
|
+
total_bytes = 0
|
|
106
|
+
async for chunk in storage.get_stream(path=path):
|
|
107
|
+
if total_bytes + len(chunk) > max_bytes:
|
|
108
|
+
import flyte.errors
|
|
109
|
+
|
|
110
|
+
raise flyte.errors.InlineIOMaxBytesBreached(
|
|
111
|
+
f"Input file at {path} exceeds max_bytes limit of {max_bytes}"
|
|
112
|
+
)
|
|
113
|
+
proto_bytes.append(chunk)
|
|
114
|
+
total_bytes += len(chunk)
|
|
115
|
+
proto_str = b"".join(proto_bytes)
|
|
116
|
+
|
|
96
117
|
lm.ParseFromString(proto_str)
|
|
97
118
|
return Inputs(proto_inputs=lm)
|
|
98
119
|
|
|
99
120
|
|
|
100
|
-
async def load_outputs(path: str) -> Outputs:
|
|
121
|
+
async def load_outputs(path: str, max_bytes: int = -1) -> Outputs:
|
|
101
122
|
"""
|
|
102
123
|
:param path: output file to be loaded
|
|
124
|
+
:param max_bytes: Maximum number of bytes to read from the output file.
|
|
125
|
+
If -1, reads the entire file.
|
|
103
126
|
:return: Outputs object
|
|
104
127
|
"""
|
|
105
128
|
lm = run_definition_pb2.Outputs()
|
|
106
|
-
|
|
129
|
+
|
|
130
|
+
if max_bytes == -1:
|
|
131
|
+
proto_str = b"".join([c async for c in storage.get_stream(path=path)])
|
|
132
|
+
else:
|
|
133
|
+
proto_bytes = []
|
|
134
|
+
total_bytes = 0
|
|
135
|
+
async for chunk in storage.get_stream(path=path):
|
|
136
|
+
if total_bytes + len(chunk) > max_bytes:
|
|
137
|
+
import flyte.errors
|
|
138
|
+
|
|
139
|
+
raise flyte.errors.InlineIOMaxBytesBreached(
|
|
140
|
+
f"Output file at {path} exceeds max_bytes limit of {max_bytes}"
|
|
141
|
+
)
|
|
142
|
+
proto_bytes.append(chunk)
|
|
143
|
+
total_bytes += len(chunk)
|
|
144
|
+
proto_str = b"".join(proto_bytes)
|
|
145
|
+
|
|
107
146
|
lm.ParseFromString(proto_str)
|
|
108
147
|
return Outputs(proto_outputs=lm)
|
|
109
148
|
|
flyte/_secret.py
CHANGED
|
@@ -7,8 +7,9 @@ from typing import List, Optional, Union
|
|
|
7
7
|
@dataclass
|
|
8
8
|
class Secret:
|
|
9
9
|
"""
|
|
10
|
-
Secrets are used to inject sensitive information into tasks
|
|
11
|
-
|
|
10
|
+
Secrets are used to inject sensitive information into tasks or image build context.
|
|
11
|
+
Secrets can be mounted as environment variables or files.
|
|
12
|
+
The secret key is the name of the secret in the secret store. The group is optional and maybe used with some
|
|
12
13
|
secret stores to organize secrets. The secret_mount is used to specify how the secret should be mounted. If the
|
|
13
14
|
secret_mount is set to "env" the secret will be mounted as an environment variable. If the secret_mount is set to
|
|
14
15
|
"file" the secret will be mounted as a file. The as_env_var is an optional parameter that can be used to specify the
|
flyte/_task.py
CHANGED
|
@@ -33,7 +33,7 @@ from ._retry import RetryStrategy
|
|
|
33
33
|
from ._reusable_environment import ReusePolicy
|
|
34
34
|
from ._secret import SecretRequest
|
|
35
35
|
from ._timeout import TimeoutType
|
|
36
|
-
from .models import NativeInterface, SerializationContext
|
|
36
|
+
from .models import MAX_INLINE_IO_BYTES, NativeInterface, SerializationContext
|
|
37
37
|
|
|
38
38
|
if TYPE_CHECKING:
|
|
39
39
|
from flyteidl.core.tasks_pb2 import DataLoadingConfig
|
|
@@ -79,6 +79,8 @@ class TaskTemplate(Generic[P, R]):
|
|
|
79
79
|
:param env: Optional The environment variables to set for the task.
|
|
80
80
|
:param secrets: Optional The secrets that will be injected into the task at runtime.
|
|
81
81
|
:param timeout: Optional The timeout for the task.
|
|
82
|
+
:param max_inline_io_bytes: Maximum allowed size (in bytes) for all inputs and outputs passed directly to the task
|
|
83
|
+
(e.g., primitives, strings, dicts). Does not apply to files, directories, or dataframes.
|
|
82
84
|
"""
|
|
83
85
|
|
|
84
86
|
name: str
|
|
@@ -100,8 +102,8 @@ class TaskTemplate(Generic[P, R]):
|
|
|
100
102
|
report: bool = False
|
|
101
103
|
|
|
102
104
|
parent_env: Optional[weakref.ReferenceType[TaskEnvironment]] = None
|
|
103
|
-
local: bool = field(default=False, init=False)
|
|
104
105
|
ref: bool = field(default=False, init=False, repr=False, compare=False)
|
|
106
|
+
max_inline_io_bytes: int = MAX_INLINE_IO_BYTES
|
|
105
107
|
|
|
106
108
|
# Only used in python 3.10 and 3.11, where we cannot use markcoroutinefunction
|
|
107
109
|
_call_as_synchronous: bool = False
|
|
@@ -315,6 +317,7 @@ class TaskTemplate(Generic[P, R]):
|
|
|
315
317
|
reusable: Union[ReusePolicy, Literal["off"], None] = None,
|
|
316
318
|
env: Optional[Dict[str, str]] = None,
|
|
317
319
|
secrets: Optional[SecretRequest] = None,
|
|
320
|
+
max_inline_io_bytes: int | None = None,
|
|
318
321
|
**kwargs: Any,
|
|
319
322
|
) -> TaskTemplate:
|
|
320
323
|
"""
|
|
@@ -324,6 +327,8 @@ class TaskTemplate(Generic[P, R]):
|
|
|
324
327
|
cache = cache or self.cache
|
|
325
328
|
retries = retries or self.retries
|
|
326
329
|
timeout = timeout or self.timeout
|
|
330
|
+
max_inline_io_bytes = max_inline_io_bytes or self.max_inline_io_bytes
|
|
331
|
+
|
|
327
332
|
reusable = reusable or self.reusable
|
|
328
333
|
if reusable == "off":
|
|
329
334
|
reusable = None
|
|
@@ -371,6 +376,7 @@ class TaskTemplate(Generic[P, R]):
|
|
|
371
376
|
reusable=cast(Optional[ReusePolicy], reusable),
|
|
372
377
|
env=env,
|
|
373
378
|
secrets=secrets,
|
|
379
|
+
max_inline_io_bytes=max_inline_io_bytes,
|
|
374
380
|
)
|
|
375
381
|
|
|
376
382
|
|
flyte/_task_environment.py
CHANGED
|
@@ -27,7 +27,7 @@ from ._retry import RetryStrategy
|
|
|
27
27
|
from ._reusable_environment import ReusePolicy
|
|
28
28
|
from ._secret import SecretRequest
|
|
29
29
|
from ._task import AsyncFunctionTaskTemplate, TaskTemplate
|
|
30
|
-
from .models import NativeInterface
|
|
30
|
+
from .models import MAX_INLINE_IO_BYTES, NativeInterface
|
|
31
31
|
|
|
32
32
|
if TYPE_CHECKING:
|
|
33
33
|
from kubernetes.client import V1PodTemplate
|
|
@@ -141,6 +141,7 @@ class TaskEnvironment(Environment):
|
|
|
141
141
|
secrets: Optional[SecretRequest] = None,
|
|
142
142
|
pod_template: Optional[Union[str, "V1PodTemplate"]] = None,
|
|
143
143
|
report: bool = False,
|
|
144
|
+
max_inline_io_bytes: int = MAX_INLINE_IO_BYTES,
|
|
144
145
|
) -> Union[AsyncFunctionTaskTemplate, Callable[P, R]]:
|
|
145
146
|
"""
|
|
146
147
|
Decorate a function to be a task.
|
|
@@ -157,6 +158,8 @@ class TaskEnvironment(Environment):
|
|
|
157
158
|
:param pod_template: Optional The pod template for the task, if not provided the default pod template will be
|
|
158
159
|
used.
|
|
159
160
|
:param report: Optional Whether to generate the html report for the task, defaults to False.
|
|
161
|
+
:param max_inline_io_bytes: Maximum allowed size (in bytes) for all inputs and outputs passed directly to the
|
|
162
|
+
task (e.g., primitives, strings, dicts). Does not apply to files, directories, or dataframes.
|
|
160
163
|
"""
|
|
161
164
|
from ._task import P, R
|
|
162
165
|
|
|
@@ -208,6 +211,7 @@ class TaskEnvironment(Environment):
|
|
|
208
211
|
report=report,
|
|
209
212
|
friendly_name=friendly_name,
|
|
210
213
|
plugin_config=self.plugin_config,
|
|
214
|
+
max_inline_io_bytes=max_inline_io_bytes,
|
|
211
215
|
)
|
|
212
216
|
self._tasks[task_name] = tmpl
|
|
213
217
|
return tmpl
|
flyte/_version.py
CHANGED
|
@@ -17,5 +17,5 @@ __version__: str
|
|
|
17
17
|
__version_tuple__: VERSION_TUPLE
|
|
18
18
|
version_tuple: VERSION_TUPLE
|
|
19
19
|
|
|
20
|
-
__version__ = version = '2.0.
|
|
21
|
-
__version_tuple__ = version_tuple = (2, 0, 0, '
|
|
20
|
+
__version__ = version = '2.0.0b4'
|
|
21
|
+
__version_tuple__ = version_tuple = (2, 0, 0, 'b4')
|
flyte/errors.py
CHANGED
|
@@ -179,3 +179,13 @@ class ImageBuildError(RuntimeUserError):
|
|
|
179
179
|
|
|
180
180
|
def __init__(self, message: str):
|
|
181
181
|
super().__init__("ImageBuildError", message, "user")
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class InlineIOMaxBytesBreached(RuntimeUserError):
|
|
185
|
+
"""
|
|
186
|
+
This error is raised when the inline IO max bytes limit is breached.
|
|
187
|
+
This can be adjusted per task by setting max_inline_io_bytes in the task definition.
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
def __init__(self, message: str):
|
|
191
|
+
super().__init__("InlineIOMaxBytesBreached", message, "user")
|
flyte/extras/_container.py
CHANGED
|
@@ -192,7 +192,9 @@ class ContainerTask(TaskTemplate):
|
|
|
192
192
|
microseconds=microseconds,
|
|
193
193
|
)
|
|
194
194
|
|
|
195
|
-
async def _convert_output_val_to_correct_type(
|
|
195
|
+
async def _convert_output_val_to_correct_type(
|
|
196
|
+
self, output_path: pathlib.Path, output_val: Any, output_type: Type
|
|
197
|
+
) -> Any:
|
|
196
198
|
import datetime
|
|
197
199
|
|
|
198
200
|
if issubclass(output_type, bool):
|
flyte/models.py
CHANGED
flyte/remote/_task.py
CHANGED
|
@@ -81,6 +81,7 @@ AutoVersioning = Literal["latest", "current"]
|
|
|
81
81
|
@dataclass
|
|
82
82
|
class TaskDetails:
|
|
83
83
|
pb2: task_definition_pb2.TaskDetails
|
|
84
|
+
max_inline_io_bytes: int = 10 * 1024 * 1024 # 10 MB
|
|
84
85
|
|
|
85
86
|
@classmethod
|
|
86
87
|
def get(
|
|
@@ -252,7 +253,7 @@ class TaskDetails:
|
|
|
252
253
|
|
|
253
254
|
controller = get_controller()
|
|
254
255
|
if controller:
|
|
255
|
-
return await controller.submit_task_ref(self.pb2, *args, **kwargs)
|
|
256
|
+
return await controller.submit_task_ref(self.pb2, self.max_inline_io_bytes, *args, **kwargs)
|
|
256
257
|
raise flyte.errors
|
|
257
258
|
|
|
258
259
|
def override(
|
|
@@ -267,6 +268,7 @@ class TaskDetails:
|
|
|
267
268
|
reusable: Union[flyte.ReusePolicy, Literal["auto"], None] = None,
|
|
268
269
|
env: Optional[Dict[str, str]] = None,
|
|
269
270
|
secrets: Optional[flyte.SecretRequest] = None,
|
|
271
|
+
max_inline_io_bytes: int | None = None,
|
|
270
272
|
**kwargs: Any,
|
|
271
273
|
) -> TaskDetails:
|
|
272
274
|
raise NotImplementedError
|
flyte/report/_report.py
CHANGED
|
@@ -145,7 +145,11 @@ async def flush():
|
|
|
145
145
|
assert report_html is not None
|
|
146
146
|
assert isinstance(report_html, str)
|
|
147
147
|
report_path = io.report_path(internal_ctx().data.task_context.output_path)
|
|
148
|
-
|
|
148
|
+
content_types = {
|
|
149
|
+
"Content-Type": "text/html", # For s3
|
|
150
|
+
"content_type": "text/html", # For gcs
|
|
151
|
+
}
|
|
152
|
+
final_path = await storage.put_stream(report_html.encode("utf-8"), to_path=report_path, attributes=content_types)
|
|
149
153
|
logger.debug(f"Report flushed to {final_path}")
|
|
150
154
|
|
|
151
155
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flyte
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.0b4
|
|
4
4
|
Summary: Add your description here
|
|
5
5
|
Author-email: Ketan Umare <kumare3@users.noreply.github.com>
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -12,7 +12,7 @@ Requires-Dist: flyteidl>=1.15.4b0
|
|
|
12
12
|
Requires-Dist: cloudpickle>=3.1.1
|
|
13
13
|
Requires-Dist: fsspec>=2025.3.0
|
|
14
14
|
Requires-Dist: grpcio>=1.71.0
|
|
15
|
-
Requires-Dist: obstore>=0.
|
|
15
|
+
Requires-Dist: obstore>=0.7.3
|
|
16
16
|
Requires-Dist: protobuf>=6.30.1
|
|
17
17
|
Requires-Dist: pydantic>=2.10.6
|
|
18
18
|
Requires-Dist: pyyaml>=6.0.2
|
|
@@ -28,7 +28,7 @@ Dynamic: license-file
|
|
|
28
28
|
|
|
29
29
|
# Flyte 2 SDK 🚀
|
|
30
30
|
|
|
31
|
-
**
|
|
31
|
+
**Type-safe, distributed orchestration of agents, ML pipelines, and more — in pure Python with async/await or sync!**
|
|
32
32
|
|
|
33
33
|
[](https://pypi.org/project/flyte/)
|
|
34
34
|
[](https://pypi.org/project/flyte/)
|
|
@@ -8,7 +8,7 @@ flyte/_environment.py,sha256=oKVXLBX0ky2eE_wjBdzvQGI_2LiT2Nbx58ur7GMt50c,3231
|
|
|
8
8
|
flyte/_excepthook.py,sha256=nXts84rzEg6-7RtFarbKzOsRZTQR4plnbWVIFMAEprs,1310
|
|
9
9
|
flyte/_group.py,sha256=7o1j16sZyUmYB50mOiq1ui4TBAKhRpDqLakV8Ya1kw4,803
|
|
10
10
|
flyte/_hash.py,sha256=Of_Zl_DzzzF2jp4ZsLm-3o-xJFCCJ8_GubmLI1htx78,504
|
|
11
|
-
flyte/_image.py,sha256=
|
|
11
|
+
flyte/_image.py,sha256=G6o2z3JY1epk8oSDuUkTbj-5bNuSetC4w-ciG64FoHQ,35322
|
|
12
12
|
flyte/_initialize.py,sha256=xKl_LYMluRt21wWqa6RTKuLo0_DCbSaTfUk27_brtNk,18232
|
|
13
13
|
flyte/_interface.py,sha256=1B9zIwFDjiVp_3l_mk8EpA4g3Re-6DUBEBi9z9vDvPs,3504
|
|
14
14
|
flyte/_logging.py,sha256=QrT4Z30C2tsZ-yIojisQODTuq6Y6zSJYuTrLgF58UYc,3664
|
|
@@ -18,17 +18,17 @@ flyte/_resources.py,sha256=L2JuvQDlMo1JLJeUmJPRwtWbunhR2xJEhFgQW5yc72c,9690
|
|
|
18
18
|
flyte/_retry.py,sha256=rfLv0MvWxzPByKESTglEmjPsytEAKiIvvmzlJxXwsfE,941
|
|
19
19
|
flyte/_reusable_environment.py,sha256=f8Y1GilUwGcXH4n2Fckrnx0SrZmhk3nCfoe-TqUKivI,3740
|
|
20
20
|
flyte/_run.py,sha256=HkTD3rHL34pAwvn1WPN6OXYmk-GWX0txLdRH1OIMvEA,24338
|
|
21
|
-
flyte/_secret.py,sha256=
|
|
22
|
-
flyte/_task.py,sha256=
|
|
23
|
-
flyte/_task_environment.py,sha256=
|
|
21
|
+
flyte/_secret.py,sha256=89VIihdXI03irHb217GMfipt7jzXBafm17YYmyv6gHo,3245
|
|
22
|
+
flyte/_task.py,sha256=rYR7SVGihfBp7UYrhdmqhHp0ngl2K7Za8IJfhv1K2oc,19402
|
|
23
|
+
flyte/_task_environment.py,sha256=Zpfr8gjwXg5KuCfIbT4s3l2mtJCFqDxXwv6ZStHWBuc,9840
|
|
24
24
|
flyte/_task_plugins.py,sha256=9MH3nFPOH_e8_92BT4sFk4oyAnj6GJFvaPYWaraX7yE,1037
|
|
25
25
|
flyte/_timeout.py,sha256=zx5sFcbYmjJAJbZWSGzzX-BpC9HC7Jfs35T7vVhKwkk,1571
|
|
26
26
|
flyte/_tools.py,sha256=tWb0sx3t3mm4jbaQVjCTc9y39oR_Ibo3z_KHToP3Lto,966
|
|
27
27
|
flyte/_trace.py,sha256=SSE1nzUgmVTS2xFNtchEOjEjlRavMOIInasXzY8i9lU,4911
|
|
28
|
-
flyte/_version.py,sha256=
|
|
29
|
-
flyte/errors.py,sha256=
|
|
28
|
+
flyte/_version.py,sha256=_khCeGxTPxDDeGWBrQG01YziZI4eo5tW-iu_1TCc42w,519
|
|
29
|
+
flyte/errors.py,sha256=MsVZxq6oeIOXe6AVWpxqA3ZiyxCGM0A6UqnYuvmSUCU,5514
|
|
30
30
|
flyte/extend.py,sha256=GB4ZedGzKa30vYWRVPOdxEeK62xnUVFY4z2tD6H9eEw,376
|
|
31
|
-
flyte/models.py,sha256=
|
|
31
|
+
flyte/models.py,sha256=AJZXL8eRmZ2RHEvjL6VSdpTmgF5S1Ekh_tAKRn0b6mM,15321
|
|
32
32
|
flyte/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
33
|
flyte/_bin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
34
|
flyte/_bin/runtime.py,sha256=2jTy3ccvrJ__Xrfdo2t0Fxhsojc5o2zIxDHt98RE_eU,6475
|
|
@@ -42,18 +42,18 @@ flyte/_code_bundle/_packaging.py,sha256=5QUuea6kg9s-ebBg7gFAHaxOMchxR5MhTQ8KohWs
|
|
|
42
42
|
flyte/_code_bundle/_utils.py,sha256=qlAVmik9rLasfd1oNrCxhL870w5ntk5ZlNGeaKSKaAU,12028
|
|
43
43
|
flyte/_code_bundle/bundle.py,sha256=nUAwYTVAE3Z9dfgkBtsqCoKJImjSl4AicG36yweWHLc,8797
|
|
44
44
|
flyte/_internal/__init__.py,sha256=vjXgGzAAjy609YFkAy9_RVPuUlslsHSJBXCLNTVnqOY,136
|
|
45
|
-
flyte/_internal/controllers/__init__.py,sha256=
|
|
46
|
-
flyte/_internal/controllers/_local_controller.py,sha256=
|
|
45
|
+
flyte/_internal/controllers/__init__.py,sha256=TVAc4ydsldcIFmN3PW9-IX5UkKeD8oOmuIukIgEae9M,4341
|
|
46
|
+
flyte/_internal/controllers/_local_controller.py,sha256=__-eEira0k18DsBu1LBXeEjhFGFcp1Uai9K0YEBbwKM,7300
|
|
47
47
|
flyte/_internal/controllers/_trace.py,sha256=ywFg_M2nGrCKYLbh4iVdsVlRtPT1K2S-XZMvDJU8AKU,1499
|
|
48
48
|
flyte/_internal/controllers/remote/__init__.py,sha256=9_azH1eHLqY6VULpDugXi7Kf1kK1ODqEnsQ_3wM6IqU,1919
|
|
49
49
|
flyte/_internal/controllers/remote/_action.py,sha256=ENV1thRXllSpi2s4idL-vCVcmXQNS17hmP2lMDKzNdo,7397
|
|
50
50
|
flyte/_internal/controllers/remote/_client.py,sha256=HPbzbfaWZVv5wpOvKNtFXR6COiZDwd1cUJQqi60A7oU,1421
|
|
51
|
-
flyte/_internal/controllers/remote/_controller.py,sha256=
|
|
52
|
-
flyte/_internal/controllers/remote/_core.py,sha256=
|
|
51
|
+
flyte/_internal/controllers/remote/_controller.py,sha256=tnHHWYd2nc16_1Slm8lShrbQdrfHjyNiSiByFk3A1mg,24988
|
|
52
|
+
flyte/_internal/controllers/remote/_core.py,sha256=PhqI_qwKieH0abOzXYzUZt3v166DxX9HkZPqLWXqH6w,18975
|
|
53
53
|
flyte/_internal/controllers/remote/_informer.py,sha256=w4p29_dzS_ns762eNBljvnbJLgCm36d1Ogo2ZkgV1yg,14418
|
|
54
54
|
flyte/_internal/controllers/remote/_service_protocol.py,sha256=B9qbIg6DiGeac-iSccLmX_AL2xUgX4ezNUOiAbSy4V0,1357
|
|
55
55
|
flyte/_internal/imagebuild/__init__.py,sha256=dwXdJ1jMhw9RF8itF7jkPLanvX1yCviSns7hE5eoIts,102
|
|
56
|
-
flyte/_internal/imagebuild/docker_builder.py,sha256=
|
|
56
|
+
flyte/_internal/imagebuild/docker_builder.py,sha256=8k8YPqhr22p_SRx3_EaOlaXB67CGWFP3reRdQ0iOS8E,19770
|
|
57
57
|
flyte/_internal/imagebuild/image_builder.py,sha256=dXBXl62qcPabus6dR3eP8P9mBGNhpZHZ2Xm12AymKkk,11150
|
|
58
58
|
flyte/_internal/imagebuild/remote_builder.py,sha256=vxPhosMbFXQGQY0Um6il4C8I4vZEWsDyBK3uJQN3icg,10602
|
|
59
59
|
flyte/_internal/resolvers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -63,7 +63,7 @@ flyte/_internal/resolvers/default.py,sha256=nX4DHUYod1nRvEsl_vSgutQVEdExu2xL8pRk
|
|
|
63
63
|
flyte/_internal/runtime/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
64
64
|
flyte/_internal/runtime/convert.py,sha256=yK5Fy25-CVSqTtWF8BuBel2jwlVoh8R5F4UhIMYpgmg,16086
|
|
65
65
|
flyte/_internal/runtime/entrypoints.py,sha256=9Ng-aQ45M-_MMWeOe9uGmgx69qO9b0xaMRiu542ZI9g,6581
|
|
66
|
-
flyte/_internal/runtime/io.py,sha256=
|
|
66
|
+
flyte/_internal/runtime/io.py,sha256=ysL7hMpfVumvsEYWOM-_VPa8MXn5_X_CZorKbOThyv4,5935
|
|
67
67
|
flyte/_internal/runtime/resources_serde.py,sha256=TObMVsSjVcQhcY8-nY81pbvrz7TP-adDD5xV-LqAaxM,4813
|
|
68
68
|
flyte/_internal/runtime/reuse.py,sha256=WEuBfC9tBezxaIXeUQDgnJfnRHiUmPK0S25nTOFle4E,4676
|
|
69
69
|
flyte/_internal/runtime/rusty.py,sha256=puMaa6aLaoR4Tl5xxZulC4AzY58nmGg-5ok4ydHHjdM,6145
|
|
@@ -172,7 +172,7 @@ flyte/config/_internal.py,sha256=LMcAtDjvTjf5bGlsJVxPuLxQQ82mLd00xK5-JlYGCi8,298
|
|
|
172
172
|
flyte/config/_reader.py,sha256=coidKV5CVODEnqDnsHZ9-VMC0UGXanXhAZE1SmYRhxI,7021
|
|
173
173
|
flyte/connectors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
174
174
|
flyte/extras/__init__.py,sha256=FhB0uK7H1Yo5De9vOuF7UGnezTKncj3u2Wo5uQdWN0g,74
|
|
175
|
-
flyte/extras/_container.py,sha256=
|
|
175
|
+
flyte/extras/_container.py,sha256=xfbxaGrx2JyQyrvQx1UWTzho2ion8_xujPW5V4uDEIs,11818
|
|
176
176
|
flyte/io/__init__.py,sha256=qF8jq_IKuocuGU0LAvoy_uxjMs4G-vXjDA9Prba0JGU,538
|
|
177
177
|
flyte/io/_dir.py,sha256=rih9CY1YjNX05bcAu5LG62Xoyij5GXAlv7jLyVF0je8,15310
|
|
178
178
|
flyte/io/_file.py,sha256=kp5700SKPy5htmMhm4hE2ybb99Ykny1b0Kwm3huCWXs,15572
|
|
@@ -187,7 +187,7 @@ flyte/remote/_logs.py,sha256=aDG18-uPVb2J3PxmqmAY1C0Z4Iv1P1agg-iF4nSQR4U,6709
|
|
|
187
187
|
flyte/remote/_project.py,sha256=CFNTGpgXU3X599tkJ_oxijs9zPzzCWOB6mAWn6WeDEU,2828
|
|
188
188
|
flyte/remote/_run.py,sha256=fpr_YcGZIv6K7Jt1if3-HHHVB2TVt_8AWcZ55rN_fgk,9750
|
|
189
189
|
flyte/remote/_secret.py,sha256=l5xeMS83uMcWWeSSTRsSZUNhS0N--1Dze09C-thSOQs,4341
|
|
190
|
-
flyte/remote/_task.py,sha256=
|
|
190
|
+
flyte/remote/_task.py,sha256=xHfWChnC-L7Hdp2IB9qDnbNyaOgM2TWOlnkmevoc2dA,15314
|
|
191
191
|
flyte/remote/_client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
192
192
|
flyte/remote/_client/_protocols.py,sha256=JyBWHs5WsVOxEDUyG9X7wPLDzzzjkoaNhJlU-X4YlN0,5599
|
|
193
193
|
flyte/remote/_client/controlplane.py,sha256=FsOfj4rO4MIMnYrpAT53F8q588VVf5t4sDuwoPuc840,3102
|
|
@@ -210,7 +210,7 @@ flyte/remote/_client/auth/_grpc_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCe
|
|
|
210
210
|
flyte/remote/_client/auth/_grpc_utils/auth_interceptor.py,sha256=JCjdoWV41sjdvfJcUmrJdIfQ0meuGFwv2ArU7FQGDDA,12403
|
|
211
211
|
flyte/remote/_client/auth/_grpc_utils/default_metadata_interceptor.py,sha256=IoMGWM42_VyzxqIVYe458o0uKsqhH-mcERUs9CY1L5U,6194
|
|
212
212
|
flyte/report/__init__.py,sha256=yLbeUxYaVaDlgBod3Oh34zGBSotl1UlXq1vUkb9q7cs,152
|
|
213
|
-
flyte/report/_report.py,sha256=
|
|
213
|
+
flyte/report/_report.py,sha256=eIk0q-aZi5Yfm63cHJb72qndlDV4kWvl2jxZ2LItGGw,5324
|
|
214
214
|
flyte/report/_template.html,sha256=YehmLJG3QMYQ10UT1YZBu2ncVmAJ4iyqVp5hF3sXRAs,3458
|
|
215
215
|
flyte/storage/__init__.py,sha256=0tcI9qtIVf0Fxczkno03vpwBDVlKMDSNN38uxMTH1bE,569
|
|
216
216
|
flyte/storage/_config.py,sha256=xVibWJaioOnkeTb_M30azgiUe1jvmQaOWRZEkpdoTao,8680
|
|
@@ -226,10 +226,10 @@ flyte/types/_renderer.py,sha256=ygcCo5l60lHufyQISFddZfWwLlQ8kJAKxUT_XnR_6dY,4818
|
|
|
226
226
|
flyte/types/_string_literals.py,sha256=NlG1xV8RSA-sZ-n-IFQCAsdB6jXJOAKkHWtnopxVVDk,4231
|
|
227
227
|
flyte/types/_type_engine.py,sha256=Tas_OXYddOi0nDuORjqan2SkJ96wKD8937I2l1bo8vk,97916
|
|
228
228
|
flyte/types/_utils.py,sha256=pbts9E1_2LTdLygAY0UYTLYJ8AsN3BZyviSXvrtcutc,2626
|
|
229
|
-
flyte-2.0.
|
|
230
|
-
flyte-2.0.
|
|
231
|
-
flyte-2.0.
|
|
232
|
-
flyte-2.0.
|
|
233
|
-
flyte-2.0.
|
|
234
|
-
flyte-2.0.
|
|
235
|
-
flyte-2.0.
|
|
229
|
+
flyte-2.0.0b4.data/scripts/runtime.py,sha256=2jTy3ccvrJ__Xrfdo2t0Fxhsojc5o2zIxDHt98RE_eU,6475
|
|
230
|
+
flyte-2.0.0b4.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
231
|
+
flyte-2.0.0b4.dist-info/METADATA,sha256=hrYyZeScWhM_utQk4oz22SQDBQi6V4W5OqOa2rRnSTw,10004
|
|
232
|
+
flyte-2.0.0b4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
233
|
+
flyte-2.0.0b4.dist-info/entry_points.txt,sha256=MIq2z5dBurdCJfpXfMKzgBv7sJOakKRYxr8G0cMiTrg,75
|
|
234
|
+
flyte-2.0.0b4.dist-info/top_level.txt,sha256=7dkyFbikvA12LEZEqawx8oDG1CMod6hTliPj7iWzgYo,6
|
|
235
|
+
flyte-2.0.0b4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|