flyte 2.0.0b22__py3-none-any.whl → 2.0.0b30__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 (197) hide show
  1. flyte/__init__.py +18 -2
  2. flyte/_bin/runtime.py +43 -5
  3. flyte/_cache/cache.py +4 -2
  4. flyte/_cache/local_cache.py +216 -0
  5. flyte/_code_bundle/_ignore.py +1 -1
  6. flyte/_code_bundle/_packaging.py +4 -4
  7. flyte/_code_bundle/_utils.py +14 -8
  8. flyte/_code_bundle/bundle.py +13 -5
  9. flyte/_constants.py +1 -0
  10. flyte/_context.py +4 -1
  11. flyte/_custom_context.py +73 -0
  12. flyte/_debug/constants.py +0 -1
  13. flyte/_debug/vscode.py +6 -1
  14. flyte/_deploy.py +223 -59
  15. flyte/_environment.py +5 -0
  16. flyte/_excepthook.py +1 -1
  17. flyte/_image.py +144 -82
  18. flyte/_initialize.py +95 -12
  19. flyte/_interface.py +2 -0
  20. flyte/_internal/controllers/_local_controller.py +65 -24
  21. flyte/_internal/controllers/_trace.py +1 -1
  22. flyte/_internal/controllers/remote/_action.py +13 -11
  23. flyte/_internal/controllers/remote/_client.py +1 -1
  24. flyte/_internal/controllers/remote/_controller.py +9 -4
  25. flyte/_internal/controllers/remote/_core.py +16 -16
  26. flyte/_internal/controllers/remote/_informer.py +4 -4
  27. flyte/_internal/controllers/remote/_service_protocol.py +7 -7
  28. flyte/_internal/imagebuild/docker_builder.py +139 -84
  29. flyte/_internal/imagebuild/image_builder.py +7 -13
  30. flyte/_internal/imagebuild/remote_builder.py +65 -13
  31. flyte/_internal/imagebuild/utils.py +51 -3
  32. flyte/_internal/resolvers/_task_module.py +5 -38
  33. flyte/_internal/resolvers/default.py +2 -2
  34. flyte/_internal/runtime/convert.py +42 -20
  35. flyte/_internal/runtime/entrypoints.py +24 -1
  36. flyte/_internal/runtime/io.py +21 -8
  37. flyte/_internal/runtime/resources_serde.py +20 -6
  38. flyte/_internal/runtime/reuse.py +1 -1
  39. flyte/_internal/runtime/rusty.py +20 -5
  40. flyte/_internal/runtime/task_serde.py +33 -27
  41. flyte/_internal/runtime/taskrunner.py +10 -1
  42. flyte/_internal/runtime/trigger_serde.py +160 -0
  43. flyte/_internal/runtime/types_serde.py +1 -1
  44. flyte/_keyring/file.py +39 -9
  45. flyte/_logging.py +79 -12
  46. flyte/_map.py +31 -12
  47. flyte/_module.py +70 -0
  48. flyte/_pod.py +2 -2
  49. flyte/_resources.py +213 -31
  50. flyte/_run.py +107 -41
  51. flyte/_task.py +66 -10
  52. flyte/_task_environment.py +96 -24
  53. flyte/_task_plugins.py +4 -2
  54. flyte/_trigger.py +1000 -0
  55. flyte/_utils/__init__.py +2 -1
  56. flyte/_utils/asyn.py +3 -1
  57. flyte/_utils/docker_credentials.py +173 -0
  58. flyte/_utils/module_loader.py +17 -2
  59. flyte/_version.py +3 -3
  60. flyte/cli/_abort.py +3 -3
  61. flyte/cli/_build.py +1 -3
  62. flyte/cli/_common.py +78 -7
  63. flyte/cli/_create.py +178 -3
  64. flyte/cli/_delete.py +23 -1
  65. flyte/cli/_deploy.py +49 -11
  66. flyte/cli/_get.py +79 -34
  67. flyte/cli/_params.py +8 -6
  68. flyte/cli/_plugins.py +209 -0
  69. flyte/cli/_run.py +127 -11
  70. flyte/cli/_serve.py +64 -0
  71. flyte/cli/_update.py +37 -0
  72. flyte/cli/_user.py +17 -0
  73. flyte/cli/main.py +30 -4
  74. flyte/config/_config.py +2 -0
  75. flyte/config/_internal.py +1 -0
  76. flyte/config/_reader.py +3 -3
  77. flyte/connectors/__init__.py +11 -0
  78. flyte/connectors/_connector.py +270 -0
  79. flyte/connectors/_server.py +197 -0
  80. flyte/connectors/utils.py +135 -0
  81. flyte/errors.py +10 -1
  82. flyte/extend.py +8 -1
  83. flyte/extras/_container.py +6 -1
  84. flyte/git/_config.py +11 -9
  85. flyte/io/__init__.py +2 -0
  86. flyte/io/_dataframe/__init__.py +2 -0
  87. flyte/io/_dataframe/basic_dfs.py +1 -1
  88. flyte/io/_dataframe/dataframe.py +12 -8
  89. flyte/io/_dir.py +551 -120
  90. flyte/io/_file.py +538 -141
  91. flyte/models.py +57 -12
  92. flyte/remote/__init__.py +6 -1
  93. flyte/remote/_action.py +18 -16
  94. flyte/remote/_client/_protocols.py +39 -4
  95. flyte/remote/_client/auth/_channel.py +10 -6
  96. flyte/remote/_client/controlplane.py +17 -5
  97. flyte/remote/_console.py +3 -2
  98. flyte/remote/_data.py +4 -3
  99. flyte/remote/_logs.py +3 -3
  100. flyte/remote/_run.py +47 -7
  101. flyte/remote/_secret.py +26 -17
  102. flyte/remote/_task.py +21 -9
  103. flyte/remote/_trigger.py +306 -0
  104. flyte/remote/_user.py +33 -0
  105. flyte/storage/__init__.py +6 -1
  106. flyte/storage/_parallel_reader.py +274 -0
  107. flyte/storage/_storage.py +185 -103
  108. flyte/types/__init__.py +16 -0
  109. flyte/types/_interface.py +2 -2
  110. flyte/types/_pickle.py +17 -4
  111. flyte/types/_string_literals.py +8 -9
  112. flyte/types/_type_engine.py +26 -19
  113. flyte/types/_utils.py +1 -1
  114. {flyte-2.0.0b22.data → flyte-2.0.0b30.data}/scripts/runtime.py +43 -5
  115. {flyte-2.0.0b22.dist-info → flyte-2.0.0b30.dist-info}/METADATA +8 -1
  116. flyte-2.0.0b30.dist-info/RECORD +192 -0
  117. flyte/_protos/__init__.py +0 -0
  118. flyte/_protos/common/authorization_pb2.py +0 -66
  119. flyte/_protos/common/authorization_pb2.pyi +0 -108
  120. flyte/_protos/common/authorization_pb2_grpc.py +0 -4
  121. flyte/_protos/common/identifier_pb2.py +0 -99
  122. flyte/_protos/common/identifier_pb2.pyi +0 -120
  123. flyte/_protos/common/identifier_pb2_grpc.py +0 -4
  124. flyte/_protos/common/identity_pb2.py +0 -48
  125. flyte/_protos/common/identity_pb2.pyi +0 -72
  126. flyte/_protos/common/identity_pb2_grpc.py +0 -4
  127. flyte/_protos/common/list_pb2.py +0 -36
  128. flyte/_protos/common/list_pb2.pyi +0 -71
  129. flyte/_protos/common/list_pb2_grpc.py +0 -4
  130. flyte/_protos/common/policy_pb2.py +0 -37
  131. flyte/_protos/common/policy_pb2.pyi +0 -27
  132. flyte/_protos/common/policy_pb2_grpc.py +0 -4
  133. flyte/_protos/common/role_pb2.py +0 -37
  134. flyte/_protos/common/role_pb2.pyi +0 -53
  135. flyte/_protos/common/role_pb2_grpc.py +0 -4
  136. flyte/_protos/common/runtime_version_pb2.py +0 -28
  137. flyte/_protos/common/runtime_version_pb2.pyi +0 -24
  138. flyte/_protos/common/runtime_version_pb2_grpc.py +0 -4
  139. flyte/_protos/imagebuilder/definition_pb2.py +0 -60
  140. flyte/_protos/imagebuilder/definition_pb2.pyi +0 -153
  141. flyte/_protos/imagebuilder/definition_pb2_grpc.py +0 -4
  142. flyte/_protos/imagebuilder/payload_pb2.py +0 -32
  143. flyte/_protos/imagebuilder/payload_pb2.pyi +0 -21
  144. flyte/_protos/imagebuilder/payload_pb2_grpc.py +0 -4
  145. flyte/_protos/imagebuilder/service_pb2.py +0 -29
  146. flyte/_protos/imagebuilder/service_pb2.pyi +0 -5
  147. flyte/_protos/imagebuilder/service_pb2_grpc.py +0 -66
  148. flyte/_protos/logs/dataplane/payload_pb2.py +0 -100
  149. flyte/_protos/logs/dataplane/payload_pb2.pyi +0 -177
  150. flyte/_protos/logs/dataplane/payload_pb2_grpc.py +0 -4
  151. flyte/_protos/secret/definition_pb2.py +0 -49
  152. flyte/_protos/secret/definition_pb2.pyi +0 -93
  153. flyte/_protos/secret/definition_pb2_grpc.py +0 -4
  154. flyte/_protos/secret/payload_pb2.py +0 -62
  155. flyte/_protos/secret/payload_pb2.pyi +0 -94
  156. flyte/_protos/secret/payload_pb2_grpc.py +0 -4
  157. flyte/_protos/secret/secret_pb2.py +0 -38
  158. flyte/_protos/secret/secret_pb2.pyi +0 -6
  159. flyte/_protos/secret/secret_pb2_grpc.py +0 -198
  160. flyte/_protos/secret/secret_pb2_grpc_grpc.py +0 -198
  161. flyte/_protos/validate/validate/validate_pb2.py +0 -76
  162. flyte/_protos/workflow/common_pb2.py +0 -27
  163. flyte/_protos/workflow/common_pb2.pyi +0 -14
  164. flyte/_protos/workflow/common_pb2_grpc.py +0 -4
  165. flyte/_protos/workflow/environment_pb2.py +0 -29
  166. flyte/_protos/workflow/environment_pb2.pyi +0 -12
  167. flyte/_protos/workflow/environment_pb2_grpc.py +0 -4
  168. flyte/_protos/workflow/node_execution_service_pb2.py +0 -26
  169. flyte/_protos/workflow/node_execution_service_pb2.pyi +0 -4
  170. flyte/_protos/workflow/node_execution_service_pb2_grpc.py +0 -32
  171. flyte/_protos/workflow/queue_service_pb2.py +0 -111
  172. flyte/_protos/workflow/queue_service_pb2.pyi +0 -168
  173. flyte/_protos/workflow/queue_service_pb2_grpc.py +0 -172
  174. flyte/_protos/workflow/run_definition_pb2.py +0 -123
  175. flyte/_protos/workflow/run_definition_pb2.pyi +0 -352
  176. flyte/_protos/workflow/run_definition_pb2_grpc.py +0 -4
  177. flyte/_protos/workflow/run_logs_service_pb2.py +0 -41
  178. flyte/_protos/workflow/run_logs_service_pb2.pyi +0 -28
  179. flyte/_protos/workflow/run_logs_service_pb2_grpc.py +0 -69
  180. flyte/_protos/workflow/run_service_pb2.py +0 -137
  181. flyte/_protos/workflow/run_service_pb2.pyi +0 -185
  182. flyte/_protos/workflow/run_service_pb2_grpc.py +0 -446
  183. flyte/_protos/workflow/state_service_pb2.py +0 -67
  184. flyte/_protos/workflow/state_service_pb2.pyi +0 -76
  185. flyte/_protos/workflow/state_service_pb2_grpc.py +0 -138
  186. flyte/_protos/workflow/task_definition_pb2.py +0 -82
  187. flyte/_protos/workflow/task_definition_pb2.pyi +0 -88
  188. flyte/_protos/workflow/task_definition_pb2_grpc.py +0 -4
  189. flyte/_protos/workflow/task_service_pb2.py +0 -60
  190. flyte/_protos/workflow/task_service_pb2.pyi +0 -59
  191. flyte/_protos/workflow/task_service_pb2_grpc.py +0 -138
  192. flyte-2.0.0b22.dist-info/RECORD +0 -250
  193. {flyte-2.0.0b22.data → flyte-2.0.0b30.data}/scripts/debug.py +0 -0
  194. {flyte-2.0.0b22.dist-info → flyte-2.0.0b30.dist-info}/WHEEL +0 -0
  195. {flyte-2.0.0b22.dist-info → flyte-2.0.0b30.dist-info}/entry_points.txt +0 -0
  196. {flyte-2.0.0b22.dist-info → flyte-2.0.0b30.dist-info}/licenses/LICENSE +0 -0
  197. {flyte-2.0.0b22.dist-info → flyte-2.0.0b30.dist-info}/top_level.txt +0 -0
