flyte 2.0.0b6__py3-none-any.whl → 2.0.0b8__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 +4 -17
- flyte/_cache/cache.py +1 -1
- flyte/_deploy.py +2 -1
- flyte/_image.py +5 -3
- flyte/_internal/controllers/remote/_controller.py +15 -0
- flyte/_internal/controllers/remote/_core.py +3 -3
- flyte/_internal/imagebuild/docker_builder.py +36 -17
- flyte/_internal/imagebuild/remote_builder.py +4 -16
- flyte/_internal/imagebuild/utils.py +29 -0
- flyte/_internal/runtime/rusty.py +41 -18
- flyte/_run.py +2 -2
- flyte/_task_environment.py +2 -2
- flyte/_version.py +2 -2
- flyte/cli/_build.py +1 -1
- flyte/cli/_common.py +34 -18
- flyte/cli/_create.py +24 -2
- flyte/cli/_deploy.py +6 -10
- flyte/cli/_get.py +18 -11
- flyte/cli/_option.py +33 -0
- flyte/cli/_run.py +18 -12
- flyte/cli/main.py +10 -6
- flyte/errors.py +22 -0
- flyte/remote/_action.py +6 -4
- flyte/remote/_common.py +30 -0
- flyte/remote/_logs.py +3 -3
- flyte/remote/_project.py +9 -7
- flyte/remote/_run.py +12 -4
- flyte/remote/_secret.py +2 -1
- flyte/remote/_task.py +51 -13
- flyte/syncify/_api.py +4 -0
- {flyte-2.0.0b6.data → flyte-2.0.0b8.data}/scripts/runtime.py +4 -17
- {flyte-2.0.0b6.dist-info → flyte-2.0.0b8.dist-info}/METADATA +1 -1
- {flyte-2.0.0b6.dist-info → flyte-2.0.0b8.dist-info}/RECORD +37 -34
- {flyte-2.0.0b6.dist-info → flyte-2.0.0b8.dist-info}/WHEEL +0 -0
- {flyte-2.0.0b6.dist-info → flyte-2.0.0b8.dist-info}/entry_points.txt +0 -0
- {flyte-2.0.0b6.dist-info → flyte-2.0.0b8.dist-info}/licenses/LICENSE +0 -0
- {flyte-2.0.0b6.dist-info → flyte-2.0.0b8.dist-info}/top_level.txt +0 -0
flyte/_bin/runtime.py
CHANGED
|
@@ -26,7 +26,6 @@ DOMAIN_NAME = "FLYTE_INTERNAL_TASK_DOMAIN"
|
|
|
26
26
|
ORG_NAME = "_U_ORG_NAME"
|
|
27
27
|
ENDPOINT_OVERRIDE = "_U_EP_OVERRIDE"
|
|
28
28
|
RUN_OUTPUT_BASE_DIR = "_U_RUN_BASE"
|
|
29
|
-
ENABLE_REF_TASKS = "_REF_TASKS" # This is a temporary flag to enable reference tasks in the runtime.
|
|
30
29
|
|
|
31
30
|
# TODO: Remove this after proper auth is implemented
|
|
32
31
|
_UNION_EAGER_API_KEY_ENV_VAR = "_UNION_EAGER_API_KEY"
|
|
@@ -87,6 +86,7 @@ def main(
|
|
|
87
86
|
|
|
88
87
|
import flyte
|
|
89
88
|
import flyte._utils as utils
|
|
89
|
+
import flyte.errors
|
|
90
90
|
from flyte._initialize import init
|
|
91
91
|
from flyte._internal.controllers import create_controller
|
|
92
92
|
from flyte._internal.imagebuild.image_builder import ImageCache
|
|
@@ -123,22 +123,7 @@ def main(
|
|
|
123
123
|
logger.debug(f"Using controller endpoint: {ep} with kwargs: {controller_kwargs}")
|
|
124
124
|
|
|
125
125
|
bundle = CodeBundle(tgz=tgz, pkl=pkl, destination=dest, computed_version=version)
|
|
126
|
-
|
|
127
|
-
# We init regular client here so that reference tasks can work
|
|
128
|
-
# Current reference tasks will not work with remote controller, because we create 2 different
|
|
129
|
-
# channels on different threads and this is not supported by grpcio or the auth system. It ends up leading
|
|
130
|
-
# File "src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi", line 147,
|
|
131
|
-
# in grpc._cython.cygrpc.PollerCompletionQueue._handle_events
|
|
132
|
-
# BlockingIOError: [Errno 11] Resource temporarily unavailable
|
|
133
|
-
# TODO solution is to use a single channel for both controller and reference tasks, but this requires a refactor
|
|
134
|
-
if enable_ref_tasks:
|
|
135
|
-
logger.warning(
|
|
136
|
-
"Reference tasks are enabled. This will initialize client and you will see a BlockIOError. "
|
|
137
|
-
"This is harmless, but a nuisance. We are working on a fix."
|
|
138
|
-
)
|
|
139
|
-
init(org=org, project=project, domain=domain, **controller_kwargs)
|
|
140
|
-
else:
|
|
141
|
-
init()
|
|
126
|
+
init(org=org, project=project, domain=domain, **controller_kwargs)
|
|
142
127
|
# Controller is created with the same kwargs as init, so that it can be used to run tasks
|
|
143
128
|
controller = create_controller(ct="remote", **controller_kwargs)
|
|
144
129
|
|
|
@@ -164,6 +149,8 @@ def main(
|
|
|
164
149
|
|
|
165
150
|
# Run both coroutines concurrently and wait for first to finish and cancel the other
|
|
166
151
|
async def _run_and_stop():
|
|
152
|
+
loop = asyncio.get_event_loop()
|
|
153
|
+
loop.set_exception_handler(flyte.errors.silence_grpc_polling_error)
|
|
167
154
|
await utils.run_coros(controller_failure, task_coroutine)
|
|
168
155
|
await controller.stop()
|
|
169
156
|
|
flyte/_cache/cache.py
CHANGED
flyte/_deploy.py
CHANGED
|
@@ -5,7 +5,6 @@ import typing
|
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple
|
|
7
7
|
|
|
8
|
-
import grpc.aio
|
|
9
8
|
import rich.repr
|
|
10
9
|
|
|
11
10
|
import flyte.errors
|
|
@@ -86,6 +85,8 @@ async def _deploy_task(
|
|
|
86
85
|
Deploy the given task.
|
|
87
86
|
"""
|
|
88
87
|
ensure_client()
|
|
88
|
+
import grpc.aio
|
|
89
|
+
|
|
89
90
|
from ._internal.runtime.convert import convert_upload_default_inputs
|
|
90
91
|
from ._internal.runtime.task_serde import translate_task_to_wire
|
|
91
92
|
from ._protos.workflow import task_definition_pb2, task_service_pb2
|
flyte/_image.py
CHANGED
|
@@ -154,6 +154,7 @@ class PipPackages(PipOption, Layer):
|
|
|
154
154
|
class PythonWheels(PipOption, Layer):
|
|
155
155
|
wheel_dir: Path = field(metadata={"identifier": False})
|
|
156
156
|
wheel_dir_name: str = field(init=False)
|
|
157
|
+
package_name: str
|
|
157
158
|
|
|
158
159
|
def __post_init__(self):
|
|
159
160
|
object.__setattr__(self, "wheel_dir_name", self.wheel_dir.name)
|
|
@@ -498,7 +499,8 @@ class Image:
|
|
|
498
499
|
image = image.with_pip_packages(f"flyte=={flyte_version}", pre=True)
|
|
499
500
|
else:
|
|
500
501
|
image = image.with_pip_packages(f"flyte=={flyte_version}")
|
|
501
|
-
|
|
502
|
+
if not dev_mode:
|
|
503
|
+
object.__setattr__(image, "_tag", preset_tag)
|
|
502
504
|
# Set this to auto for all auto images because the meaning of "auto" can change (based on logic inside
|
|
503
505
|
# _get_default_image_for, acts differently in a running task container) so let's make sure it stays auto.
|
|
504
506
|
object.__setattr__(image, "_identifier_override", "auto")
|
|
@@ -546,7 +548,7 @@ class Image:
|
|
|
546
548
|
platform=platform,
|
|
547
549
|
)
|
|
548
550
|
|
|
549
|
-
if registry
|
|
551
|
+
if registry or name:
|
|
550
552
|
return base_image.clone(registry=registry, name=name)
|
|
551
553
|
|
|
552
554
|
# # Set this to auto for all auto images because the meaning of "auto" can change (based on logic inside
|
|
@@ -940,7 +942,7 @@ class Image:
|
|
|
940
942
|
dist_folder = Path(__file__).parent.parent.parent / "dist"
|
|
941
943
|
# Manually declare the PythonWheel so we can set the hashing
|
|
942
944
|
# used to compute the identifier. Can remove if we ever decide to expose the lambda in with_ commands
|
|
943
|
-
with_dist = self.clone(addl_layer=PythonWheels(wheel_dir=dist_folder))
|
|
945
|
+
with_dist = self.clone(addl_layer=PythonWheels(wheel_dir=dist_folder, package_name="flyte", pre=True))
|
|
944
946
|
|
|
945
947
|
return with_dist
|
|
946
948
|
|
|
@@ -253,6 +253,21 @@ class RemoteController(Controller):
|
|
|
253
253
|
await self.cancel_action(action)
|
|
254
254
|
raise
|
|
255
255
|
|
|
256
|
+
# If the action is aborted, we should abort the controller as well
|
|
257
|
+
if n.phase == run_definition_pb2.PHASE_ABORTED:
|
|
258
|
+
logger.warning(f"Action {n.action_id.name} was aborted, aborting current Action{current_action_id.name}")
|
|
259
|
+
raise flyte.errors.RunAbortedError(
|
|
260
|
+
f"Action {n.action_id.name} was aborted, aborting current Action {current_action_id.name}"
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
if n.phase == run_definition_pb2.PHASE_TIMED_OUT:
|
|
264
|
+
logger.warning(
|
|
265
|
+
f"Action {n.action_id.name} timed out, raising timeout exception Action {current_action_id.name}"
|
|
266
|
+
)
|
|
267
|
+
raise flyte.errors.TaskTimeoutError(
|
|
268
|
+
f"Action {n.action_id.name} timed out, raising exception in current Action {current_action_id.name}"
|
|
269
|
+
)
|
|
270
|
+
|
|
256
271
|
if n.has_error() or n.phase == run_definition_pb2.PHASE_FAILED:
|
|
257
272
|
exc = await handle_action_failure(action, _task.name)
|
|
258
273
|
raise exc
|
|
@@ -16,7 +16,6 @@ from flyte._protos.workflow import (
|
|
|
16
16
|
queue_service_pb2,
|
|
17
17
|
task_definition_pb2,
|
|
18
18
|
)
|
|
19
|
-
from flyte.errors import RuntimeSystemError
|
|
20
19
|
|
|
21
20
|
from ._action import Action
|
|
22
21
|
from ._informer import InformerCache
|
|
@@ -125,7 +124,7 @@ class Controller:
|
|
|
125
124
|
async def watch_for_errors(self):
|
|
126
125
|
"""Watch for errors in the background thread"""
|
|
127
126
|
await self._run_coroutine_in_controller_thread(self._bg_watch_for_errors())
|
|
128
|
-
raise RuntimeSystemError(
|
|
127
|
+
raise flyte.errors.RuntimeSystemError(
|
|
129
128
|
code="InformerWatchFailure",
|
|
130
129
|
message=f"Controller thread failed with exception: {self._get_exception()}",
|
|
131
130
|
)
|
|
@@ -164,7 +163,7 @@ class Controller:
|
|
|
164
163
|
raise TimeoutError("Controller thread failed to start in time")
|
|
165
164
|
|
|
166
165
|
if self._get_exception():
|
|
167
|
-
raise RuntimeSystemError(
|
|
166
|
+
raise flyte.errors.RuntimeSystemError(
|
|
168
167
|
type(self._get_exception()).__name__,
|
|
169
168
|
f"Controller thread startup failed: {self._get_exception()}",
|
|
170
169
|
)
|
|
@@ -212,6 +211,7 @@ class Controller:
|
|
|
212
211
|
# Create a new event loop for this thread
|
|
213
212
|
self._loop = asyncio.new_event_loop()
|
|
214
213
|
asyncio.set_event_loop(self._loop)
|
|
214
|
+
self._loop.set_exception_handler(flyte.errors.silence_grpc_polling_error)
|
|
215
215
|
logger.debug(f"Controller thread started with new event loop: {threading.current_thread().name}")
|
|
216
216
|
|
|
217
217
|
# Create an event to signal the errors were observed in the thread's loop
|
|
@@ -37,6 +37,7 @@ from flyte._internal.imagebuild.image_builder import (
|
|
|
37
37
|
LocalDockerCommandImageChecker,
|
|
38
38
|
LocalPodmanCommandImageChecker,
|
|
39
39
|
)
|
|
40
|
+
from flyte._internal.imagebuild.utils import copy_files_to_context
|
|
40
41
|
from flyte._logging import logger
|
|
41
42
|
|
|
42
43
|
_F_IMG_ID = "_F_IMG_ID"
|
|
@@ -109,7 +110,7 @@ RUN uv venv $$VIRTUALENV --python=$PYTHON_VERSION
|
|
|
109
110
|
|
|
110
111
|
# Adds nvidia just in case it exists
|
|
111
112
|
ENV PATH="$$PATH:/usr/local/nvidia/bin:/usr/local/cuda/bin" \
|
|
112
|
-
LD_LIBRARY_PATH="/usr/local/nvidia/lib64
|
|
113
|
+
LD_LIBRARY_PATH="/usr/local/nvidia/lib64"
|
|
113
114
|
""")
|
|
114
115
|
|
|
115
116
|
# This gets added on to the end of the dockerfile
|
|
@@ -128,30 +129,29 @@ class Handler(Protocol):
|
|
|
128
129
|
class PipAndRequirementsHandler:
|
|
129
130
|
@staticmethod
|
|
130
131
|
async def handle(layer: PipPackages, context_path: Path, dockerfile: str) -> str:
|
|
132
|
+
# Set pip_install_args based on the layer type - either a requirements file or a list of packages
|
|
131
133
|
if isinstance(layer, Requirements):
|
|
132
134
|
if not layer.file.exists():
|
|
133
135
|
raise FileNotFoundError(f"Requirements file {layer.file} does not exist")
|
|
134
136
|
if not layer.file.is_file():
|
|
135
137
|
raise ValueError(f"Requirements file {layer.file} is not a file")
|
|
136
138
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
requirements.append(requirement.strip())
|
|
139
|
+
# Copy the requirements file to the context path
|
|
140
|
+
requirements_path = copy_files_to_context(layer.file, context_path)
|
|
141
|
+
pip_install_args = layer.get_pip_install_args()
|
|
142
|
+
pip_install_args.extend(["--requirement", str(requirements_path)])
|
|
142
143
|
else:
|
|
143
144
|
requirements = list(layer.packages) if layer.packages else []
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
await f.write(reqs)
|
|
145
|
+
reqs = " ".join(requirements)
|
|
146
|
+
pip_install_args = layer.get_pip_install_args()
|
|
147
|
+
pip_install_args.append(reqs)
|
|
148
148
|
|
|
149
|
-
pip_install_args = layer.get_pip_install_args()
|
|
150
|
-
pip_install_args.extend(["--requirement", "requirements_uv.txt"])
|
|
151
149
|
secret_mounts = _get_secret_mounts_layer(layer.secret_mounts)
|
|
152
150
|
delta = UV_PACKAGE_INSTALL_COMMAND_TEMPLATE.substitute(
|
|
153
|
-
|
|
151
|
+
SECRET_MOUNT=secret_mounts,
|
|
152
|
+
PIP_INSTALL_ARGS=" ".join(pip_install_args),
|
|
154
153
|
)
|
|
154
|
+
|
|
155
155
|
dockerfile += delta
|
|
156
156
|
|
|
157
157
|
return dockerfile
|
|
@@ -162,12 +162,31 @@ class PythonWheelHandler:
|
|
|
162
162
|
async def handle(layer: PythonWheels, context_path: Path, dockerfile: str) -> str:
|
|
163
163
|
shutil.copytree(layer.wheel_dir, context_path / "dist", dirs_exist_ok=True)
|
|
164
164
|
pip_install_args = layer.get_pip_install_args()
|
|
165
|
-
pip_install_args.extend(["/dist/*.whl"])
|
|
166
165
|
secret_mounts = _get_secret_mounts_layer(layer.secret_mounts)
|
|
167
|
-
|
|
168
|
-
|
|
166
|
+
|
|
167
|
+
# First install: Install the wheel without dependencies using --no-deps
|
|
168
|
+
pip_install_args_no_deps = [
|
|
169
|
+
*pip_install_args,
|
|
170
|
+
*[
|
|
171
|
+
"--find-links",
|
|
172
|
+
"/dist",
|
|
173
|
+
"--no-deps",
|
|
174
|
+
"--no-index",
|
|
175
|
+
layer.package_name,
|
|
176
|
+
],
|
|
177
|
+
]
|
|
178
|
+
|
|
179
|
+
delta1 = UV_WHEEL_INSTALL_COMMAND_TEMPLATE.substitute(
|
|
180
|
+
PIP_INSTALL_ARGS=" ".join(pip_install_args_no_deps), SECRET_MOUNT=secret_mounts
|
|
169
181
|
)
|
|
170
|
-
dockerfile +=
|
|
182
|
+
dockerfile += delta1
|
|
183
|
+
|
|
184
|
+
# Second install: Install dependencies from PyPI
|
|
185
|
+
pip_install_args_deps = [*pip_install_args, layer.package_name]
|
|
186
|
+
delta2 = UV_WHEEL_INSTALL_COMMAND_TEMPLATE.substitute(
|
|
187
|
+
PIP_INSTALL_ARGS=" ".join(pip_install_args_deps), SECRET_MOUNT=secret_mounts
|
|
188
|
+
)
|
|
189
|
+
dockerfile += delta2
|
|
171
190
|
|
|
172
191
|
return dockerfile
|
|
173
192
|
|
|
@@ -26,6 +26,7 @@ from flyte._image import (
|
|
|
26
26
|
UVScript,
|
|
27
27
|
)
|
|
28
28
|
from flyte._internal.imagebuild.image_builder import ImageBuilder, ImageChecker
|
|
29
|
+
from flyte._internal.imagebuild.utils import copy_files_to_context
|
|
29
30
|
from flyte._logging import logger
|
|
30
31
|
from flyte.remote import ActionOutputs, Run
|
|
31
32
|
|
|
@@ -169,7 +170,7 @@ def _get_layers_proto(image: Image, context_path: Path) -> "image_definition_pb2
|
|
|
169
170
|
)
|
|
170
171
|
layers.append(apt_layer)
|
|
171
172
|
elif isinstance(layer, PythonWheels):
|
|
172
|
-
dst_path =
|
|
173
|
+
dst_path = copy_files_to_context(layer.wheel_dir, context_path)
|
|
173
174
|
wheel_layer = image_definition_pb2.Layer(
|
|
174
175
|
python_wheels=image_definition_pb2.PythonWheels(
|
|
175
176
|
dir=str(dst_path.relative_to(context_path)),
|
|
@@ -184,7 +185,7 @@ def _get_layers_proto(image: Image, context_path: Path) -> "image_definition_pb2
|
|
|
184
185
|
layers.append(wheel_layer)
|
|
185
186
|
|
|
186
187
|
elif isinstance(layer, Requirements):
|
|
187
|
-
dst_path =
|
|
188
|
+
dst_path = copy_files_to_context(layer.file, context_path)
|
|
188
189
|
requirements_layer = image_definition_pb2.Layer(
|
|
189
190
|
requirements=image_definition_pb2.Requirements(
|
|
190
191
|
file=str(dst_path.relative_to(context_path)),
|
|
@@ -240,7 +241,7 @@ def _get_layers_proto(image: Image, context_path: Path) -> "image_definition_pb2
|
|
|
240
241
|
elif isinstance(layer, DockerIgnore):
|
|
241
242
|
shutil.copy(layer.path, context_path)
|
|
242
243
|
elif isinstance(layer, CopyConfig):
|
|
243
|
-
dst_path =
|
|
244
|
+
dst_path = copy_files_to_context(layer.src, context_path)
|
|
244
245
|
|
|
245
246
|
copy_layer = image_definition_pb2.Layer(
|
|
246
247
|
copy_config=image_definition_pb2.CopyConfig(
|
|
@@ -264,18 +265,5 @@ def _get_layers_proto(image: Image, context_path: Path) -> "image_definition_pb2
|
|
|
264
265
|
)
|
|
265
266
|
|
|
266
267
|
|
|
267
|
-
def _copy_files_to_context(src: Path, context_path: Path) -> Path:
|
|
268
|
-
if src.is_absolute() or ".." in str(src):
|
|
269
|
-
dst_path = context_path / str(src.absolute()).replace("/", "./_flyte_abs_context/", 1)
|
|
270
|
-
else:
|
|
271
|
-
dst_path = context_path / src
|
|
272
|
-
dst_path.parent.mkdir(parents=True, exist_ok=True)
|
|
273
|
-
if src.is_dir():
|
|
274
|
-
shutil.copytree(src, dst_path, dirs_exist_ok=True)
|
|
275
|
-
else:
|
|
276
|
-
shutil.copy(src, dst_path)
|
|
277
|
-
return dst_path
|
|
278
|
-
|
|
279
|
-
|
|
280
268
|
def _get_fully_qualified_image_name(outputs: ActionOutputs) -> str:
|
|
281
269
|
return outputs.pb2.literals[0].value.scalar.primitive.string_value
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def copy_files_to_context(src: Path, context_path: Path) -> Path:
|
|
6
|
+
"""
|
|
7
|
+
This helper function ensures that absolute paths that users specify are converted correctly to a path in the
|
|
8
|
+
context directory. Doing this prevents collisions while ensuring files are available in the context.
|
|
9
|
+
|
|
10
|
+
For example, if a user has
|
|
11
|
+
img.with_requirements(Path("/Users/username/requirements.txt"))
|
|
12
|
+
.with_requirements(Path("requirements.txt"))
|
|
13
|
+
.with_requirements(Path("../requirements.txt"))
|
|
14
|
+
|
|
15
|
+
copying with this function ensures that the Docker context folder has all three files.
|
|
16
|
+
|
|
17
|
+
:param src: The source path to copy
|
|
18
|
+
:param context_path: The context path where the files should be copied to
|
|
19
|
+
"""
|
|
20
|
+
if src.is_absolute() or ".." in str(src):
|
|
21
|
+
dst_path = context_path / str(src.absolute()).replace("/", "./_flyte_abs_context/", 1)
|
|
22
|
+
else:
|
|
23
|
+
dst_path = context_path / src
|
|
24
|
+
dst_path.parent.mkdir(parents=True, exist_ok=True)
|
|
25
|
+
if src.is_dir():
|
|
26
|
+
shutil.copytree(src, dst_path, dirs_exist_ok=True)
|
|
27
|
+
else:
|
|
28
|
+
shutil.copy(src, dst_path)
|
|
29
|
+
return dst_path
|
flyte/_internal/runtime/rusty.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import sys
|
|
3
|
+
import time
|
|
2
4
|
from typing import Any, List, Tuple
|
|
3
5
|
|
|
4
6
|
from flyte._context import contextual_run
|
|
@@ -75,19 +77,23 @@ async def create_controller(
|
|
|
75
77
|
:return:
|
|
76
78
|
"""
|
|
77
79
|
logger.info(f"[rusty] Creating controller with endpoint {endpoint}")
|
|
80
|
+
import flyte.errors
|
|
78
81
|
from flyte._initialize import init
|
|
79
82
|
|
|
80
|
-
|
|
83
|
+
loop = asyncio.get_event_loop()
|
|
84
|
+
loop.set_exception_handler(flyte.errors.silence_grpc_polling_error)
|
|
85
|
+
|
|
86
|
+
# TODO Currently reference tasks are not supported in Rusty.
|
|
81
87
|
await init.aio()
|
|
82
88
|
controller_kwargs: dict[str, Any] = {"insecure": insecure}
|
|
83
89
|
if api_key:
|
|
84
|
-
logger.info("Using api key from environment")
|
|
90
|
+
logger.info("[rusty] Using api key from environment")
|
|
85
91
|
controller_kwargs["api_key"] = api_key
|
|
86
92
|
else:
|
|
87
93
|
controller_kwargs["endpoint"] = endpoint
|
|
88
94
|
if "localhost" in endpoint or "docker" in endpoint:
|
|
89
95
|
controller_kwargs["insecure"] = True
|
|
90
|
-
logger.debug(f"Using controller endpoint: {endpoint} with kwargs: {controller_kwargs}")
|
|
96
|
+
logger.debug(f"[rusty] Using controller endpoint: {endpoint} with kwargs: {controller_kwargs}")
|
|
91
97
|
|
|
92
98
|
return _create_controller(ct="remote", **controller_kwargs)
|
|
93
99
|
|
|
@@ -130,22 +136,39 @@ async def run_task(
|
|
|
130
136
|
:param input_path: Optional input path for the task.
|
|
131
137
|
:return: The loaded task template.
|
|
132
138
|
"""
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
controller=controller,
|
|
140
|
-
raw_data_path=RawDataPath(path=raw_data_path),
|
|
141
|
-
output_path=output_path,
|
|
142
|
-
run_base_dir=run_base_dir,
|
|
143
|
-
checkpoints=Checkpoints(prev_checkpoint_path=prev_checkpoint, checkpoint_path=checkpoint_path),
|
|
144
|
-
code_bundle=code_bundle,
|
|
145
|
-
input_path=input_path,
|
|
146
|
-
image_cache=ImageCache.from_transport(image_cache) if image_cache else None,
|
|
139
|
+
start_time = time.time()
|
|
140
|
+
action_id = f"{org}/{project}/{domain}/{run_name}/{name}"
|
|
141
|
+
|
|
142
|
+
logger.info(
|
|
143
|
+
f"[rusty] Running task '{task.name}' (action: {action_id})"
|
|
144
|
+
f" at {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(start_time))}"
|
|
147
145
|
)
|
|
148
|
-
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
await contextual_run(
|
|
149
|
+
extract_download_run_upload,
|
|
150
|
+
task,
|
|
151
|
+
action=ActionID(name=name, org=org, project=project, domain=domain, run_name=run_name),
|
|
152
|
+
version=version,
|
|
153
|
+
controller=controller,
|
|
154
|
+
raw_data_path=RawDataPath(path=raw_data_path),
|
|
155
|
+
output_path=output_path,
|
|
156
|
+
run_base_dir=run_base_dir,
|
|
157
|
+
checkpoints=Checkpoints(prev_checkpoint_path=prev_checkpoint, checkpoint_path=checkpoint_path),
|
|
158
|
+
code_bundle=code_bundle,
|
|
159
|
+
input_path=input_path,
|
|
160
|
+
image_cache=ImageCache.from_transport(image_cache) if image_cache else None,
|
|
161
|
+
)
|
|
162
|
+
except Exception as e:
|
|
163
|
+
logger.error(f"[rusty] Task failed: {e!s}")
|
|
164
|
+
raise
|
|
165
|
+
finally:
|
|
166
|
+
end_time = time.time()
|
|
167
|
+
duration = end_time - start_time
|
|
168
|
+
logger.info(
|
|
169
|
+
f"[rusty] TASK_EXECUTION_END: Task '{task.name}' (action: {action_id})"
|
|
170
|
+
f" done after {duration:.2f}s at {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(end_time))}"
|
|
171
|
+
)
|
|
149
172
|
|
|
150
173
|
|
|
151
174
|
async def ping(name: str) -> str:
|
flyte/_run.py
CHANGED
|
@@ -18,7 +18,6 @@ from flyte._initialize import (
|
|
|
18
18
|
requires_storage,
|
|
19
19
|
)
|
|
20
20
|
from flyte._logging import logger
|
|
21
|
-
from flyte._protos.common import identifier_pb2
|
|
22
21
|
from flyte._task import P, R, TaskTemplate
|
|
23
22
|
from flyte._tools import ipython_check
|
|
24
23
|
from flyte.errors import InitializationError
|
|
@@ -411,6 +410,7 @@ class _Runner:
|
|
|
411
410
|
async def _run_local(self, obj: TaskTemplate[P, R], *args: P.args, **kwargs: P.kwargs) -> Run:
|
|
412
411
|
from flyte._internal.controllers import create_controller
|
|
413
412
|
from flyte._internal.controllers._local_controller import LocalController
|
|
413
|
+
from flyte._protos.common import identifier_pb2
|
|
414
414
|
from flyte.remote import Run
|
|
415
415
|
from flyte.report import Report
|
|
416
416
|
|
|
@@ -512,7 +512,7 @@ class _Runner:
|
|
|
512
512
|
raise ValueError("Remote task can only be run in remote mode.")
|
|
513
513
|
|
|
514
514
|
if not isinstance(task, TaskTemplate) and not isinstance(task, LazyEntity):
|
|
515
|
-
raise TypeError("On Flyte tasks can be run, not generic functions or methods.")
|
|
515
|
+
raise TypeError(f"On Flyte tasks can be run, not generic functions or methods '{type(task)}'.")
|
|
516
516
|
|
|
517
517
|
if self._mode == "remote":
|
|
518
518
|
return await self._run_remote(task, *args, **kwargs)
|
flyte/_task_environment.py
CHANGED
|
@@ -62,7 +62,7 @@ class TaskEnvironment(Environment):
|
|
|
62
62
|
:param reusable: Reuse policy for the environment, if set, a python process may be reused for multiple tasks.
|
|
63
63
|
"""
|
|
64
64
|
|
|
65
|
-
cache:
|
|
65
|
+
cache: CacheRequest = "disable"
|
|
66
66
|
reusable: ReusePolicy | None = None
|
|
67
67
|
plugin_config: Optional[Any] = None
|
|
68
68
|
# TODO Shall we make this union of string or env? This way we can lookup the env by module/file:name
|
|
@@ -134,7 +134,7 @@ class TaskEnvironment(Environment):
|
|
|
134
134
|
_func=None,
|
|
135
135
|
*,
|
|
136
136
|
name: Optional[str] = None,
|
|
137
|
-
cache:
|
|
137
|
+
cache: CacheRequest | None = None,
|
|
138
138
|
retries: Union[int, RetryStrategy] = 0,
|
|
139
139
|
timeout: Union[timedelta, int] = 0,
|
|
140
140
|
docs: Optional[Documentation] = None,
|
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.0b8'
|
|
21
|
+
__version_tuple__ = version_tuple = (2, 0, 0, 'b8')
|
flyte/cli/_build.py
CHANGED
|
@@ -54,7 +54,7 @@ class BuildEnvCommand(click.Command):
|
|
|
54
54
|
with console.status("Building...", spinner="dots"):
|
|
55
55
|
image_cache = flyte.build_images(self.obj)
|
|
56
56
|
|
|
57
|
-
console.print(common.
|
|
57
|
+
console.print(common.format("Images", image_cache.repr(), obj.output_format))
|
|
58
58
|
|
|
59
59
|
|
|
60
60
|
class EnvPerFileGroup(common.ObjectsPerFileGroup):
|
flyte/cli/_common.py
CHANGED
|
@@ -8,19 +8,22 @@ from abc import abstractmethod
|
|
|
8
8
|
from dataclasses import dataclass, replace
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
from types import MappingProxyType, ModuleType
|
|
11
|
-
from typing import Any, Dict, Iterable, List, Optional
|
|
11
|
+
from typing import Any, Dict, Iterable, List, Literal, Optional
|
|
12
12
|
|
|
13
13
|
import rich.box
|
|
14
14
|
import rich.repr
|
|
15
15
|
import rich_click as click
|
|
16
16
|
from rich.console import Console
|
|
17
17
|
from rich.panel import Panel
|
|
18
|
+
from rich.pretty import pretty_repr
|
|
18
19
|
from rich.table import Table
|
|
19
20
|
from rich.traceback import Traceback
|
|
20
21
|
|
|
21
22
|
import flyte.errors
|
|
22
23
|
from flyte.config import Config
|
|
23
24
|
|
|
25
|
+
OutputFormat = Literal["table", "json", "table-simple"]
|
|
26
|
+
|
|
24
27
|
PREFERRED_BORDER_COLOR = "dim cyan"
|
|
25
28
|
PREFERRED_ACCENT_COLOR = "bold #FFD700"
|
|
26
29
|
HEADER_STYLE = f"{PREFERRED_ACCENT_COLOR} on black"
|
|
@@ -99,8 +102,8 @@ class CLIConfig:
|
|
|
99
102
|
endpoint: str | None = None
|
|
100
103
|
insecure: bool = False
|
|
101
104
|
org: str | None = None
|
|
102
|
-
simple: bool = False
|
|
103
105
|
auth_type: str | None = None
|
|
106
|
+
output_format: OutputFormat = "table"
|
|
104
107
|
|
|
105
108
|
def replace(self, **kwargs) -> CLIConfig:
|
|
106
109
|
"""
|
|
@@ -327,20 +330,7 @@ class FileGroup(GroupBase):
|
|
|
327
330
|
raise NotImplementedError
|
|
328
331
|
|
|
329
332
|
|
|
330
|
-
def
|
|
331
|
-
"""
|
|
332
|
-
Get a table from a list of values.
|
|
333
|
-
"""
|
|
334
|
-
if simple:
|
|
335
|
-
table = Table(title, box=None)
|
|
336
|
-
else:
|
|
337
|
-
table = Table(
|
|
338
|
-
title=title,
|
|
339
|
-
box=rich.box.SQUARE_DOUBLE_HEAD,
|
|
340
|
-
header_style=HEADER_STYLE,
|
|
341
|
-
show_header=True,
|
|
342
|
-
border_style=PREFERRED_BORDER_COLOR,
|
|
343
|
-
)
|
|
333
|
+
def _table_format(table: Table, vals: Iterable[Any]) -> Table:
|
|
344
334
|
headers = None
|
|
345
335
|
has_rich_repr = False
|
|
346
336
|
for p in vals:
|
|
@@ -357,11 +347,37 @@ def get_table(title: str, vals: Iterable[Any], simple: bool = False) -> Table:
|
|
|
357
347
|
return table
|
|
358
348
|
|
|
359
349
|
|
|
360
|
-
def
|
|
350
|
+
def format(title: str, vals: Iterable[Any], of: OutputFormat = "table") -> Table | Any:
|
|
351
|
+
"""
|
|
352
|
+
Get a table from a list of values.
|
|
353
|
+
"""
|
|
354
|
+
|
|
355
|
+
match of:
|
|
356
|
+
case "table-simple":
|
|
357
|
+
return _table_format(Table(title, box=None), vals)
|
|
358
|
+
case "table":
|
|
359
|
+
return _table_format(
|
|
360
|
+
Table(
|
|
361
|
+
title=title,
|
|
362
|
+
box=rich.box.SQUARE_DOUBLE_HEAD,
|
|
363
|
+
header_style=HEADER_STYLE,
|
|
364
|
+
show_header=True,
|
|
365
|
+
border_style=PREFERRED_BORDER_COLOR,
|
|
366
|
+
),
|
|
367
|
+
vals,
|
|
368
|
+
)
|
|
369
|
+
case "json":
|
|
370
|
+
if not vals:
|
|
371
|
+
return pretty_repr([])
|
|
372
|
+
return pretty_repr([v.to_dict() for v in vals])
|
|
373
|
+
raise click.ClickException("Unknown output format. Supported formats are: table, table-simple, json.")
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def get_panel(title: str, renderable: Any, of: OutputFormat = "table") -> Panel:
|
|
361
377
|
"""
|
|
362
378
|
Get a panel from a list of values.
|
|
363
379
|
"""
|
|
364
|
-
if simple:
|
|
380
|
+
if of in ["table-simple", "json"]:
|
|
365
381
|
return renderable
|
|
366
382
|
return Panel.fit(
|
|
367
383
|
renderable,
|
flyte/cli/_create.py
CHANGED
|
@@ -4,6 +4,7 @@ from typing import Any, Dict, get_args
|
|
|
4
4
|
import rich_click as click
|
|
5
5
|
|
|
6
6
|
import flyte.cli._common as common
|
|
7
|
+
from flyte.cli._option import MutuallyExclusiveOption
|
|
7
8
|
from flyte.remote import SecretTypes
|
|
8
9
|
|
|
9
10
|
|
|
@@ -16,8 +17,21 @@ def create():
|
|
|
16
17
|
|
|
17
18
|
@create.command(cls=common.CommandBase)
|
|
18
19
|
@click.argument("name", type=str, required=True)
|
|
19
|
-
@click.
|
|
20
|
-
|
|
20
|
+
@click.option(
|
|
21
|
+
"--value",
|
|
22
|
+
help="Secret value",
|
|
23
|
+
prompt="Enter secret value",
|
|
24
|
+
hide_input=True,
|
|
25
|
+
cls=MutuallyExclusiveOption,
|
|
26
|
+
mutually_exclusive=["from_file"],
|
|
27
|
+
)
|
|
28
|
+
@click.option(
|
|
29
|
+
"--from-file",
|
|
30
|
+
type=click.Path(exists=True),
|
|
31
|
+
help="Path to the file with the binary secret.",
|
|
32
|
+
cls=MutuallyExclusiveOption,
|
|
33
|
+
mutually_exclusive=["value"],
|
|
34
|
+
)
|
|
21
35
|
@click.option(
|
|
22
36
|
"--type", type=click.Choice(get_args(SecretTypes)), default="regular", help="Type of the secret.", show_default=True
|
|
23
37
|
)
|
|
@@ -38,6 +52,14 @@ def secret(
|
|
|
38
52
|
$ flyte create secret my_secret --value my_value
|
|
39
53
|
```
|
|
40
54
|
|
|
55
|
+
If you don't provide a `--value` flag, you will be prompted to enter the
|
|
56
|
+
secret value in the terminal.
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
$ flyte create secret my_secret
|
|
60
|
+
Enter secret value:
|
|
61
|
+
```
|
|
62
|
+
|
|
41
63
|
If `--from-file` is specified, the value will be read from the file instead of being provided directly:
|
|
42
64
|
|
|
43
65
|
```bash
|