flyte 0.1.0__py3-none-any.whl → 0.2.0a0__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/__init__.py +78 -2
- flyte/_bin/__init__.py +0 -0
- flyte/_bin/runtime.py +152 -0
- flyte/_build.py +26 -0
- flyte/_cache/__init__.py +12 -0
- flyte/_cache/cache.py +145 -0
- flyte/_cache/defaults.py +9 -0
- flyte/_cache/policy_function_body.py +42 -0
- flyte/_code_bundle/__init__.py +8 -0
- flyte/_code_bundle/_ignore.py +113 -0
- flyte/_code_bundle/_packaging.py +187 -0
- flyte/_code_bundle/_utils.py +323 -0
- flyte/_code_bundle/bundle.py +209 -0
- flyte/_context.py +152 -0
- flyte/_deploy.py +243 -0
- flyte/_doc.py +29 -0
- flyte/_docstring.py +32 -0
- flyte/_environment.py +84 -0
- flyte/_excepthook.py +37 -0
- flyte/_group.py +32 -0
- flyte/_hash.py +23 -0
- flyte/_image.py +762 -0
- flyte/_initialize.py +492 -0
- flyte/_interface.py +84 -0
- flyte/_internal/__init__.py +3 -0
- flyte/_internal/controllers/__init__.py +128 -0
- flyte/_internal/controllers/_local_controller.py +193 -0
- flyte/_internal/controllers/_trace.py +41 -0
- flyte/_internal/controllers/remote/__init__.py +60 -0
- flyte/_internal/controllers/remote/_action.py +146 -0
- flyte/_internal/controllers/remote/_client.py +47 -0
- flyte/_internal/controllers/remote/_controller.py +494 -0
- flyte/_internal/controllers/remote/_core.py +410 -0
- flyte/_internal/controllers/remote/_informer.py +361 -0
- flyte/_internal/controllers/remote/_service_protocol.py +50 -0
- flyte/_internal/imagebuild/__init__.py +11 -0
- flyte/_internal/imagebuild/docker_builder.py +427 -0
- flyte/_internal/imagebuild/image_builder.py +246 -0
- flyte/_internal/imagebuild/remote_builder.py +0 -0
- flyte/_internal/resolvers/__init__.py +0 -0
- flyte/_internal/resolvers/_task_module.py +54 -0
- flyte/_internal/resolvers/common.py +31 -0
- flyte/_internal/resolvers/default.py +28 -0
- flyte/_internal/runtime/__init__.py +0 -0
- flyte/_internal/runtime/convert.py +342 -0
- flyte/_internal/runtime/entrypoints.py +135 -0
- flyte/_internal/runtime/io.py +136 -0
- flyte/_internal/runtime/resources_serde.py +138 -0
- flyte/_internal/runtime/task_serde.py +330 -0
- flyte/_internal/runtime/taskrunner.py +191 -0
- flyte/_internal/runtime/types_serde.py +54 -0
- flyte/_logging.py +135 -0
- flyte/_map.py +215 -0
- flyte/_pod.py +19 -0
- flyte/_protos/__init__.py +0 -0
- flyte/_protos/common/authorization_pb2.py +66 -0
- flyte/_protos/common/authorization_pb2.pyi +108 -0
- flyte/_protos/common/authorization_pb2_grpc.py +4 -0
- flyte/_protos/common/identifier_pb2.py +71 -0
- flyte/_protos/common/identifier_pb2.pyi +82 -0
- flyte/_protos/common/identifier_pb2_grpc.py +4 -0
- flyte/_protos/common/identity_pb2.py +48 -0
- flyte/_protos/common/identity_pb2.pyi +72 -0
- flyte/_protos/common/identity_pb2_grpc.py +4 -0
- flyte/_protos/common/list_pb2.py +36 -0
- flyte/_protos/common/list_pb2.pyi +71 -0
- flyte/_protos/common/list_pb2_grpc.py +4 -0
- flyte/_protos/common/policy_pb2.py +37 -0
- flyte/_protos/common/policy_pb2.pyi +27 -0
- flyte/_protos/common/policy_pb2_grpc.py +4 -0
- flyte/_protos/common/role_pb2.py +37 -0
- flyte/_protos/common/role_pb2.pyi +53 -0
- flyte/_protos/common/role_pb2_grpc.py +4 -0
- flyte/_protos/common/runtime_version_pb2.py +28 -0
- flyte/_protos/common/runtime_version_pb2.pyi +24 -0
- flyte/_protos/common/runtime_version_pb2_grpc.py +4 -0
- flyte/_protos/logs/dataplane/payload_pb2.py +100 -0
- flyte/_protos/logs/dataplane/payload_pb2.pyi +177 -0
- flyte/_protos/logs/dataplane/payload_pb2_grpc.py +4 -0
- flyte/_protos/secret/definition_pb2.py +49 -0
- flyte/_protos/secret/definition_pb2.pyi +93 -0
- flyte/_protos/secret/definition_pb2_grpc.py +4 -0
- flyte/_protos/secret/payload_pb2.py +62 -0
- flyte/_protos/secret/payload_pb2.pyi +94 -0
- flyte/_protos/secret/payload_pb2_grpc.py +4 -0
- flyte/_protos/secret/secret_pb2.py +38 -0
- flyte/_protos/secret/secret_pb2.pyi +6 -0
- flyte/_protos/secret/secret_pb2_grpc.py +198 -0
- flyte/_protos/secret/secret_pb2_grpc_grpc.py +198 -0
- flyte/_protos/validate/validate/validate_pb2.py +76 -0
- flyte/_protos/workflow/common_pb2.py +27 -0
- flyte/_protos/workflow/common_pb2.pyi +14 -0
- flyte/_protos/workflow/common_pb2_grpc.py +4 -0
- flyte/_protos/workflow/environment_pb2.py +29 -0
- flyte/_protos/workflow/environment_pb2.pyi +12 -0
- flyte/_protos/workflow/environment_pb2_grpc.py +4 -0
- flyte/_protos/workflow/node_execution_service_pb2.py +26 -0
- flyte/_protos/workflow/node_execution_service_pb2.pyi +4 -0
- flyte/_protos/workflow/node_execution_service_pb2_grpc.py +32 -0
- flyte/_protos/workflow/queue_service_pb2.py +105 -0
- flyte/_protos/workflow/queue_service_pb2.pyi +146 -0
- flyte/_protos/workflow/queue_service_pb2_grpc.py +172 -0
- flyte/_protos/workflow/run_definition_pb2.py +128 -0
- flyte/_protos/workflow/run_definition_pb2.pyi +314 -0
- flyte/_protos/workflow/run_definition_pb2_grpc.py +4 -0
- flyte/_protos/workflow/run_logs_service_pb2.py +41 -0
- flyte/_protos/workflow/run_logs_service_pb2.pyi +28 -0
- flyte/_protos/workflow/run_logs_service_pb2_grpc.py +69 -0
- flyte/_protos/workflow/run_service_pb2.py +129 -0
- flyte/_protos/workflow/run_service_pb2.pyi +171 -0
- flyte/_protos/workflow/run_service_pb2_grpc.py +412 -0
- flyte/_protos/workflow/state_service_pb2.py +66 -0
- flyte/_protos/workflow/state_service_pb2.pyi +75 -0
- flyte/_protos/workflow/state_service_pb2_grpc.py +138 -0
- flyte/_protos/workflow/task_definition_pb2.py +79 -0
- flyte/_protos/workflow/task_definition_pb2.pyi +81 -0
- flyte/_protos/workflow/task_definition_pb2_grpc.py +4 -0
- flyte/_protos/workflow/task_service_pb2.py +60 -0
- flyte/_protos/workflow/task_service_pb2.pyi +59 -0
- flyte/_protos/workflow/task_service_pb2_grpc.py +138 -0
- flyte/_resources.py +226 -0
- flyte/_retry.py +32 -0
- flyte/_reusable_environment.py +25 -0
- flyte/_run.py +482 -0
- flyte/_secret.py +61 -0
- flyte/_task.py +449 -0
- flyte/_task_environment.py +183 -0
- flyte/_timeout.py +47 -0
- flyte/_tools.py +27 -0
- flyte/_trace.py +120 -0
- flyte/_utils/__init__.py +26 -0
- flyte/_utils/asyn.py +119 -0
- flyte/_utils/async_cache.py +139 -0
- flyte/_utils/coro_management.py +23 -0
- flyte/_utils/file_handling.py +72 -0
- flyte/_utils/helpers.py +134 -0
- flyte/_utils/lazy_module.py +54 -0
- flyte/_utils/org_discovery.py +57 -0
- flyte/_utils/uv_script_parser.py +49 -0
- flyte/_version.py +21 -0
- flyte/cli/__init__.py +3 -0
- flyte/cli/_abort.py +28 -0
- flyte/cli/_common.py +337 -0
- flyte/cli/_create.py +145 -0
- flyte/cli/_delete.py +23 -0
- flyte/cli/_deploy.py +152 -0
- flyte/cli/_gen.py +163 -0
- flyte/cli/_get.py +310 -0
- flyte/cli/_params.py +538 -0
- flyte/cli/_run.py +231 -0
- flyte/cli/main.py +166 -0
- flyte/config/__init__.py +3 -0
- flyte/config/_config.py +216 -0
- flyte/config/_internal.py +64 -0
- flyte/config/_reader.py +207 -0
- flyte/connectors/__init__.py +0 -0
- flyte/errors.py +172 -0
- flyte/extras/__init__.py +5 -0
- flyte/extras/_container.py +263 -0
- flyte/io/__init__.py +27 -0
- flyte/io/_dir.py +448 -0
- flyte/io/_file.py +467 -0
- flyte/io/_structured_dataset/__init__.py +129 -0
- flyte/io/_structured_dataset/basic_dfs.py +219 -0
- flyte/io/_structured_dataset/structured_dataset.py +1061 -0
- flyte/models.py +391 -0
- flyte/remote/__init__.py +26 -0
- flyte/remote/_client/__init__.py +0 -0
- flyte/remote/_client/_protocols.py +133 -0
- flyte/remote/_client/auth/__init__.py +12 -0
- flyte/remote/_client/auth/_auth_utils.py +14 -0
- flyte/remote/_client/auth/_authenticators/__init__.py +0 -0
- flyte/remote/_client/auth/_authenticators/base.py +397 -0
- flyte/remote/_client/auth/_authenticators/client_credentials.py +73 -0
- flyte/remote/_client/auth/_authenticators/device_code.py +118 -0
- flyte/remote/_client/auth/_authenticators/external_command.py +79 -0
- flyte/remote/_client/auth/_authenticators/factory.py +200 -0
- flyte/remote/_client/auth/_authenticators/pkce.py +516 -0
- flyte/remote/_client/auth/_channel.py +215 -0
- flyte/remote/_client/auth/_client_config.py +83 -0
- flyte/remote/_client/auth/_default_html.py +32 -0
- flyte/remote/_client/auth/_grpc_utils/__init__.py +0 -0
- flyte/remote/_client/auth/_grpc_utils/auth_interceptor.py +288 -0
- flyte/remote/_client/auth/_grpc_utils/default_metadata_interceptor.py +151 -0
- flyte/remote/_client/auth/_keyring.py +143 -0
- flyte/remote/_client/auth/_token_client.py +260 -0
- flyte/remote/_client/auth/errors.py +16 -0
- flyte/remote/_client/controlplane.py +95 -0
- flyte/remote/_console.py +18 -0
- flyte/remote/_data.py +159 -0
- flyte/remote/_logs.py +176 -0
- flyte/remote/_project.py +85 -0
- flyte/remote/_run.py +970 -0
- flyte/remote/_secret.py +132 -0
- flyte/remote/_task.py +391 -0
- flyte/report/__init__.py +3 -0
- flyte/report/_report.py +178 -0
- flyte/report/_template.html +124 -0
- flyte/storage/__init__.py +29 -0
- flyte/storage/_config.py +233 -0
- flyte/storage/_remote_fs.py +34 -0
- flyte/storage/_storage.py +271 -0
- flyte/storage/_utils.py +5 -0
- flyte/syncify/__init__.py +56 -0
- flyte/syncify/_api.py +371 -0
- flyte/types/__init__.py +36 -0
- flyte/types/_interface.py +40 -0
- flyte/types/_pickle.py +118 -0
- flyte/types/_renderer.py +162 -0
- flyte/types/_string_literals.py +120 -0
- flyte/types/_type_engine.py +2287 -0
- flyte/types/_utils.py +80 -0
- flyte-0.2.0a0.dist-info/METADATA +249 -0
- flyte-0.2.0a0.dist-info/RECORD +218 -0
- {flyte-0.1.0.dist-info → flyte-0.2.0a0.dist-info}/WHEEL +2 -1
- flyte-0.2.0a0.dist-info/entry_points.txt +3 -0
- flyte-0.2.0a0.dist-info/top_level.txt +1 -0
- flyte-0.1.0.dist-info/METADATA +0 -6
- flyte-0.1.0.dist-info/RECORD +0 -5
flyte/_run.py
ADDED
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import pathlib
|
|
5
|
+
import uuid
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Literal, Optional, Tuple, Union, cast
|
|
7
|
+
|
|
8
|
+
import flyte.errors
|
|
9
|
+
from flyte.errors import InitializationError
|
|
10
|
+
from flyte.models import ActionID, Checkpoints, CodeBundle, RawDataPath, SerializationContext, TaskContext
|
|
11
|
+
from flyte.syncify import syncify
|
|
12
|
+
|
|
13
|
+
from ._context import contextual_run, internal_ctx
|
|
14
|
+
from ._environment import Environment
|
|
15
|
+
from ._initialize import (
|
|
16
|
+
_get_init_config,
|
|
17
|
+
get_client,
|
|
18
|
+
get_common_config,
|
|
19
|
+
get_storage,
|
|
20
|
+
requires_initialization,
|
|
21
|
+
requires_storage,
|
|
22
|
+
)
|
|
23
|
+
from ._logging import logger
|
|
24
|
+
from ._task import P, R, TaskTemplate
|
|
25
|
+
from ._tools import ipython_check
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from flyte.remote import Run
|
|
29
|
+
from flyte.remote._task import LazyEntity
|
|
30
|
+
|
|
31
|
+
from ._code_bundle import CopyFiles
|
|
32
|
+
|
|
33
|
+
Mode = Literal["local", "remote", "hybrid"]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
async def _get_code_bundle_for_run(name: str) -> CodeBundle | None:
|
|
37
|
+
"""
|
|
38
|
+
Get the code bundle for the run with the given name.
|
|
39
|
+
This is used to get the code bundle for the run when running in hybrid mode.
|
|
40
|
+
"""
|
|
41
|
+
from flyte._internal.runtime.task_serde import extract_code_bundle
|
|
42
|
+
from flyte.remote import Run
|
|
43
|
+
|
|
44
|
+
run = await Run.get.aio(name=name)
|
|
45
|
+
if run:
|
|
46
|
+
run_details = await run.details()
|
|
47
|
+
spec = run_details.action_details.pb2.resolved_task_spec
|
|
48
|
+
return extract_code_bundle(spec)
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class _Runner:
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
force_mode: Mode | None = None,
|
|
56
|
+
name: Optional[str] = None,
|
|
57
|
+
service_account: Optional[str] = None,
|
|
58
|
+
version: Optional[str] = None,
|
|
59
|
+
copy_style: CopyFiles = "loaded_modules",
|
|
60
|
+
dry_run: bool = False,
|
|
61
|
+
copy_bundle_to: pathlib.Path | None = None,
|
|
62
|
+
interactive_mode: bool | None = None,
|
|
63
|
+
raw_data_path: str | None = None,
|
|
64
|
+
metadata_path: str | None = None,
|
|
65
|
+
run_base_dir: str | None = None,
|
|
66
|
+
overwrite_cache: bool = False,
|
|
67
|
+
):
|
|
68
|
+
init_config = _get_init_config()
|
|
69
|
+
client = init_config.client if init_config else None
|
|
70
|
+
if not force_mode and client is not None:
|
|
71
|
+
force_mode = "remote"
|
|
72
|
+
force_mode = force_mode or "local"
|
|
73
|
+
logger.debug(f"Effective run mode: `{force_mode}`, client configured: `{client is not None}`")
|
|
74
|
+
self._mode = force_mode
|
|
75
|
+
self._name = name
|
|
76
|
+
self._service_account = service_account
|
|
77
|
+
self._version = version
|
|
78
|
+
self._copy_files = copy_style
|
|
79
|
+
self._dry_run = dry_run
|
|
80
|
+
self._copy_bundle_to = copy_bundle_to
|
|
81
|
+
self._interactive_mode = interactive_mode if interactive_mode else ipython_check()
|
|
82
|
+
self._raw_data_path = raw_data_path
|
|
83
|
+
self._metadata_path = metadata_path or "/tmp"
|
|
84
|
+
self._run_base_dir = run_base_dir or "/tmp/base"
|
|
85
|
+
self._overwrite_cache = overwrite_cache
|
|
86
|
+
|
|
87
|
+
@requires_initialization
|
|
88
|
+
async def _run_remote(self, obj: TaskTemplate[P, R] | LazyEntity, *args: P.args, **kwargs: P.kwargs) -> Run:
|
|
89
|
+
import grpc
|
|
90
|
+
|
|
91
|
+
from flyte.remote import Run
|
|
92
|
+
from flyte.remote._task import LazyEntity
|
|
93
|
+
|
|
94
|
+
from ._code_bundle import build_code_bundle, build_pkl_bundle
|
|
95
|
+
from ._deploy import build_images, plan_deploy
|
|
96
|
+
from ._internal.runtime.convert import convert_from_native_to_inputs
|
|
97
|
+
from ._internal.runtime.task_serde import translate_task_to_wire
|
|
98
|
+
from ._protos.common import identifier_pb2
|
|
99
|
+
from ._protos.workflow import run_definition_pb2, run_service_pb2
|
|
100
|
+
|
|
101
|
+
cfg = get_common_config()
|
|
102
|
+
|
|
103
|
+
if isinstance(obj, LazyEntity):
|
|
104
|
+
task = await obj.fetch.aio()
|
|
105
|
+
task_spec = task.pb2.spec
|
|
106
|
+
inputs = await convert_from_native_to_inputs(task.interface, *args, **kwargs)
|
|
107
|
+
version = task.pb2.task_id.version
|
|
108
|
+
code_bundle = None
|
|
109
|
+
else:
|
|
110
|
+
if obj.parent_env is None:
|
|
111
|
+
raise ValueError("Task is not attached to an environment. Please attach the task to an environment")
|
|
112
|
+
|
|
113
|
+
deploy_plan = plan_deploy(cast(Environment, obj.parent_env()))
|
|
114
|
+
image_cache = await build_images(deploy_plan)
|
|
115
|
+
|
|
116
|
+
if self._interactive_mode:
|
|
117
|
+
code_bundle = await build_pkl_bundle(
|
|
118
|
+
obj, upload_to_controlplane=not self._dry_run, copy_bundle_to=self._copy_bundle_to
|
|
119
|
+
)
|
|
120
|
+
else:
|
|
121
|
+
if self._copy_files != "none":
|
|
122
|
+
code_bundle = await build_code_bundle(
|
|
123
|
+
from_dir=cfg.root_dir,
|
|
124
|
+
dryrun=self._dry_run,
|
|
125
|
+
copy_bundle_to=self._copy_bundle_to,
|
|
126
|
+
copy_style=self._copy_files,
|
|
127
|
+
)
|
|
128
|
+
else:
|
|
129
|
+
code_bundle = None
|
|
130
|
+
|
|
131
|
+
version = self._version or (
|
|
132
|
+
code_bundle.computed_version if code_bundle and code_bundle.computed_version else None
|
|
133
|
+
)
|
|
134
|
+
if not version:
|
|
135
|
+
raise ValueError("Version is required when running a task")
|
|
136
|
+
s_ctx = SerializationContext(
|
|
137
|
+
code_bundle=code_bundle,
|
|
138
|
+
version=version,
|
|
139
|
+
image_cache=image_cache,
|
|
140
|
+
root_dir=cfg.root_dir,
|
|
141
|
+
)
|
|
142
|
+
task_spec = translate_task_to_wire(obj, s_ctx)
|
|
143
|
+
inputs = await convert_from_native_to_inputs(obj.native_interface, *args, **kwargs)
|
|
144
|
+
|
|
145
|
+
if not self._dry_run:
|
|
146
|
+
if get_client() is None:
|
|
147
|
+
# This can only happen, if the user forces flyte.run(mode="remote") without initializing the client
|
|
148
|
+
raise InitializationError(
|
|
149
|
+
"ClientNotInitializedError",
|
|
150
|
+
"user",
|
|
151
|
+
"flyte.run requires client to be initialized. "
|
|
152
|
+
"Call flyte.init() with a valid endpoint or api-key before using this function.",
|
|
153
|
+
)
|
|
154
|
+
run_id = None
|
|
155
|
+
project_id = None
|
|
156
|
+
if self._name:
|
|
157
|
+
run_id = run_definition_pb2.RunIdentifier(
|
|
158
|
+
project=cfg.project,
|
|
159
|
+
domain=cfg.domain,
|
|
160
|
+
org=cfg.org,
|
|
161
|
+
name=self._name if self._name else None,
|
|
162
|
+
)
|
|
163
|
+
else:
|
|
164
|
+
project_id = identifier_pb2.ProjectIdentifier(
|
|
165
|
+
name=cfg.project,
|
|
166
|
+
domain=cfg.domain,
|
|
167
|
+
organization=cfg.org,
|
|
168
|
+
)
|
|
169
|
+
# Fill in task id inside the task template if it's not provided.
|
|
170
|
+
# Maybe this should be done here, or the backend.
|
|
171
|
+
if task_spec.task_template.id.project == "":
|
|
172
|
+
task_spec.task_template.id.project = cfg.project if cfg.project else ""
|
|
173
|
+
if task_spec.task_template.id.domain == "":
|
|
174
|
+
task_spec.task_template.id.domain = cfg.domain if cfg.domain else ""
|
|
175
|
+
if task_spec.task_template.id.org == "":
|
|
176
|
+
task_spec.task_template.id.org = cfg.org if cfg.org else ""
|
|
177
|
+
if task_spec.task_template.id.version == "":
|
|
178
|
+
task_spec.task_template.id.version = version
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
resp = await get_client().run_service.CreateRun(
|
|
182
|
+
run_service_pb2.CreateRunRequest(
|
|
183
|
+
run_id=run_id,
|
|
184
|
+
project_id=project_id,
|
|
185
|
+
task_spec=task_spec,
|
|
186
|
+
inputs=inputs.proto_inputs,
|
|
187
|
+
run_spec=run_definition_pb2.RunSpec(
|
|
188
|
+
overwrite_cache=self._overwrite_cache,
|
|
189
|
+
),
|
|
190
|
+
),
|
|
191
|
+
)
|
|
192
|
+
return Run(pb2=resp.run)
|
|
193
|
+
except grpc.aio.AioRpcError as e:
|
|
194
|
+
if e.code() == grpc.StatusCode.UNAVAILABLE:
|
|
195
|
+
raise flyte.errors.RuntimeSystemError(
|
|
196
|
+
"SystemUnavailableError",
|
|
197
|
+
"Flyte system is currently unavailable. check your configuration, or the service status.",
|
|
198
|
+
) from e
|
|
199
|
+
elif e.code() == grpc.StatusCode.INVALID_ARGUMENT:
|
|
200
|
+
raise flyte.errors.RuntimeUserError("InvalidArgumentError", e.details())
|
|
201
|
+
elif e.code() == grpc.StatusCode.ALREADY_EXISTS:
|
|
202
|
+
# TODO maybe this should be a pass and return existing run?
|
|
203
|
+
raise flyte.errors.RuntimeUserError(
|
|
204
|
+
"RunAlreadyExistsError",
|
|
205
|
+
f"A run with the name '{self._name}' already exists. Please choose a different name.",
|
|
206
|
+
)
|
|
207
|
+
else:
|
|
208
|
+
raise flyte.errors.RuntimeSystemError(
|
|
209
|
+
"RunCreationError",
|
|
210
|
+
f"Failed to create run: {e.details()}",
|
|
211
|
+
) from e
|
|
212
|
+
|
|
213
|
+
class DryRun(Run):
|
|
214
|
+
def __init__(self, _task_spec, _inputs, _code_bundle):
|
|
215
|
+
super().__init__(
|
|
216
|
+
pb2=run_definition_pb2.Run(
|
|
217
|
+
action=run_definition_pb2.Action(
|
|
218
|
+
id=run_definition_pb2.ActionIdentifier(
|
|
219
|
+
name="a0", run=run_definition_pb2.RunIdentifier(name="dry-run")
|
|
220
|
+
)
|
|
221
|
+
)
|
|
222
|
+
)
|
|
223
|
+
)
|
|
224
|
+
self.task_spec = _task_spec
|
|
225
|
+
self.inputs = _inputs
|
|
226
|
+
self.code_bundle = _code_bundle
|
|
227
|
+
|
|
228
|
+
return DryRun(_task_spec=task_spec, _inputs=inputs, _code_bundle=code_bundle)
|
|
229
|
+
|
|
230
|
+
@requires_storage
|
|
231
|
+
@requires_initialization
|
|
232
|
+
async def _run_hybrid(self, obj: TaskTemplate[P, R], *args: P.args, **kwargs: P.kwargs) -> R:
|
|
233
|
+
"""
|
|
234
|
+
Run a task in hybrid mode. This means that the parent action will be run locally, but the child actions will be
|
|
235
|
+
run in the cluster remotely. This is currently only used for testing,
|
|
236
|
+
over the longer term we will productize this.
|
|
237
|
+
"""
|
|
238
|
+
import flyte.report
|
|
239
|
+
from flyte._code_bundle import build_code_bundle, build_pkl_bundle
|
|
240
|
+
from flyte._deploy import build_images, plan_deploy
|
|
241
|
+
from flyte.models import RawDataPath
|
|
242
|
+
from flyte.storage import ABFS, GCS, S3
|
|
243
|
+
|
|
244
|
+
from ._internal import create_controller
|
|
245
|
+
from ._internal.runtime.taskrunner import run_task
|
|
246
|
+
|
|
247
|
+
cfg = get_common_config()
|
|
248
|
+
|
|
249
|
+
if obj.parent_env is None:
|
|
250
|
+
raise ValueError("Task is not attached to an environment. Please attach the task to an environment.")
|
|
251
|
+
|
|
252
|
+
deploy_plan = plan_deploy(cast(Environment, obj.parent_env()))
|
|
253
|
+
image_cache = await build_images(deploy_plan)
|
|
254
|
+
|
|
255
|
+
code_bundle = None
|
|
256
|
+
if self._name is not None:
|
|
257
|
+
# Check if remote run service has this run name already and if exists, then extract the code bundle from it.
|
|
258
|
+
code_bundle = await _get_code_bundle_for_run(name=self._name)
|
|
259
|
+
|
|
260
|
+
if not code_bundle:
|
|
261
|
+
if self._interactive_mode:
|
|
262
|
+
code_bundle = await build_pkl_bundle(
|
|
263
|
+
obj, upload_to_controlplane=not self._dry_run, copy_bundle_to=self._copy_bundle_to
|
|
264
|
+
)
|
|
265
|
+
else:
|
|
266
|
+
if self._copy_files != "none":
|
|
267
|
+
code_bundle = await build_code_bundle(
|
|
268
|
+
from_dir=cfg.root_dir,
|
|
269
|
+
dryrun=self._dry_run,
|
|
270
|
+
copy_bundle_to=self._copy_bundle_to,
|
|
271
|
+
copy_style=self._copy_files,
|
|
272
|
+
)
|
|
273
|
+
else:
|
|
274
|
+
code_bundle = None
|
|
275
|
+
|
|
276
|
+
version = self._version or (
|
|
277
|
+
code_bundle.computed_version if code_bundle and code_bundle.computed_version else None
|
|
278
|
+
)
|
|
279
|
+
if not version:
|
|
280
|
+
raise ValueError("Version is required when running a task")
|
|
281
|
+
|
|
282
|
+
project = cfg.project
|
|
283
|
+
domain = cfg.domain
|
|
284
|
+
org = cfg.org
|
|
285
|
+
action_name = "a0"
|
|
286
|
+
run_name = self._name
|
|
287
|
+
random_id = str(uuid.uuid4())[:6]
|
|
288
|
+
|
|
289
|
+
controller = create_controller("remote", endpoint="localhost:8090", insecure=True)
|
|
290
|
+
action = ActionID(name=action_name, run_name=run_name, project=project, domain=domain, org=org)
|
|
291
|
+
|
|
292
|
+
inputs = obj.native_interface.convert_to_kwargs(*args, **kwargs)
|
|
293
|
+
# TODO: Ideally we should get this from runService
|
|
294
|
+
# The API should be:
|
|
295
|
+
# create new run, from run, in mode hybrid -> new run id, output_base, raw_data_path, inputs_path
|
|
296
|
+
storage = get_storage()
|
|
297
|
+
if type(storage) not in (S3, GCS, ABFS):
|
|
298
|
+
raise ValueError(f"Unsupported storage type: {type(storage)}")
|
|
299
|
+
if self._run_base_dir is None:
|
|
300
|
+
raise ValueError(
|
|
301
|
+
"Raw data path is required when running task, please set it in the run context:",
|
|
302
|
+
" flyte.with_runcontext(run_base_dir='s3://bucket/metadata/outputs')",
|
|
303
|
+
)
|
|
304
|
+
output_path = self._run_base_dir
|
|
305
|
+
raw_data_path = f"{output_path}/rd/{random_id}"
|
|
306
|
+
raw_data_path_obj = RawDataPath(path=raw_data_path)
|
|
307
|
+
checkpoint_path = f"{raw_data_path}/checkpoint"
|
|
308
|
+
prev_checkpoint = f"{raw_data_path}/prev_checkpoint"
|
|
309
|
+
checkpoints = Checkpoints(checkpoint_path, prev_checkpoint)
|
|
310
|
+
|
|
311
|
+
async def _run_task() -> Tuple[Any, Optional[Exception]]:
|
|
312
|
+
ctx = internal_ctx()
|
|
313
|
+
tctx = TaskContext(
|
|
314
|
+
action=action,
|
|
315
|
+
checkpoints=checkpoints,
|
|
316
|
+
code_bundle=code_bundle,
|
|
317
|
+
output_path=output_path,
|
|
318
|
+
version=version if version else "na",
|
|
319
|
+
raw_data_path=raw_data_path_obj,
|
|
320
|
+
compiled_image_cache=image_cache,
|
|
321
|
+
run_base_dir=self._run_base_dir,
|
|
322
|
+
report=flyte.report.Report(name=action.name),
|
|
323
|
+
)
|
|
324
|
+
async with ctx.replace_task_context(tctx):
|
|
325
|
+
return await run_task(tctx=tctx, controller=controller, task=obj, inputs=inputs)
|
|
326
|
+
|
|
327
|
+
outputs, err = await contextual_run(_run_task)
|
|
328
|
+
if err:
|
|
329
|
+
raise err
|
|
330
|
+
return outputs
|
|
331
|
+
|
|
332
|
+
async def _run_local(self, obj: TaskTemplate[P, R], *args: P.args, **kwargs: P.kwargs) -> R:
|
|
333
|
+
from flyte._internal.controllers import create_controller
|
|
334
|
+
from flyte._internal.controllers._local_controller import LocalController
|
|
335
|
+
from flyte.report import Report
|
|
336
|
+
|
|
337
|
+
controller = cast(LocalController, create_controller("local"))
|
|
338
|
+
|
|
339
|
+
if self._name is None:
|
|
340
|
+
action = ActionID.create_random()
|
|
341
|
+
else:
|
|
342
|
+
action = ActionID(name=self._name)
|
|
343
|
+
|
|
344
|
+
ctx = internal_ctx()
|
|
345
|
+
tctx = TaskContext(
|
|
346
|
+
action=action,
|
|
347
|
+
checkpoints=Checkpoints(
|
|
348
|
+
prev_checkpoint_path=internal_ctx().raw_data.path, checkpoint_path=internal_ctx().raw_data.path
|
|
349
|
+
),
|
|
350
|
+
code_bundle=None,
|
|
351
|
+
output_path=self._metadata_path,
|
|
352
|
+
run_base_dir=self._metadata_path,
|
|
353
|
+
version="na",
|
|
354
|
+
raw_data_path=internal_ctx().raw_data,
|
|
355
|
+
compiled_image_cache=None,
|
|
356
|
+
report=Report(name=action.name),
|
|
357
|
+
mode="local",
|
|
358
|
+
)
|
|
359
|
+
with ctx.replace_task_context(tctx):
|
|
360
|
+
# make the local version always runs on a different thread, returns a wrapped future.
|
|
361
|
+
if obj._call_as_synchronous:
|
|
362
|
+
fut = controller.submit_sync(obj, *args, **kwargs)
|
|
363
|
+
awaitable = asyncio.wrap_future(fut)
|
|
364
|
+
return await awaitable
|
|
365
|
+
else:
|
|
366
|
+
return await controller.submit(obj, *args, **kwargs)
|
|
367
|
+
|
|
368
|
+
@syncify
|
|
369
|
+
async def run(
|
|
370
|
+
self, task: TaskTemplate[P, Union[R, Run]] | LazyEntity, *args: P.args, **kwargs: P.kwargs
|
|
371
|
+
) -> Union[R, Run]:
|
|
372
|
+
"""
|
|
373
|
+
Run an async `@env.task` or `TaskTemplate` instance. The existing async context will be used.
|
|
374
|
+
|
|
375
|
+
Example:
|
|
376
|
+
```python
|
|
377
|
+
import flyte
|
|
378
|
+
env = flyte.TaskEnvironment("example")
|
|
379
|
+
|
|
380
|
+
@env.task
|
|
381
|
+
async def example_task(x: int, y: str) -> str:
|
|
382
|
+
return f"{x} {y}"
|
|
383
|
+
|
|
384
|
+
if __name__ == "__main__":
|
|
385
|
+
flyte.run(example_task, 1, y="hello")
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
:param task: TaskTemplate instance `@env.task` or `TaskTemplate`
|
|
389
|
+
:param args: Arguments to pass to the Task
|
|
390
|
+
:param kwargs: Keyword arguments to pass to the Task
|
|
391
|
+
:return: Run instance or the result of the task
|
|
392
|
+
"""
|
|
393
|
+
from flyte.remote._task import LazyEntity
|
|
394
|
+
|
|
395
|
+
if isinstance(task, LazyEntity) and self._mode != "remote":
|
|
396
|
+
raise ValueError("Remote task can only be run in remote mode.")
|
|
397
|
+
if self._mode == "remote":
|
|
398
|
+
return await self._run_remote(task, *args, **kwargs)
|
|
399
|
+
task = cast(TaskTemplate, task)
|
|
400
|
+
if self._mode == "hybrid":
|
|
401
|
+
return await self._run_hybrid(task, *args, **kwargs)
|
|
402
|
+
|
|
403
|
+
# TODO We could use this for remote as well and users could simply pass flyte:// or s3:// or file://
|
|
404
|
+
with internal_ctx().new_raw_data_path(
|
|
405
|
+
raw_data_path=RawDataPath.from_local_folder(local_folder=self._raw_data_path)
|
|
406
|
+
):
|
|
407
|
+
return await self._run_local(task, *args, **kwargs)
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def with_runcontext(
|
|
411
|
+
mode: Mode | None = None,
|
|
412
|
+
*,
|
|
413
|
+
name: Optional[str] = None,
|
|
414
|
+
service_account: Optional[str] = None,
|
|
415
|
+
version: Optional[str] = None,
|
|
416
|
+
copy_style: CopyFiles = "loaded_modules",
|
|
417
|
+
dry_run: bool = False,
|
|
418
|
+
copy_bundle_to: pathlib.Path | None = None,
|
|
419
|
+
interactive_mode: bool | None = None,
|
|
420
|
+
raw_data_path: str | None = None,
|
|
421
|
+
run_base_dir: str | None = None,
|
|
422
|
+
overwrite_cache: bool = False,
|
|
423
|
+
) -> _Runner:
|
|
424
|
+
"""
|
|
425
|
+
Launch a new run with the given parameters as the context.
|
|
426
|
+
|
|
427
|
+
Example:
|
|
428
|
+
```python
|
|
429
|
+
import flyte
|
|
430
|
+
env = flyte.TaskEnvironment("example")
|
|
431
|
+
|
|
432
|
+
@env.task
|
|
433
|
+
async def example_task(x: int, y: str) -> str:
|
|
434
|
+
return f"{x} {y}"
|
|
435
|
+
|
|
436
|
+
if __name__ == "__main__":
|
|
437
|
+
flyte.with_runcontext(name="example_run_id").run(example_task, 1, y="hello")
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
:param mode: Optional The mode to use for the run, if not provided, it will be computed from flyte.init
|
|
441
|
+
:param version: Optional The version to use for the run, if not provided, it will be computed from the code bundle
|
|
442
|
+
:param name: Optional The name to use for the run
|
|
443
|
+
:param service_account: Optional The service account to use for the run context
|
|
444
|
+
:param copy_style: Optional The copy style to use for the run context
|
|
445
|
+
:param dry_run: Optional If true, the run will not be executed, but the bundle will be created
|
|
446
|
+
:param copy_bundle_to: When dry_run is True, the bundle will be copied to this location if specified
|
|
447
|
+
:param interactive_mode: Optional, can be forced to True or False.
|
|
448
|
+
If not provided, it will be set based on the current environment. For example Jupyter notebooks are considered
|
|
449
|
+
interactive mode, while scripts are not. This is used to determine how the code bundle is created.
|
|
450
|
+
:param raw_data_path: Use this path to store the raw data for the run. Currently only supported for local runs,
|
|
451
|
+
and can be used to store raw data in specific locations. TODO coming soon for remote runs as well.
|
|
452
|
+
:param run_base_dir: Optional The base directory to use for the run. This is used to store the metadata for the run,
|
|
453
|
+
that is passed between tasks.
|
|
454
|
+
:return: runner
|
|
455
|
+
"""
|
|
456
|
+
if mode == "hybrid" and not name and not run_base_dir:
|
|
457
|
+
raise ValueError("Run name and run base dir are required for hybrid mode")
|
|
458
|
+
return _Runner(
|
|
459
|
+
force_mode=mode,
|
|
460
|
+
name=name,
|
|
461
|
+
service_account=service_account,
|
|
462
|
+
version=version,
|
|
463
|
+
copy_style=copy_style,
|
|
464
|
+
dry_run=dry_run,
|
|
465
|
+
copy_bundle_to=copy_bundle_to,
|
|
466
|
+
interactive_mode=interactive_mode,
|
|
467
|
+
raw_data_path=raw_data_path,
|
|
468
|
+
run_base_dir=run_base_dir,
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
@syncify
|
|
473
|
+
async def run(task: TaskTemplate[P, R], *args: P.args, **kwargs: P.kwargs) -> Union[R, Run]:
|
|
474
|
+
"""
|
|
475
|
+
Run a task with the given parameters
|
|
476
|
+
:param task: task to run
|
|
477
|
+
:param args: args to pass to the task
|
|
478
|
+
:param kwargs: kwargs to pass to the task
|
|
479
|
+
:return: Run | Result of the task
|
|
480
|
+
"""
|
|
481
|
+
# using syncer causes problems
|
|
482
|
+
return await _Runner().run.aio(task, *args, **kwargs) # type: ignore
|
flyte/_secret.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import pathlib
|
|
2
|
+
import re
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import List, Optional, Union
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class Secret:
|
|
9
|
+
"""
|
|
10
|
+
Secrets are used to inject sensitive information into tasks. Secrets can be mounted as environment variables or
|
|
11
|
+
files. The secret key is the name of the secret in the secret store. The group is optional and maybe used with some
|
|
12
|
+
secret stores to organize secrets. The secret_mount is used to specify how the secret should be mounted. If the
|
|
13
|
+
secret_mount is set to "env" the secret will be mounted as an environment variable. If the secret_mount is set to
|
|
14
|
+
"file" the secret will be mounted as a file. The as_env_var is an optional parameter that can be used to specify the
|
|
15
|
+
name of the environment variable that the secret should be mounted as.
|
|
16
|
+
|
|
17
|
+
Example:
|
|
18
|
+
```python
|
|
19
|
+
@task(secrets="MY_SECRET")
|
|
20
|
+
async def my_task():
|
|
21
|
+
os.environ["MY_SECRET"] # This will be set to the value of the secret
|
|
22
|
+
|
|
23
|
+
@task(secrets=Secret("MY_SECRET", mount="/path/to/secret"))
|
|
24
|
+
async def my_task2():
|
|
25
|
+
async with open("/path/to/secret") as f:
|
|
26
|
+
secret_value = f.read()
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
TODO: Add support for secret versioning (some stores) and secret groups (some stores) and mounting as files.
|
|
30
|
+
|
|
31
|
+
:param key: The name of the secret in the secret store.
|
|
32
|
+
:param group: The group of the secret in the secret store.
|
|
33
|
+
:param mount: Use this to specify the path where the secret should be mounted.
|
|
34
|
+
:param as_env_var: The name of the environment variable that the secret should be mounted as.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
key: str
|
|
38
|
+
group: Optional[str] = None
|
|
39
|
+
mount: pathlib.Path | None = None
|
|
40
|
+
as_env_var: Optional[str] = None
|
|
41
|
+
|
|
42
|
+
def __post_init__(self):
|
|
43
|
+
if self.as_env_var is not None:
|
|
44
|
+
pattern = r"^[A-Z_][A-Z0-9_]*$"
|
|
45
|
+
if not re.match(pattern, self.as_env_var):
|
|
46
|
+
raise ValueError(f"Invalid environment variable name: {self.as_env_var}, must match {pattern}")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
SecretRequest = Union[str, Secret, List[str | Secret]]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def secrets_from_request(secrets: SecretRequest) -> List[Secret]:
|
|
53
|
+
"""
|
|
54
|
+
Converts a secret request into a list of secrets.
|
|
55
|
+
"""
|
|
56
|
+
if isinstance(secrets, str):
|
|
57
|
+
return [Secret(key=secrets)]
|
|
58
|
+
elif isinstance(secrets, Secret):
|
|
59
|
+
return [secrets]
|
|
60
|
+
else:
|
|
61
|
+
return [Secret(key=s) if isinstance(s, str) else s for s in secrets]
|