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.
Files changed (266) hide show
  1. flyte/__init__.py +83 -30
  2. flyte/_bin/connect.py +61 -0
  3. flyte/_bin/debug.py +38 -0
  4. flyte/_bin/runtime.py +87 -19
  5. flyte/_bin/serve.py +351 -0
  6. flyte/_build.py +3 -2
  7. flyte/_cache/cache.py +6 -5
  8. flyte/_cache/local_cache.py +216 -0
  9. flyte/_code_bundle/_ignore.py +31 -5
  10. flyte/_code_bundle/_packaging.py +42 -11
  11. flyte/_code_bundle/_utils.py +57 -34
  12. flyte/_code_bundle/bundle.py +130 -27
  13. flyte/_constants.py +1 -0
  14. flyte/_context.py +21 -5
  15. flyte/_custom_context.py +73 -0
  16. flyte/_debug/constants.py +37 -0
  17. flyte/_debug/utils.py +17 -0
  18. flyte/_debug/vscode.py +315 -0
  19. flyte/_deploy.py +396 -75
  20. flyte/_deployer.py +109 -0
  21. flyte/_environment.py +94 -11
  22. flyte/_excepthook.py +37 -0
  23. flyte/_group.py +2 -1
  24. flyte/_hash.py +1 -16
  25. flyte/_image.py +544 -231
  26. flyte/_initialize.py +456 -316
  27. flyte/_interface.py +40 -5
  28. flyte/_internal/controllers/__init__.py +22 -8
  29. flyte/_internal/controllers/_local_controller.py +159 -35
  30. flyte/_internal/controllers/_trace.py +18 -10
  31. flyte/_internal/controllers/remote/__init__.py +38 -9
  32. flyte/_internal/controllers/remote/_action.py +82 -12
  33. flyte/_internal/controllers/remote/_client.py +6 -2
  34. flyte/_internal/controllers/remote/_controller.py +290 -64
  35. flyte/_internal/controllers/remote/_core.py +155 -95
  36. flyte/_internal/controllers/remote/_informer.py +40 -20
  37. flyte/_internal/controllers/remote/_service_protocol.py +2 -2
  38. flyte/_internal/imagebuild/__init__.py +2 -10
  39. flyte/_internal/imagebuild/docker_builder.py +391 -84
  40. flyte/_internal/imagebuild/image_builder.py +111 -55
  41. flyte/_internal/imagebuild/remote_builder.py +409 -0
  42. flyte/_internal/imagebuild/utils.py +79 -0
  43. flyte/_internal/resolvers/_app_env_module.py +92 -0
  44. flyte/_internal/resolvers/_task_module.py +5 -38
  45. flyte/_internal/resolvers/app_env.py +26 -0
  46. flyte/_internal/resolvers/common.py +8 -1
  47. flyte/_internal/resolvers/default.py +2 -2
  48. flyte/_internal/runtime/convert.py +319 -36
  49. flyte/_internal/runtime/entrypoints.py +106 -18
  50. flyte/_internal/runtime/io.py +71 -23
  51. flyte/_internal/runtime/resources_serde.py +21 -7
  52. flyte/_internal/runtime/reuse.py +125 -0
  53. flyte/_internal/runtime/rusty.py +196 -0
  54. flyte/_internal/runtime/task_serde.py +239 -66
  55. flyte/_internal/runtime/taskrunner.py +48 -8
  56. flyte/_internal/runtime/trigger_serde.py +162 -0
  57. flyte/_internal/runtime/types_serde.py +7 -16
  58. flyte/_keyring/file.py +115 -0
  59. flyte/_link.py +30 -0
  60. flyte/_logging.py +241 -42
  61. flyte/_map.py +312 -0
  62. flyte/_metrics.py +59 -0
  63. flyte/_module.py +74 -0
  64. flyte/_pod.py +30 -0
  65. flyte/_resources.py +296 -33
  66. flyte/_retry.py +1 -7
  67. flyte/_reusable_environment.py +72 -7
  68. flyte/_run.py +462 -132
  69. flyte/_secret.py +47 -11
  70. flyte/_serve.py +333 -0
  71. flyte/_task.py +245 -56
  72. flyte/_task_environment.py +219 -97
  73. flyte/_task_plugins.py +47 -0
  74. flyte/_tools.py +8 -8
  75. flyte/_trace.py +15 -24
  76. flyte/_trigger.py +1027 -0
  77. flyte/_utils/__init__.py +12 -1
  78. flyte/_utils/asyn.py +3 -1
  79. flyte/_utils/async_cache.py +139 -0
  80. flyte/_utils/coro_management.py +5 -4
  81. flyte/_utils/description_parser.py +19 -0
  82. flyte/_utils/docker_credentials.py +173 -0
  83. flyte/_utils/helpers.py +45 -19
  84. flyte/_utils/module_loader.py +123 -0
  85. flyte/_utils/org_discovery.py +57 -0
  86. flyte/_utils/uv_script_parser.py +8 -1
  87. flyte/_version.py +16 -3
  88. flyte/app/__init__.py +27 -0
  89. flyte/app/_app_environment.py +362 -0
  90. flyte/app/_connector_environment.py +40 -0
  91. flyte/app/_deploy.py +130 -0
  92. flyte/app/_parameter.py +343 -0
  93. flyte/app/_runtime/__init__.py +3 -0
  94. flyte/app/_runtime/app_serde.py +383 -0
  95. flyte/app/_types.py +113 -0
  96. flyte/app/extras/__init__.py +9 -0
  97. flyte/app/extras/_auth_middleware.py +217 -0
  98. flyte/app/extras/_fastapi.py +93 -0
  99. flyte/app/extras/_model_loader/__init__.py +3 -0
  100. flyte/app/extras/_model_loader/config.py +7 -0
  101. flyte/app/extras/_model_loader/loader.py +288 -0
  102. flyte/cli/__init__.py +12 -0
  103. flyte/cli/_abort.py +28 -0
  104. flyte/cli/_build.py +114 -0
  105. flyte/cli/_common.py +493 -0
  106. flyte/cli/_create.py +371 -0
  107. flyte/cli/_delete.py +45 -0
  108. flyte/cli/_deploy.py +401 -0
  109. flyte/cli/_gen.py +316 -0
  110. flyte/cli/_get.py +446 -0
  111. flyte/cli/_option.py +33 -0
  112. flyte/{_cli → cli}/_params.py +57 -17
  113. flyte/cli/_plugins.py +209 -0
  114. flyte/cli/_prefetch.py +292 -0
  115. flyte/cli/_run.py +690 -0
  116. flyte/cli/_serve.py +338 -0
  117. flyte/cli/_update.py +86 -0
  118. flyte/cli/_user.py +20 -0
  119. flyte/cli/main.py +246 -0
  120. flyte/config/__init__.py +2 -167
  121. flyte/config/_config.py +215 -163
  122. flyte/config/_internal.py +10 -1
  123. flyte/config/_reader.py +225 -0
  124. flyte/connectors/__init__.py +11 -0
  125. flyte/connectors/_connector.py +330 -0
  126. flyte/connectors/_server.py +194 -0
  127. flyte/connectors/utils.py +159 -0
  128. flyte/errors.py +134 -2
  129. flyte/extend.py +24 -0
  130. flyte/extras/_container.py +69 -56
  131. flyte/git/__init__.py +3 -0
  132. flyte/git/_config.py +279 -0
  133. flyte/io/__init__.py +8 -1
  134. flyte/io/{structured_dataset → _dataframe}/__init__.py +32 -30
  135. flyte/io/{structured_dataset → _dataframe}/basic_dfs.py +75 -68
  136. flyte/io/{structured_dataset/structured_dataset.py → _dataframe/dataframe.py} +207 -242
  137. flyte/io/_dir.py +575 -113
  138. flyte/io/_file.py +587 -141
  139. flyte/io/_hashing_io.py +342 -0
  140. flyte/io/extend.py +7 -0
  141. flyte/models.py +635 -0
  142. flyte/prefetch/__init__.py +22 -0
  143. flyte/prefetch/_hf_model.py +563 -0
  144. flyte/remote/__init__.py +14 -3
  145. flyte/remote/_action.py +879 -0
  146. flyte/remote/_app.py +346 -0
  147. flyte/remote/_auth_metadata.py +42 -0
  148. flyte/remote/_client/_protocols.py +62 -4
  149. flyte/remote/_client/auth/_auth_utils.py +19 -0
  150. flyte/remote/_client/auth/_authenticators/base.py +8 -2
  151. flyte/remote/_client/auth/_authenticators/device_code.py +4 -5
  152. flyte/remote/_client/auth/_authenticators/factory.py +4 -0
  153. flyte/remote/_client/auth/_authenticators/passthrough.py +79 -0
  154. flyte/remote/_client/auth/_authenticators/pkce.py +17 -18
  155. flyte/remote/_client/auth/_channel.py +47 -18
  156. flyte/remote/_client/auth/_client_config.py +5 -3
  157. flyte/remote/_client/auth/_keyring.py +15 -2
  158. flyte/remote/_client/auth/_token_client.py +3 -3
  159. flyte/remote/_client/controlplane.py +206 -18
  160. flyte/remote/_common.py +66 -0
  161. flyte/remote/_data.py +107 -22
  162. flyte/remote/_logs.py +116 -33
  163. flyte/remote/_project.py +21 -19
  164. flyte/remote/_run.py +164 -631
  165. flyte/remote/_secret.py +72 -29
  166. flyte/remote/_task.py +387 -46
  167. flyte/remote/_trigger.py +368 -0
  168. flyte/remote/_user.py +43 -0
  169. flyte/report/_report.py +10 -6
  170. flyte/storage/__init__.py +13 -1
  171. flyte/storage/_config.py +237 -0
  172. flyte/storage/_parallel_reader.py +289 -0
  173. flyte/storage/_storage.py +268 -59
  174. flyte/syncify/__init__.py +56 -0
  175. flyte/syncify/_api.py +414 -0
  176. flyte/types/__init__.py +39 -0
  177. flyte/types/_interface.py +22 -7
  178. flyte/{io/pickle/transformer.py → types/_pickle.py} +37 -9
  179. flyte/types/_string_literals.py +8 -9
  180. flyte/types/_type_engine.py +226 -126
  181. flyte/types/_utils.py +1 -1
  182. flyte-2.0.0b46.data/scripts/debug.py +38 -0
  183. flyte-2.0.0b46.data/scripts/runtime.py +194 -0
  184. flyte-2.0.0b46.dist-info/METADATA +352 -0
  185. flyte-2.0.0b46.dist-info/RECORD +221 -0
  186. flyte-2.0.0b46.dist-info/entry_points.txt +8 -0
  187. flyte-2.0.0b46.dist-info/licenses/LICENSE +201 -0
  188. flyte/_api_commons.py +0 -3
  189. flyte/_cli/_common.py +0 -299
  190. flyte/_cli/_create.py +0 -42
  191. flyte/_cli/_delete.py +0 -23
  192. flyte/_cli/_deploy.py +0 -140
  193. flyte/_cli/_get.py +0 -235
  194. flyte/_cli/_run.py +0 -174
  195. flyte/_cli/main.py +0 -98
  196. flyte/_datastructures.py +0 -342
  197. flyte/_internal/controllers/pbhash.py +0 -39
  198. flyte/_protos/common/authorization_pb2.py +0 -66
  199. flyte/_protos/common/authorization_pb2.pyi +0 -108
  200. flyte/_protos/common/authorization_pb2_grpc.py +0 -4
  201. flyte/_protos/common/identifier_pb2.py +0 -71
  202. flyte/_protos/common/identifier_pb2.pyi +0 -82
  203. flyte/_protos/common/identifier_pb2_grpc.py +0 -4
  204. flyte/_protos/common/identity_pb2.py +0 -48
  205. flyte/_protos/common/identity_pb2.pyi +0 -72
  206. flyte/_protos/common/identity_pb2_grpc.py +0 -4
  207. flyte/_protos/common/list_pb2.py +0 -36
  208. flyte/_protos/common/list_pb2.pyi +0 -69
  209. flyte/_protos/common/list_pb2_grpc.py +0 -4
  210. flyte/_protos/common/policy_pb2.py +0 -37
  211. flyte/_protos/common/policy_pb2.pyi +0 -27
  212. flyte/_protos/common/policy_pb2_grpc.py +0 -4
  213. flyte/_protos/common/role_pb2.py +0 -37
  214. flyte/_protos/common/role_pb2.pyi +0 -53
  215. flyte/_protos/common/role_pb2_grpc.py +0 -4
  216. flyte/_protos/common/runtime_version_pb2.py +0 -28
  217. flyte/_protos/common/runtime_version_pb2.pyi +0 -24
  218. flyte/_protos/common/runtime_version_pb2_grpc.py +0 -4
  219. flyte/_protos/logs/dataplane/payload_pb2.py +0 -96
  220. flyte/_protos/logs/dataplane/payload_pb2.pyi +0 -168
  221. flyte/_protos/logs/dataplane/payload_pb2_grpc.py +0 -4
  222. flyte/_protos/secret/definition_pb2.py +0 -49
  223. flyte/_protos/secret/definition_pb2.pyi +0 -93
  224. flyte/_protos/secret/definition_pb2_grpc.py +0 -4
  225. flyte/_protos/secret/payload_pb2.py +0 -62
  226. flyte/_protos/secret/payload_pb2.pyi +0 -94
  227. flyte/_protos/secret/payload_pb2_grpc.py +0 -4
  228. flyte/_protos/secret/secret_pb2.py +0 -38
  229. flyte/_protos/secret/secret_pb2.pyi +0 -6
  230. flyte/_protos/secret/secret_pb2_grpc.py +0 -198
  231. flyte/_protos/secret/secret_pb2_grpc_grpc.py +0 -198
  232. flyte/_protos/validate/validate/validate_pb2.py +0 -76
  233. flyte/_protos/workflow/node_execution_service_pb2.py +0 -26
  234. flyte/_protos/workflow/node_execution_service_pb2.pyi +0 -4
  235. flyte/_protos/workflow/node_execution_service_pb2_grpc.py +0 -32
  236. flyte/_protos/workflow/queue_service_pb2.py +0 -106
  237. flyte/_protos/workflow/queue_service_pb2.pyi +0 -141
  238. flyte/_protos/workflow/queue_service_pb2_grpc.py +0 -172
  239. flyte/_protos/workflow/run_definition_pb2.py +0 -128
  240. flyte/_protos/workflow/run_definition_pb2.pyi +0 -310
  241. flyte/_protos/workflow/run_definition_pb2_grpc.py +0 -4
  242. flyte/_protos/workflow/run_logs_service_pb2.py +0 -41
  243. flyte/_protos/workflow/run_logs_service_pb2.pyi +0 -28
  244. flyte/_protos/workflow/run_logs_service_pb2_grpc.py +0 -69
  245. flyte/_protos/workflow/run_service_pb2.py +0 -133
  246. flyte/_protos/workflow/run_service_pb2.pyi +0 -175
  247. flyte/_protos/workflow/run_service_pb2_grpc.py +0 -412
  248. flyte/_protos/workflow/state_service_pb2.py +0 -58
  249. flyte/_protos/workflow/state_service_pb2.pyi +0 -71
  250. flyte/_protos/workflow/state_service_pb2_grpc.py +0 -138
  251. flyte/_protos/workflow/task_definition_pb2.py +0 -72
  252. flyte/_protos/workflow/task_definition_pb2.pyi +0 -65
  253. flyte/_protos/workflow/task_definition_pb2_grpc.py +0 -4
  254. flyte/_protos/workflow/task_service_pb2.py +0 -44
  255. flyte/_protos/workflow/task_service_pb2.pyi +0 -31
  256. flyte/_protos/workflow/task_service_pb2_grpc.py +0 -104
  257. flyte/io/_dataframe.py +0 -0
  258. flyte/io/pickle/__init__.py +0 -0
  259. flyte/remote/_console.py +0 -18
  260. flyte-0.2.0b1.dist-info/METADATA +0 -179
  261. flyte-0.2.0b1.dist-info/RECORD +0 -204
  262. flyte-0.2.0b1.dist-info/entry_points.txt +0 -3
  263. /flyte/{_cli → _debug}/__init__.py +0 -0
  264. /flyte/{_protos → _keyring}/__init__.py +0 -0
  265. {flyte-0.2.0b1.dist-info → flyte-2.0.0b46.dist-info}/WHEEL +0 -0
  266. {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