flyte 2.0.0b13__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 (211) hide show
  1. flyte/__init__.py +18 -2
  2. flyte/_bin/debug.py +38 -0
  3. flyte/_bin/runtime.py +62 -8
  4. flyte/_cache/cache.py +4 -2
  5. flyte/_cache/local_cache.py +216 -0
  6. flyte/_code_bundle/_ignore.py +12 -4
  7. flyte/_code_bundle/_packaging.py +13 -9
  8. flyte/_code_bundle/_utils.py +18 -10
  9. flyte/_code_bundle/bundle.py +17 -9
  10. flyte/_constants.py +1 -0
  11. flyte/_context.py +4 -1
  12. flyte/_custom_context.py +73 -0
  13. flyte/_debug/constants.py +38 -0
  14. flyte/_debug/utils.py +17 -0
  15. flyte/_debug/vscode.py +307 -0
  16. flyte/_deploy.py +235 -61
  17. flyte/_environment.py +20 -6
  18. flyte/_excepthook.py +1 -1
  19. flyte/_hash.py +1 -16
  20. flyte/_image.py +178 -81
  21. flyte/_initialize.py +132 -51
  22. flyte/_interface.py +39 -2
  23. flyte/_internal/controllers/__init__.py +4 -5
  24. flyte/_internal/controllers/_local_controller.py +70 -29
  25. flyte/_internal/controllers/_trace.py +1 -1
  26. flyte/_internal/controllers/remote/__init__.py +0 -2
  27. flyte/_internal/controllers/remote/_action.py +14 -16
  28. flyte/_internal/controllers/remote/_client.py +1 -1
  29. flyte/_internal/controllers/remote/_controller.py +68 -70
  30. flyte/_internal/controllers/remote/_core.py +127 -99
  31. flyte/_internal/controllers/remote/_informer.py +19 -10
  32. flyte/_internal/controllers/remote/_service_protocol.py +7 -7
  33. flyte/_internal/imagebuild/docker_builder.py +181 -69
  34. flyte/_internal/imagebuild/image_builder.py +0 -5
  35. flyte/_internal/imagebuild/remote_builder.py +155 -64
  36. flyte/_internal/imagebuild/utils.py +51 -2
  37. flyte/_internal/resolvers/_task_module.py +5 -38
  38. flyte/_internal/resolvers/default.py +2 -2
  39. flyte/_internal/runtime/convert.py +110 -21
  40. flyte/_internal/runtime/entrypoints.py +27 -1
  41. flyte/_internal/runtime/io.py +21 -8
  42. flyte/_internal/runtime/resources_serde.py +20 -6
  43. flyte/_internal/runtime/reuse.py +1 -1
  44. flyte/_internal/runtime/rusty.py +20 -5
  45. flyte/_internal/runtime/task_serde.py +34 -19
  46. flyte/_internal/runtime/taskrunner.py +22 -4
  47. flyte/_internal/runtime/trigger_serde.py +160 -0
  48. flyte/_internal/runtime/types_serde.py +1 -1
  49. flyte/_keyring/__init__.py +0 -0
  50. flyte/_keyring/file.py +115 -0
  51. flyte/_logging.py +201 -39
  52. flyte/_map.py +111 -14
  53. flyte/_module.py +70 -0
  54. flyte/_pod.py +4 -3
  55. flyte/_resources.py +213 -31
  56. flyte/_run.py +110 -39
  57. flyte/_task.py +75 -16
  58. flyte/_task_environment.py +105 -29
  59. flyte/_task_plugins.py +4 -2
  60. flyte/_trace.py +5 -0
  61. flyte/_trigger.py +1000 -0
  62. flyte/_utils/__init__.py +2 -1
  63. flyte/_utils/asyn.py +3 -1
  64. flyte/_utils/coro_management.py +2 -1
  65. flyte/_utils/docker_credentials.py +173 -0
  66. flyte/_utils/module_loader.py +17 -2
  67. flyte/_version.py +3 -3
  68. flyte/cli/_abort.py +3 -3
  69. flyte/cli/_build.py +3 -6
  70. flyte/cli/_common.py +78 -7
  71. flyte/cli/_create.py +182 -4
  72. flyte/cli/_delete.py +23 -1
  73. flyte/cli/_deploy.py +63 -16
  74. flyte/cli/_get.py +79 -34
  75. flyte/cli/_params.py +26 -10
  76. flyte/cli/_plugins.py +209 -0
  77. flyte/cli/_run.py +151 -26
  78. flyte/cli/_serve.py +64 -0
  79. flyte/cli/_update.py +37 -0
  80. flyte/cli/_user.py +17 -0
  81. flyte/cli/main.py +30 -4
  82. flyte/config/_config.py +10 -6
  83. flyte/config/_internal.py +1 -0
  84. flyte/config/_reader.py +29 -8
  85. flyte/connectors/__init__.py +11 -0
  86. flyte/connectors/_connector.py +270 -0
  87. flyte/connectors/_server.py +197 -0
  88. flyte/connectors/utils.py +135 -0
  89. flyte/errors.py +22 -2
  90. flyte/extend.py +8 -1
  91. flyte/extras/_container.py +6 -1
  92. flyte/git/__init__.py +3 -0
  93. flyte/git/_config.py +21 -0
  94. flyte/io/__init__.py +2 -0
  95. flyte/io/_dataframe/__init__.py +2 -0
  96. flyte/io/_dataframe/basic_dfs.py +17 -8
  97. flyte/io/_dataframe/dataframe.py +98 -132
  98. flyte/io/_dir.py +575 -113
  99. flyte/io/_file.py +582 -139
  100. flyte/io/_hashing_io.py +342 -0
  101. flyte/models.py +74 -15
  102. flyte/remote/__init__.py +6 -1
  103. flyte/remote/_action.py +34 -26
  104. flyte/remote/_client/_protocols.py +39 -4
  105. flyte/remote/_client/auth/_authenticators/device_code.py +4 -5
  106. flyte/remote/_client/auth/_authenticators/pkce.py +1 -1
  107. flyte/remote/_client/auth/_channel.py +10 -6
  108. flyte/remote/_client/controlplane.py +17 -5
  109. flyte/remote/_console.py +3 -2
  110. flyte/remote/_data.py +6 -6
  111. flyte/remote/_logs.py +3 -3
  112. flyte/remote/_run.py +64 -8
  113. flyte/remote/_secret.py +26 -17
  114. flyte/remote/_task.py +75 -33
  115. flyte/remote/_trigger.py +306 -0
  116. flyte/remote/_user.py +33 -0
  117. flyte/report/_report.py +1 -1
  118. flyte/storage/__init__.py +6 -1
  119. flyte/storage/_config.py +5 -1
  120. flyte/storage/_parallel_reader.py +274 -0
  121. flyte/storage/_storage.py +200 -103
  122. flyte/types/__init__.py +16 -0
  123. flyte/types/_interface.py +2 -2
  124. flyte/types/_pickle.py +35 -8
  125. flyte/types/_string_literals.py +8 -9
  126. flyte/types/_type_engine.py +40 -70
  127. flyte/types/_utils.py +1 -1
  128. flyte-2.0.0b30.data/scripts/debug.py +38 -0
  129. {flyte-2.0.0b13.data → flyte-2.0.0b30.data}/scripts/runtime.py +62 -8
  130. {flyte-2.0.0b13.dist-info → flyte-2.0.0b30.dist-info}/METADATA +11 -3
  131. flyte-2.0.0b30.dist-info/RECORD +192 -0
  132. {flyte-2.0.0b13.dist-info → flyte-2.0.0b30.dist-info}/entry_points.txt +3 -0
  133. flyte/_protos/common/authorization_pb2.py +0 -66
  134. flyte/_protos/common/authorization_pb2.pyi +0 -108
  135. flyte/_protos/common/authorization_pb2_grpc.py +0 -4
  136. flyte/_protos/common/identifier_pb2.py +0 -93
  137. flyte/_protos/common/identifier_pb2.pyi +0 -110
  138. flyte/_protos/common/identifier_pb2_grpc.py +0 -4
  139. flyte/_protos/common/identity_pb2.py +0 -48
  140. flyte/_protos/common/identity_pb2.pyi +0 -72
  141. flyte/_protos/common/identity_pb2_grpc.py +0 -4
  142. flyte/_protos/common/list_pb2.py +0 -36
  143. flyte/_protos/common/list_pb2.pyi +0 -71
  144. flyte/_protos/common/list_pb2_grpc.py +0 -4
  145. flyte/_protos/common/policy_pb2.py +0 -37
  146. flyte/_protos/common/policy_pb2.pyi +0 -27
  147. flyte/_protos/common/policy_pb2_grpc.py +0 -4
  148. flyte/_protos/common/role_pb2.py +0 -37
  149. flyte/_protos/common/role_pb2.pyi +0 -53
  150. flyte/_protos/common/role_pb2_grpc.py +0 -4
  151. flyte/_protos/common/runtime_version_pb2.py +0 -28
  152. flyte/_protos/common/runtime_version_pb2.pyi +0 -24
  153. flyte/_protos/common/runtime_version_pb2_grpc.py +0 -4
  154. flyte/_protos/imagebuilder/definition_pb2.py +0 -59
  155. flyte/_protos/imagebuilder/definition_pb2.pyi +0 -140
  156. flyte/_protos/imagebuilder/definition_pb2_grpc.py +0 -4
  157. flyte/_protos/imagebuilder/payload_pb2.py +0 -32
  158. flyte/_protos/imagebuilder/payload_pb2.pyi +0 -21
  159. flyte/_protos/imagebuilder/payload_pb2_grpc.py +0 -4
  160. flyte/_protos/imagebuilder/service_pb2.py +0 -29
  161. flyte/_protos/imagebuilder/service_pb2.pyi +0 -5
  162. flyte/_protos/imagebuilder/service_pb2_grpc.py +0 -66
  163. flyte/_protos/logs/dataplane/payload_pb2.py +0 -100
  164. flyte/_protos/logs/dataplane/payload_pb2.pyi +0 -177
  165. flyte/_protos/logs/dataplane/payload_pb2_grpc.py +0 -4
  166. flyte/_protos/secret/definition_pb2.py +0 -49
  167. flyte/_protos/secret/definition_pb2.pyi +0 -93
  168. flyte/_protos/secret/definition_pb2_grpc.py +0 -4
  169. flyte/_protos/secret/payload_pb2.py +0 -62
  170. flyte/_protos/secret/payload_pb2.pyi +0 -94
  171. flyte/_protos/secret/payload_pb2_grpc.py +0 -4
  172. flyte/_protos/secret/secret_pb2.py +0 -38
  173. flyte/_protos/secret/secret_pb2.pyi +0 -6
  174. flyte/_protos/secret/secret_pb2_grpc.py +0 -198
  175. flyte/_protos/secret/secret_pb2_grpc_grpc.py +0 -198
  176. flyte/_protos/validate/validate/validate_pb2.py +0 -76
  177. flyte/_protos/workflow/common_pb2.py +0 -27
  178. flyte/_protos/workflow/common_pb2.pyi +0 -14
  179. flyte/_protos/workflow/common_pb2_grpc.py +0 -4
  180. flyte/_protos/workflow/environment_pb2.py +0 -29
  181. flyte/_protos/workflow/environment_pb2.pyi +0 -12
  182. flyte/_protos/workflow/environment_pb2_grpc.py +0 -4
  183. flyte/_protos/workflow/node_execution_service_pb2.py +0 -26
  184. flyte/_protos/workflow/node_execution_service_pb2.pyi +0 -4
  185. flyte/_protos/workflow/node_execution_service_pb2_grpc.py +0 -32
  186. flyte/_protos/workflow/queue_service_pb2.py +0 -109
  187. flyte/_protos/workflow/queue_service_pb2.pyi +0 -166
  188. flyte/_protos/workflow/queue_service_pb2_grpc.py +0 -172
  189. flyte/_protos/workflow/run_definition_pb2.py +0 -121
  190. flyte/_protos/workflow/run_definition_pb2.pyi +0 -327
  191. flyte/_protos/workflow/run_definition_pb2_grpc.py +0 -4
  192. flyte/_protos/workflow/run_logs_service_pb2.py +0 -41
  193. flyte/_protos/workflow/run_logs_service_pb2.pyi +0 -28
  194. flyte/_protos/workflow/run_logs_service_pb2_grpc.py +0 -69
  195. flyte/_protos/workflow/run_service_pb2.py +0 -137
  196. flyte/_protos/workflow/run_service_pb2.pyi +0 -185
  197. flyte/_protos/workflow/run_service_pb2_grpc.py +0 -446
  198. flyte/_protos/workflow/state_service_pb2.py +0 -67
  199. flyte/_protos/workflow/state_service_pb2.pyi +0 -76
  200. flyte/_protos/workflow/state_service_pb2_grpc.py +0 -138
  201. flyte/_protos/workflow/task_definition_pb2.py +0 -79
  202. flyte/_protos/workflow/task_definition_pb2.pyi +0 -81
  203. flyte/_protos/workflow/task_definition_pb2_grpc.py +0 -4
  204. flyte/_protos/workflow/task_service_pb2.py +0 -60
  205. flyte/_protos/workflow/task_service_pb2.pyi +0 -59
  206. flyte/_protos/workflow/task_service_pb2_grpc.py +0 -138
  207. flyte-2.0.0b13.dist-info/RECORD +0 -239
  208. /flyte/{_protos → _debug}/__init__.py +0 -0
  209. {flyte-2.0.0b13.dist-info → flyte-2.0.0b30.dist-info}/WHEEL +0 -0
  210. {flyte-2.0.0b13.dist-info → flyte-2.0.0b30.dist-info}/licenses/LICENSE +0 -0
  211. {flyte-2.0.0b13.dist-info → flyte-2.0.0b30.dist-info}/top_level.txt +0 -0
