flyte 2.0.0b8__py3-none-any.whl → 2.0.0b13__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.
- flyte/__init__.py +55 -31
- flyte/_context.py +1 -1
- flyte/_environment.py +5 -5
- flyte/_image.py +2 -13
- flyte/_initialize.py +1 -1
- flyte/_internal/imagebuild/docker_builder.py +8 -3
- flyte/_internal/imagebuild/remote_builder.py +20 -0
- flyte/_internal/runtime/reuse.py +7 -3
- flyte/_internal/runtime/task_serde.py +3 -1
- flyte/_logging.py +5 -2
- flyte/_reusable_environment.py +41 -19
- flyte/_run.py +9 -9
- flyte/_secret.py +9 -5
- flyte/_task.py +12 -7
- flyte/_task_environment.py +7 -9
- flyte/_tools.py +0 -13
- flyte/_version.py +16 -3
- flyte/cli/_common.py +16 -5
- flyte/cli/_gen.py +10 -1
- flyte/cli/_get.py +16 -14
- flyte/cli/_run.py +254 -20
- flyte/models.py +7 -0
- flyte/remote/_client/auth/_authenticators/base.py +8 -2
- flyte/remote/_client/auth/_channel.py +0 -6
- flyte/remote/_client/auth/_client_config.py +4 -2
- flyte/remote/_client/controlplane.py +14 -0
- flyte/remote/_task.py +17 -3
- flyte/storage/_storage.py +83 -7
- flyte/types/_type_engine.py +3 -33
- {flyte-2.0.0b8.dist-info → flyte-2.0.0b13.dist-info}/METADATA +1 -1
- {flyte-2.0.0b8.dist-info → flyte-2.0.0b13.dist-info}/RECORD +36 -36
- {flyte-2.0.0b8.data → flyte-2.0.0b13.data}/scripts/runtime.py +0 -0
- {flyte-2.0.0b8.dist-info → flyte-2.0.0b13.dist-info}/WHEEL +0 -0
- {flyte-2.0.0b8.dist-info → flyte-2.0.0b13.dist-info}/entry_points.txt +0 -0
- {flyte-2.0.0b8.dist-info → flyte-2.0.0b13.dist-info}/licenses/LICENSE +0 -0
- {flyte-2.0.0b8.dist-info → flyte-2.0.0b13.dist-info}/top_level.txt +0 -0
flyte/_task_environment.py
CHANGED
|
@@ -53,7 +53,7 @@ class TaskEnvironment(Environment):
|
|
|
53
53
|
:param name: Name of the environment
|
|
54
54
|
:param image: Docker image to use for the environment. If set to "auto", will use the default image.
|
|
55
55
|
:param resources: Resources to allocate for the environment.
|
|
56
|
-
:param
|
|
56
|
+
:param env_vars: Environment variables to set for the environment.
|
|
57
57
|
:param secrets: Secrets to inject into the environment.
|
|
58
58
|
:param depends_on: Environment dependencies to hint, so when you deploy the environment,
|
|
59
59
|
the dependencies are also deployed. This is useful when you have a set of environments
|
|
@@ -80,7 +80,7 @@ class TaskEnvironment(Environment):
|
|
|
80
80
|
name: str,
|
|
81
81
|
image: Optional[Union[str, Image, Literal["auto"]]] = None,
|
|
82
82
|
resources: Optional[Resources] = None,
|
|
83
|
-
|
|
83
|
+
env_vars: Optional[Dict[str, str]] = None,
|
|
84
84
|
secrets: Optional[SecretRequest] = None,
|
|
85
85
|
depends_on: Optional[List[Environment]] = None,
|
|
86
86
|
**kwargs: Any,
|
|
@@ -93,7 +93,7 @@ class TaskEnvironment(Environment):
|
|
|
93
93
|
:param name: The name of the environment.
|
|
94
94
|
:param image: The image to use for the environment.
|
|
95
95
|
:param resources: The resources to allocate for the environment.
|
|
96
|
-
:param
|
|
96
|
+
:param env_vars: The environment variables to set for the environment.
|
|
97
97
|
:param secrets: The secrets to inject into the environment.
|
|
98
98
|
:param depends_on: The environment dependencies to hint, so when you deploy the environment,
|
|
99
99
|
the dependencies are also deployed. This is useful when you have a set of environments
|
|
@@ -119,8 +119,8 @@ class TaskEnvironment(Environment):
|
|
|
119
119
|
kwargs["resources"] = resources
|
|
120
120
|
if cache is not None:
|
|
121
121
|
kwargs["cache"] = cache
|
|
122
|
-
if
|
|
123
|
-
kwargs["
|
|
122
|
+
if env_vars is not None:
|
|
123
|
+
kwargs["env_vars"] = env_vars
|
|
124
124
|
if reusable_set:
|
|
125
125
|
kwargs["reusable"] = reusable
|
|
126
126
|
if secrets is not None:
|
|
@@ -138,7 +138,6 @@ class TaskEnvironment(Environment):
|
|
|
138
138
|
retries: Union[int, RetryStrategy] = 0,
|
|
139
139
|
timeout: Union[timedelta, int] = 0,
|
|
140
140
|
docs: Optional[Documentation] = None,
|
|
141
|
-
secrets: Optional[SecretRequest] = None,
|
|
142
141
|
pod_template: Optional[Union[str, "V1PodTemplate"]] = None,
|
|
143
142
|
report: bool = False,
|
|
144
143
|
max_inline_io_bytes: int = MAX_INLINE_IO_BYTES,
|
|
@@ -153,7 +152,6 @@ class TaskEnvironment(Environment):
|
|
|
153
152
|
task.
|
|
154
153
|
:param retries: Optional The number of retries for the task, defaults to 0, which means no retries.
|
|
155
154
|
:param docs: Optional The documentation for the task, if not provided the function docstring will be used.
|
|
156
|
-
:param secrets: Optional The secrets that will be injected into the task at runtime.
|
|
157
155
|
:param timeout: Optional The timeout for the task.
|
|
158
156
|
:param pod_template: Optional The pod template for the task, if not provided the default pod template will be
|
|
159
157
|
used.
|
|
@@ -203,8 +201,8 @@ class TaskEnvironment(Environment):
|
|
|
203
201
|
timeout=timeout,
|
|
204
202
|
reusable=self.reusable,
|
|
205
203
|
docs=docs,
|
|
206
|
-
|
|
207
|
-
secrets=
|
|
204
|
+
env_vars=self.env_vars,
|
|
205
|
+
secrets=self.secrets,
|
|
208
206
|
pod_template=pod_template or self.pod_template,
|
|
209
207
|
parent_env=weakref.ref(self),
|
|
210
208
|
interface=NativeInterface.from_callable(func),
|
flyte/_tools.py
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import os
|
|
2
|
-
|
|
3
|
-
|
|
4
1
|
def ipython_check() -> bool:
|
|
5
2
|
"""
|
|
6
3
|
Check if interface is launching from iPython (not colab)
|
|
@@ -17,16 +14,6 @@ def ipython_check() -> bool:
|
|
|
17
14
|
return is_ipython
|
|
18
15
|
|
|
19
16
|
|
|
20
|
-
def is_in_cluster() -> bool:
|
|
21
|
-
"""
|
|
22
|
-
Check if the task is running in a cluster
|
|
23
|
-
:return is_in_cluster (bool): True or False
|
|
24
|
-
"""
|
|
25
|
-
if os.getenv("_UN_CLS"):
|
|
26
|
-
return True
|
|
27
|
-
return False
|
|
28
|
-
|
|
29
|
-
|
|
30
17
|
def ipywidgets_check() -> bool:
|
|
31
18
|
"""
|
|
32
19
|
Check if the interface is running in IPython with ipywidgets support.
|
flyte/_version.py
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
# file generated by setuptools-scm
|
|
2
2
|
# don't change, don't track in version control
|
|
3
3
|
|
|
4
|
-
__all__ = [
|
|
4
|
+
__all__ = [
|
|
5
|
+
"__version__",
|
|
6
|
+
"__version_tuple__",
|
|
7
|
+
"version",
|
|
8
|
+
"version_tuple",
|
|
9
|
+
"__commit_id__",
|
|
10
|
+
"commit_id",
|
|
11
|
+
]
|
|
5
12
|
|
|
6
13
|
TYPE_CHECKING = False
|
|
7
14
|
if TYPE_CHECKING:
|
|
@@ -9,13 +16,19 @@ if TYPE_CHECKING:
|
|
|
9
16
|
from typing import Union
|
|
10
17
|
|
|
11
18
|
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
+
COMMIT_ID = Union[str, None]
|
|
12
20
|
else:
|
|
13
21
|
VERSION_TUPLE = object
|
|
22
|
+
COMMIT_ID = object
|
|
14
23
|
|
|
15
24
|
version: str
|
|
16
25
|
__version__: str
|
|
17
26
|
__version_tuple__: VERSION_TUPLE
|
|
18
27
|
version_tuple: VERSION_TUPLE
|
|
28
|
+
commit_id: COMMIT_ID
|
|
29
|
+
__commit_id__: COMMIT_ID
|
|
19
30
|
|
|
20
|
-
__version__ = version = '2.0.
|
|
21
|
-
__version_tuple__ = version_tuple = (2, 0, 0, '
|
|
31
|
+
__version__ = version = '2.0.0b13'
|
|
32
|
+
__version_tuple__ = version_tuple = (2, 0, 0, 'b13')
|
|
33
|
+
|
|
34
|
+
__commit_id__ = commit_id = 'g07f30e36d'
|
flyte/cli/_common.py
CHANGED
|
@@ -316,11 +316,23 @@ class FileGroup(GroupBase):
|
|
|
316
316
|
def files(self):
|
|
317
317
|
if self._files is None:
|
|
318
318
|
directory = self._dir or Path(".").absolute()
|
|
319
|
-
|
|
320
|
-
if
|
|
321
|
-
|
|
322
|
-
|
|
319
|
+
# add python files
|
|
320
|
+
_files = [os.fspath(p) for p in directory.glob("*.py") if p.name != "__init__.py"]
|
|
321
|
+
|
|
322
|
+
# add directories
|
|
323
|
+
_files.extend(
|
|
324
|
+
[
|
|
325
|
+
os.fspath(directory / p.name)
|
|
326
|
+
for p in directory.iterdir()
|
|
327
|
+
if not p.name.startswith(("_", ".")) and p.is_dir()
|
|
323
328
|
]
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
# files that are in the current directory or subdirectories of the
|
|
332
|
+
# current directory should be displayed as relative paths
|
|
333
|
+
self._files = [
|
|
334
|
+
str(Path(f).relative_to(Path.cwd())) if Path(f).is_relative_to(Path.cwd()) else f for f in _files
|
|
335
|
+
]
|
|
324
336
|
return self._files
|
|
325
337
|
|
|
326
338
|
def list_commands(self, ctx):
|
|
@@ -351,7 +363,6 @@ def format(title: str, vals: Iterable[Any], of: OutputFormat = "table") -> Table
|
|
|
351
363
|
"""
|
|
352
364
|
Get a table from a list of values.
|
|
353
365
|
"""
|
|
354
|
-
|
|
355
366
|
match of:
|
|
356
367
|
case "table-simple":
|
|
357
368
|
return _table_format(Table(title, box=None), vals)
|
flyte/cli/_gen.py
CHANGED
|
@@ -67,8 +67,13 @@ def markdown(cfg: common.CLIConfig):
|
|
|
67
67
|
output_verb_groups: dict[str, list[str]] = {}
|
|
68
68
|
output_noun_groups: dict[str, list[str]] = {}
|
|
69
69
|
|
|
70
|
+
processed = []
|
|
70
71
|
commands = [*[("flyte", ctx.command)], *walk_commands(ctx)]
|
|
71
72
|
for cmd_path, cmd in commands:
|
|
73
|
+
if cmd in processed:
|
|
74
|
+
# We already processed this command, skip it
|
|
75
|
+
continue
|
|
76
|
+
processed.append(cmd)
|
|
72
77
|
output.append("")
|
|
73
78
|
|
|
74
79
|
cmd_path_parts = cmd_path.split(" ")
|
|
@@ -136,7 +141,11 @@ def markdown(cfg: common.CLIConfig):
|
|
|
136
141
|
output_verb_index.append("| ------ | -- |")
|
|
137
142
|
for verb, nouns in output_verb_groups.items():
|
|
138
143
|
entries = [f"[`{noun}`](#flyte-{verb}-{noun})" for noun in nouns]
|
|
139
|
-
|
|
144
|
+
if len(entries) == 0:
|
|
145
|
+
verb_link = f"[`{verb}`](#flyte-{verb})"
|
|
146
|
+
output_verb_index.append(f"| {verb_link} | - |")
|
|
147
|
+
else:
|
|
148
|
+
output_verb_index.append(f"| `{verb}` | {', '.join(entries)} |")
|
|
140
149
|
|
|
141
150
|
output_noun_index = []
|
|
142
151
|
|
flyte/cli/_get.py
CHANGED
|
@@ -5,8 +5,6 @@ import rich_click as click
|
|
|
5
5
|
from rich.console import Console
|
|
6
6
|
from rich.pretty import pretty_repr
|
|
7
7
|
|
|
8
|
-
import flyte.remote._action
|
|
9
|
-
|
|
10
8
|
from . import _common as common
|
|
11
9
|
|
|
12
10
|
|
|
@@ -76,7 +74,7 @@ def run(
|
|
|
76
74
|
console = Console()
|
|
77
75
|
if name:
|
|
78
76
|
details = RunDetails.get(name=name)
|
|
79
|
-
console.print(
|
|
77
|
+
console.print(common.format(f"Run {name}", [details], "json"))
|
|
80
78
|
else:
|
|
81
79
|
console.print(common.format("Runs", Run.listall(limit=limit), cfg.output_format))
|
|
82
80
|
|
|
@@ -110,7 +108,7 @@ def task(
|
|
|
110
108
|
if v is None:
|
|
111
109
|
raise click.BadParameter(f"Task {name} not found.")
|
|
112
110
|
t = v.fetch()
|
|
113
|
-
console.print(
|
|
111
|
+
console.print(common.format(f"Task {name}", [t], "json"))
|
|
114
112
|
else:
|
|
115
113
|
console.print(common.format("Tasks", Task.listall(by_task_name=name, limit=limit), cfg.output_format))
|
|
116
114
|
else:
|
|
@@ -131,19 +129,22 @@ def action(
|
|
|
131
129
|
"""
|
|
132
130
|
Get all actions for a run or details for a specific action.
|
|
133
131
|
"""
|
|
132
|
+
import flyte.remote as remote
|
|
134
133
|
|
|
135
134
|
cfg.init(project=project, domain=domain)
|
|
136
135
|
|
|
137
136
|
console = Console()
|
|
138
137
|
if action_name:
|
|
139
|
-
console.print(pretty_repr(flyte.remote._action.Action.get(run_name=run_name, name=action_name)))
|
|
140
|
-
else:
|
|
141
|
-
# List all actions for the run
|
|
142
138
|
console.print(
|
|
143
139
|
common.format(
|
|
144
|
-
f"
|
|
140
|
+
f"Action {run_name}.{action_name}", [remote.Action.get(run_name=run_name, name=action_name)], "json"
|
|
145
141
|
)
|
|
146
142
|
)
|
|
143
|
+
else:
|
|
144
|
+
# List all actions for the run
|
|
145
|
+
console.print(
|
|
146
|
+
common.format(f"Actions for {run_name}", remote.Action.listall(for_run_name=run_name), cfg.output_format)
|
|
147
|
+
)
|
|
147
148
|
|
|
148
149
|
|
|
149
150
|
@get.command(cls=common.CommandBase)
|
|
@@ -211,7 +212,7 @@ def logs(
|
|
|
211
212
|
task.cancel()
|
|
212
213
|
|
|
213
214
|
if action_name:
|
|
214
|
-
obj =
|
|
215
|
+
obj = remote.Action.get(run_name=run_name, name=action_name)
|
|
215
216
|
else:
|
|
216
217
|
obj = remote.Run.get(run_name)
|
|
217
218
|
asyncio.run(_run_log_view(obj))
|
|
@@ -235,7 +236,7 @@ def secret(
|
|
|
235
236
|
|
|
236
237
|
console = Console()
|
|
237
238
|
if name:
|
|
238
|
-
console.print(
|
|
239
|
+
console.print(common.format("Secret", [remote.Secret.get(name)], "json"))
|
|
239
240
|
else:
|
|
240
241
|
console.print(common.format("Secrets", remote.Secret.listall(), cfg.output_format))
|
|
241
242
|
|
|
@@ -275,24 +276,25 @@ def io(
|
|
|
275
276
|
raise click.BadParameter("Cannot use both --inputs-only and --outputs-only")
|
|
276
277
|
|
|
277
278
|
import flyte.remote as remote
|
|
279
|
+
from flyte.remote import ActionDetails, ActionInputs, ActionOutputs
|
|
278
280
|
|
|
279
281
|
cfg.init(project=project, domain=domain)
|
|
280
282
|
console = Console()
|
|
281
283
|
if action_name:
|
|
282
|
-
obj =
|
|
284
|
+
obj = ActionDetails.get(run_name=run_name, name=action_name)
|
|
283
285
|
else:
|
|
284
286
|
obj = remote.RunDetails.get(run_name)
|
|
285
287
|
|
|
286
288
|
async def _get_io(
|
|
287
|
-
details: Union[remote.RunDetails,
|
|
288
|
-
) -> Tuple[
|
|
289
|
+
details: Union[remote.RunDetails, ActionDetails],
|
|
290
|
+
) -> Tuple[ActionInputs | None, ActionOutputs | None | str]:
|
|
289
291
|
if inputs_only or outputs_only:
|
|
290
292
|
if inputs_only:
|
|
291
293
|
return await details.inputs(), None
|
|
292
294
|
elif outputs_only:
|
|
293
295
|
return None, await details.outputs()
|
|
294
296
|
inputs = await details.inputs()
|
|
295
|
-
outputs:
|
|
297
|
+
outputs: ActionOutputs | None | str = None
|
|
296
298
|
try:
|
|
297
299
|
outputs = await details.outputs()
|
|
298
300
|
except Exception:
|
flyte/cli/_run.py
CHANGED
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import inspect
|
|
5
5
|
from dataclasses import dataclass, field, fields
|
|
6
|
+
from functools import lru_cache
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
from types import ModuleType
|
|
8
9
|
from typing import Any, Dict, List, cast
|
|
@@ -19,6 +20,34 @@ from . import _common as common
|
|
|
19
20
|
from ._common import CLIConfig
|
|
20
21
|
from ._params import to_click_option
|
|
21
22
|
|
|
23
|
+
RUN_REMOTE_CMD = "deployed-task"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@lru_cache()
|
|
27
|
+
def _initialize_config(ctx: Context, project: str, domain: str):
|
|
28
|
+
obj: CLIConfig | None = ctx.obj
|
|
29
|
+
if obj is None:
|
|
30
|
+
import flyte.config
|
|
31
|
+
|
|
32
|
+
obj = CLIConfig(flyte.config.auto(), ctx)
|
|
33
|
+
|
|
34
|
+
obj.init(project, domain)
|
|
35
|
+
return obj
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@lru_cache()
|
|
39
|
+
def _list_tasks(
|
|
40
|
+
ctx: Context,
|
|
41
|
+
project: str,
|
|
42
|
+
domain: str,
|
|
43
|
+
by_task_name: str | None = None,
|
|
44
|
+
by_task_env: str | None = None,
|
|
45
|
+
) -> list[str]:
|
|
46
|
+
import flyte.remote
|
|
47
|
+
|
|
48
|
+
_initialize_config(ctx, project, domain)
|
|
49
|
+
return [task.name for task in flyte.remote.Task.listall(by_task_name=by_task_name, by_task_env=by_task_env)]
|
|
50
|
+
|
|
22
51
|
|
|
23
52
|
@dataclass
|
|
24
53
|
class RunArguments:
|
|
@@ -60,7 +89,7 @@ class RunArguments:
|
|
|
60
89
|
},
|
|
61
90
|
)
|
|
62
91
|
follow: bool = field(
|
|
63
|
-
default=
|
|
92
|
+
default=False,
|
|
64
93
|
metadata={
|
|
65
94
|
"click.option": click.Option(
|
|
66
95
|
["--follow", "-f"],
|
|
@@ -93,22 +122,16 @@ class RunTaskCommand(click.Command):
|
|
|
93
122
|
super().__init__(obj_name, *args, **kwargs)
|
|
94
123
|
|
|
95
124
|
def invoke(self, ctx: Context):
|
|
96
|
-
obj: CLIConfig = ctx.
|
|
97
|
-
if obj is None:
|
|
98
|
-
import flyte.config
|
|
99
|
-
|
|
100
|
-
obj = CLIConfig(flyte.config.auto(), ctx)
|
|
101
|
-
|
|
102
|
-
obj.init(self.run_args.project, self.run_args.domain)
|
|
125
|
+
obj: CLIConfig = _initialize_config(ctx, self.run_args.project, self.run_args.domain)
|
|
103
126
|
|
|
104
127
|
async def _run():
|
|
105
128
|
import flyte
|
|
106
129
|
|
|
107
|
-
r = flyte.with_runcontext(
|
|
130
|
+
r = await flyte.with_runcontext(
|
|
108
131
|
copy_style=self.run_args.copy_style,
|
|
109
132
|
mode="local" if self.run_args.local else "remote",
|
|
110
133
|
name=self.run_args.name,
|
|
111
|
-
).run(self.obj, **ctx.params)
|
|
134
|
+
).run.aio(self.obj, **ctx.params)
|
|
112
135
|
if isinstance(r, Run) and r.action is not None:
|
|
113
136
|
console = Console()
|
|
114
137
|
console.print(
|
|
@@ -156,8 +179,9 @@ class TaskPerFileGroup(common.ObjectsPerFileGroup):
|
|
|
156
179
|
"""
|
|
157
180
|
|
|
158
181
|
def __init__(self, filename: Path, run_args: RunArguments, *args, **kwargs):
|
|
159
|
-
|
|
160
|
-
|
|
182
|
+
if filename.is_absolute():
|
|
183
|
+
filename = filename.relative_to(Path.cwd())
|
|
184
|
+
super().__init__(*(filename, *args), **kwargs)
|
|
161
185
|
self.run_args = run_args
|
|
162
186
|
|
|
163
187
|
def _filter_objects(self, module: ModuleType) -> Dict[str, Any]:
|
|
@@ -173,6 +197,173 @@ class TaskPerFileGroup(common.ObjectsPerFileGroup):
|
|
|
173
197
|
)
|
|
174
198
|
|
|
175
199
|
|
|
200
|
+
class RunReferenceTaskCommand(click.Command):
|
|
201
|
+
def __init__(self, task_name: str, run_args: RunArguments, version: str | None, *args, **kwargs):
|
|
202
|
+
self.task_name = task_name
|
|
203
|
+
self.run_args = run_args
|
|
204
|
+
self.version = version
|
|
205
|
+
|
|
206
|
+
super().__init__(*args, **kwargs)
|
|
207
|
+
|
|
208
|
+
def invoke(self, ctx: click.Context):
|
|
209
|
+
obj: CLIConfig = _initialize_config(ctx, self.run_args.project, self.run_args.domain)
|
|
210
|
+
|
|
211
|
+
async def _run():
|
|
212
|
+
import flyte
|
|
213
|
+
import flyte.remote
|
|
214
|
+
|
|
215
|
+
task = flyte.remote.Task.get(self.task_name, version=self.version, auto_version="latest")
|
|
216
|
+
|
|
217
|
+
r = await flyte.with_runcontext(
|
|
218
|
+
copy_style=self.run_args.copy_style,
|
|
219
|
+
mode="local" if self.run_args.local else "remote",
|
|
220
|
+
name=self.run_args.name,
|
|
221
|
+
).run.aio(task, **ctx.params)
|
|
222
|
+
if isinstance(r, Run) and r.action is not None:
|
|
223
|
+
console = Console()
|
|
224
|
+
console.print(
|
|
225
|
+
common.get_panel(
|
|
226
|
+
"Run",
|
|
227
|
+
f"[green bold]Created Run: {r.name} [/green bold] "
|
|
228
|
+
f"(Project: {r.action.action_id.run.project}, Domain: {r.action.action_id.run.domain})\n"
|
|
229
|
+
f"➡️ [blue bold][link={r.url}]{r.url}[/link][/blue bold]",
|
|
230
|
+
obj.output_format,
|
|
231
|
+
)
|
|
232
|
+
)
|
|
233
|
+
if self.run_args.follow:
|
|
234
|
+
console.print(
|
|
235
|
+
"[dim]Log streaming enabled, will wait for task to start running "
|
|
236
|
+
"and log stream to be available[/dim]"
|
|
237
|
+
)
|
|
238
|
+
await r.show_logs.aio(max_lines=30, show_ts=True, raw=False)
|
|
239
|
+
|
|
240
|
+
asyncio.run(_run())
|
|
241
|
+
|
|
242
|
+
def get_params(self, ctx: Context) -> List[Parameter]:
|
|
243
|
+
# Note this function may be called multiple times by click.
|
|
244
|
+
import flyte.remote
|
|
245
|
+
from flyte._internal.runtime.types_serde import transform_native_to_typed_interface
|
|
246
|
+
|
|
247
|
+
_initialize_config(ctx, self.run_args.project, self.run_args.domain)
|
|
248
|
+
|
|
249
|
+
task = flyte.remote.Task.get(self.task_name, auto_version="latest")
|
|
250
|
+
task_details = task.fetch()
|
|
251
|
+
|
|
252
|
+
interface = transform_native_to_typed_interface(task_details.interface)
|
|
253
|
+
if interface is None:
|
|
254
|
+
return super().get_params(ctx)
|
|
255
|
+
inputs_interface = task_details.interface.inputs
|
|
256
|
+
|
|
257
|
+
params: List[Parameter] = []
|
|
258
|
+
for name, var in interface.inputs.variables.items():
|
|
259
|
+
default_val = None
|
|
260
|
+
if inputs_interface[name][1] is not inspect._empty:
|
|
261
|
+
default_val = inputs_interface[name][1]
|
|
262
|
+
params.append(to_click_option(name, var, inputs_interface[name][0], default_val))
|
|
263
|
+
|
|
264
|
+
self.params = params
|
|
265
|
+
return super().get_params(ctx)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
class ReferenceEnvGroup(common.GroupBase):
|
|
269
|
+
def __init__(self, name: str, *args, run_args, env: str, **kwargs):
|
|
270
|
+
super().__init__(*args, **kwargs)
|
|
271
|
+
self.name = name
|
|
272
|
+
self.env = env
|
|
273
|
+
self.run_args = run_args
|
|
274
|
+
|
|
275
|
+
def list_commands(self, ctx):
|
|
276
|
+
return _list_tasks(ctx, self.run_args.project, self.run_args.domain, by_task_env=self.env)
|
|
277
|
+
|
|
278
|
+
def get_command(self, ctx, name):
|
|
279
|
+
return RunReferenceTaskCommand(
|
|
280
|
+
task_name=name,
|
|
281
|
+
run_args=self.run_args,
|
|
282
|
+
name=name,
|
|
283
|
+
version=None,
|
|
284
|
+
help=f"Run deployed task '{name}' from the Flyte backend",
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
class ReferenceTaskGroup(common.GroupBase):
|
|
289
|
+
"""
|
|
290
|
+
Group that creates a command for each reference task in the current directory that is not __init__.py.
|
|
291
|
+
"""
|
|
292
|
+
|
|
293
|
+
def __init__(self, name: str, *args, run_args, tasks: list[str] | None = None, **kwargs):
|
|
294
|
+
super().__init__(*args, **kwargs)
|
|
295
|
+
self.name = name
|
|
296
|
+
self.run_args = run_args
|
|
297
|
+
|
|
298
|
+
def list_commands(self, ctx):
|
|
299
|
+
# list envs of all reference tasks
|
|
300
|
+
envs = []
|
|
301
|
+
for task in _list_tasks(ctx, self.run_args.project, self.run_args.domain):
|
|
302
|
+
env = task.split(".")[0]
|
|
303
|
+
if env not in envs:
|
|
304
|
+
envs.append(env)
|
|
305
|
+
return envs
|
|
306
|
+
|
|
307
|
+
@staticmethod
|
|
308
|
+
def _parse_task_name(task_name: str) -> tuple[str, str | None, str | None]:
|
|
309
|
+
import re
|
|
310
|
+
|
|
311
|
+
pattern = r"^([^.:]+)(?:\.([^:]+))?(?::(.+))?$"
|
|
312
|
+
match = re.match(pattern, task_name)
|
|
313
|
+
if not match:
|
|
314
|
+
raise click.BadParameter(f"Invalid task name format: {task_name}")
|
|
315
|
+
return match.group(1), match.group(2), match.group(3)
|
|
316
|
+
|
|
317
|
+
def _env_is_task(self, ctx: click.Context, env: str) -> bool:
|
|
318
|
+
# check if the env name is the full task name, since sometimes task
|
|
319
|
+
# names don't have an environment prefix
|
|
320
|
+
tasks = [*_list_tasks(ctx, self.run_args.project, self.run_args.domain, by_task_name=env)]
|
|
321
|
+
return len(tasks) > 0
|
|
322
|
+
|
|
323
|
+
def get_command(self, ctx, name):
|
|
324
|
+
env, task, version = self._parse_task_name(name)
|
|
325
|
+
|
|
326
|
+
match env, task, version:
|
|
327
|
+
case env, None, None:
|
|
328
|
+
if self._env_is_task(ctx, env):
|
|
329
|
+
# this handles cases where task names do not have a environment prefix
|
|
330
|
+
task_name = env
|
|
331
|
+
return RunReferenceTaskCommand(
|
|
332
|
+
task_name=task_name,
|
|
333
|
+
run_args=self.run_args,
|
|
334
|
+
name=task_name,
|
|
335
|
+
version=None,
|
|
336
|
+
help=f"Run reference task `{task_name}` from the Flyte backend",
|
|
337
|
+
)
|
|
338
|
+
else:
|
|
339
|
+
return ReferenceEnvGroup(
|
|
340
|
+
name=name,
|
|
341
|
+
run_args=self.run_args,
|
|
342
|
+
env=env,
|
|
343
|
+
help=f"Run reference tasks in the `{env}` environment from the Flyte backend",
|
|
344
|
+
)
|
|
345
|
+
case env, task, None:
|
|
346
|
+
task_name = f"{env}.{task}"
|
|
347
|
+
return RunReferenceTaskCommand(
|
|
348
|
+
task_name=task_name,
|
|
349
|
+
run_args=self.run_args,
|
|
350
|
+
name=task_name,
|
|
351
|
+
version=None,
|
|
352
|
+
help=f"Run reference task '{task_name}' from the Flyte backend",
|
|
353
|
+
)
|
|
354
|
+
case env, task, version:
|
|
355
|
+
task_name = f"{env}.{task}"
|
|
356
|
+
return RunReferenceTaskCommand(
|
|
357
|
+
task_name=task_name,
|
|
358
|
+
run_args=self.run_args,
|
|
359
|
+
version=version,
|
|
360
|
+
name=f"{task_name}:{version}",
|
|
361
|
+
help=f"Run reference task '{task_name}' from the Flyte backend",
|
|
362
|
+
)
|
|
363
|
+
case _:
|
|
364
|
+
raise click.BadParameter(f"Invalid task name format: {task_name}")
|
|
365
|
+
|
|
366
|
+
|
|
176
367
|
class TaskFiles(common.FileGroup):
|
|
177
368
|
"""
|
|
178
369
|
Group that creates a command for each file in the current directory that is not __init__.py.
|
|
@@ -191,25 +382,44 @@ class TaskFiles(common.FileGroup):
|
|
|
191
382
|
kwargs["params"].extend(RunArguments.options())
|
|
192
383
|
super().__init__(*args, directory=directory, **kwargs)
|
|
193
384
|
|
|
194
|
-
def
|
|
385
|
+
def list_commands(self, ctx):
|
|
386
|
+
return [
|
|
387
|
+
RUN_REMOTE_CMD,
|
|
388
|
+
*self.files,
|
|
389
|
+
]
|
|
390
|
+
|
|
391
|
+
def get_command(self, ctx, cmd_name):
|
|
195
392
|
run_args = RunArguments.from_dict(ctx.params)
|
|
196
|
-
|
|
393
|
+
|
|
394
|
+
if cmd_name == RUN_REMOTE_CMD:
|
|
395
|
+
return ReferenceTaskGroup(
|
|
396
|
+
name=cmd_name,
|
|
397
|
+
run_args=run_args,
|
|
398
|
+
help="Run reference task from the Flyte backend",
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
fp = Path(cmd_name)
|
|
197
402
|
if not fp.exists():
|
|
198
|
-
raise click.BadParameter(f"File {
|
|
403
|
+
raise click.BadParameter(f"File {cmd_name} does not exist")
|
|
199
404
|
if fp.is_dir():
|
|
200
|
-
return TaskFiles(
|
|
405
|
+
return TaskFiles(
|
|
406
|
+
directory=fp,
|
|
407
|
+
help=f"Run `*.py` file inside the {fp} directory",
|
|
408
|
+
)
|
|
201
409
|
return TaskPerFileGroup(
|
|
202
410
|
filename=fp,
|
|
203
411
|
run_args=run_args,
|
|
204
|
-
name=
|
|
205
|
-
help=f"Run
|
|
412
|
+
name=cmd_name,
|
|
413
|
+
help=f"Run functions decorated with `env.task` in {cmd_name}",
|
|
206
414
|
)
|
|
207
415
|
|
|
208
416
|
|
|
209
417
|
run = TaskFiles(
|
|
210
418
|
name="run",
|
|
211
|
-
help="""
|
|
212
|
-
Run a task from a python file.
|
|
419
|
+
help=f"""
|
|
420
|
+
Run a task from a python file or deployed task.
|
|
421
|
+
|
|
422
|
+
To run a remote task that already exists in Flyte, use the {RUN_REMOTE_CMD} command:
|
|
213
423
|
|
|
214
424
|
Example usage:
|
|
215
425
|
|
|
@@ -227,6 +437,30 @@ Flyte environment:
|
|
|
227
437
|
flyte run --local hello.py my_task --arg1 value1 --arg2 value2
|
|
228
438
|
```
|
|
229
439
|
|
|
440
|
+
To run tasks that you've already deployed to Flyte, use the {RUN_REMOTE_CMD} command:
|
|
441
|
+
|
|
442
|
+
```bash
|
|
443
|
+
flyte run {RUN_REMOTE_CMD} my_env.my_task --arg1 value1 --arg2 value2
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
To run a specific version of a deployed task, use the `env.task:version` syntax:
|
|
447
|
+
|
|
448
|
+
```bash
|
|
449
|
+
flyte run {RUN_REMOTE_CMD} my_env.my_task:xyz123 --arg1 value1 --arg2 value2
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
You can specify the `--config` flag to point to a specific Flyte cluster:
|
|
453
|
+
|
|
454
|
+
```bash
|
|
455
|
+
flyte run --config my-config.yaml {RUN_REMOTE_CMD} ...
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
You can discover what deployed tasks are available by running:
|
|
459
|
+
|
|
460
|
+
```bash
|
|
461
|
+
flyte run {RUN_REMOTE_CMD}
|
|
462
|
+
```
|
|
463
|
+
|
|
230
464
|
Other arguments to the run command are listed below.
|
|
231
465
|
|
|
232
466
|
Arguments for the task itself are provided after the task name and can be retrieved using `--help`. For example:
|
flyte/models.py
CHANGED
|
@@ -191,6 +191,13 @@ class TaskContext:
|
|
|
191
191
|
def __getitem__(self, key: str) -> Optional[Any]:
|
|
192
192
|
return self.data.get(key)
|
|
193
193
|
|
|
194
|
+
def is_in_cluster(self):
|
|
195
|
+
"""
|
|
196
|
+
Check if the task is running in a cluster.
|
|
197
|
+
:return: bool
|
|
198
|
+
"""
|
|
199
|
+
return self.mode == "remote"
|
|
200
|
+
|
|
194
201
|
|
|
195
202
|
@rich.repr.auto
|
|
196
203
|
@dataclass(frozen=True, kw_only=True)
|
|
@@ -34,6 +34,7 @@ class Authenticator(object):
|
|
|
34
34
|
http_proxy_url: typing.Optional[str] = None,
|
|
35
35
|
verify: bool = True,
|
|
36
36
|
ca_cert_path: typing.Optional[str] = None,
|
|
37
|
+
default_header_key: str = "authorization",
|
|
37
38
|
**kwargs,
|
|
38
39
|
):
|
|
39
40
|
"""
|
|
@@ -80,6 +81,7 @@ class Authenticator(object):
|
|
|
80
81
|
self._http_session = http_session or get_async_session(**kwargs)
|
|
81
82
|
# Id for tracking credential refresh state
|
|
82
83
|
self._creds_id = self._creds.id if self._creds else None
|
|
84
|
+
self._default_header_key = default_header_key
|
|
83
85
|
|
|
84
86
|
async def _resolve_config(self) -> ClientConfig:
|
|
85
87
|
"""
|
|
@@ -131,10 +133,14 @@ class Authenticator(object):
|
|
|
131
133
|
"""
|
|
132
134
|
creds = self.get_credentials()
|
|
133
135
|
if creds:
|
|
134
|
-
|
|
136
|
+
header_key = self._default_header_key
|
|
137
|
+
if self._resolved_config is not None:
|
|
138
|
+
# We only resolve the config during authentication flow, to avoid unnecessary network calls
|
|
139
|
+
# and usually the header_key is consistent.
|
|
140
|
+
header_key = self._resolved_config.header_key
|
|
135
141
|
return GrpcAuthMetadata(
|
|
136
142
|
creds_id=creds.id,
|
|
137
|
-
pairs=Metadata((
|
|
143
|
+
pairs=Metadata((header_key, f"Bearer {creds.access_token}")),
|
|
138
144
|
)
|
|
139
145
|
return None
|
|
140
146
|
|