flyte 2.0.0b32__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 +108 -0
- flyte/_bin/__init__.py +0 -0
- flyte/_bin/debug.py +38 -0
- flyte/_bin/runtime.py +195 -0
- flyte/_bin/serve.py +178 -0
- flyte/_build.py +26 -0
- flyte/_cache/__init__.py +12 -0
- flyte/_cache/cache.py +147 -0
- flyte/_cache/defaults.py +9 -0
- flyte/_cache/local_cache.py +216 -0
- flyte/_cache/policy_function_body.py +42 -0
- flyte/_code_bundle/__init__.py +8 -0
- flyte/_code_bundle/_ignore.py +121 -0
- flyte/_code_bundle/_packaging.py +218 -0
- flyte/_code_bundle/_utils.py +347 -0
- flyte/_code_bundle/bundle.py +266 -0
- flyte/_constants.py +1 -0
- flyte/_context.py +155 -0
- flyte/_custom_context.py +73 -0
- flyte/_debug/__init__.py +0 -0
- flyte/_debug/constants.py +38 -0
- flyte/_debug/utils.py +17 -0
- flyte/_debug/vscode.py +307 -0
- flyte/_deploy.py +408 -0
- flyte/_deployer.py +109 -0
- flyte/_doc.py +29 -0
- flyte/_docstring.py +32 -0
- flyte/_environment.py +122 -0
- flyte/_excepthook.py +37 -0
- flyte/_group.py +32 -0
- flyte/_hash.py +8 -0
- flyte/_image.py +1055 -0
- flyte/_initialize.py +628 -0
- flyte/_interface.py +119 -0
- flyte/_internal/__init__.py +3 -0
- flyte/_internal/controllers/__init__.py +129 -0
- flyte/_internal/controllers/_local_controller.py +239 -0
- flyte/_internal/controllers/_trace.py +48 -0
- flyte/_internal/controllers/remote/__init__.py +58 -0
- flyte/_internal/controllers/remote/_action.py +211 -0
- flyte/_internal/controllers/remote/_client.py +47 -0
- flyte/_internal/controllers/remote/_controller.py +583 -0
- flyte/_internal/controllers/remote/_core.py +465 -0
- flyte/_internal/controllers/remote/_informer.py +381 -0
- flyte/_internal/controllers/remote/_service_protocol.py +50 -0
- flyte/_internal/imagebuild/__init__.py +3 -0
- flyte/_internal/imagebuild/docker_builder.py +706 -0
- flyte/_internal/imagebuild/image_builder.py +277 -0
- flyte/_internal/imagebuild/remote_builder.py +386 -0
- flyte/_internal/imagebuild/utils.py +78 -0
- flyte/_internal/resolvers/__init__.py +0 -0
- flyte/_internal/resolvers/_task_module.py +21 -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 +486 -0
- flyte/_internal/runtime/entrypoints.py +204 -0
- flyte/_internal/runtime/io.py +188 -0
- flyte/_internal/runtime/resources_serde.py +152 -0
- flyte/_internal/runtime/reuse.py +125 -0
- flyte/_internal/runtime/rusty.py +193 -0
- flyte/_internal/runtime/task_serde.py +362 -0
- flyte/_internal/runtime/taskrunner.py +209 -0
- flyte/_internal/runtime/trigger_serde.py +160 -0
- flyte/_internal/runtime/types_serde.py +54 -0
- flyte/_keyring/__init__.py +0 -0
- flyte/_keyring/file.py +115 -0
- flyte/_logging.py +300 -0
- flyte/_map.py +312 -0
- flyte/_module.py +72 -0
- flyte/_pod.py +30 -0
- flyte/_resources.py +473 -0
- flyte/_retry.py +32 -0
- flyte/_reusable_environment.py +102 -0
- flyte/_run.py +724 -0
- flyte/_secret.py +96 -0
- flyte/_task.py +550 -0
- flyte/_task_environment.py +316 -0
- flyte/_task_plugins.py +47 -0
- flyte/_timeout.py +47 -0
- flyte/_tools.py +27 -0
- flyte/_trace.py +119 -0
- flyte/_trigger.py +1000 -0
- flyte/_utils/__init__.py +30 -0
- flyte/_utils/asyn.py +121 -0
- flyte/_utils/async_cache.py +139 -0
- flyte/_utils/coro_management.py +27 -0
- flyte/_utils/docker_credentials.py +173 -0
- flyte/_utils/file_handling.py +72 -0
- flyte/_utils/helpers.py +134 -0
- flyte/_utils/lazy_module.py +54 -0
- flyte/_utils/module_loader.py +104 -0
- flyte/_utils/org_discovery.py +57 -0
- flyte/_utils/uv_script_parser.py +49 -0
- flyte/_version.py +34 -0
- flyte/app/__init__.py +22 -0
- flyte/app/_app_environment.py +157 -0
- flyte/app/_deploy.py +125 -0
- flyte/app/_input.py +160 -0
- flyte/app/_runtime/__init__.py +3 -0
- flyte/app/_runtime/app_serde.py +347 -0
- flyte/app/_types.py +101 -0
- flyte/app/extras/__init__.py +3 -0
- flyte/app/extras/_fastapi.py +151 -0
- flyte/cli/__init__.py +12 -0
- flyte/cli/_abort.py +28 -0
- flyte/cli/_build.py +114 -0
- flyte/cli/_common.py +468 -0
- flyte/cli/_create.py +371 -0
- flyte/cli/_delete.py +45 -0
- flyte/cli/_deploy.py +293 -0
- flyte/cli/_gen.py +176 -0
- flyte/cli/_get.py +370 -0
- flyte/cli/_option.py +33 -0
- flyte/cli/_params.py +554 -0
- flyte/cli/_plugins.py +209 -0
- flyte/cli/_run.py +597 -0
- flyte/cli/_serve.py +64 -0
- flyte/cli/_update.py +37 -0
- flyte/cli/_user.py +17 -0
- flyte/cli/main.py +221 -0
- flyte/config/__init__.py +3 -0
- flyte/config/_config.py +248 -0
- flyte/config/_internal.py +73 -0
- flyte/config/_reader.py +225 -0
- flyte/connectors/__init__.py +11 -0
- flyte/connectors/_connector.py +270 -0
- flyte/connectors/_server.py +197 -0
- flyte/connectors/utils.py +135 -0
- flyte/errors.py +243 -0
- flyte/extend.py +19 -0
- flyte/extras/__init__.py +5 -0
- flyte/extras/_container.py +286 -0
- flyte/git/__init__.py +3 -0
- flyte/git/_config.py +21 -0
- flyte/io/__init__.py +29 -0
- flyte/io/_dataframe/__init__.py +131 -0
- flyte/io/_dataframe/basic_dfs.py +223 -0
- flyte/io/_dataframe/dataframe.py +1026 -0
- flyte/io/_dir.py +910 -0
- flyte/io/_file.py +914 -0
- flyte/io/_hashing_io.py +342 -0
- flyte/models.py +479 -0
- flyte/py.typed +0 -0
- flyte/remote/__init__.py +35 -0
- flyte/remote/_action.py +738 -0
- flyte/remote/_app.py +57 -0
- flyte/remote/_client/__init__.py +0 -0
- flyte/remote/_client/_protocols.py +189 -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 +403 -0
- flyte/remote/_client/auth/_authenticators/client_credentials.py +73 -0
- flyte/remote/_client/auth/_authenticators/device_code.py +117 -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 +213 -0
- flyte/remote/_client/auth/_client_config.py +85 -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 +152 -0
- flyte/remote/_client/auth/_token_client.py +260 -0
- flyte/remote/_client/auth/errors.py +16 -0
- flyte/remote/_client/controlplane.py +128 -0
- flyte/remote/_common.py +30 -0
- flyte/remote/_console.py +19 -0
- flyte/remote/_data.py +161 -0
- flyte/remote/_logs.py +185 -0
- flyte/remote/_project.py +88 -0
- flyte/remote/_run.py +386 -0
- flyte/remote/_secret.py +142 -0
- flyte/remote/_task.py +527 -0
- flyte/remote/_trigger.py +306 -0
- flyte/remote/_user.py +33 -0
- flyte/report/__init__.py +3 -0
- flyte/report/_report.py +182 -0
- flyte/report/_template.html +124 -0
- flyte/storage/__init__.py +36 -0
- flyte/storage/_config.py +237 -0
- flyte/storage/_parallel_reader.py +274 -0
- flyte/storage/_remote_fs.py +34 -0
- flyte/storage/_storage.py +456 -0
- flyte/storage/_utils.py +5 -0
- flyte/syncify/__init__.py +56 -0
- flyte/syncify/_api.py +375 -0
- flyte/types/__init__.py +52 -0
- flyte/types/_interface.py +40 -0
- flyte/types/_pickle.py +145 -0
- flyte/types/_renderer.py +162 -0
- flyte/types/_string_literals.py +119 -0
- flyte/types/_type_engine.py +2254 -0
- flyte/types/_utils.py +80 -0
- flyte-2.0.0b32.data/scripts/debug.py +38 -0
- flyte-2.0.0b32.data/scripts/runtime.py +195 -0
- flyte-2.0.0b32.dist-info/METADATA +351 -0
- flyte-2.0.0b32.dist-info/RECORD +204 -0
- flyte-2.0.0b32.dist-info/WHEEL +5 -0
- flyte-2.0.0b32.dist-info/entry_points.txt +7 -0
- flyte-2.0.0b32.dist-info/licenses/LICENSE +201 -0
- flyte-2.0.0b32.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module is responsible for running tasks in the V2 runtime. All methods in this file should be
|
|
3
|
+
invoked within a context tree.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import pathlib
|
|
7
|
+
import time
|
|
8
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
9
|
+
|
|
10
|
+
import flyte.report
|
|
11
|
+
from flyte._context import internal_ctx
|
|
12
|
+
from flyte._internal.imagebuild.image_builder import ImageCache
|
|
13
|
+
from flyte._logging import log, logger
|
|
14
|
+
from flyte._task import TaskTemplate
|
|
15
|
+
from flyte.errors import CustomError, RuntimeSystemError, RuntimeUnknownError, RuntimeUserError
|
|
16
|
+
from flyte.models import ActionID, Checkpoints, CodeBundle, RawDataPath, TaskContext
|
|
17
|
+
|
|
18
|
+
from .. import Controller
|
|
19
|
+
from .convert import (
|
|
20
|
+
Error,
|
|
21
|
+
Inputs,
|
|
22
|
+
Outputs,
|
|
23
|
+
convert_from_native_to_error,
|
|
24
|
+
convert_from_native_to_outputs,
|
|
25
|
+
convert_inputs_to_native,
|
|
26
|
+
)
|
|
27
|
+
from .io import load_inputs, upload_error, upload_outputs
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def replace_task_cli(args: List[str], inputs: Inputs, tmp_path: pathlib.Path, action: ActionID) -> List[str]:
|
|
31
|
+
"""
|
|
32
|
+
This method can be used to run an task from the cli, if you have cli for the task. It will replace,
|
|
33
|
+
all the args with the task args.
|
|
34
|
+
|
|
35
|
+
The urun cli is of the format
|
|
36
|
+
```python
|
|
37
|
+
['urun', '--inputs', '{{.Inputs}}', '--outputs-path', '{{.Outputs}}', '--version', '',
|
|
38
|
+
'--raw-data-path', '{{.rawOutputDataPrefix}}',
|
|
39
|
+
'--checkpoint-path', '{{.checkpointOutputPrefix}}', '--prev-checkpoint', '{{.prevCheckpointPrefix}}',
|
|
40
|
+
'--run-name', '{{.runName}}', '--name', '{{.actionName}}',
|
|
41
|
+
'--tgz', 'some-path', '--dest', '.',
|
|
42
|
+
'--resolver', 'flyte._internal.resolvers.default.DefaultTaskResolver', '--resolver-args',
|
|
43
|
+
'mod', 'test_round_trip', 'instance', 'task1']
|
|
44
|
+
```
|
|
45
|
+
We will replace, inputs, outputs, raw_data_path, checkpoint_path, prev_checkpoint, run_name, name
|
|
46
|
+
with supplied values.
|
|
47
|
+
|
|
48
|
+
:param args: urun command
|
|
49
|
+
:param inputs: converted inputs to the task
|
|
50
|
+
:param tmp_path: temporary path to use for the task
|
|
51
|
+
:param action: run id to use for the task
|
|
52
|
+
:return: modified args
|
|
53
|
+
"""
|
|
54
|
+
# Iterate over all the args and replace the inputs, outputs, raw_data_path, checkpoint_path, prev_checkpoint,
|
|
55
|
+
# root_name, run_name with the supplied values
|
|
56
|
+
# first we will write the inputs to a file called inputs.pb
|
|
57
|
+
inputs_path = tmp_path / "inputs.pb"
|
|
58
|
+
with open(inputs_path, "wb") as f:
|
|
59
|
+
f.write(inputs.proto_inputs.SerializeToString())
|
|
60
|
+
# now modify the args
|
|
61
|
+
args = list(args) # copy first because it's a proto container
|
|
62
|
+
for i, arg in enumerate(args):
|
|
63
|
+
match arg:
|
|
64
|
+
case "--inputs":
|
|
65
|
+
args[i + 1] = str(inputs_path)
|
|
66
|
+
case "--outputs-path":
|
|
67
|
+
args[i + 1] = str(tmp_path)
|
|
68
|
+
case "--raw-data-path":
|
|
69
|
+
args[i + 1] = str(tmp_path / "raw_data_path")
|
|
70
|
+
case "--checkpoint-path":
|
|
71
|
+
args[i + 1] = str(tmp_path / "checkpoint_path")
|
|
72
|
+
case "--prev-checkpoint":
|
|
73
|
+
args[i + 1] = str(tmp_path / "prev_checkpoint")
|
|
74
|
+
case "--run-name":
|
|
75
|
+
args[i + 1] = action.run_name or ""
|
|
76
|
+
case "--name":
|
|
77
|
+
args[i + 1] = action.name
|
|
78
|
+
insert_point = args.index("--raw-data-path")
|
|
79
|
+
args.insert(insert_point, str(tmp_path))
|
|
80
|
+
args.insert(insert_point, "--run-base-dir")
|
|
81
|
+
return args
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@log
|
|
85
|
+
async def run_task(
|
|
86
|
+
tctx: TaskContext, controller: Controller, task: TaskTemplate, inputs: Dict[str, Any]
|
|
87
|
+
) -> Tuple[Any, Optional[Exception]]:
|
|
88
|
+
try:
|
|
89
|
+
logger.info(f"Parent task executing {tctx.action}")
|
|
90
|
+
outputs = await task.execute(**inputs)
|
|
91
|
+
logger.info(f"Parent task completed successfully, {tctx.action}")
|
|
92
|
+
return outputs, None
|
|
93
|
+
except RuntimeSystemError as e:
|
|
94
|
+
logger.exception(f"Task failed with error: {e}")
|
|
95
|
+
return {}, e
|
|
96
|
+
except RuntimeUnknownError as e:
|
|
97
|
+
logger.exception(f"Task failed with error: {e}")
|
|
98
|
+
return {}, e
|
|
99
|
+
except RuntimeUserError as e:
|
|
100
|
+
logger.exception(f"Task failed with error: {e}")
|
|
101
|
+
return {}, e
|
|
102
|
+
except Exception as e:
|
|
103
|
+
logger.exception(f"Task failed with error: {e}")
|
|
104
|
+
return {}, CustomError.from_exception(e)
|
|
105
|
+
finally:
|
|
106
|
+
logger.info(f"Parent task finalized {tctx.action}")
|
|
107
|
+
# reconstruct run id here
|
|
108
|
+
await controller.finalize_parent_action(tctx.action)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
async def convert_and_run(
|
|
112
|
+
*,
|
|
113
|
+
task: TaskTemplate,
|
|
114
|
+
action: ActionID,
|
|
115
|
+
controller: Controller,
|
|
116
|
+
raw_data_path: RawDataPath,
|
|
117
|
+
version: str,
|
|
118
|
+
output_path: str,
|
|
119
|
+
run_base_dir: str,
|
|
120
|
+
inputs: Inputs = Inputs.empty(),
|
|
121
|
+
input_path: str | None = None,
|
|
122
|
+
checkpoints: Checkpoints | None = None,
|
|
123
|
+
code_bundle: CodeBundle | None = None,
|
|
124
|
+
image_cache: ImageCache | None = None,
|
|
125
|
+
interactive_mode: bool = False,
|
|
126
|
+
) -> Tuple[Optional[Outputs], Optional[Error]]:
|
|
127
|
+
"""
|
|
128
|
+
This method is used to convert the inputs to native types, and run the task. It assumes you are running
|
|
129
|
+
in a context tree.
|
|
130
|
+
"""
|
|
131
|
+
ctx = internal_ctx()
|
|
132
|
+
|
|
133
|
+
# Load inputs first to get context
|
|
134
|
+
if input_path:
|
|
135
|
+
inputs = await load_inputs(input_path, path_rewrite_config=raw_data_path.path_rewrite)
|
|
136
|
+
|
|
137
|
+
# Extract context from inputs
|
|
138
|
+
custom_context = inputs.context if inputs else {}
|
|
139
|
+
|
|
140
|
+
tctx = TaskContext(
|
|
141
|
+
action=action,
|
|
142
|
+
checkpoints=checkpoints,
|
|
143
|
+
code_bundle=code_bundle,
|
|
144
|
+
input_path=input_path,
|
|
145
|
+
output_path=output_path,
|
|
146
|
+
run_base_dir=run_base_dir,
|
|
147
|
+
version=version,
|
|
148
|
+
raw_data_path=raw_data_path,
|
|
149
|
+
compiled_image_cache=image_cache,
|
|
150
|
+
report=flyte.report.Report(name=action.name),
|
|
151
|
+
mode="remote" if not ctx.data.task_context else ctx.data.task_context.mode,
|
|
152
|
+
interactive_mode=interactive_mode,
|
|
153
|
+
custom_context=custom_context,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
with ctx.replace_task_context(tctx):
|
|
157
|
+
inputs_kwargs = await convert_inputs_to_native(inputs, task.native_interface)
|
|
158
|
+
out, err = await run_task(tctx=tctx, controller=controller, task=task, inputs=inputs_kwargs)
|
|
159
|
+
if err is not None:
|
|
160
|
+
return None, convert_from_native_to_error(err)
|
|
161
|
+
if task.report:
|
|
162
|
+
await flyte.report.flush.aio()
|
|
163
|
+
return await convert_from_native_to_outputs(out, task.native_interface, task.name), None
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
async def extract_download_run_upload(
|
|
167
|
+
task: TaskTemplate,
|
|
168
|
+
*,
|
|
169
|
+
action: ActionID,
|
|
170
|
+
controller: Controller,
|
|
171
|
+
raw_data_path: RawDataPath,
|
|
172
|
+
output_path: str,
|
|
173
|
+
run_base_dir: str,
|
|
174
|
+
version: str,
|
|
175
|
+
checkpoints: Checkpoints | None = None,
|
|
176
|
+
code_bundle: CodeBundle | None = None,
|
|
177
|
+
input_path: str | None = None,
|
|
178
|
+
image_cache: ImageCache | None = None,
|
|
179
|
+
interactive_mode: bool = False,
|
|
180
|
+
):
|
|
181
|
+
"""
|
|
182
|
+
This method is invoked from the CLI (urun) and is used to run a task. This assumes that the context tree
|
|
183
|
+
has already been created, and the task has been loaded. It also handles the loading of the task.
|
|
184
|
+
"""
|
|
185
|
+
t = time.time()
|
|
186
|
+
logger.warning(f"Task {action.name} started at {t}")
|
|
187
|
+
outputs, err = await convert_and_run(
|
|
188
|
+
task=task,
|
|
189
|
+
input_path=input_path,
|
|
190
|
+
action=action,
|
|
191
|
+
controller=controller,
|
|
192
|
+
raw_data_path=raw_data_path,
|
|
193
|
+
output_path=output_path,
|
|
194
|
+
run_base_dir=run_base_dir,
|
|
195
|
+
version=version,
|
|
196
|
+
checkpoints=checkpoints,
|
|
197
|
+
code_bundle=code_bundle,
|
|
198
|
+
image_cache=image_cache,
|
|
199
|
+
interactive_mode=interactive_mode,
|
|
200
|
+
)
|
|
201
|
+
if err is not None:
|
|
202
|
+
path = await upload_error(err.err, output_path)
|
|
203
|
+
logger.error(f"Task {task.name} failed with error: {err}. Uploaded error to {path}")
|
|
204
|
+
return
|
|
205
|
+
if outputs is None:
|
|
206
|
+
logger.info(f"Task {task.name} completed successfully, no outputs")
|
|
207
|
+
return
|
|
208
|
+
await upload_outputs(outputs, output_path) if output_path else None
|
|
209
|
+
logger.warning(f"Task {task.name} completed successfully, uploaded outputs to {output_path} in {time.time() - t}s")
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from typing import Union
|
|
3
|
+
|
|
4
|
+
from flyteidl2.core import interface_pb2, literals_pb2
|
|
5
|
+
from flyteidl2.task import common_pb2, run_pb2, task_definition_pb2
|
|
6
|
+
from google.protobuf import timestamp_pb2, wrappers_pb2
|
|
7
|
+
|
|
8
|
+
import flyte.types
|
|
9
|
+
from flyte import Cron, FixedRate, Trigger, TriggerTime
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _to_schedule(m: Union[Cron, FixedRate], kickoff_arg_name: str | None = None) -> common_pb2.Schedule:
|
|
13
|
+
if isinstance(m, Cron):
|
|
14
|
+
return common_pb2.Schedule(
|
|
15
|
+
cron=common_pb2.Cron(
|
|
16
|
+
expression=m.expression,
|
|
17
|
+
timezone=m.timezone,
|
|
18
|
+
),
|
|
19
|
+
kickoff_time_input_arg=kickoff_arg_name,
|
|
20
|
+
)
|
|
21
|
+
elif isinstance(m, FixedRate):
|
|
22
|
+
start_time = None
|
|
23
|
+
if m.start_time is not None:
|
|
24
|
+
start_time = timestamp_pb2.Timestamp()
|
|
25
|
+
start_time.FromDatetime(m.start_time)
|
|
26
|
+
|
|
27
|
+
return common_pb2.Schedule(
|
|
28
|
+
rate=common_pb2.FixedRate(
|
|
29
|
+
value=m.interval_minutes,
|
|
30
|
+
unit=common_pb2.FixedRateUnit.FIXED_RATE_UNIT_MINUTE,
|
|
31
|
+
start_time=start_time,
|
|
32
|
+
),
|
|
33
|
+
kickoff_time_input_arg=kickoff_arg_name,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
async def process_default_inputs(
|
|
38
|
+
default_inputs: dict,
|
|
39
|
+
task_name: str,
|
|
40
|
+
task_inputs: interface_pb2.VariableMap,
|
|
41
|
+
task_default_inputs: list[common_pb2.NamedParameter],
|
|
42
|
+
) -> list[common_pb2.NamedLiteral]:
|
|
43
|
+
"""
|
|
44
|
+
Process default inputs and convert them to NamedLiteral objects.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
default_inputs: Dictionary of default input values
|
|
48
|
+
task_name: Name of the task for error messages
|
|
49
|
+
task_inputs: Task input variable map
|
|
50
|
+
task_default_inputs: List of default parameters from task
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
List of NamedLiteral objects
|
|
54
|
+
"""
|
|
55
|
+
keys = []
|
|
56
|
+
literal_coros = []
|
|
57
|
+
for k, v in default_inputs.items():
|
|
58
|
+
if k not in task_inputs.variables:
|
|
59
|
+
raise ValueError(
|
|
60
|
+
f"Trigger default input '{k}' must be an input to the task, but not found in task {task_name}. "
|
|
61
|
+
f"Available inputs: {list(task_inputs.variables.keys())}"
|
|
62
|
+
)
|
|
63
|
+
else:
|
|
64
|
+
literal_coros.append(flyte.types.TypeEngine.to_literal(v, type(v), task_inputs.variables[k].type))
|
|
65
|
+
keys.append(k)
|
|
66
|
+
|
|
67
|
+
final_literals: list[literals_pb2.Literal] = await asyncio.gather(*literal_coros)
|
|
68
|
+
|
|
69
|
+
for p in task_default_inputs or []:
|
|
70
|
+
if p.name not in keys:
|
|
71
|
+
keys.append(p.name)
|
|
72
|
+
final_literals.append(p.parameter.default)
|
|
73
|
+
|
|
74
|
+
literals: list[common_pb2.NamedLiteral] = []
|
|
75
|
+
for k, lit in zip(keys, final_literals):
|
|
76
|
+
literals.append(
|
|
77
|
+
common_pb2.NamedLiteral(
|
|
78
|
+
name=k,
|
|
79
|
+
value=lit,
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return literals
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
async def to_task_trigger(
|
|
87
|
+
t: Trigger,
|
|
88
|
+
task_name: str,
|
|
89
|
+
task_inputs: interface_pb2.VariableMap,
|
|
90
|
+
task_default_inputs: list[common_pb2.NamedParameter],
|
|
91
|
+
) -> task_definition_pb2.TaskTrigger:
|
|
92
|
+
"""
|
|
93
|
+
Converts a Trigger object to a TaskTrigger protobuf object.
|
|
94
|
+
Args:
|
|
95
|
+
t:
|
|
96
|
+
task_name:
|
|
97
|
+
task_inputs:
|
|
98
|
+
task_default_inputs:
|
|
99
|
+
Returns:
|
|
100
|
+
|
|
101
|
+
"""
|
|
102
|
+
env = None
|
|
103
|
+
if t.env_vars:
|
|
104
|
+
env = run_pb2.Envs()
|
|
105
|
+
for k, v in t.env_vars.items():
|
|
106
|
+
env.values.append(literals_pb2.KeyValuePair(key=k, value=v))
|
|
107
|
+
|
|
108
|
+
labels = run_pb2.Labels(values=t.labels) if t.labels else None
|
|
109
|
+
|
|
110
|
+
annotations = run_pb2.Annotations(values=t.annotations) if t.annotations else None
|
|
111
|
+
|
|
112
|
+
run_spec = run_pb2.RunSpec(
|
|
113
|
+
overwrite_cache=t.overwrite_cache,
|
|
114
|
+
envs=env,
|
|
115
|
+
interruptible=wrappers_pb2.BoolValue(value=t.interruptible) if t.interruptible is not None else None,
|
|
116
|
+
cluster=t.queue,
|
|
117
|
+
labels=labels,
|
|
118
|
+
annotations=annotations,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
kickoff_arg_name = None
|
|
122
|
+
default_inputs = {}
|
|
123
|
+
if t.inputs:
|
|
124
|
+
for k, v in t.inputs.items():
|
|
125
|
+
if v is TriggerTime:
|
|
126
|
+
if k == "trigger_time" and k not in task_inputs.variables:
|
|
127
|
+
# the 'trigger_time' input name that by default Triggers look for so it's always added.
|
|
128
|
+
# Remove it here by skipping if it's not actually an input to the task
|
|
129
|
+
continue
|
|
130
|
+
kickoff_arg_name = k
|
|
131
|
+
else:
|
|
132
|
+
default_inputs[k] = v
|
|
133
|
+
|
|
134
|
+
# assert that default_inputs and the kickoff_arg_name are infact in the task inputs
|
|
135
|
+
if kickoff_arg_name is not None and kickoff_arg_name not in task_inputs.variables:
|
|
136
|
+
raise ValueError(
|
|
137
|
+
f"For a scheduled trigger, the TriggerTime input '{kickoff_arg_name}' "
|
|
138
|
+
f"must be an input to the task, but not found in task {task_name}. "
|
|
139
|
+
f"Available inputs: {list(task_inputs.variables.keys())}"
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
literals = await process_default_inputs(default_inputs, task_name, task_inputs, task_default_inputs)
|
|
143
|
+
|
|
144
|
+
automation = _to_schedule(
|
|
145
|
+
t.automation,
|
|
146
|
+
kickoff_arg_name=kickoff_arg_name,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
return task_definition_pb2.TaskTrigger(
|
|
150
|
+
name=t.name,
|
|
151
|
+
spec=task_definition_pb2.TaskTriggerSpec(
|
|
152
|
+
active=t.auto_activate,
|
|
153
|
+
run_spec=run_spec,
|
|
154
|
+
inputs=common_pb2.Inputs(literals=literals),
|
|
155
|
+
),
|
|
156
|
+
automation_spec=common_pb2.TriggerAutomationSpec(
|
|
157
|
+
type=common_pb2.TriggerAutomationSpecType.TYPE_SCHEDULE,
|
|
158
|
+
schedule=automation,
|
|
159
|
+
),
|
|
160
|
+
)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from typing import Dict, Optional, TypeVar
|
|
2
|
+
|
|
3
|
+
from flyteidl2.core import interface_pb2
|
|
4
|
+
|
|
5
|
+
from flyte.models import NativeInterface
|
|
6
|
+
from flyte.types._type_engine import TypeEngine
|
|
7
|
+
|
|
8
|
+
T = TypeVar("T")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def transform_variable_map(
|
|
12
|
+
variable_map: Dict[str, type],
|
|
13
|
+
descriptions: Optional[Dict[str, str]] = None,
|
|
14
|
+
) -> Dict[str, interface_pb2.Variable]:
|
|
15
|
+
"""
|
|
16
|
+
Given a map of str (names of inputs for instance) to their Python native types, return a map of the name to a
|
|
17
|
+
Flyte Variable object with that type.
|
|
18
|
+
"""
|
|
19
|
+
res = {}
|
|
20
|
+
descriptions = descriptions or {}
|
|
21
|
+
if variable_map:
|
|
22
|
+
for k, v in variable_map.items():
|
|
23
|
+
res[k] = transform_type(v, descriptions.get(k, k))
|
|
24
|
+
return res
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def transform_native_to_typed_interface(
|
|
28
|
+
interface: Optional[NativeInterface],
|
|
29
|
+
) -> Optional[interface_pb2.TypedInterface]:
|
|
30
|
+
"""
|
|
31
|
+
Transform the given simple python native interface to FlyteIDL's interface
|
|
32
|
+
"""
|
|
33
|
+
if interface is None:
|
|
34
|
+
return None
|
|
35
|
+
input_descriptions: Dict[str, str] = {}
|
|
36
|
+
output_descriptions: Dict[str, str] = {}
|
|
37
|
+
if interface.docstring:
|
|
38
|
+
# Fill in descriptions from docstring in the future
|
|
39
|
+
input_descriptions = {}
|
|
40
|
+
output_descriptions = {}
|
|
41
|
+
|
|
42
|
+
inputs_map = transform_variable_map(interface.get_input_types(), input_descriptions)
|
|
43
|
+
outputs_map = transform_variable_map(interface.outputs, output_descriptions)
|
|
44
|
+
return interface_pb2.TypedInterface(
|
|
45
|
+
inputs=interface_pb2.VariableMap(variables=inputs_map), outputs=interface_pb2.VariableMap(variables=outputs_map)
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def transform_type(x: type, description: Optional[str] = None) -> interface_pb2.Variable:
|
|
50
|
+
# add artifact handling eventually
|
|
51
|
+
return interface_pb2.Variable(
|
|
52
|
+
type=TypeEngine.to_literal_type(x),
|
|
53
|
+
description=description,
|
|
54
|
+
)
|
|
File without changes
|
flyte/_keyring/file.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
from base64 import decodebytes, encodebytes
|
|
2
|
+
from configparser import ConfigParser, NoOptionError, NoSectionError
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from keyring.backend import KeyringBackend
|
|
7
|
+
from keyring.errors import PasswordDeleteError
|
|
8
|
+
|
|
9
|
+
_FLYTE_KEYRING_PATH: Path = Path.home() / ".flyte" / "keyring.cfg"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SimplePlainTextKeyring(KeyringBackend):
|
|
13
|
+
"""
|
|
14
|
+
Simple plain text keyring for remote notebook environments.
|
|
15
|
+
|
|
16
|
+
This backend is only active when running in IPython/Jupyter notebooks.
|
|
17
|
+
For local development, the system keyring is used instead.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def priority(self):
|
|
22
|
+
"""
|
|
23
|
+
Return priority based on whether we're in a notebook environment.
|
|
24
|
+
Negative priority means this backend will be skipped by keyring.
|
|
25
|
+
"""
|
|
26
|
+
from flyte._tools import ipython_check
|
|
27
|
+
|
|
28
|
+
if ipython_check():
|
|
29
|
+
# In IPython/Jupyter - use this backend
|
|
30
|
+
return 0.5
|
|
31
|
+
else:
|
|
32
|
+
# Not in IPython - skip this backend, let system keyring handle it
|
|
33
|
+
return -1
|
|
34
|
+
|
|
35
|
+
def get_password(self, service: str, username: str) -> Optional[str]:
|
|
36
|
+
"""Get password."""
|
|
37
|
+
if not self.file_path.exists():
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
config = ConfigParser(interpolation=None)
|
|
41
|
+
config.read(self.file_path, encoding="utf-8")
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
password_base64 = config.get(service, username).encode("utf-8")
|
|
45
|
+
return decodebytes(password_base64).decode("utf-8")
|
|
46
|
+
except (NoOptionError, NoSectionError):
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
def delete_password(self, service: str, username: str) -> None:
|
|
50
|
+
"""Delete password."""
|
|
51
|
+
if not self.file_path.exists():
|
|
52
|
+
raise PasswordDeleteError("Config file does not exist")
|
|
53
|
+
|
|
54
|
+
config = ConfigParser(interpolation=None)
|
|
55
|
+
config.read(self.file_path, encoding="utf-8")
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
if not config.remove_option(service, username):
|
|
59
|
+
raise PasswordDeleteError("Password not found")
|
|
60
|
+
except NoSectionError:
|
|
61
|
+
raise PasswordDeleteError("Password not found")
|
|
62
|
+
|
|
63
|
+
with self.file_path.open("w", encoding="utf-8") as config_file:
|
|
64
|
+
config.write(config_file)
|
|
65
|
+
|
|
66
|
+
def set_password(self, service: str, username: str, password: str) -> None:
|
|
67
|
+
"""Set password."""
|
|
68
|
+
if not username:
|
|
69
|
+
raise ValueError("Username must be provided")
|
|
70
|
+
|
|
71
|
+
file_path = self._ensure_file_path()
|
|
72
|
+
value = encodebytes(password.encode("utf-8")).decode("utf-8")
|
|
73
|
+
|
|
74
|
+
config = ConfigParser(interpolation=None)
|
|
75
|
+
config.read(file_path, encoding="utf-8")
|
|
76
|
+
|
|
77
|
+
if not config.has_section(service):
|
|
78
|
+
config.add_section(service)
|
|
79
|
+
|
|
80
|
+
config.set(service, username, value)
|
|
81
|
+
|
|
82
|
+
with file_path.open("w", encoding="utf-8") as config_file:
|
|
83
|
+
config.write(config_file)
|
|
84
|
+
|
|
85
|
+
def _ensure_file_path(self):
|
|
86
|
+
self.file_path.parent.mkdir(exist_ok=True, parents=True)
|
|
87
|
+
if not self.file_path.is_file():
|
|
88
|
+
self.file_path.touch(0o600)
|
|
89
|
+
return self.file_path
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def file_path(self) -> Path:
|
|
93
|
+
from flyte._initialize import get_init_config, is_initialized
|
|
94
|
+
from flyte._logging import logger
|
|
95
|
+
|
|
96
|
+
# Only try to use source_config_path if flyte.init() has been called
|
|
97
|
+
if is_initialized():
|
|
98
|
+
try:
|
|
99
|
+
config = get_init_config()
|
|
100
|
+
config_path = config.source_config_path
|
|
101
|
+
if config_path and str(config_path.parent.name) == ".flyte":
|
|
102
|
+
# if the config is in a .flyte directory, use that as the path
|
|
103
|
+
return config_path.parent / "keyring.cfg"
|
|
104
|
+
except Exception as e:
|
|
105
|
+
# If anything fails, fall back to default path
|
|
106
|
+
logger.debug(f"Skipping config-based keyring path due to error: {e}")
|
|
107
|
+
else:
|
|
108
|
+
# flyte.init() hasn't been called, use default path
|
|
109
|
+
logger.debug("flyte.init() not called, using default keyring path")
|
|
110
|
+
|
|
111
|
+
# Default path
|
|
112
|
+
return _FLYTE_KEYRING_PATH
|
|
113
|
+
|
|
114
|
+
def __repr__(self):
|
|
115
|
+
return f"<{self.__class__.__name__}> at {self.file_path}>"
|