flyte/cli/_delete.py CHANGED
@@ -20,4 +20,26 @@ def secret(cfg: common.CLIConfig, name: str, project: str | None = None, domain:
20
20
  from flyte.remote import Secret
21
21
 
22
22
  cfg.init(project, domain)
23
- Secret.delete(name=name)
23
+ console = common.get_console()
24
+ with console.status(f"Deleting secret {name}..."):
25
+ Secret.delete(name=name)
26
+ console.print(f"Successfully deleted secret {name}.")
27
+
28
+
29
+ @delete.command(cls=common.CommandBase)
30
+ @click.argument("name", type=str, required=True)
31
+ @click.argument("task-name", type=str, required=True)
32
+ @click.pass_obj
33
+ def trigger(cfg: common.CLIConfig, name: str, task_name: str, project: str | None = None, domain: str | None = None):
34
+ """
35
+ Delete a trigger. The name of the trigger is required.
36
+ """
37
+ from flyte.remote import Trigger
38
+
39
+ cfg.init(project, domain)
40
+ console = common.get_console()
41
+
42
+ with console.status(f"Deleting trigger {name}..."):
43
+ Trigger.delete(name=name, task_name=task_name)
44
+
45
+ console.log(f"[green]Successfully deleted trigger {name}[/green]")
flyte/cli/_deploy.py CHANGED
@@ -7,8 +7,8 @@ from typing import Any, Dict, List, cast, get_args
7
7
  import rich_click as click
8
8
 
9
9
  import flyte
10
+ from flyte._code_bundle._utils import CopyFiles
10
11
 
11
- from .._code_bundle._utils import CopyFiles
12
12
  from . import _common as common
13
13
  from ._common import CLIConfig
14
14
 
@@ -83,6 +83,19 @@ class DeployArguments:
83
83
  )