flyte/cli/_create.py CHANGED
@@ -3,6 +3,7 @@ from typing import Any, Dict, get_args
3
3
 
4
4
  import rich_click as click
5
5
 
6
+ import flyte
6
7
  import flyte.cli._common as common
7
8
  from flyte.cli._option import MutuallyExclusiveOption
8
9
  from flyte.remote import SecretTypes
@@ -23,18 +24,49 @@ def create():
23
24
  prompt="Enter secret value",
24
25
  hide_input=True,
25
26
  cls=MutuallyExclusiveOption,
26
- mutually_exclusive=["from_file"],
27
+ mutually_exclusive=["from_file", "from_docker_config", "registry"],
27
28
  )
28
29
  @click.option(
29
30
  "--from-file",
30
31
  type=click.Path(exists=True),
31
32
  help="Path to the file with the binary secret.",
32
33
  cls=MutuallyExclusiveOption,
33
- mutually_exclusive=["value"],
34
+ mutually_exclusive=["value", "from_docker_config", "registry"],
34
35
  )
35
36
  @click.option(
36
37
  "--type", type=click.Choice(get_args(SecretTypes)), default="regular", help="Type of the secret.", show_default=True
37
38
  )
39
+ @click.option(
40
+ "--from-docker-config",
41
+ is_flag=True,
42
+ help="Create image pull secret from Docker config file (only for --type image_pull).",
43
+ cls=MutuallyExclusiveOption,
44
+ mutually_exclusive=["value", "from_file", "registry", "username", "password"],
45
+ )
46
+ @click.option(
47
+ "--docker-config-path",
48
+ type=click.Path(exists=True),
49
+ help="Path to Docker config file (defaults to ~/.docker/config.json or $DOCKER_CONFIG).",
50
+ )
51
+ @click.option(
52
+ "--registries",
53
+ help="Comma-separated list of registries to include (only with --from-docker-config).",
54
+ )
55
+ @click.option(
56
+ "--registry",
57
+ help="Registry hostname (e.g., ghcr.io, docker.io) for explicit credentials (only for --type image_pull).",
58
+ cls=MutuallyExclusiveOption,
59
+ mutually_exclusive=["value", "from_file", "from_docker_config"],
60
+ )
61
+ @click.option(
62
+ "--username",
63
+ help="Username for the registry (only with --registry).",
64
+ )
65
+ @click.option(
66
+ "--password",
67
+ help="Password for the registry (only with --registry). If not provided, will prompt.",
68
+ hide_input=True,
69
+ )
38
70
  @click.pass_obj
