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/_run.py ADDED
@@ -0,0 +1,690 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import inspect
5
+ from dataclasses import dataclass, field, fields
6
+ from functools import lru_cache
7
+ from pathlib import Path
8
+ from types import ModuleType
9
+ from typing import Any, Dict, List, cast
10
+
11
+ import rich_click as click
12
+ from typing_extensions import get_args
13
+
14
+ from .._code_bundle._utils import CopyFiles
15
+ from .._task import TaskTemplate
16
+ from ..remote import Run
17
+ from . import _common as common
18
+ from ._params import to_click_option
19
+
20
+ RUN_REMOTE_CMD = "deployed-task"
21
+ initialize_config = common.initialize_config
22
+
23
+
24
+ @lru_cache()
25
+ def _list_tasks(
26
+ ctx: click.Context,
27
+ project: str,
28
+ domain: str,
29
+ by_task_name: str | None = None,
30
+ by_task_env: str | None = None,
31
+ ) -> list[str]:
32
+ import flyte.remote
33
+
34
+ common.initialize_config(ctx, project, domain)
35
+ return [task.name for task in flyte.remote.Task.listall(by_task_name=by_task_name, by_task_env=by_task_env)]
36
+
37
+
38
+ @dataclass
39
+ class RunArguments:
40
+ project: str = field(
41
+ default=cast(str, common.PROJECT_OPTION.default), metadata={"click.option": common.PROJECT_OPTION}
42
+ )
43
+ domain: str = field(
44
+ default=cast(str, common.DOMAIN_OPTION.default), metadata={"click.option": common.DOMAIN_OPTION}
45
+ )
46
+ local: bool = field(
47
+ default=False,
48
+ metadata={
49
+ "click.option": click.Option(
50
+ ["--local"],
51
+ is_flag=True,
52
+ help="Run the task locally",
53
+ )
54
+ },
55
+ )
56
+ copy_style: CopyFiles = field(
57
+ default="loaded_modules",
58
+ metadata={
59
+ "click.option": click.Option(
60
+ ["--copy-style"],
61
+ type=click.Choice(get_args(CopyFiles)),
62
+ default="loaded_modules",
63
+ help="Copy style to use when running the task",
64
+ )
65
+ },
66
+ )
67
+ root_dir: str | None = field(
68
+ default=None,
69
+ metadata={
70
+ "click.option": click.Option(
71
+ ["--root-dir"],
72
+ type=str,
73
+ help="Override the root source directory, helpful when working with monorepos.",
74
+ )
75
+ },
76
+ )
77
+ raw_data_path: str | None = field(
78
+ default=None,
79
+ metadata={
80
+ "click.option": click.Option(
81
+ ["--raw-data-path"],
82
+ type=str,
83
+ help="Override the output prefix used to store offloaded data types. e.g. s3://bucket/",
84
+ )
85
+ },
86
+ )
87
+ service_account: str | None = field(
88
+ default=None,
89
+ metadata={
90
+ "click.option": click.Option(
91
+ ["--service-account"],
92
+ type=str,
93
+ help="Kubernetes service account. If not provided, the configured default will be used",
94
+ )
95
+ },
96
+ )
97
+ name: str | None = field(
98
+ default=None,
99
+ metadata={
100
+ "click.option": click.Option(
101
+ ["--name"],
102
+ type=str,
103
+ help="Name of the run. If not provided, a random name will be generated.",
104
+ )
105
+ },
106
+ )
107
+ follow: bool = field(
108
+ default=False,
109
+ metadata={
110
+ "click.option": click.Option(
111
+ ["--follow", "-f"],
112
+ is_flag=True,
113
+ default=False,
114
+ help="Wait and watch logs for the parent action. If not provided, the CLI will exit after "
115
+ "successfully launching a remote execution with a link to the UI.",
116
+ )
117
+ },
118
+ )
119
+ image: List[str] = field(
120
+ default_factory=list,
121
+ metadata={
122
+ "click.option": click.Option(
123
+ ["--image"],
124
+ type=str,
125
+ multiple=True,
126
+ help="Image to be used in the run. Format: imagename=imageuri. Can be specified multiple times.",
127
+ )
128
+ },
129
+ )
130
+ no_sync_local_sys_paths: bool = field(
131
+ default=True,
132
+ metadata={
133
+ "click.option": click.Option(
134
+ ["--no-sync-local-sys-paths"],
135
+ is_flag=True,
136
+ flag_value=True,
137
+ default=False,
138
+ help="Disable synchronization of local sys.path entries under the root directory "
139
+ "to the remote container.",
140
+ )
141
+ },
142
+ )
143
+ run_project: str | None = field(
144
+ default=None,
145
+ metadata={
146
+ "click.option": click.Option(
147
+ param_decls=["--run-project"],
148
+ required=False,
149
+ type=str,
150
+ default=None,
151
+ help="Run the remote task in this project, only applicable when using `deployed-task` subcommand.",
152
+ show_default=True,
153
+ )
154
+ },
155
+ )
156
+ run_domain: str | None = field(
157
+ default=None,
158
+ metadata={
159
+ "click.option": click.Option(
160
+ ["--run-domain"],
161
+ required=False,
162
+ type=str,
163
+ default=None,
164
+ help="Run the remote task in this domain, only applicable when using `deployed-task` subcommand.",
165
+ show_default=True,
166
+ )
167
+ },
168
+ )
169
+
170
+ @classmethod
171
+ def from_dict(cls, d: Dict[str, Any]) -> RunArguments:
172
+ modified = {k: v for k, v in d.items() if k in {f.name for f in fields(cls)}}
173
+ return cls(**modified)
174
+
175
+ @classmethod
176
+ def options(cls) -> List[click.Option]:
177
+ """
178
+ Return the set of base parameters added to run subcommand.
179
+ """
180
+ return [common.get_option_from_metadata(f.metadata) for f in fields(cls) if f.metadata]
181
+
182
+
183
+ class RunTaskCommand(click.RichCommand):
184
+ def __init__(self, obj_name: str, obj: Any, run_args: RunArguments, *args, **kwargs):
185
+ self.obj_name = obj_name
186
+ self.obj = cast(TaskTemplate, obj)
187
+ self.run_args = run_args
188
+ kwargs.pop("name", None)
189
+ super().__init__(obj_name, *args, **kwargs)
190
+
191
+ def _validate_required_params(self, ctx: click.Context) -> None:
192
+ """
193
+ Validate that all required parameters are provided.
194
+ """
195
+ missing_params = []
196
+ for param in self.params:
197
+ if isinstance(param, click.Option) and param.required:
198
+ param_name = param.name
199
+ if param_name not in ctx.params or ctx.params[param_name] is None:
200
+ missing_params.append(
201
+ (param_name, param.type.get_metavar(param, ctx) or param.type.name.upper() or param.type)
202
+ )
203
+
204
+ if missing_params:
205
+ raise click.UsageError(
206
+ f"Missing required parameter(s): {', '.join(f'--{p[0]} (type: {p[1]})' for p in missing_params)}"
207
+ )
208
+
209
+ async def _execute_and_render(self, ctx: click.Context, config: common.CLIConfig):
210
+ """Separate execution logic from the Click entry point for better testability."""
211
+ import flyte
212
+
213
+ console = common.get_console()
214
+
215
+ # 2. Execute with a UX Status Spinner
216
+ try:
217
+ with console.status(
218
+ f"[bold blue]Launching {'local' if self.run_args.local else 'remote'} execution...", spinner="dots"
219
+ ):
220
+ execution_context = flyte.with_runcontext(
221
+ copy_style=self.run_args.copy_style,
222
+ mode="local" if self.run_args.local else "remote",
223
+ name=self.run_args.name,
224
+ raw_data_path=self.run_args.raw_data_path,
225
+ service_account=self.run_args.service_account,
226
+ log_format=config.log_format,
227
+ reset_root_logger=config.reset_root_logger,
228
+ )
229
+ result = await execution_context.run.aio(self.obj, **ctx.params)
230
+ except Exception as e:
231
+ console.print(common.get_panel("Exception", f"[red]✕ Execution failed:[/red] {e}", config.output_format))
232
+ return
233
+
234
+ # 3. UI Branching
235
+ if self.run_args.local:
236
+ self._render_local_success(console, result, config)
237
+ else:
238
+ await self._render_remote_success(console, result, config)
239
+
240
+ def _render_local_success(self, console, result, config):
241
+ content = f"[green]Completed Local Run[/green]\nPath: {result.url}\n➡️ Outputs: {result.outputs()}"
242
+ console.print(common.get_panel("Local Success", content, config.output_format))
243
+
244
+ async def _render_remote_success(self, console, result, config):
245
+ if not (isinstance(result, Run) and result.action):
246
+ return
247
+
248
+ run_info = (
249
+ f"[green bold]Created Run: {result.name}[/green bold]\n"
250
+ f"URL: [blue bold][link={result.url}]{result.url}[/link][/blue bold]"
251
+ )
252
+ console.print(common.get_panel("Remote Run", run_info, config.output_format))
253
+
254
+ if self.run_args.follow:
255
+ console.print("[dim]Waiting for log stream...[/dim]")
256
+ await result.show_logs.aio(max_lines=30, show_ts=True, raw=False)
257
+
258
+ def invoke(self, ctx: click.Context):
259
+ config: common.CLIConfig = common.initialize_config(
260
+ ctx,
261
+ self.run_args.project,
262
+ self.run_args.domain,
263
+ self.run_args.root_dir,
264
+ tuple(self.run_args.image) or None,
265
+ not self.run_args.no_sync_local_sys_paths,
266
+ )
267
+ self._validate_required_params(ctx)
268
+ # Main entry point remains very thin
269
+ asyncio.run(self._execute_and_render(ctx, config))
270
+
271
+ def get_params(self, ctx: click.Context) -> List[click.Parameter]:
272
+ # Note this function may be called multiple times by click.
273
+ task = self.obj
274
+ from .._internal.runtime.types_serde import transform_native_to_typed_interface
275
+
276
+ interface = transform_native_to_typed_interface(task.native_interface)
277
+ if interface is None:
278
+ return super().get_params(ctx)
279
+ inputs_interface = task.native_interface.inputs
280
+
281
+ params: List[click.Parameter] = []
282
+ for name, var in interface.inputs.variables.items():
283
+ default_val = None
284
+ if inputs_interface[name][1] is not inspect._empty:
285
+ default_val = inputs_interface[name][1]
286
+ params.append(to_click_option(name, var, inputs_interface[name][0], default_val))
287
+
288
+ self.params = params
289
+ return super().get_params(ctx)
290
+
291
+
292
+ class TaskPerFileGroup(common.ObjectsPerFileGroup):
293
+ """
294
+ Group that creates a command for each task in the current directory that is not __init__.py.
295
+ """
296
+
297
+ def __init__(self, filename: Path, run_args: RunArguments, *args, **kwargs):
298
+ if filename.is_absolute():
299
+ filename = filename.relative_to(Path.cwd())
300
+ super().__init__(*(filename, *args), **kwargs)
301
+ self.run_args = run_args
302
+
303
+ def _filter_objects(self, module: ModuleType) -> Dict[str, Any]:
304
+ return {k: v for k, v in module.__dict__.items() if isinstance(v, TaskTemplate)}
305
+
306
+ def list_commands(self, ctx):
307
+ common.initialize_config(
308
+ ctx,
309
+ self.run_args.project,
310
+ self.run_args.domain,
311
+ self.run_args.root_dir,
312
+ sync_local_sys_paths=not self.run_args.no_sync_local_sys_paths,
313
+ )
314
+ return super().list_commands(ctx)
315
+
316
+ def get_command(self, ctx, obj_name):
317
+ common.initialize_config(
318
+ ctx,
319
+ self.run_args.project,
320
+ self.run_args.domain,
321
+ self.run_args.root_dir,
322
+ sync_local_sys_paths=not self.run_args.no_sync_local_sys_paths,
323
+ )
324
+ return super().get_command(ctx, obj_name)
325
+
326
+ def _get_command_for_obj(self, ctx: click.Context, obj_name: str, obj: Any) -> click.Command:
327
+ obj = cast(TaskTemplate, obj)
328
+ return RunTaskCommand(
329
+ obj_name=obj_name,
330
+ obj=obj,
331
+ help=obj.docs.__help__str__() if obj.docs else None,
332
+ run_args=self.run_args,
333
+ )
334
+
335
+
336
+ class RunRemoteTaskCommand(click.RichCommand):
337
+ def __init__(self, task_name: str, run_args: RunArguments, version: str | None, *args, **kwargs):
338
+ self.task_name = task_name
339
+ self.run_args = run_args
340
+ self.version = version
341
+
342
+ super().__init__(*args, **kwargs)
343
+
344
+ def _validate_required_params(self, ctx: click.Context) -> None:
345
+ """
346
+ Validate that all required parameters are provided.
347
+ """
348
+ missing_params = []
349
+ for param in self.params:
350
+ if isinstance(param, click.Option) and param.required:
351
+ param_name = param.name
352
+ if param_name not in ctx.params or ctx.params[param_name] is None:
353
+ missing_params.append(
354
+ (param_name, param.type.get_metavar(param, ctx) or param.type.name.upper() or param.type)
355
+ )
356
+
357
+ if missing_params:
358
+ raise click.UsageError(
359
+ f"Missing required parameter(s): {', '.join(f'--{p[0]} (type: {p[1]})' for p in missing_params)}"
360
+ )
361
+
362
+ async def _execute_and_render(self, ctx: click.Context, config: common.CLIConfig):
363
+ """Separate execution logic from the Click entry point for better testability."""
364
+ import flyte.remote
365
+
366
+ task = flyte.remote.Task.get(self.task_name, version=self.version, auto_version="latest")
367
+ console = common.get_console()
368
+ if self.run_args.run_project or self.run_args.run_domain:
369
+ console.print(
370
+ f"Separate Run project/domain set, using {self.run_args.run_project} and {self.run_args.run_domain}"
371
+ )
372
+
373
+ # 2. Execute with a UX Status Spinner
374
+ try:
375
+ with console.status(
376
+ f"[bold blue]Launching {'local' if self.run_args.local else 'remote'} execution...", spinner="dots"
377
+ ):
378
+ execution_context = flyte.with_runcontext(
379
+ copy_style=self.run_args.copy_style,
380
+ mode="local" if self.run_args.local else "remote",
381
+ name=self.run_args.name,
382
+ project=self.run_args.run_project,
383
+ domain=self.run_args.run_domain,
384
+ )
385
+ result = await execution_context.run.aio(task, **ctx.params)
386
+ except Exception as e:
387
+ console.print(f"[red]✕ Execution failed:[/red] {e}")
388
+ return
389
+
390
+ # 3. UI Branching
391
+ if self.run_args.local:
392
+ self._render_local_success(console, result, config)
393
+ else:
394
+ await self._render_remote_success(console, result, config)
395
+
396
+ def _render_local_success(self, console, result, config):
397
+ content = f"[green]Completed Local Run[/green]\nPath: {result.url}\n➡️ Outputs: {result.outputs()}"
398
+ console.print(common.get_panel("Local Success", content, config.output_format))
399
+
400
+ async def _render_remote_success(self, console, result, config):
401
+ if not (isinstance(result, Run) and result.action):
402
+ return
403
+
404
+ run_info = (
405
+ f"[green bold]Created Run: {result.name}[/green bold]\n"
406
+ f"(Project: {result.action.action_id.run.project}, Domain: {result.action.action_id.run.domain})\n"
407
+ f"➡️ [blue bold][link={result.url}]{result.url}[/link][/blue bold]",
408
+ )
409
+ console.print(common.get_panel("Remote Run", run_info, config.output_format))
410
+
411
+ if self.run_args.follow:
412
+ console.print(
413
+ "[dim]Log streaming enabled, will wait for task to start running and log stream to be available[/dim]"
414
+ )
415
+ await result.show_logs.aio(max_lines=30, show_ts=True, raw=False)
416
+
417
+ def invoke(self, ctx: click.Context):
418
+ config: common.CLIConfig = common.initialize_config(
419
+ ctx,
420
+ project=self.run_args.project,
421
+ domain=self.run_args.domain,
422
+ root_dir=self.run_args.root_dir,
423
+ images=tuple(self.run_args.image) or None,
424
+ sync_local_sys_paths=not self.run_args.no_sync_local_sys_paths,
425
+ )
426
+ self._validate_required_params(ctx)
427
+ # Main entry point remains very thin
428
+ asyncio.run(self._execute_and_render(ctx, config))
429
+
430
+ def get_params(self, ctx: click.Context) -> List[click.Parameter]:
431
+ # Note this function may be called multiple times by click.
432
+ import flyte.remote
433
+ from flyte._internal.runtime.types_serde import transform_native_to_typed_interface
434
+
435
+ common.initialize_config(
436
+ ctx,
437
+ self.run_args.project,
438
+ self.run_args.domain,
439
+ sync_local_sys_paths=not self.run_args.no_sync_local_sys_paths,
440
+ )
441
+
442
+ task = flyte.remote.Task.get(self.task_name, auto_version="latest")
443
+ task_details = task.fetch()
444
+
445
+ interface = transform_native_to_typed_interface(task_details.interface)
446
+ if interface is None:
447
+ return super().get_params(ctx)
448
+ inputs_interface = task_details.interface.inputs
449
+
450
+ params: List[click.Parameter] = []
451
+ for name, var in interface.inputs.variables.items():
452
+ default_val = None
453
+ if inputs_interface[name][1] is not inspect._empty:
454
+ default_val = inputs_interface[name][1]
455
+ params.append(to_click_option(name, var, inputs_interface[name][0], default_val))
456
+
457
+ self.params = params
458
+ return super().get_params(ctx)
459
+
460
+
461
+ class RemoteEnvGroup(common.GroupBase):
462
+ def __init__(self, name: str, *args, run_args, env: str, **kwargs):
463
+ super().__init__(*args, **kwargs)
464
+ self.name = name
465
+ self.env = env
466
+ self.run_args = run_args
467
+
468
+ def list_commands(self, ctx):
469
+ return _list_tasks(ctx, self.run_args.project, self.run_args.domain, by_task_env=self.env)
470
+
471
+ def get_command(self, ctx, name):
472
+ return RunRemoteTaskCommand(
473
+ task_name=name,
474
+ run_args=self.run_args,
475
+ name=name,
476
+ version=None,
477
+ help=f"Run deployed task '{name}' from the Flyte backend",
478
+ )
479
+
480
+
481
+ class RemoteTaskGroup(common.GroupBase):
482
+ """
483
+ Group that creates a command for each remote task in the current directory that is not __init__.py.
484
+ """
485
+
486
+ def __init__(self, name: str, *args, run_args, tasks: list[str] | None = None, **kwargs):
487
+ super().__init__(*args, **kwargs)
488
+ self.name = name
489
+ self.run_args = run_args
490
+
491
+ def list_commands(self, ctx):
492
+ # list envs of all remote tasks
493
+ envs = []
494
+ for task in _list_tasks(ctx, self.run_args.project, self.run_args.domain):
495
+ env = task.split(".")[0]
496
+ if env not in envs:
497
+ envs.append(env)
498
+ return envs
499
+
500
+ @staticmethod
501
+ def _parse_task_name(task_name: str) -> tuple[str, str | None, str | None]:
502
+ import re
503
+
504
+ pattern = r"^([^.:]+)(?:\.([^:]+))?(?::(.+))?$"
505
+ match = re.match(pattern, task_name)
506
+ if not match:
507
+ raise click.BadParameter(f"Invalid task name format: {task_name}")
508
+ return match.group(1), match.group(2), match.group(3)
509
+
510
+ def _env_is_task(self, ctx: click.Context, env: str) -> bool:
511
+ # check if the env name is the full task name, since sometimes task
512
+ # names don't have an environment prefix
513
+ tasks = [*_list_tasks(ctx, self.run_args.project, self.run_args.domain, by_task_name=env)]
514
+ return len(tasks) > 0
515
+
516
+ def get_command(self, ctx, name):
517
+ env, task, version = self._parse_task_name(name)
518
+ match env, task, version:
519
+ case env, None, None:
520
+ if self._env_is_task(ctx, env):
521
+ # this handles cases where task names do not have a environment prefix
522
+ task_name = env
523
+ return RunRemoteTaskCommand(
524
+ task_name=task_name,
525
+ run_args=self.run_args,
526
+ name=task_name,
527
+ version=None,
528
+ help=f"Run remote task `{task_name}` from the Flyte backend",
529
+ )
530
+ else:
531
+ return RemoteEnvGroup(
532
+ name=name,
533
+ run_args=self.run_args,
534
+ env=env,
535
+ help=f"Run remote tasks in the `{env}` environment from the Flyte backend",
536
+ )
537
+ case env, task, None:
538
+ task_name = f"{env}.{task}"
539
+ return RunRemoteTaskCommand(
540
+ task_name=task_name,
541
+ run_args=self.run_args,
542
+ name=task_name,
543
+ version=None,
544
+ help=f"Run remote task '{task_name}' from the Flyte backend",
545
+ )
546
+ case env, task, version:
547
+ task_name = f"{env}.{task}"
548
+ return RunRemoteTaskCommand(
549
+ task_name=task_name,
550
+ run_args=self.run_args,
551
+ version=version,
552
+ name=f"{task_name}:{version}",
553
+ help=f"Run remote task '{task_name}' from the Flyte backend",
554
+ )
555
+ case _:
556
+ raise click.BadParameter(f"Invalid task name format: {task_name}")
557
+
558
+
559
+ class TaskFiles(common.FileGroup):
560
+ """
561
+ Group that creates a command for each file in the current directory that is not __init__.py.
562
+ """
563
+
564
+ common_options_enabled = False
565
+
566
+ def __init__(
567
+ self,
568
+ *args,
569
+ directory: Path | None = None,
570
+ **kwargs,
571
+ ):
572
+ if "params" not in kwargs:
573
+ kwargs["params"] = []
574
+ kwargs["params"].extend(RunArguments.options())
575
+ super().__init__(*args, directory=directory, **kwargs)
576
+
577
+ def list_commands(self, ctx):
578
+ v = [
579
+ RUN_REMOTE_CMD,
580
+ *super().list_commands(ctx),
581
+ ]
582
+ return v
583
+
584
+ def get_command(self, ctx, cmd_name):
585
+ run_args = RunArguments.from_dict(ctx.params)
586
+ if cmd_name == RUN_REMOTE_CMD:
587
+ return RemoteTaskGroup(
588
+ name=cmd_name,
589
+ run_args=run_args,
590
+ help="Run remote task from the Flyte backend",
591
+ )
592
+
593
+ fp = Path(cmd_name)
594
+ if not fp.exists():
595
+ raise click.BadParameter(f"File {cmd_name} does not exist")
596
+ if fp.is_dir():
597
+ return TaskFiles(
598
+ directory=fp,
599
+ help=f"Run `*.py` file inside the {fp} directory",
600
+ )
601
+ return TaskPerFileGroup(
602
+ filename=fp,
603
+ run_args=run_args,
604
+ name=cmd_name,
605
+ help=f"Run functions decorated with `env.task` in {cmd_name}",
606
+ )
607
+
608
+
609
+ run = TaskFiles(
610
+ name="run",
611
+ help=f"""
612
+ Run a task from a python file or deployed task.
613
+
614
+ Example usage:
615
+
616
+ ```bash
617
+ flyte run hello.py my_task --arg1 value1 --arg2 value2
618
+ ```
619
+
620
+ Arguments to the run command are provided right after the `run` command and before the file name.
621
+ Arguments for the task itself are provided after the task name.
622
+
623
+ To run a task locally, use the `--local` flag. This will run the task in the local environment instead of the remote
624
+ Flyte environment:
625
+
626
+ ```bash
627
+ flyte run --local hello.py my_task --arg1 value1 --arg2 value2
628
+ ```
629
+
630
+ You can provide image mappings with `--image` flag. This allows you to specify
631
+ the image URI for the task environment during CLI execution without changing
632
+ the code. Any images defined with `Image.from_ref_name("name")` will resolve to the
633
+ corresponding URIs you specify here.
634
+
635
+ ```bash
636
+ flyte run --image my_image=ghcr.io/myorg/my-image:v1.0 hello.py my_task
637
+ ```
638
+
639
+ If the image name is not provided, it is regarded as a default image and will
640
+ be used when no image is specified in TaskEnvironment:
641
+
642
+ ```bash
643
+ flyte run --image ghcr.io/myorg/default-image:latest hello.py my_task
644
+ ```
645
+
646
+ You can specify multiple image arguments:
647
+
648
+ ```bash
649
+ flyte run --image ghcr.io/org/default:latest --image gpu=ghcr.io/org/gpu:v2.0 hello.py my_task
650
+ ```
651
+
652
+ To run tasks that you've already deployed to Flyte, use the {RUN_REMOTE_CMD} command:
653
+
654
+ ```bash
655
+ flyte run {RUN_REMOTE_CMD} my_env.my_task --arg1 value1 --arg2 value2
656
+ ```
657
+
658
+ To run a specific version of a deployed task, use the `env.task:version` syntax:
659
+
660
+ ```bash
661
+ flyte run {RUN_REMOTE_CMD} my_env.my_task:xyz123 --arg1 value1 --arg2 value2
662
+ ```
663
+
664
+ You can specify the `--config` flag to point to a specific Flyte cluster:
665
+
666
+ ```bash
667
+ flyte run --config my-config.yaml {RUN_REMOTE_CMD} ...
668
+ ```
669
+
670
+ You can override the default configured project and domain:
671
+
672
+ ```bash
673
+ flyte run --project my-project --domain development hello.py my_task
674
+ ```
675
+
676
+ You can discover what deployed tasks are available by running:
677
+
678
+ ```bash
679
+ flyte run {RUN_REMOTE_CMD}
680
+ ```
681
+
682
+ Other arguments to the run command are listed below.
683
+
684
+ Arguments for the task itself are provided after the task name and can be retrieved using `--help`. For example:
685
+
686
+ ```bash
687
+ flyte run hello.py my_task --help
688
+ ```
689
+ """,
690
+ )