flyte 0.2.0b1__py3-none-any.whl → 2.0.0b46__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (266) hide show
  1. flyte/__init__.py +83 -30
  2. flyte/_bin/connect.py +61 -0
  3. flyte/_bin/debug.py +38 -0
  4. flyte/_bin/runtime.py +87 -19
  5. flyte/_bin/serve.py +351 -0
  6. flyte/_build.py +3 -2
  7. flyte/_cache/cache.py +6 -5
  8. flyte/_cache/local_cache.py +216 -0
  9. flyte/_code_bundle/_ignore.py +31 -5
  10. flyte/_code_bundle/_packaging.py +42 -11
  11. flyte/_code_bundle/_utils.py +57 -34
  12. flyte/_code_bundle/bundle.py +130 -27
  13. flyte/_constants.py +1 -0
  14. flyte/_context.py +21 -5
  15. flyte/_custom_context.py +73 -0
  16. flyte/_debug/constants.py +37 -0
  17. flyte/_debug/utils.py +17 -0
  18. flyte/_debug/vscode.py +315 -0
  19. flyte/_deploy.py +396 -75
  20. flyte/_deployer.py +109 -0
  21. flyte/_environment.py +94 -11
  22. flyte/_excepthook.py +37 -0
  23. flyte/_group.py +2 -1
  24. flyte/_hash.py +1 -16
  25. flyte/_image.py +544 -231
  26. flyte/_initialize.py +456 -316
  27. flyte/_interface.py +40 -5
  28. flyte/_internal/controllers/__init__.py +22 -8
  29. flyte/_internal/controllers/_local_controller.py +159 -35
  30. flyte/_internal/controllers/_trace.py +18 -10
  31. flyte/_internal/controllers/remote/__init__.py +38 -9
  32. flyte/_internal/controllers/remote/_action.py +82 -12
  33. flyte/_internal/controllers/remote/_client.py +6 -2
  34. flyte/_internal/controllers/remote/_controller.py +290 -64
  35. flyte/_internal/controllers/remote/_core.py +155 -95
  36. flyte/_internal/controllers/remote/_informer.py +40 -20
  37. flyte/_internal/controllers/remote/_service_protocol.py +2 -2
  38. flyte/_internal/imagebuild/__init__.py +2 -10
  39. flyte/_internal/imagebuild/docker_builder.py +391 -84
  40. flyte/_internal/imagebuild/image_builder.py +111 -55
  41. flyte/_internal/imagebuild/remote_builder.py +409 -0
  42. flyte/_internal/imagebuild/utils.py +79 -0
  43. flyte/_internal/resolvers/_app_env_module.py +92 -0
  44. flyte/_internal/resolvers/_task_module.py +5 -38
  45. flyte/_internal/resolvers/app_env.py +26 -0
  46. flyte/_internal/resolvers/common.py +8 -1
  47. flyte/_internal/resolvers/default.py +2 -2
  48. flyte/_internal/runtime/convert.py +319 -36
  49. flyte/_internal/runtime/entrypoints.py +106 -18
  50. flyte/_internal/runtime/io.py +71 -23
  51. flyte/_internal/runtime/resources_serde.py +21 -7
  52. flyte/_internal/runtime/reuse.py +125 -0
  53. flyte/_internal/runtime/rusty.py +196 -0
  54. flyte/_internal/runtime/task_serde.py +239 -66
  55. flyte/_internal/runtime/taskrunner.py +48 -8
  56. flyte/_internal/runtime/trigger_serde.py +162 -0
  57. flyte/_internal/runtime/types_serde.py +7 -16
  58. flyte/_keyring/file.py +115 -0
  59. flyte/_link.py +30 -0
  60. flyte/_logging.py +241 -42
  61. flyte/_map.py +312 -0
  62. flyte/_metrics.py +59 -0
  63. flyte/_module.py +74 -0
  64. flyte/_pod.py +30 -0
  65. flyte/_resources.py +296 -33
  66. flyte/_retry.py +1 -7
  67. flyte/_reusable_environment.py +72 -7
  68. flyte/_run.py +462 -132
  69. flyte/_secret.py +47 -11
  70. flyte/_serve.py +333 -0
  71. flyte/_task.py +245 -56
  72. flyte/_task_environment.py +219 -97
  73. flyte/_task_plugins.py +47 -0
  74. flyte/_tools.py +8 -8
  75. flyte/_trace.py +15 -24
  76. flyte/_trigger.py +1027 -0
  77. flyte/_utils/__init__.py +12 -1
  78. flyte/_utils/asyn.py +3 -1
  79. flyte/_utils/async_cache.py +139 -0
  80. flyte/_utils/coro_management.py +5 -4
  81. flyte/_utils/description_parser.py +19 -0
  82. flyte/_utils/docker_credentials.py +173 -0
  83. flyte/_utils/helpers.py +45 -19
  84. flyte/_utils/module_loader.py +123 -0
  85. flyte/_utils/org_discovery.py +57 -0
  86. flyte/_utils/uv_script_parser.py +8 -1
  87. flyte/_version.py +16 -3
  88. flyte/app/__init__.py +27 -0
  89. flyte/app/_app_environment.py +362 -0
  90. flyte/app/_connector_environment.py +40 -0
  91. flyte/app/_deploy.py +130 -0
  92. flyte/app/_parameter.py +343 -0
  93. flyte/app/_runtime/__init__.py +3 -0
  94. flyte/app/_runtime/app_serde.py +383 -0
  95. flyte/app/_types.py +113 -0
  96. flyte/app/extras/__init__.py +9 -0
  97. flyte/app/extras/_auth_middleware.py +217 -0
  98. flyte/app/extras/_fastapi.py +93 -0
  99. flyte/app/extras/_model_loader/__init__.py +3 -0
  100. flyte/app/extras/_model_loader/config.py +7 -0
  101. flyte/app/extras/_model_loader/loader.py +288 -0
  102. flyte/cli/__init__.py +12 -0
  103. flyte/cli/_abort.py +28 -0
  104. flyte/cli/_build.py +114 -0
  105. flyte/cli/_common.py +493 -0
  106. flyte/cli/_create.py +371 -0
  107. flyte/cli/_delete.py +45 -0
  108. flyte/cli/_deploy.py +401 -0
  109. flyte/cli/_gen.py +316 -0
  110. flyte/cli/_get.py +446 -0
  111. flyte/cli/_option.py +33 -0
  112. flyte/{_cli → cli}/_params.py +57 -17
  113. flyte/cli/_plugins.py +209 -0
  114. flyte/cli/_prefetch.py +292 -0
  115. flyte/cli/_run.py +690 -0
  116. flyte/cli/_serve.py +338 -0
  117. flyte/cli/_update.py +86 -0
  118. flyte/cli/_user.py +20 -0
  119. flyte/cli/main.py +246 -0
  120. flyte/config/__init__.py +2 -167
  121. flyte/config/_config.py +215 -163
  122. flyte/config/_internal.py +10 -1
  123. flyte/config/_reader.py +225 -0
  124. flyte/connectors/__init__.py +11 -0
  125. flyte/connectors/_connector.py +330 -0
  126. flyte/connectors/_server.py +194 -0
  127. flyte/connectors/utils.py +159 -0
  128. flyte/errors.py +134 -2
  129. flyte/extend.py +24 -0
  130. flyte/extras/_container.py +69 -56
  131. flyte/git/__init__.py +3 -0
  132. flyte/git/_config.py +279 -0
  133. flyte/io/__init__.py +8 -1
  134. flyte/io/{structured_dataset → _dataframe}/__init__.py +32 -30
  135. flyte/io/{structured_dataset → _dataframe}/basic_dfs.py +75 -68
  136. flyte/io/{structured_dataset/structured_dataset.py → _dataframe/dataframe.py} +207 -242
  137. flyte/io/_dir.py +575 -113
  138. flyte/io/_file.py +587 -141
  139. flyte/io/_hashing_io.py +342 -0
  140. flyte/io/extend.py +7 -0
  141. flyte/models.py +635 -0
  142. flyte/prefetch/__init__.py +22 -0
  143. flyte/prefetch/_hf_model.py +563 -0
  144. flyte/remote/__init__.py +14 -3
  145. flyte/remote/_action.py +879 -0
  146. flyte/remote/_app.py +346 -0
  147. flyte/remote/_auth_metadata.py +42 -0
  148. flyte/remote/_client/_protocols.py +62 -4
  149. flyte/remote/_client/auth/_auth_utils.py +19 -0
  150. flyte/remote/_client/auth/_authenticators/base.py +8 -2
  151. flyte/remote/_client/auth/_authenticators/device_code.py +4 -5
  152. flyte/remote/_client/auth/_authenticators/factory.py +4 -0
  153. flyte/remote/_client/auth/_authenticators/passthrough.py +79 -0
  154. flyte/remote/_client/auth/_authenticators/pkce.py +17 -18
  155. flyte/remote/_client/auth/_channel.py +47 -18
  156. flyte/remote/_client/auth/_client_config.py +5 -3
  157. flyte/remote/_client/auth/_keyring.py +15 -2
  158. flyte/remote/_client/auth/_token_client.py +3 -3
  159. flyte/remote/_client/controlplane.py +206 -18
  160. flyte/remote/_common.py +66 -0
  161. flyte/remote/_data.py +107 -22
  162. flyte/remote/_logs.py +116 -33
  163. flyte/remote/_project.py +21 -19
  164. flyte/remote/_run.py +164 -631
  165. flyte/remote/_secret.py +72 -29
  166. flyte/remote/_task.py +387 -46
  167. flyte/remote/_trigger.py +368 -0
  168. flyte/remote/_user.py +43 -0
  169. flyte/report/_report.py +10 -6
  170. flyte/storage/__init__.py +13 -1
  171. flyte/storage/_config.py +237 -0
  172. flyte/storage/_parallel_reader.py +289 -0
  173. flyte/storage/_storage.py +268 -59
  174. flyte/syncify/__init__.py +56 -0
  175. flyte/syncify/_api.py +414 -0
  176. flyte/types/__init__.py +39 -0
  177. flyte/types/_interface.py +22 -7
  178. flyte/{io/pickle/transformer.py → types/_pickle.py} +37 -9
  179. flyte/types/_string_literals.py +8 -9
  180. flyte/types/_type_engine.py +226 -126
  181. flyte/types/_utils.py +1 -1
  182. flyte-2.0.0b46.data/scripts/debug.py +38 -0
  183. flyte-2.0.0b46.data/scripts/runtime.py +194 -0
  184. flyte-2.0.0b46.dist-info/METADATA +352 -0
  185. flyte-2.0.0b46.dist-info/RECORD +221 -0
  186. flyte-2.0.0b46.dist-info/entry_points.txt +8 -0
  187. flyte-2.0.0b46.dist-info/licenses/LICENSE +201 -0
  188. flyte/_api_commons.py +0 -3
  189. flyte/_cli/_common.py +0 -299
  190. flyte/_cli/_create.py +0 -42
  191. flyte/_cli/_delete.py +0 -23
  192. flyte/_cli/_deploy.py +0 -140
  193. flyte/_cli/_get.py +0 -235
  194. flyte/_cli/_run.py +0 -174
  195. flyte/_cli/main.py +0 -98
  196. flyte/_datastructures.py +0 -342
  197. flyte/_internal/controllers/pbhash.py +0 -39
  198. flyte/_protos/common/authorization_pb2.py +0 -66
  199. flyte/_protos/common/authorization_pb2.pyi +0 -108
  200. flyte/_protos/common/authorization_pb2_grpc.py +0 -4
  201. flyte/_protos/common/identifier_pb2.py +0 -71
  202. flyte/_protos/common/identifier_pb2.pyi +0 -82
  203. flyte/_protos/common/identifier_pb2_grpc.py +0 -4
  204. flyte/_protos/common/identity_pb2.py +0 -48
  205. flyte/_protos/common/identity_pb2.pyi +0 -72
  206. flyte/_protos/common/identity_pb2_grpc.py +0 -4
  207. flyte/_protos/common/list_pb2.py +0 -36
  208. flyte/_protos/common/list_pb2.pyi +0 -69
  209. flyte/_protos/common/list_pb2_grpc.py +0 -4
  210. flyte/_protos/common/policy_pb2.py +0 -37
  211. flyte/_protos/common/policy_pb2.pyi +0 -27
  212. flyte/_protos/common/policy_pb2_grpc.py +0 -4
  213. flyte/_protos/common/role_pb2.py +0 -37
  214. flyte/_protos/common/role_pb2.pyi +0 -53
  215. flyte/_protos/common/role_pb2_grpc.py +0 -4
  216. flyte/_protos/common/runtime_version_pb2.py +0 -28
  217. flyte/_protos/common/runtime_version_pb2.pyi +0 -24
  218. flyte/_protos/common/runtime_version_pb2_grpc.py +0 -4
  219. flyte/_protos/logs/dataplane/payload_pb2.py +0 -96
  220. flyte/_protos/logs/dataplane/payload_pb2.pyi +0 -168
  221. flyte/_protos/logs/dataplane/payload_pb2_grpc.py +0 -4
  222. flyte/_protos/secret/definition_pb2.py +0 -49
  223. flyte/_protos/secret/definition_pb2.pyi +0 -93
  224. flyte/_protos/secret/definition_pb2_grpc.py +0 -4
  225. flyte/_protos/secret/payload_pb2.py +0 -62
  226. flyte/_protos/secret/payload_pb2.pyi +0 -94
  227. flyte/_protos/secret/payload_pb2_grpc.py +0 -4
  228. flyte/_protos/secret/secret_pb2.py +0 -38
  229. flyte/_protos/secret/secret_pb2.pyi +0 -6
  230. flyte/_protos/secret/secret_pb2_grpc.py +0 -198
  231. flyte/_protos/secret/secret_pb2_grpc_grpc.py +0 -198
  232. flyte/_protos/validate/validate/validate_pb2.py +0 -76
  233. flyte/_protos/workflow/node_execution_service_pb2.py +0 -26
  234. flyte/_protos/workflow/node_execution_service_pb2.pyi +0 -4
  235. flyte/_protos/workflow/node_execution_service_pb2_grpc.py +0 -32
  236. flyte/_protos/workflow/queue_service_pb2.py +0 -106
  237. flyte/_protos/workflow/queue_service_pb2.pyi +0 -141
  238. flyte/_protos/workflow/queue_service_pb2_grpc.py +0 -172
  239. flyte/_protos/workflow/run_definition_pb2.py +0 -128
  240. flyte/_protos/workflow/run_definition_pb2.pyi +0 -310
  241. flyte/_protos/workflow/run_definition_pb2_grpc.py +0 -4
  242. flyte/_protos/workflow/run_logs_service_pb2.py +0 -41
  243. flyte/_protos/workflow/run_logs_service_pb2.pyi +0 -28
  244. flyte/_protos/workflow/run_logs_service_pb2_grpc.py +0 -69
  245. flyte/_protos/workflow/run_service_pb2.py +0 -133
  246. flyte/_protos/workflow/run_service_pb2.pyi +0 -175
  247. flyte/_protos/workflow/run_service_pb2_grpc.py +0 -412
  248. flyte/_protos/workflow/state_service_pb2.py +0 -58
  249. flyte/_protos/workflow/state_service_pb2.pyi +0 -71
  250. flyte/_protos/workflow/state_service_pb2_grpc.py +0 -138
  251. flyte/_protos/workflow/task_definition_pb2.py +0 -72
  252. flyte/_protos/workflow/task_definition_pb2.pyi +0 -65
  253. flyte/_protos/workflow/task_definition_pb2_grpc.py +0 -4
  254. flyte/_protos/workflow/task_service_pb2.py +0 -44
  255. flyte/_protos/workflow/task_service_pb2.pyi +0 -31
  256. flyte/_protos/workflow/task_service_pb2_grpc.py +0 -104
  257. flyte/io/_dataframe.py +0 -0
  258. flyte/io/pickle/__init__.py +0 -0
  259. flyte/remote/_console.py +0 -18
  260. flyte-0.2.0b1.dist-info/METADATA +0 -179
  261. flyte-0.2.0b1.dist-info/RECORD +0 -204
  262. flyte-0.2.0b1.dist-info/entry_points.txt +0 -3
  263. /flyte/{_cli → _debug}/__init__.py +0 -0
  264. /flyte/{_protos → _keyring}/__init__.py +0 -0
  265. {flyte-0.2.0b1.dist-info → flyte-2.0.0b46.dist-info}/WHEEL +0 -0
  266. {flyte-0.2.0b1.dist-info → flyte-2.0.0b46.dist-info}/top_level.txt +0 -0