39
71
  def secret(
40
72
  cfg: common.CLIConfig,
@@ -42,6 +74,12 @@ def secret(
42
74
  value: str | bytes | None = None,
43
75
  from_file: str | None = None,
44
76
  type: SecretTypes = "regular",
77
+ from_docker_config: bool = False,
78
+ docker_config_path: str | None = None,
79
+ registries: str | None = None,
80
+ registry: str | None = None,
81
+ username: str | None = None,
82
+ password: str | None = None,
45
83
  project: str | None = None,
46
84
  domain: str | None = None,
47
85
  ):
@@ -72,16 +110,80 @@ def secret(
72
110
  Other secrets should be specified as `regular`.
73
111
  If no type is specified, `regular` is assumed.
74
112
 
113
+ For image pull secrets, you have several options:
114
+
115
+ 1. Interactive mode (prompts for registry, username, password):
75
116
  ```bash
76
117
  $ flyte create secret my_secret --type image_pull
77
118
  ```
119
+
120
+ 2. With explicit credentials:
121
+ ```bash
122
+ $ flyte create secret my_secret --type image_pull --registry ghcr.io --username myuser
123
+ ```
124
+
125
+ 3. Lastly, you can create a secret from your existing Docker installation (i.e., you've run `docker login` in
126
+ the past) and you just want to pull from those credentials. Since you may have logged in to multiple registries,
127
+ you can specify which registries to include. If no registries are specified, all registries are added.
128
+ ```bash
129
+ $ flyte create secret my_secret --type image_pull --from-docker-config --registries ghcr.io,docker.io
130
+ ```
78
131
  """
79
132
  from flyte.remote import Secret
80
133
 
134
+ # todo: remove this hack when secrets creation more easily distinguishes between org and project/domain level
135
+ # (and domain level) secrets
136
+ project = "" if project is None else project
137
+ domain = "" if domain is None else domain
81
138
  cfg.init(project, domain)
82
- if from_file:
139
+
140
+ # Handle image pull secret creation
141
+ if type == "image_pull":
142
+ if project != "" or domain != "":
143
+ raise click.ClickException("Project and domain must not be set when creating an image pull secret.")
144
+
145
+ if from_docker_config:
146
+ # Mode 3: From Docker config
147
+ from flyte._utils.docker_credentials import create_dockerconfigjson_from_config
148
+
149
+ registry_list = [r.strip() for r in registries.split(",")] if registries else None
150
+ try:
151
+ value = create_dockerconfigjson_from_config(
152
+ registries=registry_list,
153
+ docker_config_path=docker_config_path,
154
+ )
155
+ except Exception as e:
156
+ raise click.ClickException(f"Failed to create dockerconfigjson from Docker config: {e}") from e
157
+
158
+ elif registry:
159
+ # Mode 2: Explicit credentials
160
+ from flyte._utils.docker_credentials import create_dockerconfigjson_from_credentials
161
+
162
+ if not username:
163
+ username = click.prompt("Username")
164
+ if not password:
165
+ password = click.prompt("Password", hide_input=True)
166
+
167
+ value = create_dockerconfigjson_from_credentials(registry, username, password)
168
+
169
+ else:
170
+ # Mode 1: Interactive prompts
171
+ from flyte._utils.docker_credentials import create_dockerconfigjson_from_credentials
172
+
173
+ registry = click.prompt("Registry (e.g., ghcr.io, docker.io)")
174
+ username = click.prompt("Username")
175
+ password = click.prompt("Password", hide_input=True)
176
+
177
+ value = create_dockerconfigjson_from_credentials(registry, username, password)
178
+
179
+ elif from_file:
83
180
  with open(from_file, "rb") as f:
84
181
  value = f.read()
182
+
183
+ # Encode string values to bytes
184
+ if isinstance(value, str):
185
+ value = value.encode("utf-8")
186
+
85
187
  Secret.create(name=name, value=value, type=type)
86
188
 
87
189
 
@@ -98,7 +200,7 @@ def secret(
98
200
  "-o",
99
201
  "--output",
100
202
  type=click.Path(exists=False, writable=True),
101
- default=Path.cwd() / "config.yaml",
203
+ default=Path.cwd() / ".flyte" / "config.yaml",
102
204
  help="Path to the output directory where the configuration will be saved. Defaults to current directory.",
103
205
  show_default=True,
104
206
  )
@@ -147,6 +249,9 @@ def config(
147
249
 
148
250
  output_path = Path(output)
149
251
 
252
+ if not output_path.parent.exists():
253
+ output_path.parent.mkdir(parents=True)
254
+
150
255
  if output_path.exists() and not force:
151
256
  force = click.confirm(f"Overwrite [{output_path}]?", default=False)
152
257
  if not force:
@@ -191,3 +296,76 @@ def config(
191
296
  yaml.dump(d, f)
192
297
 
193
298
  click.echo(f"Config file written to {output_path}")
299
+
300
+
301
+ @create.command(cls=common.CommandBase)
302
+ @click.argument("task_name", type=str, required=True)
303
+ @click.argument("name", type=str, required=True)
304
+ @click.option(
305
+ "--schedule",
306
+ type=str,
307
+ required=True,
308
+ help="Cron schedule for the trigger. Defaults to every minute.",
309
+ show_default=True,
310
+ )
311
+ @click.option(
312
+ "--description",
313
+ type=str,
314
+ default="",
315
+ help="Description of the trigger.",
316
+ show_default=True,
317
+ )
318
+ @click.option(
319
+ "--auto-activate",
320
+ is_flag=True,
321
+ default=True,
322
+ help="Whether the trigger should not be automatically activated. Defaults to True.",
323
+ show_default=True,
324
+ )
325
+ @click.option(
326
+ "--trigger-time-var",
327
+ type=str,
328
+ default="trigger_time",
329
+ help="Variable name for the trigger time in the task inputs. Defaults to 'trigger_time'.",
330
+ show_default=True,
331
+ )
332
+ @click.pass_obj
333
+ def trigger(
334
+ cfg: common.CLIConfig,
335
+ task_name: str,
336
+ name: str,
337
+ schedule: str,
338
+ trigger_time_var: str = "trigger_time",
339
+ auto_activate: bool = True,
340
+ description: str = "",
341
+ project: str | None = None,
342
+ domain: str | None = None,
343
+ ):
344
+ """
345
+ Create a new trigger for a task. The task name and trigger name are required.
346
+
347
+ Example:
348
+
349
+ ```bash
350
+ $ flyte create trigger my_task my_trigger --schedule "0 0 * * *"
351
+ ```
352
+
353
+ This will create a trigger that runs every day at midnight.
354
+ """
355
+ from flyte.remote import Trigger
356
+
357
+ cfg.init(project, domain)
358
+ console = common.get_console()
359
+
360
+ trigger = flyte.Trigger(
361
+ name=name,
362
+ automation=flyte.Cron(schedule),
363
+ description=description,
364
+ auto_activate=auto_activate,
365
+ inputs={trigger_time_var: flyte.TriggerTime}, # Use the trigger time variable in inputs
366
+ env_vars=None,
367
+ interruptible=None,
368
+ )
369
+ with console.status("Creating trigger..."):
370
+ v = Trigger.create(trigger, task_name=task_name)
371
+ console.print(f"[bold green]Trigger {v.name} created successfully![/bold green]")
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
@@ -4,12 +4,11 @@ from pathlib import Path
4
4
  from types import ModuleType
5
5
  from typing import Any, Dict, List, cast, get_args
6
6
 
7
- import click
8
- from click import Context
7
+ import rich_click as click
9
8
 
10
9
  import flyte
10
+ from flyte._code_bundle._utils import CopyFiles
11
11
 
12
- from .._code_bundle._utils import CopyFiles
13
12
  from . import _common as common
14
13
  from ._common import CLIConfig
15
14
 
@@ -44,6 +43,16 @@ class DeployArguments:
44
43
  )
45
44
  },
46
45
  )
46
+ root_dir: str | None = field(
47
+ default=None,
48
+ metadata={
49
+ "click.option": click.Option(
50
+ ["--root-dir"],
51
+ type=str,
52
+ help="Override the root source directory, helpful when working with monorepos.",
53
+ )
54
+ },
55
+ )
47
56
  recursive: bool = field(
48
57
  default=False,
49
58
  metadata={
@@ -74,6 +83,19 @@ class DeployArguments:
74
83
  )
75
84
  },
76
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
+ )
77
99
 
78
100
  @classmethod
79
101
  def from_dict(cls, d: Dict[str, Any]) -> "DeployArguments":
@@ -87,20 +109,23 @@ class DeployArguments:
87
109
  return [common.get_option_from_metadata(f.metadata) for f in fields(cls) if f.metadata]
88
110
 
89
111
 
90
- class DeployEnvCommand(click.Command):
112
+ class DeployEnvCommand(click.RichCommand):
91
113
  def __init__(self, env_name: str, env: Any, deploy_args: DeployArguments, *args, **kwargs):
92
114
  self.env_name = env_name
93
115
  self.env = env
94
116
  self.deploy_args = deploy_args
95
117
  super().__init__(*args, **kwargs)
96
118
 
97
- def invoke(self, ctx: Context):
98
- from rich.console import Console
99
-
100
- console = Console()
119
+ def invoke(self, ctx: click.Context):
120
+ console = common.get_console()
101
121
  console.print(f"Deploying root - environment: {self.env_name}")
102
122
  obj: CLIConfig = ctx.obj
103
- obj.init(self.deploy_args.project, self.deploy_args.domain)
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
+ )
104
129
  with console.status("Deploying...", spinner="dots"):
105
130
  deployment = flyte.deploy(
106
131
  self.env,
@@ -110,7 +135,7 @@ class DeployEnvCommand(click.Command):
110
135
  )
111
136
 
112
137
  console.print(common.format("Environments", deployment[0].env_repr(), obj.output_format))
113
- 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))
114
139
 
115
140
 
116
141
  class DeployEnvRecursiveCommand(click.Command):
@@ -125,14 +150,12 @@ class DeployEnvRecursiveCommand(click.Command):
125
150
  self.deploy_args = deploy_args
126
151
  super().__init__(*args, **kwargs)
127
152
 
128
- def invoke(self, ctx: Context):
129
- from rich.console import Console
130
-
153
+ def invoke(self, ctx: click.Context):
131
154
  from flyte._environment import list_loaded_environments
132
155
  from flyte._utils import load_python_modules
133
156
 
134
- console = Console()
135
157
  obj: CLIConfig = ctx.obj
158
+ console = common.get_console()
136
159
 
137
160
  # Load all python modules
138
161
  loaded_modules, failed_paths = load_python_modules(self.path, self.deploy_args.recursive)
@@ -156,7 +179,11 @@ class DeployEnvRecursiveCommand(click.Command):
156
179
  f"Failed to load {len(failed_paths)} files. Use --ignore-load-errors to ignore these errors."
157
180
  )
158
181
  # Now start connection and deploy all environments
159
- 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
+ )
160
187
  with console.status("Deploying...", spinner="dots"):
161
188
  deployments = flyte.deploy(
162
189
  *all_envs,
@@ -168,7 +195,7 @@ class DeployEnvRecursiveCommand(click.Command):
168
195
  console.print(
169
196
  common.format("Environments", [env for d in deployments for env in d.env_repr()], obj.output_format)
170
197
  )
171
- 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))
172
199
 
173
200
 
174
201
  class EnvPerFileGroup(common.ObjectsPerFileGroup):
@@ -184,6 +211,26 @@ class EnvPerFileGroup(common.ObjectsPerFileGroup):
184
211
  def _filter_objects(self, module: ModuleType) -> Dict[str, Any]:
185
212
  return {k: v for k, v in module.__dict__.items() if isinstance(v, flyte.Environment)}
186
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
+
187
234
  def _get_command_for_obj(self, ctx: click.Context, obj_name: str, obj: Any) -> click.Command:
188
235
  obj = cast(flyte.Environment, obj)
189
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))