84
84
  },
85
85
  )
86
+ no_sync_local_sys_paths: bool = field(
87
+ default=True,
88
+ metadata={
89
+ "click.option": click.Option(
90
+ ["--no-sync-local-sys-paths"],
91
+ is_flag=True,
92
+ flag_value=True,
93
+ default=False,
94
+ help="Disable synchronization of local sys.path entries under the root directory "
95
+ "to the remote container.",
96
+ )
97
+ },
98
+ )
86
99
 
87
100
  @classmethod
88
101
  def from_dict(cls, d: Dict[str, Any]) -> "DeployArguments":
@@ -104,12 +117,15 @@ class DeployEnvCommand(click.RichCommand):
104
117
  super().__init__(*args, **kwargs)
105
118
 
106
119
  def invoke(self, ctx: click.Context):
107
- from rich.console import Console
108
-
109
- console = Console()
120
+ console = common.get_console()
110
121
  console.print(f"Deploying root - environment: {self.env_name}")
111
122
  obj: CLIConfig = ctx.obj
112
- obj.init(self.deploy_args.project, self.deploy_args.domain, root_dir=self.deploy_args.root_dir)
123
+ obj.init(
124
+ self.deploy_args.project,
125
+ self.deploy_args.domain,
126
+ root_dir=self.deploy_args.root_dir,
127
+ sync_local_sys_paths=not self.deploy_args.no_sync_local_sys_paths,
128
+ )
113
129
  with console.status("Deploying...", spinner="dots"):
