flyte 2.0.0b32__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.

Potentially problematic release.


This version of flyte might be problematic. Click here for more details.

Files changed (204) hide show
  1. flyte/__init__.py +108 -0
  2. flyte/_bin/__init__.py +0 -0
  3. flyte/_bin/debug.py +38 -0
  4. flyte/_bin/runtime.py +195 -0
  5. flyte/_bin/serve.py +178 -0
  6. flyte/_build.py +26 -0
  7. flyte/_cache/__init__.py +12 -0
  8. flyte/_cache/cache.py +147 -0
  9. flyte/_cache/defaults.py +9 -0
  10. flyte/_cache/local_cache.py +216 -0
  11. flyte/_cache/policy_function_body.py +42 -0
  12. flyte/_code_bundle/__init__.py +8 -0
  13. flyte/_code_bundle/_ignore.py +121 -0
  14. flyte/_code_bundle/_packaging.py +218 -0
  15. flyte/_code_bundle/_utils.py +347 -0
  16. flyte/_code_bundle/bundle.py +266 -0
  17. flyte/_constants.py +1 -0
  18. flyte/_context.py +155 -0
  19. flyte/_custom_context.py +73 -0
  20. flyte/_debug/__init__.py +0 -0
  21. flyte/_debug/constants.py +38 -0
  22. flyte/_debug/utils.py +17 -0
  23. flyte/_debug/vscode.py +307 -0
  24. flyte/_deploy.py +408 -0
  25. flyte/_deployer.py +109 -0
  26. flyte/_doc.py +29 -0
  27. flyte/_docstring.py +32 -0
  28. flyte/_environment.py +122 -0
  29. flyte/_excepthook.py +37 -0
  30. flyte/_group.py +32 -0
  31. flyte/_hash.py +8 -0
  32. flyte/_image.py +1055 -0
  33. flyte/_initialize.py +628 -0
  34. flyte/_interface.py +119 -0
  35. flyte/_internal/__init__.py +3 -0
  36. flyte/_internal/controllers/__init__.py +129 -0
  37. flyte/_internal/controllers/_local_controller.py +239 -0
  38. flyte/_internal/controllers/_trace.py +48 -0
  39. flyte/_internal/controllers/remote/__init__.py +58 -0
  40. flyte/_internal/controllers/remote/_action.py +211 -0
  41. flyte/_internal/controllers/remote/_client.py +47 -0
  42. flyte/_internal/controllers/remote/_controller.py +583 -0
  43. flyte/_internal/controllers/remote/_core.py +465 -0
  44. flyte/_internal/controllers/remote/_informer.py +381 -0
  45. flyte/_internal/controllers/remote/_service_protocol.py +50 -0
  46. flyte/_internal/imagebuild/__init__.py +3 -0
  47. flyte/_internal/imagebuild/docker_builder.py +706 -0
  48. flyte/_internal/imagebuild/image_builder.py +277 -0
  49. flyte/_internal/imagebuild/remote_builder.py +386 -0
  50. flyte/_internal/imagebuild/utils.py +78 -0
  51. flyte/_internal/resolvers/__init__.py +0 -0
  52. flyte/_internal/resolvers/_task_module.py +21 -0
  53. flyte/_internal/resolvers/common.py +31 -0
  54. flyte/_internal/resolvers/default.py +28 -0
  55. flyte/_internal/runtime/__init__.py +0 -0
  56. flyte/_internal/runtime/convert.py +486 -0
  57. flyte/_internal/runtime/entrypoints.py +204 -0
  58. flyte/_internal/runtime/io.py +188 -0
  59. flyte/_internal/runtime/resources_serde.py +152 -0
  60. flyte/_internal/runtime/reuse.py +125 -0
  61. flyte/_internal/runtime/rusty.py +193 -0
  62. flyte/_internal/runtime/task_serde.py +362 -0
  63. flyte/_internal/runtime/taskrunner.py +209 -0
  64. flyte/_internal/runtime/trigger_serde.py +160 -0
  65. flyte/_internal/runtime/types_serde.py +54 -0
  66. flyte/_keyring/__init__.py +0 -0
  67. flyte/_keyring/file.py +115 -0
  68. flyte/_logging.py +300 -0
  69. flyte/_map.py +312 -0
  70. flyte/_module.py +72 -0
  71. flyte/_pod.py +30 -0
  72. flyte/_resources.py +473 -0
  73. flyte/_retry.py +32 -0
  74. flyte/_reusable_environment.py +102 -0
  75. flyte/_run.py +724 -0
  76. flyte/_secret.py +96 -0
  77. flyte/_task.py +550 -0
  78. flyte/_task_environment.py +316 -0
  79. flyte/_task_plugins.py +47 -0
  80. flyte/_timeout.py +47 -0
  81. flyte/_tools.py +27 -0
  82. flyte/_trace.py +119 -0
  83. flyte/_trigger.py +1000 -0
  84. flyte/_utils/__init__.py +30 -0
  85. flyte/_utils/asyn.py +121 -0
  86. flyte/_utils/async_cache.py +139 -0
  87. flyte/_utils/coro_management.py +27 -0
  88. flyte/_utils/docker_credentials.py +173 -0
  89. flyte/_utils/file_handling.py +72 -0
  90. flyte/_utils/helpers.py +134 -0
  91. flyte/_utils/lazy_module.py +54 -0
  92. flyte/_utils/module_loader.py +104 -0
  93. flyte/_utils/org_discovery.py +57 -0
  94. flyte/_utils/uv_script_parser.py +49 -0
  95. flyte/_version.py +34 -0
  96. flyte/app/__init__.py +22 -0
  97. flyte/app/_app_environment.py +157 -0
  98. flyte/app/_deploy.py +125 -0
  99. flyte/app/_input.py +160 -0
  100. flyte/app/_runtime/__init__.py +3 -0
  101. flyte/app/_runtime/app_serde.py +347 -0
  102. flyte/app/_types.py +101 -0
  103. flyte/app/extras/__init__.py +3 -0
  104. flyte/app/extras/_fastapi.py +151 -0
  105. flyte/cli/__init__.py +12 -0
  106. flyte/cli/_abort.py +28 -0
  107. flyte/cli/_build.py +114 -0
  108. flyte/cli/_common.py +468 -0
  109. flyte/cli/_create.py +371 -0
  110. flyte/cli/_delete.py +45 -0
  111. flyte/cli/_deploy.py +293 -0
  112. flyte/cli/_gen.py +176 -0
  113. flyte/cli/_get.py +370 -0
  114. flyte/cli/_option.py +33 -0
  115. flyte/cli/_params.py +554 -0
  116. flyte/cli/_plugins.py +209 -0
  117. flyte/cli/_run.py +597 -0
  118. flyte/cli/_serve.py +64 -0
  119. flyte/cli/_update.py +37 -0
  120. flyte/cli/_user.py +17 -0
  121. flyte/cli/main.py +221 -0
  122. flyte/config/__init__.py +3 -0
  123. flyte/config/_config.py +248 -0
  124. flyte/config/_internal.py +73 -0
  125. flyte/config/_reader.py +225 -0
  126. flyte/connectors/__init__.py +11 -0
  127. flyte/connectors/_connector.py +270 -0
  128. flyte/connectors/_server.py +197 -0
  129. flyte/connectors/utils.py +135 -0
  130. flyte/errors.py +243 -0
  131. flyte/extend.py +19 -0
  132. flyte/extras/__init__.py +5 -0
  133. flyte/extras/_container.py +286 -0
  134. flyte/git/__init__.py +3 -0
  135. flyte/git/_config.py +21 -0
  136. flyte/io/__init__.py +29 -0
  137. flyte/io/_dataframe/__init__.py +131 -0
  138. flyte/io/_dataframe/basic_dfs.py +223 -0
  139. flyte/io/_dataframe/dataframe.py +1026 -0
  140. flyte/io/_dir.py +910 -0
  141. flyte/io/_file.py +914 -0
  142. flyte/io/_hashing_io.py +342 -0
  143. flyte/models.py +479 -0
  144. flyte/py.typed +0 -0
  145. flyte/remote/__init__.py +35 -0
  146. flyte/remote/_action.py +738 -0
  147. flyte/remote/_app.py +57 -0
  148. flyte/remote/_client/__init__.py +0 -0
  149. flyte/remote/_client/_protocols.py +189 -0
  150. flyte/remote/_client/auth/__init__.py +12 -0
  151. flyte/remote/_client/auth/_auth_utils.py +14 -0
  152. flyte/remote/_client/auth/_authenticators/__init__.py +0 -0
  153. flyte/remote/_client/auth/_authenticators/base.py +403 -0
  154. flyte/remote/_client/auth/_authenticators/client_credentials.py +73 -0
  155. flyte/remote/_client/auth/_authenticators/device_code.py +117 -0
  156. flyte/remote/_client/auth/_authenticators/external_command.py +79 -0
  157. flyte/remote/_client/auth/_authenticators/factory.py +200 -0
  158. flyte/remote/_client/auth/_authenticators/pkce.py +516 -0
  159. flyte/remote/_client/auth/_channel.py +213 -0
  160. flyte/remote/_client/auth/_client_config.py +85 -0
  161. flyte/remote/_client/auth/_default_html.py +32 -0
  162. flyte/remote/_client/auth/_grpc_utils/__init__.py +0 -0
  163. flyte/remote/_client/auth/_grpc_utils/auth_interceptor.py +288 -0
  164. flyte/remote/_client/auth/_grpc_utils/default_metadata_interceptor.py +151 -0
  165. flyte/remote/_client/auth/_keyring.py +152 -0
  166. flyte/remote/_client/auth/_token_client.py +260 -0
  167. flyte/remote/_client/auth/errors.py +16 -0
  168. flyte/remote/_client/controlplane.py +128 -0
  169. flyte/remote/_common.py +30 -0
  170. flyte/remote/_console.py +19 -0
  171. flyte/remote/_data.py +161 -0
  172. flyte/remote/_logs.py +185 -0
  173. flyte/remote/_project.py +88 -0
  174. flyte/remote/_run.py +386 -0
  175. flyte/remote/_secret.py +142 -0
  176. flyte/remote/_task.py +527 -0
  177. flyte/remote/_trigger.py +306 -0
  178. flyte/remote/_user.py +33 -0
  179. flyte/report/__init__.py +3 -0
  180. flyte/report/_report.py +182 -0
  181. flyte/report/_template.html +124 -0
  182. flyte/storage/__init__.py +36 -0
  183. flyte/storage/_config.py +237 -0
  184. flyte/storage/_parallel_reader.py +274 -0
  185. flyte/storage/_remote_fs.py +34 -0
  186. flyte/storage/_storage.py +456 -0
  187. flyte/storage/_utils.py +5 -0
  188. flyte/syncify/__init__.py +56 -0
  189. flyte/syncify/_api.py +375 -0
  190. flyte/types/__init__.py +52 -0
  191. flyte/types/_interface.py +40 -0
  192. flyte/types/_pickle.py +145 -0
  193. flyte/types/_renderer.py +162 -0
  194. flyte/types/_string_literals.py +119 -0
  195. flyte/types/_type_engine.py +2254 -0
  196. flyte/types/_utils.py +80 -0
  197. flyte-2.0.0b32.data/scripts/debug.py +38 -0
  198. flyte-2.0.0b32.data/scripts/runtime.py +195 -0
  199. flyte-2.0.0b32.dist-info/METADATA +351 -0
  200. flyte-2.0.0b32.dist-info/RECORD +204 -0
  201. flyte-2.0.0b32.dist-info/WHEEL +5 -0
  202. flyte-2.0.0b32.dist-info/entry_points.txt +7 -0
  203. flyte-2.0.0b32.dist-info/licenses/LICENSE +201 -0
  204. flyte-2.0.0b32.dist-info/top_level.txt +1 -0
