flyte 0.2.0b1__py3-none-any.whl → 2.0.0b46__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 +83 -30
- flyte/_bin/connect.py +61 -0
- flyte/_bin/debug.py +38 -0
- flyte/_bin/runtime.py +87 -19
- flyte/_bin/serve.py +351 -0
- flyte/_build.py +3 -2
- flyte/_cache/cache.py +6 -5
- flyte/_cache/local_cache.py +216 -0
- flyte/_code_bundle/_ignore.py +31 -5
- flyte/_code_bundle/_packaging.py +42 -11
- flyte/_code_bundle/_utils.py +57 -34
- flyte/_code_bundle/bundle.py +130 -27
- flyte/_constants.py +1 -0
- flyte/_context.py +21 -5
- flyte/_custom_context.py +73 -0
- flyte/_debug/constants.py +37 -0
- flyte/_debug/utils.py +17 -0
- flyte/_debug/vscode.py +315 -0
- flyte/_deploy.py +396 -75
- flyte/_deployer.py +109 -0
- flyte/_environment.py +94 -11
- flyte/_excepthook.py +37 -0
- flyte/_group.py +2 -1
- flyte/_hash.py +1 -16
- flyte/_image.py +544 -231
- flyte/_initialize.py +456 -316
- flyte/_interface.py +40 -5
- flyte/_internal/controllers/__init__.py +22 -8
- flyte/_internal/controllers/_local_controller.py +159 -35
- flyte/_internal/controllers/_trace.py +18 -10
- flyte/_internal/controllers/remote/__init__.py +38 -9
- flyte/_internal/controllers/remote/_action.py +82 -12
- flyte/_internal/controllers/remote/_client.py +6 -2
- flyte/_internal/controllers/remote/_controller.py +290 -64
- flyte/_internal/controllers/remote/_core.py +155 -95
- flyte/_internal/controllers/remote/_informer.py +40 -20
- flyte/_internal/controllers/remote/_service_protocol.py +2 -2
- flyte/_internal/imagebuild/__init__.py +2 -10
- flyte/_internal/imagebuild/docker_builder.py +391 -84
- flyte/_internal/imagebuild/image_builder.py +111 -55
- flyte/_internal/imagebuild/remote_builder.py +409 -0
- flyte/_internal/imagebuild/utils.py +79 -0
- flyte/_internal/resolvers/_app_env_module.py +92 -0
- flyte/_internal/resolvers/_task_module.py +5 -38
- flyte/_internal/resolvers/app_env.py +26 -0
- flyte/_internal/resolvers/common.py +8 -1
- flyte/_internal/resolvers/default.py +2 -2
- flyte/_internal/runtime/convert.py +319 -36
- flyte/_internal/runtime/entrypoints.py +106 -18
- flyte/_internal/runtime/io.py +71 -23
- flyte/_internal/runtime/resources_serde.py +21 -7
- flyte/_internal/runtime/reuse.py +125 -0
- flyte/_internal/runtime/rusty.py +196 -0
- flyte/_internal/runtime/task_serde.py +239 -66
- flyte/_internal/runtime/taskrunner.py +48 -8
- flyte/_internal/runtime/trigger_serde.py +162 -0
- flyte/_internal/runtime/types_serde.py +7 -16
- flyte/_keyring/file.py +115 -0
- flyte/_link.py +30 -0
- flyte/_logging.py +241 -42
- flyte/_map.py +312 -0
- flyte/_metrics.py +59 -0
- flyte/_module.py +74 -0
- flyte/_pod.py +30 -0
- flyte/_resources.py +296 -33
- flyte/_retry.py +1 -7
- flyte/_reusable_environment.py +72 -7
- flyte/_run.py +462 -132
- flyte/_secret.py +47 -11
- flyte/_serve.py +333 -0
- flyte/_task.py +245 -56
- flyte/_task_environment.py +219 -97
- flyte/_task_plugins.py +47 -0
- flyte/_tools.py +8 -8
- flyte/_trace.py +15 -24
- flyte/_trigger.py +1027 -0
- flyte/_utils/__init__.py +12 -1
- flyte/_utils/asyn.py +3 -1
- flyte/_utils/async_cache.py +139 -0
- flyte/_utils/coro_management.py +5 -4
- flyte/_utils/description_parser.py +19 -0
- flyte/_utils/docker_credentials.py +173 -0
- flyte/_utils/helpers.py +45 -19
- flyte/_utils/module_loader.py +123 -0
- flyte/_utils/org_discovery.py +57 -0
- flyte/_utils/uv_script_parser.py +8 -1
- flyte/_version.py +16 -3
- flyte/app/__init__.py +27 -0
- flyte/app/_app_environment.py +362 -0
- flyte/app/_connector_environment.py +40 -0
- flyte/app/_deploy.py +130 -0
- flyte/app/_parameter.py +343 -0
- flyte/app/_runtime/__init__.py +3 -0
- flyte/app/_runtime/app_serde.py +383 -0
- flyte/app/_types.py +113 -0
- flyte/app/extras/__init__.py +9 -0
- flyte/app/extras/_auth_middleware.py +217 -0
- flyte/app/extras/_fastapi.py +93 -0
- flyte/app/extras/_model_loader/__init__.py +3 -0
- flyte/app/extras/_model_loader/config.py +7 -0
- flyte/app/extras/_model_loader/loader.py +288 -0
- flyte/cli/__init__.py +12 -0
- flyte/cli/_abort.py +28 -0
- flyte/cli/_build.py +114 -0
- flyte/cli/_common.py +493 -0
- flyte/cli/_create.py +371 -0
- flyte/cli/_delete.py +45 -0
- flyte/cli/_deploy.py +401 -0
- flyte/cli/_gen.py +316 -0
- flyte/cli/_get.py +446 -0
- flyte/cli/_option.py +33 -0
- flyte/{_cli → cli}/_params.py +57 -17
- flyte/cli/_plugins.py +209 -0
- flyte/cli/_prefetch.py +292 -0
- flyte/cli/_run.py +690 -0
- flyte/cli/_serve.py +338 -0
- flyte/cli/_update.py +86 -0
- flyte/cli/_user.py +20 -0
- flyte/cli/main.py +246 -0
- flyte/config/__init__.py +2 -167
- flyte/config/_config.py +215 -163
- flyte/config/_internal.py +10 -1
- flyte/config/_reader.py +225 -0
- flyte/connectors/__init__.py +11 -0
- flyte/connectors/_connector.py +330 -0
- flyte/connectors/_server.py +194 -0
- flyte/connectors/utils.py +159 -0
- flyte/errors.py +134 -2
- flyte/extend.py +24 -0
- flyte/extras/_container.py +69 -56
- flyte/git/__init__.py +3 -0
- flyte/git/_config.py +279 -0
- flyte/io/__init__.py +8 -1
- flyte/io/{structured_dataset → _dataframe}/__init__.py +32 -30
- flyte/io/{structured_dataset → _dataframe}/basic_dfs.py +75 -68
- flyte/io/{structured_dataset/structured_dataset.py → _dataframe/dataframe.py} +207 -242
- flyte/io/_dir.py +575 -113
- flyte/io/_file.py +587 -141
- flyte/io/_hashing_io.py +342 -0
- flyte/io/extend.py +7 -0
- flyte/models.py +635 -0
- flyte/prefetch/__init__.py +22 -0
- flyte/prefetch/_hf_model.py +563 -0
- flyte/remote/__init__.py +14 -3
- flyte/remote/_action.py +879 -0
- flyte/remote/_app.py +346 -0
- flyte/remote/_auth_metadata.py +42 -0
- flyte/remote/_client/_protocols.py +62 -4
- flyte/remote/_client/auth/_auth_utils.py +19 -0
- flyte/remote/_client/auth/_authenticators/base.py +8 -2
- flyte/remote/_client/auth/_authenticators/device_code.py +4 -5
- flyte/remote/_client/auth/_authenticators/factory.py +4 -0
- flyte/remote/_client/auth/_authenticators/passthrough.py +79 -0
- flyte/remote/_client/auth/_authenticators/pkce.py +17 -18
- flyte/remote/_client/auth/_channel.py +47 -18
- flyte/remote/_client/auth/_client_config.py +5 -3
- flyte/remote/_client/auth/_keyring.py +15 -2
- flyte/remote/_client/auth/_token_client.py +3 -3
- flyte/remote/_client/controlplane.py +206 -18
- flyte/remote/_common.py +66 -0
- flyte/remote/_data.py +107 -22
- flyte/remote/_logs.py +116 -33
- flyte/remote/_project.py +21 -19
- flyte/remote/_run.py +164 -631
- flyte/remote/_secret.py +72 -29
- flyte/remote/_task.py +387 -46
- flyte/remote/_trigger.py +368 -0
- flyte/remote/_user.py +43 -0
- flyte/report/_report.py +10 -6
- flyte/storage/__init__.py +13 -1
- flyte/storage/_config.py +237 -0
- flyte/storage/_parallel_reader.py +289 -0
- flyte/storage/_storage.py +268 -59
- flyte/syncify/__init__.py +56 -0
- flyte/syncify/_api.py +414 -0
- flyte/types/__init__.py +39 -0
- flyte/types/_interface.py +22 -7
- flyte/{io/pickle/transformer.py → types/_pickle.py} +37 -9
- flyte/types/_string_literals.py +8 -9
- flyte/types/_type_engine.py +226 -126
- flyte/types/_utils.py +1 -1
- flyte-2.0.0b46.data/scripts/debug.py +38 -0
- flyte-2.0.0b46.data/scripts/runtime.py +194 -0
- flyte-2.0.0b46.dist-info/METADATA +352 -0
- flyte-2.0.0b46.dist-info/RECORD +221 -0
- flyte-2.0.0b46.dist-info/entry_points.txt +8 -0
- flyte-2.0.0b46.dist-info/licenses/LICENSE +201 -0
- flyte/_api_commons.py +0 -3
- flyte/_cli/_common.py +0 -299
- flyte/_cli/_create.py +0 -42
- flyte/_cli/_delete.py +0 -23
- flyte/_cli/_deploy.py +0 -140
- flyte/_cli/_get.py +0 -235
- flyte/_cli/_run.py +0 -174
- flyte/_cli/main.py +0 -98
- flyte/_datastructures.py +0 -342
- flyte/_internal/controllers/pbhash.py +0 -39
- flyte/_protos/common/authorization_pb2.py +0 -66
- flyte/_protos/common/authorization_pb2.pyi +0 -108
- flyte/_protos/common/authorization_pb2_grpc.py +0 -4
- flyte/_protos/common/identifier_pb2.py +0 -71
- flyte/_protos/common/identifier_pb2.pyi +0 -82
- flyte/_protos/common/identifier_pb2_grpc.py +0 -4
- flyte/_protos/common/identity_pb2.py +0 -48
- flyte/_protos/common/identity_pb2.pyi +0 -72
- flyte/_protos/common/identity_pb2_grpc.py +0 -4
- flyte/_protos/common/list_pb2.py +0 -36
- flyte/_protos/common/list_pb2.pyi +0 -69
- flyte/_protos/common/list_pb2_grpc.py +0 -4
- flyte/_protos/common/policy_pb2.py +0 -37
- flyte/_protos/common/policy_pb2.pyi +0 -27
- flyte/_protos/common/policy_pb2_grpc.py +0 -4
- flyte/_protos/common/role_pb2.py +0 -37
- flyte/_protos/common/role_pb2.pyi +0 -53
- flyte/_protos/common/role_pb2_grpc.py +0 -4
- flyte/_protos/common/runtime_version_pb2.py +0 -28
- flyte/_protos/common/runtime_version_pb2.pyi +0 -24
- flyte/_protos/common/runtime_version_pb2_grpc.py +0 -4
- flyte/_protos/logs/dataplane/payload_pb2.py +0 -96
- flyte/_protos/logs/dataplane/payload_pb2.pyi +0 -168
- flyte/_protos/logs/dataplane/payload_pb2_grpc.py +0 -4
- flyte/_protos/secret/definition_pb2.py +0 -49
- flyte/_protos/secret/definition_pb2.pyi +0 -93
- flyte/_protos/secret/definition_pb2_grpc.py +0 -4
- flyte/_protos/secret/payload_pb2.py +0 -62
- flyte/_protos/secret/payload_pb2.pyi +0 -94
- flyte/_protos/secret/payload_pb2_grpc.py +0 -4
- flyte/_protos/secret/secret_pb2.py +0 -38
- flyte/_protos/secret/secret_pb2.pyi +0 -6
- flyte/_protos/secret/secret_pb2_grpc.py +0 -198
- flyte/_protos/secret/secret_pb2_grpc_grpc.py +0 -198
- flyte/_protos/validate/validate/validate_pb2.py +0 -76
- flyte/_protos/workflow/node_execution_service_pb2.py +0 -26
- flyte/_protos/workflow/node_execution_service_pb2.pyi +0 -4
- flyte/_protos/workflow/node_execution_service_pb2_grpc.py +0 -32
- flyte/_protos/workflow/queue_service_pb2.py +0 -106
- flyte/_protos/workflow/queue_service_pb2.pyi +0 -141
- flyte/_protos/workflow/queue_service_pb2_grpc.py +0 -172
- flyte/_protos/workflow/run_definition_pb2.py +0 -128
- flyte/_protos/workflow/run_definition_pb2.pyi +0 -310
- flyte/_protos/workflow/run_definition_pb2_grpc.py +0 -4
- flyte/_protos/workflow/run_logs_service_pb2.py +0 -41
- flyte/_protos/workflow/run_logs_service_pb2.pyi +0 -28
- flyte/_protos/workflow/run_logs_service_pb2_grpc.py +0 -69
- flyte/_protos/workflow/run_service_pb2.py +0 -133
- flyte/_protos/workflow/run_service_pb2.pyi +0 -175
- flyte/_protos/workflow/run_service_pb2_grpc.py +0 -412
- flyte/_protos/workflow/state_service_pb2.py +0 -58
- flyte/_protos/workflow/state_service_pb2.pyi +0 -71
- flyte/_protos/workflow/state_service_pb2_grpc.py +0 -138
- flyte/_protos/workflow/task_definition_pb2.py +0 -72
- flyte/_protos/workflow/task_definition_pb2.pyi +0 -65
- flyte/_protos/workflow/task_definition_pb2_grpc.py +0 -4
- flyte/_protos/workflow/task_service_pb2.py +0 -44
- flyte/_protos/workflow/task_service_pb2.pyi +0 -31
- flyte/_protos/workflow/task_service_pb2_grpc.py +0 -104
- flyte/io/_dataframe.py +0 -0
- flyte/io/pickle/__init__.py +0 -0
- flyte/remote/_console.py +0 -18
- flyte-0.2.0b1.dist-info/METADATA +0 -179
- flyte-0.2.0b1.dist-info/RECORD +0 -204
- flyte-0.2.0b1.dist-info/entry_points.txt +0 -3
- /flyte/{_cli → _debug}/__init__.py +0 -0
- /flyte/{_protos → _keyring}/__init__.py +0 -0
- {flyte-0.2.0b1.dist-info → flyte-2.0.0b46.dist-info}/WHEEL +0 -0
- {flyte-0.2.0b1.dist-info → flyte-2.0.0b46.dist-info}/top_level.txt +0 -0
flyte/cli/_build.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
from dataclasses import dataclass, field, fields
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from types import ModuleType
|
|
4
|
+
from typing import Any, Dict, List, cast
|
|
5
|
+
|
|
6
|
+
import rich_click as click
|
|
7
|
+
|
|
8
|
+
import flyte
|
|
9
|
+
|
|
10
|
+
from . import _common as common
|
|
11
|
+
from ._common import CLIConfig
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class BuildArguments:
|
|
16
|
+
noop: bool = field(
|
|
17
|
+
default=False,
|
|
18
|
+
metadata={
|
|
19
|
+
"click.option": click.Option(
|
|
20
|
+
["--noop"],
|
|
21
|
+
type=bool,
|
|
22
|
+
help="Dummy parameter, placeholder for future use. Does not affect the build process.",
|
|
23
|
+
)
|
|
24
|
+
},
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def from_dict(cls, d: Dict[str, Any]) -> "BuildArguments":
|
|
29
|
+
return cls(**d)
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def options(cls) -> List[click.Option]:
|
|
33
|
+
"""
|
|
34
|
+
Return the set of base parameters added to every flyte run workflow subcommand.
|
|
35
|
+
"""
|
|
36
|
+
return [common.get_option_from_metadata(f.metadata) for f in fields(cls) if f.metadata]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class BuildEnvCommand(click.Command):
|
|
40
|
+
def __init__(self, obj_name: str, obj: Any, build_args: BuildArguments, *args, **kwargs):
|
|
41
|
+
self.obj_name = obj_name
|
|
42
|
+
self.obj = obj
|
|
43
|
+
self.build_args = build_args
|
|
44
|
+
super().__init__(*args, **kwargs)
|
|
45
|
+
|
|
46
|
+
def invoke(self, ctx: click.Context):
|
|
47
|
+
console = common.get_console()
|
|
48
|
+
console.print(f"Building Environment: {self.obj_name}")
|
|
49
|
+
obj: CLIConfig = ctx.obj
|
|
50
|
+
obj.init()
|
|
51
|
+
with console.status("Building...", spinner="dots"):
|
|
52
|
+
image_cache = flyte.build_images(self.obj)
|
|
53
|
+
|
|
54
|
+
console.print(common.format("Images", image_cache.repr(), obj.output_format))
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class EnvPerFileGroup(common.ObjectsPerFileGroup):
|
|
58
|
+
"""
|
|
59
|
+
Group that creates a command for each task in the current directory that is not `__init__.py`.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(self, filename: Path, build_args: BuildArguments, *args, **kwargs):
|
|
63
|
+
args = (filename, *args)
|
|
64
|
+
super().__init__(*args, **kwargs)
|
|
65
|
+
self.build_args = build_args
|
|
66
|
+
|
|
67
|
+
def _filter_objects(self, module: ModuleType) -> Dict[str, Any]:
|
|
68
|
+
return {k: v for k, v in module.__dict__.items() if isinstance(v, flyte.Environment)}
|
|
69
|
+
|
|
70
|
+
def _get_command_for_obj(self, ctx: click.Context, obj_name: str, obj: Any) -> click.Command:
|
|
71
|
+
obj = cast(flyte.Environment, obj)
|
|
72
|
+
return BuildEnvCommand(
|
|
73
|
+
name=obj_name,
|
|
74
|
+
obj_name=obj_name,
|
|
75
|
+
obj=obj,
|
|
76
|
+
help=f"{obj.name}" + (f": {obj.description}" if obj.description else ""),
|
|
77
|
+
build_args=self.build_args,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class EnvFiles(common.FileGroup):
|
|
82
|
+
"""
|
|
83
|
+
Group that creates a command for each file in the current directory that is not `__init__.py`.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
common_options_enabled = False
|
|
87
|
+
|
|
88
|
+
def __init__(
|
|
89
|
+
self,
|
|
90
|
+
*args,
|
|
91
|
+
**kwargs,
|
|
92
|
+
):
|
|
93
|
+
if "params" not in kwargs:
|
|
94
|
+
kwargs["params"] = []
|
|
95
|
+
kwargs["params"].extend(BuildArguments.options())
|
|
96
|
+
super().__init__(*args, **kwargs)
|
|
97
|
+
|
|
98
|
+
def get_command(self, ctx, filename):
|
|
99
|
+
build_args = BuildArguments.from_dict(ctx.params)
|
|
100
|
+
return EnvPerFileGroup(
|
|
101
|
+
filename=Path(filename),
|
|
102
|
+
build_args=build_args,
|
|
103
|
+
name=filename,
|
|
104
|
+
help=f"Run, functions decorated `env.task` or instances of Tasks in {filename}",
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
build = EnvFiles(
|
|
109
|
+
name="build",
|
|
110
|
+
help="""
|
|
111
|
+
Build the environments defined in a python file or directory. This will build the images associated with the
|
|
112
|
+
environments.
|
|
113
|
+
""",
|
|
114
|
+
)
|
flyte/cli/_common.py
ADDED
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import importlib.util
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import pathlib
|
|
8
|
+
import sys
|
|
9
|
+
from abc import abstractmethod
|
|
10
|
+
from dataclasses import dataclass, replace
|
|
11
|
+
from functools import lru_cache
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from types import MappingProxyType, ModuleType
|
|
14
|
+
from typing import Any, Dict, Iterable, List, Literal, Optional
|
|
15
|
+
|
|
16
|
+
import rich.box
|
|
17
|
+
import rich.repr
|
|
18
|
+
import rich_click as click
|
|
19
|
+
from rich.console import Console
|
|
20
|
+
from rich.panel import Panel
|
|
21
|
+
from rich.pretty import pretty_repr
|
|
22
|
+
from rich.table import Table
|
|
23
|
+
from rich.traceback import Traceback
|
|
24
|
+
|
|
25
|
+
import flyte
|
|
26
|
+
import flyte.errors
|
|
27
|
+
from flyte._logging import LogFormat
|
|
28
|
+
from flyte.config import Config
|
|
29
|
+
|
|
30
|
+
OutputFormat = Literal["table", "json", "table-simple", "json-raw"]
|
|
31
|
+
|
|
32
|
+
PREFERRED_BORDER_COLOR = "dim cyan"
|
|
33
|
+
PREFERRED_ACCENT_COLOR = "bold #FFD700"
|
|
34
|
+
HEADER_STYLE = f"{PREFERRED_ACCENT_COLOR} on black"
|
|
35
|
+
|
|
36
|
+
PROJECT_OPTION = click.Option(
|
|
37
|
+
param_decls=["-p", "--project"],
|
|
38
|
+
required=False,
|
|
39
|
+
type=str,
|
|
40
|
+
default=None,
|
|
41
|
+
help="Project to which this command applies.",
|
|
42
|
+
show_default=True,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
DOMAIN_OPTION = click.Option(
|
|
46
|
+
param_decls=["-d", "--domain"],
|
|
47
|
+
required=False,
|
|
48
|
+
type=str,
|
|
49
|
+
default=None,
|
|
50
|
+
help="Domain to which this command applies.",
|
|
51
|
+
show_default=True,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
DRY_RUN_OPTION = click.Option(
|
|
55
|
+
param_decls=["--dry-run", "--dryrun"],
|
|
56
|
+
required=False,
|
|
57
|
+
type=bool,
|
|
58
|
+
is_flag=True,
|
|
59
|
+
default=False,
|
|
60
|
+
help="Dry run. Do not actually call the backend service.",
|
|
61
|
+
show_default=True,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _common_options() -> List[click.Option]:
|
|
66
|
+
"""
|
|
67
|
+
Common options that will be added to all commands and groups that inherit from CommandBase or GroupBase.
|
|
68
|
+
"""
|
|
69
|
+
return [PROJECT_OPTION, DOMAIN_OPTION]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# This is global state for the CLI, it is manipulated by the main command
|
|
73
|
+
_client_secret_options = ["client-secret", "client_secret", "clientsecret", "app-credential", "app_credential"]
|
|
74
|
+
_device_flow_options = ["headless", "device-flow", "device_flow"]
|
|
75
|
+
_pkce_options = ["pkce"]
|
|
76
|
+
_external_command_options = ["external-command", "external_command", "externalcommand", "command", "custom"]
|
|
77
|
+
ALL_AUTH_OPTIONS = _client_secret_options + _device_flow_options + _pkce_options + _external_command_options
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def sanitize_auth_type(auth_type: str | None) -> str:
|
|
81
|
+
"""
|
|
82
|
+
Convert the auth type to the mode that is used by the Flyte backend.
|
|
83
|
+
"""
|
|
84
|
+
if auth_type is None or auth_type.lower() in _pkce_options:
|
|
85
|
+
return "Pkce"
|
|
86
|
+
if auth_type.lower() in _device_flow_options:
|
|
87
|
+
return "DeviceFlow"
|
|
88
|
+
if auth_type.lower() in _client_secret_options:
|
|
89
|
+
return "ClientSecret"
|
|
90
|
+
if auth_type.lower() in _external_command_options:
|
|
91
|
+
return "ExternalCommand"
|
|
92
|
+
raise ValueError(f"Unknown auth type: {auth_type}. Supported types are: {ALL_AUTH_OPTIONS}.")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@rich.repr.auto
|
|
96
|
+
@dataclass(frozen=True)
|
|
97
|
+
class CLIConfig:
|
|
98
|
+
"""
|
|
99
|
+
This is the global state for the CLI. It is manipulated by the main command.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
config: Config
|
|
103
|
+
ctx: click.Context
|
|
104
|
+
log_level: int | None = logging.ERROR
|
|
105
|
+
log_format: LogFormat = "console"
|
|
106
|
+
reset_root_logger: bool = False
|
|
107
|
+
endpoint: str | None = None
|
|
108
|
+
insecure: bool = False
|
|
109
|
+
org: str | None = None
|
|
110
|
+
auth_type: str | None = None
|
|
111
|
+
output_format: OutputFormat = "table"
|
|
112
|
+
|
|
113
|
+
def replace(self, **kwargs) -> CLIConfig:
|
|
114
|
+
"""
|
|
115
|
+
Replace the global state with a new one.
|
|
116
|
+
"""
|
|
117
|
+
return replace(self, **kwargs)
|
|
118
|
+
|
|
119
|
+
def init(
|
|
120
|
+
self,
|
|
121
|
+
project: str | None = None,
|
|
122
|
+
domain: str | None = None,
|
|
123
|
+
root_dir: str | None = None,
|
|
124
|
+
images: tuple[str, ...] | None = None,
|
|
125
|
+
sync_local_sys_paths: bool = True,
|
|
126
|
+
):
|
|
127
|
+
from flyte.config._config import TaskConfig
|
|
128
|
+
|
|
129
|
+
# Check if FLYTE_API_KEY is set and no config file was found
|
|
130
|
+
api_key = os.getenv("FLYTE_API_KEY")
|
|
131
|
+
has_config_file = self.config.source is not None
|
|
132
|
+
|
|
133
|
+
# Use API key initialization only if:
|
|
134
|
+
# 1. FLYTE_API_KEY is set AND
|
|
135
|
+
# 2. No config file exists
|
|
136
|
+
if api_key and not has_config_file:
|
|
137
|
+
# The API key is encoded and contains endpoint, client_id, client_secret, and org
|
|
138
|
+
# init_from_api_key will decode it automatically
|
|
139
|
+
flyte.init_from_api_key(
|
|
140
|
+
api_key=api_key,
|
|
141
|
+
project=project if project is not None else self.config.task.project,
|
|
142
|
+
domain=domain if domain is not None else self.config.task.domain,
|
|
143
|
+
log_level=self.log_level,
|
|
144
|
+
log_format=self.log_format,
|
|
145
|
+
root_dir=pathlib.Path(root_dir) if root_dir else None,
|
|
146
|
+
sync_local_sys_paths=sync_local_sys_paths,
|
|
147
|
+
)
|
|
148
|
+
else:
|
|
149
|
+
# Use the standard config-based initialization
|
|
150
|
+
task_cfg = TaskConfig(
|
|
151
|
+
org=self.org or self.config.task.org,
|
|
152
|
+
project=project if project is not None else self.config.task.project,
|
|
153
|
+
domain=domain if domain is not None else self.config.task.domain,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
kwargs: Dict[str, Any] = {}
|
|
157
|
+
if self.endpoint:
|
|
158
|
+
kwargs["endpoint"] = self.endpoint
|
|
159
|
+
if self.insecure is not None:
|
|
160
|
+
kwargs["insecure"] = self.insecure
|
|
161
|
+
if self.auth_type:
|
|
162
|
+
kwargs["auth_mode"] = sanitize_auth_type(self.auth_type)
|
|
163
|
+
platform_cfg = self.config.platform.replace(**kwargs)
|
|
164
|
+
|
|
165
|
+
updated_config = self.config.with_params(platform_cfg, task_cfg)
|
|
166
|
+
flyte.init_from_config(
|
|
167
|
+
updated_config,
|
|
168
|
+
log_level=self.log_level,
|
|
169
|
+
log_format=self.log_format,
|
|
170
|
+
root_dir=pathlib.Path(root_dir) if root_dir else None,
|
|
171
|
+
images=images,
|
|
172
|
+
sync_local_sys_paths=sync_local_sys_paths,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class InvokeBaseMixin:
|
|
177
|
+
"""
|
|
178
|
+
Mixin to catch grpc.RpcError, flyte.RpcError, other errors and other exceptions
|
|
179
|
+
and raise them as gclick.ClickException.
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
def invoke(self, ctx):
|
|
183
|
+
import os
|
|
184
|
+
|
|
185
|
+
import grpc
|
|
186
|
+
|
|
187
|
+
try:
|
|
188
|
+
_ = super().invoke(ctx) # type: ignore
|
|
189
|
+
# Exit successfully to properly close grpc channel
|
|
190
|
+
os._exit(0)
|
|
191
|
+
except grpc.aio.AioRpcError as e:
|
|
192
|
+
if e.code() == grpc.StatusCode.UNAUTHENTICATED:
|
|
193
|
+
raise click.ClickException(f"Authentication failed. Please check your credentials. {e.details()}")
|
|
194
|
+
if e.code() == grpc.StatusCode.NOT_FOUND:
|
|
195
|
+
raise click.ClickException(f"Requested object NOT FOUND. Please check your input. Error: {e.details()}")
|
|
196
|
+
if e.code() == grpc.StatusCode.ALREADY_EXISTS:
|
|
197
|
+
raise click.ClickException("Resource already exists.")
|
|
198
|
+
if e.code() == grpc.StatusCode.INTERNAL:
|
|
199
|
+
raise click.ClickException(f"Internal server error: {e.details()}")
|
|
200
|
+
if e.code() == grpc.StatusCode.UNAVAILABLE:
|
|
201
|
+
raise click.ClickException(
|
|
202
|
+
f"Service is currently unavailable. Please try again later. Error: {e.details()}"
|
|
203
|
+
)
|
|
204
|
+
if e.code() == grpc.StatusCode.PERMISSION_DENIED:
|
|
205
|
+
raise click.ClickException(f"Permission denied. Please check your access rights. Error: {e.details()}")
|
|
206
|
+
if e.code() == grpc.StatusCode.INVALID_ARGUMENT:
|
|
207
|
+
raise click.ClickException(f"Invalid argument provided. Please check your input. Error: {e.details()}")
|
|
208
|
+
raise click.ClickException(f"RPC error invoking command: {e!s}") from e
|
|
209
|
+
except flyte.errors.InitializationError as e:
|
|
210
|
+
raise click.ClickException(f"Initialization failed. Pass remote config for CLI. (Reason: {e})")
|
|
211
|
+
except flyte.errors.BaseRuntimeError as e:
|
|
212
|
+
raise click.ClickException(f"{e.kind} failure, {e.code}. {e}") from e
|
|
213
|
+
except click.exceptions.Exit as e:
|
|
214
|
+
# This is a normal exit, do nothing
|
|
215
|
+
raise e
|
|
216
|
+
except click.exceptions.NoArgsIsHelpError:
|
|
217
|
+
# Do not raise an error if no arguments are passed, just show the help message.
|
|
218
|
+
# https://github.com/pallets/click/pull/1489
|
|
219
|
+
return None
|
|
220
|
+
except Exception as e:
|
|
221
|
+
if ctx.obj and ctx.obj.log_level and ctx.obj.log_level <= logging.DEBUG:
|
|
222
|
+
# If the user has requested verbose output, print the full traceback
|
|
223
|
+
console = get_console()
|
|
224
|
+
console.print(Traceback.from_exception(type(e), e, e.__traceback__))
|
|
225
|
+
exit(1)
|
|
226
|
+
else:
|
|
227
|
+
raise click.ClickException(f"Error invoking command: {e}") from e
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
class CommandBase(InvokeBaseMixin, click.RichCommand):
|
|
231
|
+
"""
|
|
232
|
+
Base class for all commands, that adds common options to all commands if enabled.
|
|
233
|
+
"""
|
|
234
|
+
|
|
235
|
+
common_options_enabled = True
|
|
236
|
+
|
|
237
|
+
def __init__(self, *args, **kwargs):
|
|
238
|
+
if "params" not in kwargs:
|
|
239
|
+
kwargs["params"] = []
|
|
240
|
+
if self.common_options_enabled:
|
|
241
|
+
kwargs["params"].extend(_common_options())
|
|
242
|
+
super().__init__(*args, **kwargs)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
class GroupBase(InvokeBaseMixin, click.RichGroup):
|
|
246
|
+
"""
|
|
247
|
+
Base class for all commands, that adds common options to all commands if enabled.
|
|
248
|
+
"""
|
|
249
|
+
|
|
250
|
+
common_options_enabled = True
|
|
251
|
+
|
|
252
|
+
def __init__(self, *args, **kwargs):
|
|
253
|
+
if "params" not in kwargs:
|
|
254
|
+
kwargs["params"] = []
|
|
255
|
+
if self.common_options_enabled:
|
|
256
|
+
kwargs["params"].extend(_common_options())
|
|
257
|
+
super().__init__(*args, **kwargs)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
class GroupBaseNoOptions(GroupBase):
|
|
261
|
+
common_options_enabled = False
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def get_option_from_metadata(metadata: MappingProxyType) -> click.Option:
|
|
265
|
+
return metadata["click.option"]
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def key_value_callback(_: Any, param: str, values: List[str]) -> Optional[Dict[str, str]]:
|
|
269
|
+
"""
|
|
270
|
+
Callback for click to parse key-value pairs.
|
|
271
|
+
"""
|
|
272
|
+
if not values:
|
|
273
|
+
return None
|
|
274
|
+
result = {}
|
|
275
|
+
for v in values:
|
|
276
|
+
if "=" not in v:
|
|
277
|
+
raise click.BadParameter(f"Expected key-value pair of the form key=value, got {v}")
|
|
278
|
+
k, v_ = v.split("=", 1)
|
|
279
|
+
result[k.strip()] = v_.strip()
|
|
280
|
+
return result
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
class ObjectsPerFileGroup(GroupBase):
|
|
284
|
+
"""
|
|
285
|
+
Group that creates a command for each object in a python file.
|
|
286
|
+
"""
|
|
287
|
+
|
|
288
|
+
def __init__(self, filename: Path | None = None, *args, **kwargs):
|
|
289
|
+
super().__init__(*args, **kwargs)
|
|
290
|
+
if filename is None:
|
|
291
|
+
raise ValueError("filename must be provided")
|
|
292
|
+
if not filename.exists():
|
|
293
|
+
raise click.ClickException(f"{filename} does not exists")
|
|
294
|
+
self.filename = filename
|
|
295
|
+
self._objs: Dict[str, Any] | None = None
|
|
296
|
+
|
|
297
|
+
@abstractmethod
|
|
298
|
+
def _filter_objects(self, module: ModuleType) -> Dict[str, Any]:
|
|
299
|
+
"""
|
|
300
|
+
Filter the objects in the module to only include the ones we want to expose.
|
|
301
|
+
"""
|
|
302
|
+
raise NotImplementedError
|
|
303
|
+
|
|
304
|
+
@property
|
|
305
|
+
def objs(self) -> Dict[str, Any]:
|
|
306
|
+
if self._objs is not None:
|
|
307
|
+
return self._objs
|
|
308
|
+
|
|
309
|
+
module_name = os.path.splitext(os.path.basename(self.filename))[0]
|
|
310
|
+
module_path = os.path.dirname(os.path.abspath(self.filename))
|
|
311
|
+
|
|
312
|
+
spec = importlib.util.spec_from_file_location(module_name, self.filename)
|
|
313
|
+
if spec is None or spec.loader is None:
|
|
314
|
+
raise click.ClickException(f"Could not load module {module_name} from path [{self.filename}]")
|
|
315
|
+
|
|
316
|
+
module = importlib.util.module_from_spec(spec)
|
|
317
|
+
sys.modules[module_name] = module
|
|
318
|
+
|
|
319
|
+
sys.path.append(module_path)
|
|
320
|
+
spec.loader.exec_module(module)
|
|
321
|
+
|
|
322
|
+
self._objs = self._filter_objects(module)
|
|
323
|
+
if not self._objs:
|
|
324
|
+
raise click.ClickException(f"No objects found in {self.filename}")
|
|
325
|
+
return self._objs
|
|
326
|
+
|
|
327
|
+
def list_commands(self, ctx):
|
|
328
|
+
m = list(self.objs.keys())
|
|
329
|
+
return sorted(m)
|
|
330
|
+
|
|
331
|
+
@abstractmethod
|
|
332
|
+
def _get_command_for_obj(self, ctx: click.Context, obj_name: str, obj: Any) -> click.Command: ...
|
|
333
|
+
|
|
334
|
+
def get_command(self, ctx, obj_name):
|
|
335
|
+
obj = self.objs[obj_name]
|
|
336
|
+
return self._get_command_for_obj(ctx, obj_name, obj)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
class FileGroup(GroupBase):
|
|
340
|
+
"""
|
|
341
|
+
Group that creates a command for each file in the current directory that is not __init__.py.
|
|
342
|
+
"""
|
|
343
|
+
|
|
344
|
+
common_options_enabled = False
|
|
345
|
+
|
|
346
|
+
def __init__(
|
|
347
|
+
self,
|
|
348
|
+
*args,
|
|
349
|
+
directory: Path | None = None,
|
|
350
|
+
**kwargs,
|
|
351
|
+
):
|
|
352
|
+
if "params" not in kwargs:
|
|
353
|
+
kwargs["params"] = []
|
|
354
|
+
super().__init__(*args, **kwargs)
|
|
355
|
+
self._files = None
|
|
356
|
+
self._dir = directory
|
|
357
|
+
|
|
358
|
+
@property
|
|
359
|
+
def files(self):
|
|
360
|
+
if self._files is None:
|
|
361
|
+
directory = self._dir or Path(".").absolute()
|
|
362
|
+
# add python files
|
|
363
|
+
_files = [os.fspath(p) for p in directory.glob("*.py") if p.name != "__init__.py"]
|
|
364
|
+
|
|
365
|
+
# add directories
|
|
366
|
+
_files.extend(
|
|
367
|
+
[
|
|
368
|
+
os.fspath(directory / p.name)
|
|
369
|
+
for p in directory.iterdir()
|
|
370
|
+
if not p.name.startswith(("_", ".")) and p.is_dir()
|
|
371
|
+
]
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
# files that are in the current directory or subdirectories of the
|
|
375
|
+
# current directory should be displayed as relative paths
|
|
376
|
+
self._files = [
|
|
377
|
+
str(Path(f).relative_to(Path.cwd())) if Path(f).is_relative_to(Path.cwd()) else f for f in _files
|
|
378
|
+
]
|
|
379
|
+
return self._files
|
|
380
|
+
|
|
381
|
+
def list_commands(self, ctx):
|
|
382
|
+
return self.files
|
|
383
|
+
|
|
384
|
+
def get_command(self, ctx, filename):
|
|
385
|
+
raise NotImplementedError
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def _table_format(table: Table, vals: Iterable[Any]) -> Table:
|
|
389
|
+
headers = None
|
|
390
|
+
has_rich_repr = False
|
|
391
|
+
for p in vals:
|
|
392
|
+
if hasattr(p, "__rich_repr__"):
|
|
393
|
+
has_rich_repr = True
|
|
394
|
+
elif not isinstance(p, (list, tuple)):
|
|
395
|
+
raise ValueError("Expected a list or tuple of values, or an object with __rich_repr__ method.")
|
|
396
|
+
o = list(p.__rich_repr__()) if has_rich_repr else p
|
|
397
|
+
if headers is None:
|
|
398
|
+
headers = [k for k, _ in o]
|
|
399
|
+
for h in headers:
|
|
400
|
+
table.add_column(h.capitalize(), no_wrap=True if "name" in h.casefold() else False)
|
|
401
|
+
table.add_row(*[str(v) for _, v in o])
|
|
402
|
+
return table
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def format(title: str, vals: Iterable[Any], of: OutputFormat = "table") -> Table | Any:
|
|
406
|
+
"""
|
|
407
|
+
Get a table from a list of values.
|
|
408
|
+
"""
|
|
409
|
+
match of:
|
|
410
|
+
case "table-simple":
|
|
411
|
+
return _table_format(Table(title, box=None), vals)
|
|
412
|
+
case "table":
|
|
413
|
+
return _table_format(
|
|
414
|
+
Table(
|
|
415
|
+
title=title,
|
|
416
|
+
box=rich.box.SQUARE_DOUBLE_HEAD,
|
|
417
|
+
header_style=HEADER_STYLE,
|
|
418
|
+
show_header=True,
|
|
419
|
+
border_style=PREFERRED_BORDER_COLOR,
|
|
420
|
+
expand=True,
|
|
421
|
+
),
|
|
422
|
+
vals,
|
|
423
|
+
)
|
|
424
|
+
case "json":
|
|
425
|
+
if not vals:
|
|
426
|
+
return pretty_repr([])
|
|
427
|
+
return pretty_repr([v.to_dict() for v in vals])
|
|
428
|
+
case "json-raw":
|
|
429
|
+
if not vals:
|
|
430
|
+
return []
|
|
431
|
+
return json.dumps([v.to_dict() for v in vals])
|
|
432
|
+
|
|
433
|
+
raise click.ClickException("Unknown output format. Supported formats are: table, table-simple, json.")
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def get_panel(title: str, renderable: Any, of: OutputFormat = "table") -> Panel:
|
|
437
|
+
"""
|
|
438
|
+
Get a panel from a list of values.
|
|
439
|
+
"""
|
|
440
|
+
if of in ["table-simple", "json"]:
|
|
441
|
+
return renderable
|
|
442
|
+
return Panel.fit(
|
|
443
|
+
renderable,
|
|
444
|
+
title=f"[{PREFERRED_ACCENT_COLOR}]{title}[/{PREFERRED_ACCENT_COLOR}]",
|
|
445
|
+
border_style=PREFERRED_BORDER_COLOR,
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def get_console() -> Console:
|
|
450
|
+
"""
|
|
451
|
+
Get a console that is configured to use colors if the terminal supports it.
|
|
452
|
+
"""
|
|
453
|
+
return Console(color_system="auto", force_terminal=True, width=120)
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def parse_images(cfg: Config, values: tuple[str, ...] | None) -> None:
|
|
457
|
+
"""
|
|
458
|
+
Parse image values and update the config.
|
|
459
|
+
|
|
460
|
+
Args:
|
|
461
|
+
cfg: The Config object to write images to
|
|
462
|
+
values: List of image strings in format "imagename=imageuri" or just "imageuri"
|
|
463
|
+
"""
|
|
464
|
+
from flyte._image import _DEFAULT_IMAGE_REF_NAME
|
|
465
|
+
|
|
466
|
+
if values is None:
|
|
467
|
+
return
|
|
468
|
+
for value in values:
|
|
469
|
+
if "=" in value:
|
|
470
|
+
image_name, image_uri = value.split("=", 1)
|
|
471
|
+
cfg.image.image_refs[image_name] = image_uri
|
|
472
|
+
else:
|
|
473
|
+
# If no name specified, use "default" as the name
|
|
474
|
+
cfg.image.image_refs[_DEFAULT_IMAGE_REF_NAME] = value
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
@lru_cache()
|
|
478
|
+
def initialize_config(
|
|
479
|
+
ctx: click.Context,
|
|
480
|
+
project: str,
|
|
481
|
+
domain: str,
|
|
482
|
+
root_dir: str | None = None,
|
|
483
|
+
images: tuple[str, ...] | None = None,
|
|
484
|
+
sync_local_sys_paths: bool = True,
|
|
485
|
+
):
|
|
486
|
+
obj: CLIConfig | None = ctx.obj
|
|
487
|
+
if obj is None:
|
|
488
|
+
import flyte.config
|
|
489
|
+
|
|
490
|
+
obj = CLIConfig(flyte.config.auto(), ctx)
|
|
491
|
+
|
|
492
|
+
obj.init(project, domain, root_dir, images, sync_local_sys_paths)
|
|
493
|
+
return obj
|