114
130
  deployment = flyte.deploy(
115
131
  self.env,
@@ -119,7 +135,7 @@ class DeployEnvCommand(click.RichCommand):
119
135
  )
120
136
 
121
137
  console.print(common.format("Environments", deployment[0].env_repr(), obj.output_format))
122
- console.print(common.format("Tasks", deployment[0].task_repr(), obj.output_format))
138
+ console.print(common.format("Tasks", deployment[0].table_repr(), obj.output_format))
123
139
 
124
140
 
125
141
  class DeployEnvRecursiveCommand(click.Command):
@@ -135,13 +151,11 @@ class DeployEnvRecursiveCommand(click.Command):
135
151
  super().__init__(*args, **kwargs)
136
152
 
137
153
  def invoke(self, ctx: click.Context):
138
- from rich.console import Console
139
-
140
154
  from flyte._environment import list_loaded_environments
141
155
  from flyte._utils import load_python_modules
142
156
 
143
- console = Console()
144
157
  obj: CLIConfig = ctx.obj
158
+ console = common.get_console()
145
159
 
146
160
  # Load all python modules
147
161
  loaded_modules, failed_paths = load_python_modules(self.path, self.deploy_args.recursive)
@@ -165,7 +179,11 @@ class DeployEnvRecursiveCommand(click.Command):
165
179
  f"Failed to load {len(failed_paths)} files. Use --ignore-load-errors to ignore these errors."
166
180
  )
167
181
  # Now start connection and deploy all environments
168
- obj.init(self.deploy_args.project, self.deploy_args.domain)
182
+ obj.init(
183
+ self.deploy_args.project,
184
+ self.deploy_args.domain,
185
+ sync_local_sys_paths=not self.deploy_args.no_sync_local_sys_paths,
186
+ )
169
187
  with console.status("Deploying...", spinner="dots"):