flyte/cli/_gen.py ADDED
@@ -0,0 +1,316 @@
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, click.Context], None, None]:
31
+ """
32
+ Recursively walk a Click command tree, starting from the given context.
33
+
34
+ Yields:
35
+ (full_command_path, command_object, context)
36
+ """
37
+ command = ctx.command
38
+
39
+ if not isinstance(command, click.Group):
40
+ yield ctx.command_path, command, ctx
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 most subcommands are dynamically generated
44
+ # The exception is TaskFiles which has the special 'deployed-task' subcommand that should be documented
45
+ if type(command).__name__ == "TaskFiles":
46
+ # For TaskFiles, we only want the special non-file-based subcommands like 'deployed-task'
47
+ # Exclude all dynamic file-based commands
48
+ try:
49
+ names = command.list_commands(ctx)
50
+ for name in names:
51
+ if name == "deployed-task": # Only include the deployed-task command
52
+ try:
53
+ subcommand = command.get_command(ctx, name)
54
+ if subcommand is not None:
55
+ full_name = f"{ctx.command_path} {name}".strip()
56
+ sub_ctx = click.Context(subcommand, info_name=name, parent=ctx)
57
+ yield full_name, subcommand, sub_ctx
58
+ except click.ClickException:
59
+ continue
60
+ except click.ClickException:
61
+ pass
62
+
63
+ yield ctx.command_path, command, ctx
64
+ else:
65
+ try:
66
+ names = command.list_commands(ctx)
67
+ except click.ClickException:
68
+ # Some file-based commands might not have valid objects (e.g., test files)
69
+ # Skip these gracefully
70
+ return
71
+
72
+ for name in names:
73
+ try:
74
+ subcommand = command.get_command(ctx, name)
75
+ if subcommand is None:
76
+ continue
77
+
78
+ full_name = f"{ctx.command_path} {name}".strip()
79
+ sub_ctx = click.Context(subcommand, info_name=name, parent=ctx)
80
+ yield full_name, subcommand, sub_ctx
81
+
82
+ # Recurse if subcommand is a MultiCommand (i.e., has its own subcommands)
83
+ # But skip RemoteTaskGroup as it requires a live Flyte backend to enumerate subcommands
84
+ if isinstance(subcommand, click.Group) and type(subcommand).__name__ != "RemoteTaskGroup":
85
+ yield from walk_commands(sub_ctx)
86
+ except click.ClickException:
87
+ # Skip files/commands that can't be loaded
88
+ continue
89
+
90
+
91
+ def get_plugin_info(cmd: click.Command) -> tuple[bool, str | None]:
92
+ """
93
+ Determine if a command is from a plugin and get the plugin module name.
94
+
95
+ Returns:
96
+ (is_plugin, plugin_module_name)
97
+ """
98
+ if not cmd or not cmd.callback:
99
+ return False, None
100
+
101
+ module = cmd.callback.__module__
102
+ if "flyte." not in module:
103
+ # External plugin
104
+ parts = module.split(".")
105
+ if len(parts) == 1:
106
+ return True, parts[0]
107
+ return True, f"{parts[0]}.{parts[1]}"
108
+ elif module.startswith("flyte.") and not module.startswith("flyte.cli"):
109
+ # Check if it's from a flyte plugin (not core CLI)
110
+ # Core CLI modules are: flyte.cli.*
111
+ # Plugin modules would be things like: flyte.databricks, flyte.snowflake, etc.
112
+ parts = module.split(".")
113
+ if len(parts) > 1 and parts[1] not in ["cli", "remote", "core", "internal", "app"]:
114
+ return True, f"flyte.{parts[1]}"
115
+
116
+ return False, None
117
+
118
+
119
+ def markdown(cfg: common.CLIConfig):
120
+ """
121
+ Generate documentation in Markdown format
122
+ """
123
+ ctx = cfg.ctx
124
+
125
+ output = []
126
+ # Store verbs with their nouns: {verb_name: [(noun_name, is_plugin, plugin_module), ...]}
127
+ output_verb_groups: dict[str, list[tuple[str, bool, str | None]]] = {}
128
+ # Store verb metadata: {verb_name: (is_plugin, plugin_module)}
129
+ verb_metadata: dict[str, tuple[bool, str | None]] = {}
130
+ # Store nouns with their verbs: {noun_name: [(verb_name, is_plugin, plugin_module), ...]}
131
+ output_noun_groups: dict[str, list[tuple[str, bool, str | None]]] = {}
132
+
133
+ processed = []
134
+ commands = [*[("flyte", ctx.command, ctx)], *walk_commands(ctx)]
135
+ for cmd_path, cmd, cmd_ctx in commands:
136
+ if cmd in processed:
137
+ # We already processed this command, skip it
138
+ continue
139
+ processed.append(cmd)
140
+ output.append("")
141
+
142
+ is_plugin, plugin_module = get_plugin_info(cmd)
143
+
144
+ cmd_path_parts = cmd_path.split(" ")
145
+
146
+ if len(cmd_path_parts) > 1:
147
+ verb = cmd_path_parts[1]
148
+
149
+ # Store verb metadata
150
+ if verb not in verb_metadata:
151
+ verb_metadata[verb] = (is_plugin, plugin_module)
152
+
153
+ # Initialize verb group if needed
154
+ if verb not in output_verb_groups:
155
+ output_verb_groups[verb] = []
156
+
157
+ if len(cmd_path_parts) > 2:
158
+ noun = cmd_path_parts[2]
159
+ # Add noun to verb's list
160
+ output_verb_groups[verb].append((noun, is_plugin, plugin_module))
161
+
162
+ if len(cmd_path_parts) == 3:
163
+ noun = cmd_path_parts[2]
164
+ verb = cmd_path_parts[1]
165
+ if noun not in output_noun_groups:
166
+ output_noun_groups[noun] = []
167
+ output_noun_groups[noun].append((verb, is_plugin, plugin_module))
168
+
169
+ output.append(f"{'#' * (len(cmd_path_parts) + 1)} {cmd_path}")
170
+
171
+ # Add plugin notice if this is a plugin command
172
+ if is_plugin and plugin_module:
173
+ output.append("")
174
+ output.append(
175
+ f"> **Note:** This command is provided by the `{plugin_module}` plugin. "
176
+ f"See the plugin documentation for installation instructions."
177
+ )
178
+
179
+ # Add usage information
180
+ output.append("")
181
+ usage_line = f"{cmd_path}"
182
+
183
+ # Add [OPTIONS] if command has options
184
+ if any(isinstance(p, click.Option) for p in cmd.params):
185
+ usage_line += " [OPTIONS]"
186
+
187
+ # Add command-specific usage pattern
188
+ if isinstance(cmd, click.Group):
189
+ usage_line += " COMMAND [ARGS]..."
190
+ else:
191
+ # Add arguments if any
192
+ args = [p for p in cmd.params if isinstance(p, click.Argument)]
193
+ for arg in args:
194
+ if arg.name: # Check if name is not None
195
+ if arg.required:
196
+ usage_line += f" {arg.name.upper()}"
197
+ else:
198
+ usage_line += f" [{arg.name.upper()}]"
199
+
200
+ output.append(f"**`{usage_line}`**")
201
+
202
+ if cmd.help:
203
+ output.append("")
204
+ output.append(f"{dedent(cmd.help)}")
205
+
206
+ if not cmd.params:
207
+ continue
208
+
209
+ params = cmd.get_params(cmd_ctx)
210
+
211
+ # Collect all data first to calculate column widths
212
+ table_data = []
213
+ for param in params:
214
+ if isinstance(param, click.Option):
215
+ # Format each option with backticks before joining
216
+ all_opts = param.opts + param.secondary_opts
217
+ if len(all_opts) == 1:
218
+ opts = f"`{all_opts[0]}`"
219
+ else:
220
+ opts = "".join(
221
+ [
222
+ "{{< multiline >}}",
223
+ "\n".join([f"`{opt}`" for opt in all_opts]),
224
+ "{{< /multiline >}}",
225
+ ]
226
+ )
227
+ default_value = ""
228
+ if param.default is not None:
229
+ default_value = f"`{param.default}`"
230
+ default_value = default_value.replace(f"{getcwd()}/", "")
231
+ help_text = dedent(param.help) if param.help else ""
232
+ table_data.append([opts, f"`{param.type.name}`", default_value, help_text])
233
+
234
+ if not table_data:
235
+ continue
236
+
237
+ # Add table header with proper alignment
238
+ output.append("")
239
+ output.append("| Option | Type | Default | Description |")
240
+ output.append("|--------|------|---------|-------------|")
241
+
242
+ # Add table rows with proper alignment
243
+ for row in table_data:
244
+ output.append(f"| {row[0]} | {row[1]} | {row[2]} | {row[3]} |")
245
+
246
+ # Generate verb index table
247
+ output_verb_index = []
248
+ has_plugin_verbs = False
249
+
250
+ if len(output_verb_groups) > 0:
251
+ output_verb_index.append("| Action | On |")
252
+ output_verb_index.append("| ------ | -- |")
253
+ for verb, nouns in output_verb_groups.items():
254
+ verb_is_plugin, _ = verb_metadata.get(verb, (False, None))
255
+ verb_display = verb
256
+ if verb_is_plugin:
257
+ verb_display = f"{verb}⁺"
258
+ has_plugin_verbs = True
259
+
260
+ if len(nouns) == 0:
261
+ verb_link = f"[`{verb_display}`](#flyte-{verb})"
262
+ output_verb_index.append(f"| {verb_link} | - |")
263
+ else:
264
+ # Create links for nouns
265
+ noun_links = []
266
+ for noun, noun_is_plugin, _ in nouns:
267
+ noun_display = noun
268
+ if noun_is_plugin:
269
+ noun_display = f"{noun}⁺"
270
+ has_plugin_verbs = True
271
+ noun_links.append(f"[`{noun_display}`](#flyte-{verb}-{noun})")
272
+ output_verb_index.append(f"| `{verb_display}` | {', '.join(noun_links)} |")
273
+
274
+ if has_plugin_verbs:
275
+ output_verb_index.append("")
276
+ output_verb_index.append("**⁺** Plugin command - see command documentation for installation instructions")
277
+
278
+ # Generate noun index table
279
+ output_noun_index = []
280
+ has_plugin_nouns = False
281
+
282
+ if len(output_noun_groups) > 0:
283
+ output_noun_index.append("| Object | Action |")
284
+ output_noun_index.append("| ------ | -- |")
285
+ for obj, actions in output_noun_groups.items():
286
+ action_links = []
287
+ for action, action_is_plugin, _ in actions:
288
+ action_display = action
289
+ if action_is_plugin:
290
+ action_display = f"{action}⁺"
291
+ has_plugin_nouns = True
292
+ action_links.append(f"[`{action_display}`](#flyte-{action}-{obj})")
293
+ output_noun_index.append(f"| `{obj}` | {', '.join(action_links)} |")
294
+
295
+ if has_plugin_nouns:
296
+ output_noun_index.append("")
297
+ output_noun_index.append("**⁺** Plugin command - see command documentation for installation instructions")
298
+
299
+ print()
300
+ print("{{< grid >}}")
301
+ print("{{< markdown >}}")
302
+ print("\n".join(output_noun_index))
303
+ print("{{< /markdown >}}")
304
+ print("{{< markdown >}}")
305
+ print("\n".join(output_verb_index))
306
+ print("{{< /markdown >}}")
307
+ print("{{< /grid >}}")
308
+ print()
309
+ print("\n".join(output))
310
+
311
+
312
+ def dedent(text: str) -> str:
313
+ """
314
+ Remove leading whitespace from a string.
315
+ """
316
+ return textwrap.dedent(text).strip("\n")