flyte/cli/_gen.py ADDED
@@ -0,0 +1,176 @@
1
+ import textwrap
2
+ from os import getcwd
3
+ from typing import Generator, Tuple
4
+
5
+ import rich_click as click
6
+
7
+ import flyte.cli._common as common
8
+
9
+
10
+ @click.group(name="gen")
11
+ def gen():
12
+ """
13
+ Generate documentation.
14
+ """
15
+
16
+
17
+ @gen.command(cls=common.CommandBase)
18
+ @click.option("--type", "doc_type", type=str, required=True, help="Type of documentation (valid: markdown)")
19
+ @click.pass_obj
20
+ def docs(cfg: common.CLIConfig, doc_type: str, project: str | None = None, domain: str | None = None):
21
+ """
22
+ Generate documentation.
23
+ """
24
+ if doc_type == "markdown":
25
+ markdown(cfg)
26
+ else:
27
+ raise click.ClickException("Invalid documentation type: {}".format(doc_type))
28
+
29
+
30
+ def walk_commands(ctx: click.Context) -> Generator[Tuple[str, click.Command], None, None]:
31
+ """
32
+ Recursively walk a Click command tree, starting from the given context.
33
+
34
+ Yields:
35
+ (full_command_path, command_object)
36
+ """
37
+ command = ctx.command
38
+
39
+ if not isinstance(command, click.Group):
40
+ yield ctx.command_path, command
41
+ elif isinstance(command, common.FileGroup):
42
+ # If the command is a FileGroup, yield its file path and the command itself
43
+ # No need to recurse further into FileGroup as it doesn't have subcommands, they are dynamically generated
44
+ yield ctx.command_path, command
45
+ else:
46
+ for name in command.list_commands(ctx):
47
+ subcommand = command.get_command(ctx, name)
48
+ if subcommand is None:
49
+ continue
50
+
51
+ full_name = f"{ctx.command_path} {name}".strip()
52
+ yield full_name, subcommand
53
+
54
+ # Recurse if subcommand is a MultiCommand (i.e., has its own subcommands)
55
+ if isinstance(subcommand, click.Group):
56
+ sub_ctx = click.Context(subcommand, info_name=name, parent=ctx)
57
+ yield from walk_commands(sub_ctx)
58
+
59
+
60
+ def markdown(cfg: common.CLIConfig):
61
+ """
62
+ Generate documentation in Markdown format
63
+ """
64
+ ctx = cfg.ctx
65
+
66
+ output = []
67
+ output_verb_groups: dict[str, list[str]] = {}
68
+ output_noun_groups: dict[str, list[str]] = {}
69
+
70
+ processed = []
71
+ commands = [*[("flyte", ctx.command)], *walk_commands(ctx)]
72
+ for cmd_path, cmd in commands:
73
+ if cmd in processed:
74
+ # We already processed this command, skip it
75
+ continue
76
+ processed.append(cmd)
77
+ output.append("")
78
+
79
+ cmd_path_parts = cmd_path.split(" ")
80
+
81
+ if len(cmd_path_parts) > 1:
82
+ if cmd_path_parts[1] not in output_verb_groups:
83
+ output_verb_groups[cmd_path_parts[1]] = []
84
+ if len(cmd_path_parts) > 2:
85
+ output_verb_groups[cmd_path_parts[1]].append(cmd_path_parts[2])
86
+
87
+ if len(cmd_path_parts) == 3:
88
+ if cmd_path_parts[2] not in output_noun_groups:
89
+ output_noun_groups[cmd_path_parts[2]] = []
90
+ output_noun_groups[cmd_path_parts[2]].append(cmd_path_parts[1])
91
+
92
+ output.append(f"{'#' * (len(cmd_path_parts) + 1)} {cmd_path}")
93
+ if cmd.help:
94
+ output.append("")
95
+ output.append(f"{dedent(cmd.help)}")
96
+
97
+ if not cmd.params:
98
+ continue
99
+
100
+ params = cmd.get_params(click.Context(cmd))
101
+
102
+ # Collect all data first to calculate column widths
103
+ table_data = []
104
+ for param in params:
105
+ if isinstance(param, click.Option):
106
+ # Format each option with backticks before joining
107
+ all_opts = param.opts + param.secondary_opts
108
+ if len(all_opts) == 1:
109
+ opts = f"`{all_opts[0]}`"
110
+ else:
111
+ opts = "".join(
112
+ [
113
+ "{{< multiline >}}",
114
+ "\n".join([f"`{opt}`" for opt in all_opts]),
115
+ "{{< /multiline >}}",
116
+ ]
117
+ )
118
+ default_value = ""
119
+ if param.default is not None:
120
+ default_value = f"`{param.default}`"
121
+ default_value = default_value.replace(f"{getcwd()}/", "")
122
+ help_text = dedent(param.help) if param.help else ""
123
+ table_data.append([opts, f"`{param.type.name}`", default_value, help_text])
124
+
125
+ if not table_data:
126
+ continue
127
+
128
+ # Add table header with proper alignment
129
+ output.append("")
130
+ output.append("| Option | Type | Default | Description |")
131
+ output.append("|--------|------|---------|-------------|")
132
+
133
+ # Add table rows with proper alignment
134
+ for row in table_data:
135
+ output.append(f"| {row[0]} | {row[1]} | {row[2]} | {row[3]} |")
136
+
137
+ output_verb_index = []
138
+
139
+ if len(output_verb_groups) > 0:
140
+ output_verb_index.append("| Action | On |")
141
+ output_verb_index.append("| ------ | -- |")
142
+ for verb, nouns in output_verb_groups.items():
143
+ entries = [f"[`{noun}`](#flyte-{verb}-{noun})" for noun in nouns]
144
+ if len(entries) == 0:
145
+ verb_link = f"[`{verb}`](#flyte-{verb})"
146
+ output_verb_index.append(f"| {verb_link} | - |")
147
+ else:
148
+ output_verb_index.append(f"| `{verb}` | {', '.join(entries)} |")
149
+
150
+ output_noun_index = []
151
+
152
+ if len(output_noun_groups) > 0:
153
+ output_noun_index.append("| Object | Action |")
154
+ output_noun_index.append("| ------ | -- |")
155
+ for obj, actions in output_noun_groups.items():
156
+ entries = [f"[`{action}`](#flyte-{action}-{obj})" for action in actions]
157
+ output_noun_index.append(f"| `{obj}` | {', '.join(entries)} |")
158
+
159
+ print()
160
+ print("{{< grid >}}")
161
+ print("{{< markdown >}}")
162
+ print("\n".join(output_noun_index))
163
+ print("{{< /markdown >}}")
164
+ print("{{< markdown >}}")
165
+ print("\n".join(output_verb_index))
166
+ print("{{< /markdown >}}")
167
+ print("{{< /grid >}}")
168
+ print()
169
+ print("\n".join(output))
170
+
171
+
172
+ def dedent(text: str) -> str:
173
+ """
174
+ Remove leading whitespace from a string.
175
+ """
176
+ return textwrap.dedent(text).strip("\n")
flyte/cli/_get.py ADDED
@@ -0,0 +1,370 @@
1
+ import asyncio
2
+ from typing import Tuple, Union, get_args
3
+
4
+ import rich_click as click
5
+ from rich.pretty import pretty_repr
6
+
7
+ import flyte.remote as remote
8
+
9
+ from . import _common as common
10
+
11
+
12
+ @click.group(name="get")
13
+ def get():
14
+ """
15
+ Retrieve resources from a Flyte deployment.
16
+
17
+ You can get information about projects, runs, tasks, actions, secrets, logs and input/output values.
18
+
19
+ Each command supports optional parameters to filter or specify the resource you want to retrieve.
20
+
21
+ Using a `get` subcommand without any arguments will retrieve a list of available resources to get.
22
+ For example:
23
+
24
+ * `get project` (without specifying a project), will list all projects.
25
+ * `get project my_project` will return the details of the project named `my_project`.
26
+
27
+ In some cases, a partially specified command will act as a filter and return available further parameters.
28
+ For example:
29
+
30
+ * `get action my_run` will return all actions for the run named `my_run`.
31
+ * `get action my_run my_action` will return the details of the action named `my_action` for the run `my_run`.
32
+ """
33
+
34
+
35
+ @get.command()
36
+ @click.argument("name", type=str, required=False)
37
+ @click.pass_obj
38
+ def project(cfg: common.CLIConfig, name: str | None = None):
39
+ """
40
+ Get a list of all projects, or details of a specific project by name.
41
+ """
42
+ cfg.init()
43
+
44
+ console = common.get_console()
45
+ if name:
46
+ console.print(pretty_repr(remote.Project.get(name)))
47
+ else:
48
+ console.print(common.format("Projects", remote.Project.listall(), cfg.output_format))
49
+
50
+
51
+ @get.command(cls=common.CommandBase)
52
+ @click.argument("name", type=str, required=False)
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).")
60
+ @click.pass_obj
61
+ def run(
62
+ cfg: common.CLIConfig,
63
+ name: str | None = None,
64
+ project: str | None = None,
65
+ domain: str | None = None,
66
+ limit: int = 100,
67
+ in_phase: str | Tuple[str, ...] | None = None,
68
+ only_mine: bool = False,
69
+ ):
70
+ """
71
+ Get a list of all runs, or details of a specific run by name.
72
+
73
+ The run details will include information about the run, its status, but only the root action will be shown.
74
+
75
+ If you want to see the actions for a run, use `get action <run_name>`.
76
+ """
77
+
78
+ cfg.init(project=project, domain=domain)
79
+
80
+ console = common.get_console()
81
+ if name:
82
+ details = remote.RunDetails.get(name=name)
83
+ console.print(common.format(f"Run {name}", [details], "json"))
84
+ else:
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
+ )
100
+
101
+
102
+ @get.command(cls=common.CommandBase)
103
+ @click.argument("name", type=str, required=False)
104
+ @click.argument("version", type=str, required=False)
105
+ @click.option("--limit", type=int, default=100, help="Limit the number of tasks to fetch.")
106
+ @click.pass_obj
107
+ def task(
108
+ cfg: common.CLIConfig,
109
+ name: str | None = None,
110
+ limit: int = 100,
111
+ version: str | None = None,
112
+ project: str | None = None,
113
+ domain: str | None = None,
114
+ ):
115
+ """
116
+ Retrieve a list of all tasks, or details of a specific task by name and version.
117
+
118
+ Currently, both `name` and `version` are required to get a specific task.
119
+ """
120
+ cfg.init(project=project, domain=domain)
121
+
122
+ console = common.get_console()
123
+ if name:
124
+ if version:
125
+ v = remote.Task.get(name=name, version=version)
126
+ if v is None:
127
+ raise click.BadParameter(f"Task {name} not found.")
128
+ t = v.fetch()
129
+ console.print(common.format(f"Task {name}", [t], "json"))
130
+ else:
131
+ console.print(
132
+ common.format("Tasks", remote.Task.listall(by_task_name=name, limit=limit), cfg.output_format)
133
+ )
134
+ else:
135
+ console.print(common.format("Tasks", remote.Task.listall(limit=limit), cfg.output_format))
136
+
137
+
138
+ @get.command(cls=common.CommandBase)
139
+ @click.argument("run_name", type=str, required=True)
140
+ @click.argument("action_name", type=str, required=False)
141
+ @click.pass_obj
142
+ def action(
143
+ cfg: common.CLIConfig,
144
+ run_name: str,
145
+ action_name: str | None = None,
146
+ project: str | None = None,
147
+ domain: str | None = None,
148
+ ):
149
+ """
150
+ Get all actions for a run or details for a specific action.
151
+ """
152
+ cfg.init(project=project, domain=domain)
153
+
154
+ console = common.get_console()
155
+ if action_name:
156
+ console.print(
157
+ common.format(
158
+ f"Action {run_name}.{action_name}", [remote.Action.get(run_name=run_name, name=action_name)], "json"
159
+ )
160
+ )
161
+ else:
162
+ # List all actions for the run
163
+ console.print(
164
+ common.format(f"Actions for {run_name}", remote.Action.listall(for_run_name=run_name), cfg.output_format)
165
+ )
166
+
167
+
168
+ @get.command(cls=common.CommandBase)
169
+ @click.argument("run_name", type=str, required=True)
170
+ @click.argument("action_name", type=str, required=False)
171
+ @click.option("--lines", "-l", type=int, default=30, help="Number of lines to show, only useful for --pretty")
172
+ @click.option("--show-ts", is_flag=True, help="Show timestamps")
173
+ @click.option(
174
+ "--pretty",
175
+ is_flag=True,
176
+ default=False,
177
+ help="Show logs in an auto-scrolling box, where number of lines is limited to `--lines`",
178
+ )
179
+ @click.option(
180
+ "--attempt", "-a", type=int, default=None, help="Attempt number to show logs for, defaults to the latest attempt."
181
+ )
182
+ @click.option("--filter-system", is_flag=True, default=False, help="Filter all system logs from the output.")
183
+ @click.pass_obj
184
+ def logs(
185
+ cfg: common.CLIConfig,
186
+ run_name: str,
187
+ action_name: str | None = None,
188
+ project: str | None = None,
189
+ domain: str | None = None,
190
+ lines: int = 30,
191
+ show_ts: bool = False,
192
+ pretty: bool = True,
193
+ attempt: int | None = None,
194
+ filter_system: bool = False,
195
+ ):
196
+ """
197
+ Stream logs for the provided run or action.
198
+ If only the run is provided, only the logs for the parent action will be streamed:
199
+
200
+ ```bash
201
+ $ flyte get logs my_run
202
+ ```
203
+
204
+ If you want to see the logs for a specific action, you can provide the action name as well:
205
+
206
+ ```bash
207
+ $ flyte get logs my_run my_action
208
+ ```
209
+
210
+ By default, logs will be shown in the raw format and will scroll the terminal.
211
+ If automatic scrolling and only tailing `--lines` number of lines is desired, use the `--pretty` flag:
212
+
213
+ ```bash
214
+ $ flyte get logs my_run my_action --pretty --lines 50
215
+ ```
216
+ """
217
+ cfg.init(project=project, domain=domain)
218
+
219
+ async def _run_log_view(_obj):
220
+ task = asyncio.create_task(
221
+ _obj.show_logs.aio(
222
+ max_lines=lines, show_ts=show_ts, raw=not pretty, attempt=attempt, filter_system=filter_system
223
+ )
224
+ )
225
+ try:
226
+ await task
227
+ except KeyboardInterrupt:
228
+ task.cancel()
229
+
230
+ if action_name:
231
+ obj = remote.Action.get(run_name=run_name, name=action_name)
232
+ else:
233
+ obj = remote.Run.get(run_name)
234
+ asyncio.run(_run_log_view(obj))
235
+
236
+
237
+ @get.command(cls=common.CommandBase)
238
+ @click.argument("name", type=str, required=False)
239
+ @click.pass_obj
240
+ def secret(
241
+ cfg: common.CLIConfig,
242
+ name: str | None = None,
243
+ project: str | None = None,
244
+ domain: str | None = None,
245
+ ):
246
+ """
247
+ Get a list of all secrets, or details of a specific secret by name.
248
+ """
249
+ if project is None:
250
+ project = ""
251
+ if domain is None:
252
+ domain = ""
253
+ cfg.init(project=project, domain=domain)
254
+
255
+ console = common.get_console()
256
+ if name:
257
+ console.print(common.format("Secret", [remote.Secret.get(name)], "json"))
258
+ else:
259
+ console.print(common.format("Secrets", remote.Secret.listall(), cfg.output_format))
260
+
261
+
262
+ @get.command(cls=common.CommandBase)
263
+ @click.argument("run_name", type=str, required=True)
264
+ @click.argument("action_name", type=str, required=False)
265
+ @click.option("--inputs-only", "-i", is_flag=True, help="Show only inputs")
266
+ @click.option("--outputs-only", "-o", is_flag=True, help="Show only outputs")
267
+ @click.pass_obj
268
+ def io(
269
+ cfg: common.CLIConfig,
270
+ run_name: str,
271
+ action_name: str | None = None,
272
+ project: str | None = None,
273
+ domain: str | None = None,
274
+ inputs_only: bool = False,
275
+ outputs_only: bool = False,
276
+ ):
277
+ """
278
+ Get the inputs and outputs of a run or action.
279
+ If only the run name is provided, it will show the inputs and outputs of the root action of that run.
280
+ If an action name is provided, it will show the inputs and outputs for that action.
281
+ If `--inputs-only` or `--outputs-only` is specified, it will only show the inputs or outputs respectively.
282
+
283
+ Examples:
284
+
285
+ ```bash
286
+ $ flyte get io my_run
287
+ ```
288
+
289
+ ```bash
290
+ $ flyte get io my_run my_action
291
+ ```
292
+ """
293
+ if inputs_only and outputs_only:
294
+ raise click.BadParameter("Cannot use both --inputs-only and --outputs-only")
295
+
296
+ cfg.init(project=project, domain=domain)
297
+ console = common.get_console()
298
+ if action_name:
299
+ obj = remote.ActionDetails.get(run_name=run_name, name=action_name)
300
+ else:
301
+ obj = remote.RunDetails.get(run_name)
302
+
303
+ async def _get_io(
304
+ details: Union[remote.RunDetails, remote.ActionDetails],
305
+ ) -> Tuple[remote.ActionInputs | None, remote.ActionOutputs | None | str]:
306
+ if inputs_only or outputs_only:
307
+ if inputs_only:
308
+ return await details.inputs(), None
309
+ elif outputs_only:
310
+ return None, await details.outputs()
311
+ inputs = await details.inputs()
312
+ outputs: remote.ActionOutputs | None | str = None
313
+ try:
314
+ outputs = await details.outputs()
315
+ except Exception:
316
+ # If the outputs are not available, we can still show the inputs
317
+ outputs = "[red]not yet available[/red]"
318
+ return inputs, outputs
319
+
320
+ inputs, outputs = asyncio.run(_get_io(obj))
321
+ # Show inputs and outputs side by side
322
+ console.print(
323
+ common.get_panel(
324
+ "Inputs & Outputs",
325
+ f"[green bold]Inputs[/green bold]\n{inputs}\n\n[blue bold]Outputs[/blue bold]\n{outputs}",
326
+ cfg.output_format,
327
+ )
328
+ )
329
+
330
+
331
+ @get.command(cls=click.RichCommand)
332
+ @click.pass_obj
333
+ def config(cfg: common.CLIConfig):
334
+ """
335
+ Shows the automatically detected configuration to connect with the remote backend.
336
+
337
+ The configuration will include the endpoint, organization, and other settings that are used by the CLI.
338
+ """
339
+ console = common.get_console()
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/_option.py ADDED
@@ -0,0 +1,33 @@
1
+ from click import Option, UsageError
2
+
3
+
4
+ class MutuallyExclusiveMixin:
5
+ def __init__(self, *args, **kwargs):
6
+ self.mutually_exclusive = set(kwargs.pop("mutually_exclusive", []))
7
+ self.error_format = kwargs.pop(
8
+ "error_msg", "Illegal usage: options '{name}' and '{invalid}' are mutually exclusive"
9
+ )
10
+ super().__init__(*args, **kwargs)
11
+
12
+ def handle_parse_result(self, ctx, opts, args):
13
+ self_present = self.name in opts and opts[self.name] is not None
14
+ others_intersect = self.mutually_exclusive.intersection(opts)
15
+ others_present = others_intersect and any(opts[value] is not None for value in others_intersect)
16
+
17
+ if others_present:
18
+ if self_present:
19
+ raise UsageError(self.error_format.format(name=self.name, invalid=", ".join(self.mutually_exclusive)))
20
+ else:
21
+ self.prompt = None
22
+
23
+ return super().handle_parse_result(ctx, opts, args)
24
+
25
+
26
+ # See https://stackoverflow.com/a/37491504/499285 and https://stackoverflow.com/a/44349292/499285
27
+ class MutuallyExclusiveOption(MutuallyExclusiveMixin, Option):
28
+ def __init__(self, *args, **kwargs):
29
+ mutually_exclusive = kwargs.get("mutually_exclusive", [])
30
+ help = kwargs.get("help", "")
31
+ if mutually_exclusive:
32
+ kwargs["help"] = help + f" Mutually exclusive with {', '.join(mutually_exclusive)}."
33
+ super().__init__(*args, **kwargs)