170
188
  deployments = flyte.deploy(
171
189
  *all_envs,
@@ -177,7 +195,7 @@ class DeployEnvRecursiveCommand(click.Command):
177
195
  console.print(
178
196
  common.format("Environments", [env for d in deployments for env in d.env_repr()], obj.output_format)
179
197
  )
180
- console.print(common.format("Tasks", [task for d in deployments for task in d.task_repr()], obj.output_format))
198
+ console.print(common.format("Tasks", [task for d in deployments for task in d.table_repr()], obj.output_format))
181
199
 
182
200
 
183
201
  class EnvPerFileGroup(common.ObjectsPerFileGroup):
@@ -193,6 +211,26 @@ class EnvPerFileGroup(common.ObjectsPerFileGroup):
193
211
  def _filter_objects(self, module: ModuleType) -> Dict[str, Any]:
194
212
  return {k: v for k, v in module.__dict__.items() if isinstance(v, flyte.Environment)}
195
213
 
214
+ def list_commands(self, ctx):
215
+ common.initialize_config(
216
+ ctx,
217
+ self.deploy_args.project,
218
+ self.deploy_args.domain,
219
+ self.deploy_args.root_dir,
220
+ sync_local_sys_paths=not self.deploy_args.no_sync_local_sys_paths,
221
+ )
222
+ return super().list_commands(ctx)
223
+
224
+ def get_command(self, ctx, obj_name):
225
+ common.initialize_config(
226
+ ctx,
227
+ self.deploy_args.project,
228
+ self.deploy_args.domain,
229
+ self.deploy_args.root_dir,
230
+ sync_local_sys_paths=not self.deploy_args.no_sync_local_sys_paths,
231
+ )
232
+ return super().get_command(ctx, obj_name)
233
+
196
234
  def _get_command_for_obj(self, ctx: click.Context, obj_name: str, obj: Any) -> click.Command:
197
235
  obj = cast(flyte.Environment, obj)
198
236
  return DeployEnvCommand(
flyte/cli/_get.py CHANGED
@@ -1,10 +1,11 @@
1
1
  import asyncio
2
- from typing import Tuple, Union
2
+ from typing import Tuple, Union, get_args
3
3
 
4
4
  import rich_click as click
5
- from rich.console import Console
6
5
  from rich.pretty import pretty_repr
7
6
 
7
+ import flyte.remote as remote
8
+
8
9
  from . import _common as common
9
10
 
10
11
 
@@ -38,20 +39,24 @@ def project(cfg: common.CLIConfig, name: str | None = None):
38
39
  """
39
40
  Get a list of all projects, or details of a specific project by name.
40
41
  """
41
- from flyte.remote import Project
42
-
43
42
  cfg.init()
44
43
 
45
- console = Console()
44
+ console = common.get_console()
46
45
  if name:
47
- console.print(pretty_repr(Project.get(name)))
46
+ console.print(pretty_repr(remote.Project.get(name)))
48
47
  else:
49
- console.print(common.format("Projects", Project.listall(), cfg.output_format))
48
+ console.print(common.format("Projects", remote.Project.listall(), cfg.output_format))
50
49
 
51
50
 
52
51
  @get.command(cls=common.CommandBase)
53
52
  @click.argument("name", type=str, required=False)
54
53
  @click.option("--limit", type=int, default=100, help="Limit the number of runs to fetch when listing.")
54
+ @click.option(
55
+ "--in-phase", # multiple=True, TODO support multiple phases once values in works
56
+ type=click.Choice(get_args(remote.Phase), case_sensitive=False),
57
+ help="Filter runs by their status.",
58
+ )
59
+ @click.option("--only-mine", is_flag=True, default=False, help="Show only runs created by the current user (you).")
55
60
  @click.pass_obj
56
61
  def run(
57
62
  cfg: common.CLIConfig,
@@ -59,6 +64,8 @@ def run(
59
64
  project: str | None = None,
60
65
  domain: str | None = None,
61
66
  limit: int = 100,
67
+ in_phase: str | Tuple[str, ...] | None = None,
68
+ only_mine: bool = False,
62
69
  ):
63
70
  """
64
71
  Get a list of all runs, or details of a specific run by name.
@@ -67,16 +74,29 @@ def run(
67
74
 
68
75
  If you want to see the actions for a run, use `get action <run_name>`.
69
76
  """
70
- from flyte.remote import Run, RunDetails
71
77
 
72
78
  cfg.init(project=project, domain=domain)
73
79
 
74
- console = Console()
80
+ console = common.get_console()
75
81
  if name:
76
- details = RunDetails.get(name=name)
82
+ details = remote.RunDetails.get(name=name)
77
83
  console.print(common.format(f"Run {name}", [details], "json"))
78
84
  else:
79
- console.print(common.format("Runs", Run.listall(limit=limit), cfg.output_format))
85
+ if in_phase and isinstance(in_phase, str):
86
+ in_phase = (in_phase,)
87
+
88
+ subject = None
89
+ if only_mine:
90
+ usr = remote.User.get()
91
+ subject = usr.subject()
92
+
93
+ console.print(
94
+ common.format(
95
+ "Runs",
96
+ remote.Run.listall(limit=limit, in_phase=in_phase, created_by_subject=subject),
97
+ cfg.output_format,
98
+ )
99
+ )
80
100
 
81
101
 
82
102
  @get.command(cls=common.CommandBase)
@@ -97,22 +117,22 @@ def task(
97
117
 
98
118
  Currently, both `name` and `version` are required to get a specific task.
99
119
  """
100
- from flyte.remote import Task
101
-
102
120
  cfg.init(project=project, domain=domain)
103
121
 
104
- console = Console()
122
+ console = common.get_console()
105
123
  if name:
106
124
  if version:
107
- v = Task.get(name=name, version=version)
125
+ v = remote.Task.get(name=name, version=version)
108
126
  if v is None:
109
127
  raise click.BadParameter(f"Task {name} not found.")
110
128
  t = v.fetch()
111
129
  console.print(common.format(f"Task {name}", [t], "json"))
112
130
  else:
113
- console.print(common.format("Tasks", Task.listall(by_task_name=name, limit=limit), cfg.output_format))
131
+ console.print(
132
+ common.format("Tasks", remote.Task.listall(by_task_name=name, limit=limit), cfg.output_format)
133
+ )
114
134
  else:
115
- console.print(common.format("Tasks", Task.listall(limit=limit), cfg.output_format))
135
+ console.print(common.format("Tasks", remote.Task.listall(limit=limit), cfg.output_format))
116
136
 
117
137
 
118
138
  @get.command(cls=common.CommandBase)
@@ -129,11 +149,9 @@ def action(
129
149
  """
130
150
  Get all actions for a run or details for a specific action.
131
151
  """
132
- import flyte.remote as remote
133
-
134
152
  cfg.init(project=project, domain=domain)
135
153
 
136
- console = Console()
154
+ console = common.get_console()
137
155
  if action_name:
138
156
  console.print(
139
157
  common.format(
@@ -196,8 +214,6 @@ def logs(
196
214
  $ flyte get logs my_run my_action --pretty --lines 50
197
215
  ```
198
216
  """
199
- import flyte.remote as remote
200
-
201
217
  cfg.init(project=project, domain=domain)
202
218
 
203
219
  async def _run_log_view(_obj):
@@ -230,11 +246,13 @@ def secret(
230
246
  """
231
247
  Get a list of all secrets, or details of a specific secret by name.
232
248
  """
233
- import flyte.remote as remote
234
-
249
+ if project is None:
250
+ project = ""
251
+ if domain is None:
252
+ domain = ""
235
253
  cfg.init(project=project, domain=domain)
236
254
 
237
- console = Console()
255
+ console = common.get_console()
238
256
  if name:
239
257
  console.print(common.format("Secret", [remote.Secret.get(name)], "json"))
240
258
  else:
@@ -275,26 +293,23 @@ def io(
275
293
  if inputs_only and outputs_only:
276
294
  raise click.BadParameter("Cannot use both --inputs-only and --outputs-only")
277
295
 
278
- import flyte.remote as remote
279
- from flyte.remote import ActionDetails, ActionInputs, ActionOutputs
280
-
281
296
  cfg.init(project=project, domain=domain)
282
- console = Console()
297
+ console = common.get_console()
283
298
  if action_name:
284
- obj = ActionDetails.get(run_name=run_name, name=action_name)
299
+ obj = remote.ActionDetails.get(run_name=run_name, name=action_name)
285
300
  else:
286
301
  obj = remote.RunDetails.get(run_name)
287
302
 
288
303
  async def _get_io(
289
- details: Union[remote.RunDetails, ActionDetails],
290
- ) -> Tuple[ActionInputs | None, ActionOutputs | None | str]:
304
+ details: Union[remote.RunDetails, remote.ActionDetails],
305
+ ) -> Tuple[remote.ActionInputs | None, remote.ActionOutputs | None | str]:
291
306
  if inputs_only or outputs_only:
292
307
  if inputs_only:
293
308
  return await details.inputs(), None
294
309
  elif outputs_only:
295
310
  return None, await details.outputs()
296
311
  inputs = await details.inputs()
297
- outputs: ActionOutputs | None | str = None
312
+ outputs: remote.ActionOutputs | None | str = None
298
313
  try:
299
314
  outputs = await details.outputs()
300
315
  except Exception:
@@ -321,5 +336,35 @@ def config(cfg: common.CLIConfig):
321
336
 
322
337
  The configuration will include the endpoint, organization, and other settings that are used by the CLI.
323
338
  """
324
- console = Console()
339
+ console = common.get_console()
325
340
  console.print(cfg)
341
+
342
+
343
+ @get.command(cls=common.CommandBase)
344
+ @click.argument("task_name", type=str, required=False)
345
+ @click.argument("name", type=str, required=False)
346
+ @click.option("--limit", type=int, default=100, help="Limit the number of triggers to fetch.")
347
+ @click.pass_obj
348
+ def trigger(
349
+ cfg: common.CLIConfig,
350
+ task_name: str | None = None,
351
+ name: str | None = None,
352
+ limit: int = 100,
353
+ project: str | None = None,
354
+ domain: str | None = None,
355
+ ):
356
+ """
357
+ Get a list of all triggers, or details of a specific trigger by name.
358
+ """
359
+ if name and not task_name:
360
+ raise click.BadParameter("If you provide a trigger name, you must also provide the task name.")
361
+
362
+ from flyte.remote import Trigger
363
+
364
+ cfg.init(project=project, domain=domain)
365
+
366
+ console = common.get_console()
367
+ if name:
368
+ console.print(pretty_repr(Trigger.get(name=name, task_name=task_name)))
369
+ else:
370
+ console.print(common.format("Triggers", Trigger.listall(task_name=task_name, limit=limit), cfg.output_format))
flyte/cli/_params.py CHANGED
@@ -15,9 +15,9 @@ from typing import get_args
15
15
  import rich_click as click
16
16
  import yaml
17
17
  from click import Parameter
18
- from flyteidl.core.interface_pb2 import Variable
19
- from flyteidl.core.literals_pb2 import Literal
20
- from flyteidl.core.types_pb2 import BlobType, LiteralType, SimpleType
18
+ from flyteidl2.core.interface_pb2 import Variable
19
+ from flyteidl2.core.literals_pb2 import Literal
20
+ from flyteidl2.core.types_pb2 import BlobType, LiteralType, SimpleType
21
21
  from google.protobuf.json_format import MessageToDict
22
22
  from mashumaro.codecs.json import JSONEncoder
23
23
 
@@ -505,7 +505,7 @@ def to_click_option(
505
505
  This handles converting workflow input types to supported click parameters with callbacks to initialize
506
506
  the input values to their expected types.
507
507
  """
508
- from flyteidl.core.types_pb2 import SimpleType
508
+ from flyteidl2.core.types_pb2 import SimpleType
509
509
 
510
510
  if input_name != input_name.lower():
511
511
  # Click does not support uppercase option names: https://github.com/pallets/click/issues/837
@@ -531,17 +531,19 @@ def to_click_option(
531
531
  if literal_var.type.metadata:
532
532
  description_extra = f": {MessageToDict(literal_var.type.metadata)}"
533
533
 
534
- # If a query has been specified, the input is never strictly required at this layer
535
534
  required = False if default_val is not None else True
536
535
  is_flag: typing.Optional[bool] = None
536
+ param_decls = [f"--{input_name}"]
537
537
  if literal_converter.is_bool():
538
538
  required = False
539
539
  is_flag = True
540
+ if default_val is True:
541
+ param_decls = [f"--{input_name}/--no-{input_name}"]
540
542
  if literal_converter.is_optional():
541
543
  required = False
542
544
 
543
545
  return click.Option(
544
- param_decls=[f"--{input_name}"],
546
+ param_decls=param_decls,
545
547
  type=literal_converter.click_type,
546
548
  is_flag=is_flag,
547
549
  default=default_val,
flyte/cli/_plugins.py ADDED
@@ -0,0 +1,209 @@
1
+ """CLI Plugin System for Flyte.
2
+
3
+ This module provides a plugin system that allows external packages to:
4
+ 1. Register new top-level CLI commands (e.g., flyte my-command)
5
+ 2. Register new subcommands in existing groups (e.g., flyte get my-object)
6
+ 3. Modify behavior of existing commands via hooks
7
+
8
+ Plugins are discovered via Python entry points.
9
+
10
+ Entry Point Groups:
11
+ - flyte.plugins.cli.commands: Register new commands
12
+ - Entry point name "foo" -> flyte foo (top-level command)
13
+ - Entry point name "get.bar" -> flyte get bar (adds subcommand to get group)
14
+ - Note: At most one dot is supported. For nested groups, register the entire
15
+ group hierarchy as a top-level command (without dots).
16
+
17
+ - flyte.plugins.cli.hooks: Modify existing commands
18
+ - Entry point name "run" -> modifies flyte run
19
+ - Entry point name "get.project" -> modifies flyte get project
20
+ - Note: At most one dot is supported.
21
+
22
+ Example Plugin Package:
23
+ # In your-plugin/pyproject.toml
24
+ [project.entry-points."flyte.plugins.cli.commands"]
25
+ my-command = "your_plugin.cli:my_command"
26
+ get.my-object = "your_plugin.cli:get_my_object"
27
+
28
+ [project.entry-points."flyte.plugins.cli.hooks"]
29
+ run = "your_plugin.hooks:modify_run"
30
+
31
+ # In your-plugin/your_plugin/cli.py
32
+ import rich_click as click
33
+
34
+ @click.command()
35
+ def my_command():
36
+ '''My custom top-level command.'''
37
+ click.echo("Hello from plugin!")
38
+
39
+ @click.command()
40
+ def get_my_object():
41
+ '''Get my custom object.'''
42
+ click.echo("Getting my object...")
43
+
44
+ # In your-plugin/your_plugin/hooks.py
45
+ def modify_run(command):
46
+ '''Add behavior to flyte run command.'''
47
+ # Wrap invoke() instead of callback to ensure Click's full machinery runs
48
+ original_invoke = command.invoke
49
+
50
+ def wrapper(ctx):
51
+ # Do something before
52
+ click.echo("Plugin: Starting task...")
53
+
54
+ result = original_invoke(ctx)
55
+
56
+ # Do something after
57
+ click.echo("Plugin: Task completed!")
58
+ return result
59
+
60
+ command.invoke = wrapper
61
+ return command
62
+ """
63
+
64
+ from importlib.metadata import entry_points
65
+ from typing import Callable
66
+
67
+ import rich_click as click
68
+
69
+ from flyte._logging import logger
70
+
71
+ # Type alias for command hooks
72
+ CommandHook = Callable[[click.Command], click.Command]
73
+
74
+
75
+ def discover_and_register_plugins(root_group: click.Group):
76
+ """
77
+ Discover all CLI plugins from installed packages and register them.
78
+
79
+ This function:
80
+ 1. Discovers command plugins and adds them to the CLI
81
+ 2. Discovers hook plugins and applies them to existing commands
82
+
83
+ Args:
84
+ root_group: The root Click command group (main CLI group)
85
+ """
86
+ _load_command_plugins(root_group)
87
+ _load_hook_plugins(root_group)
88
+
89
+
90
+ def _load_command_plugins(root_group: click.Group):
91
+ """Load and register command plugins."""
92
+ for ep in entry_points(group="flyte.plugins.cli.commands"):
93
+ try:
94
+ command = ep.load()
95
+ if not isinstance(command, click.Command):
96
+ logger.warning(f"Plugin {ep.name} did not return a click.Command, got {type(command).__name__}")
97
+ continue
98
+
99
+ # Check if this is a subcommand (contains dot notation)
100
+ if "." in ep.name:
101
+ group_name, command_name = ep.name.split(".", 1)
102
+
103
+ # Validate: only support one level of nesting (group.command)
104
+ if "." in command_name:
105
+ logger.error(
106
+ f"Plugin {ep.name} uses multiple dots, which is not supported. "
107
+ f"Use at most one dot (e.g., 'group.command'). "
108
+ f"For nested groups, register the entire group hierarchy as a top-level command."
109
+ )
110
+ continue
111
+
112
+ _add_subcommand_to_group(root_group, group_name, command_name, command)
113
+ else:
114
+ # Top-level command
115
+ root_group.add_command(command, name=ep.name)
116
+ logger.info(f"Registered plugin command: flyte {ep.name}")
117
+
118
+ except Exception as e:
119
+ logger.error(f"Failed to load plugin command {ep.name}: {e}")
120
+
121
+
122
+ def _load_hook_plugins(root_group: click.Group):
123
+ """Load and apply hook plugins to existing commands."""
124
+ for ep in entry_points(group="flyte.plugins.cli.hooks"):
125
+ try:
126
+ hook = ep.load()
127
+ if not callable(hook):
128
+ logger.warning(f"Plugin hook {ep.name} is not callable")
129
+ continue
130
+
131
+ # Check if this is a subcommand hook (contains dot notation)
132
+ if "." in ep.name:
133
+ group_name, command_name = ep.name.split(".", 1)
134
+
135
+ # Validate: only support one level of nesting (group.command)
136
+ if "." in command_name:
137
+ logger.error(
138
+ f"Plugin hook {ep.name} uses multiple dots, which is not supported. "
139
+ f"Use at most one dot (e.g., 'group.command')."
140
+ )
141
+ continue
142
+
143
+ _apply_hook_to_subcommand(root_group, group_name, command_name, hook)
144
+ else:
145
+ # Top-level command hook
146
+ _apply_hook_to_command(root_group, ep.name, hook)
147
+
148
+ except Exception as e:
149
+ logger.error(f"Failed to apply hook {ep.name}: {e}")
150
+
151
+
152
+ def _add_subcommand_to_group(root_group: click.Group, group_name: str, command_name: str, command: click.Command):
153
+ """Add a subcommand to an existing command group."""
154
+ if group_name not in root_group.commands:
155
+ logger.warning(f"Cannot add plugin subcommand '{command_name}' - group '{group_name}' does not exist")
156
+ return
157
+
158
+ group = root_group.commands[group_name]
159
+ if not isinstance(group, click.Group):
160
+ logger.warning(f"Cannot add plugin subcommand '{command_name}' - '{group_name}' is not a command group")
161
+ return
162
+
163
+ group.add_command(command, name=command_name)
164
+ # lower to debug later
165
+ logger.info(f"Registered plugin subcommand: flyte {group_name} {command_name}")
166
+
167
+
168
+ def _apply_hook_to_command(root_group: click.Group, command_name: str, hook: CommandHook):
169
+ """Apply a hook to a top-level command."""
170
+ if command_name not in root_group.commands:
171
+ logger.warning(f"Cannot apply hook - command '{command_name}' does not exist")
172
+ return
173
+
174
+ original_command = root_group.commands[command_name]
175
+ try:
176
+ modified_command = hook(original_command)
177
+ root_group.commands[command_name] = modified_command
178
+ # lower to debug later
179
+ logger.info(f"Applied hook to command: flyte {command_name}")
180
+ except Exception as e:
181
+ logger.error(f"Hook failed for command {command_name}: {e}")
182
+ root_group.commands[command_name] = original_command
183
+
184
+
185
+ def _apply_hook_to_subcommand(root_group: click.Group, group_name: str, command_name: str, hook: CommandHook):
186
+ """Apply a hook to a subcommand within a group."""
187
+ if group_name not in root_group.commands:
188
+ logger.warning(f"Cannot apply hook - group '{group_name}' does not exist")
189
+ return
190
+
191
+ group = root_group.commands[group_name]
192
+ if not isinstance(group, click.Group):
193
+ logger.warning(f"Cannot apply hook - '{group_name}' is not a command group")
194
+ return
195
+
196
+ if command_name not in group.commands:
197
+ logger.warning(f"Cannot apply hook - subcommand '{command_name}' does not exist in group '{group_name}'")
198
+ return
199
+
200
+ original_command = group.commands[command_name]
201
+ if original_command.callback is not None:
202
+ original_command.callback()
203
+ try:
204
+ modified_command = hook(original_command)
205
+ group.commands[command_name] = modified_command
206
+ logger.info(f"Applied hook to subcommand: flyte {group_name} {command_name}")
207
+ except Exception as e:
208
+ logger.error(f"Hook failed for subcommand {group_name}.{command_name}: {e}")
209
+ group.commands[command_name] = original_command