flyte 0.2.0b7__py3-none-any.whl → 0.2.0b9__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.

flyte/cli/_gen.py ADDED
@@ -0,0 +1,163 @@
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
+ else:
42
+ for name in command.list_commands(ctx):
43
+ subcommand = command.get_command(ctx, name)
44
+ if subcommand is None:
45
+ continue
46
+
47
+ full_name = f"{ctx.command_path} {name}".strip()
48
+ yield full_name, subcommand
49
+
50
+ # Recurse if subcommand is a MultiCommand (i.e., has its own subcommands)
51
+ if isinstance(subcommand, click.Group):
52
+ sub_ctx = click.Context(subcommand, info_name=name, parent=ctx)
53
+ yield from walk_commands(sub_ctx)
54
+
55
+
56
+ def markdown(cfg: common.CLIConfig):
57
+ """
58
+ Generate documentation in Markdown format
59
+ """
60
+ ctx = cfg.ctx
61
+
62
+ output = []
63
+ output_verb_groups: dict[str, list[str]] = {}
64
+ output_noun_groups: dict[str, list[str]] = {}
65
+
66
+ commands = [*[("flyte", ctx.command)], *walk_commands(ctx)]
67
+ for cmd_path, cmd in commands:
68
+ output.append("")
69
+
70
+ cmd_path_parts = cmd_path.split(" ")
71
+
72
+ if len(cmd_path_parts) > 1:
73
+ if cmd_path_parts[1] not in output_verb_groups:
74
+ output_verb_groups[cmd_path_parts[1]] = []
75
+ if len(cmd_path_parts) > 2:
76
+ output_verb_groups[cmd_path_parts[1]].append(cmd_path_parts[2])
77
+
78
+ if len(cmd_path_parts) == 3:
79
+ if cmd_path_parts[2] not in output_noun_groups:
80
+ output_noun_groups[cmd_path_parts[2]] = []
81
+ output_noun_groups[cmd_path_parts[2]].append(cmd_path_parts[1])
82
+
83
+ output.append(f"{'#' * (len(cmd_path_parts) + 1)} {cmd_path}")
84
+ if cmd.help:
85
+ output.append("")
86
+ output.append(f"{dedent(cmd.help)}")
87
+
88
+ if not cmd.params:
89
+ continue
90
+
91
+ params = cmd.get_params(click.Context(cmd))
92
+
93
+ # Collect all data first to calculate column widths
94
+ table_data = []
95
+ for param in params:
96
+ if isinstance(param, click.Option):
97
+ # Format each option with backticks before joining
98
+ all_opts = param.opts + param.secondary_opts
99
+ if len(all_opts) == 1:
100
+ opts = f"`{all_opts[0]}`"
101
+ else:
102
+ opts = "".join(
103
+ [
104
+ "{{< multiline >}}",
105
+ "\n".join([f"`{opt}`" for opt in all_opts]),
106
+ "{{< /multiline >}}",
107
+ ]
108
+ )
109
+ default_value = ""
110
+ if param.default is not None:
111
+ default_value = f"`{param.default}`"
112
+ default_value = default_value.replace(f"{getcwd()}/", "")
113
+ help_text = dedent(param.help) if param.help else ""
114
+ table_data.append([opts, f"`{param.type.name}`", default_value, help_text])
115
+
116
+ if not table_data:
117
+ continue
118
+
119
+ # Add table header with proper alignment
120
+ output.append("")
121
+ output.append("| Option | Type | Default | Description |")
122
+ output.append("|--------|------|---------|-------------|")
123
+
124
+ # Add table rows with proper alignment
125
+ for row in table_data:
126
+ output.append(f"| {row[0]} | {row[1]} | {row[2]} | {row[3]} |")
127
+
128
+ output_verb_index = []
129
+
130
+ if len(output_verb_groups) > 0:
131
+ output_verb_index.append("| Action | On |")
132
+ output_verb_index.append("| ------ | -- |")
133
+ for verb, nouns in output_verb_groups.items():
134
+ entries = [f"[`{noun}`](#flyte-{verb}-{noun})" for noun in nouns]
135
+ output_verb_index.append(f"| `{verb}` | {', '.join(entries)} |")
136
+
137
+ output_noun_index = []
138
+
139
+ if len(output_noun_groups) > 0:
140
+ output_noun_index.append("| Object | Action |")
141
+ output_noun_index.append("| ------ | -- |")
142
+ for obj, actions in output_noun_groups.items():
143
+ entries = [f"[`{action}`](#flyte-{action}-{obj})" for action in actions]
144
+ output_noun_index.append(f"| `{obj}` | {', '.join(entries)} |")
145
+
146
+ print()
147
+ print("{{< grid >}}")
148
+ print("{{< markdown >}}")
149
+ print("\n".join(output_noun_index))
150
+ print("{{< /markdown >}}")
151
+ print("{{< markdown >}}")
152
+ print("\n".join(output_verb_index))
153
+ print("{{< /markdown >}}")
154
+ print("{{< /grid >}}")
155
+ print()
156
+ print("\n".join(output))
157
+
158
+
159
+ def dedent(text: str) -> str:
160
+ """
161
+ Remove leading whitespace from a string.
162
+ """
163
+ return textwrap.dedent(text).strip("\n")
flyte/cli/_get.py CHANGED
@@ -11,7 +11,23 @@ from . import _common as common
11
11
  @click.group(name="get")
12
12
  def get():
13
13
  """
14
- Get the value of a task or environment.
14
+ Retrieve resources from a Flyte deployment.
15
+
16
+ You can get information about projects, runs, tasks, actions, secrets, logs and input/output values.
17
+
18
+ Each command supports optional parameters to filter or specify the resource you want to retrieve.
19
+
20
+ Using a `get` subcommand without any arguments will retrieve a list of available resources to get.
21
+ For example:
22
+
23
+ * `get project` (without specifiying aproject), will list all projects.
24
+ * `get project my_project` will return the details of the project named `my_project`.
25
+
26
+ In some cases, a partially specified command will act as a filter and return available further parameters.
27
+ For example:
28
+
29
+ * `get action my_run` will return all actions for the run named `my_run`.
30
+ * `get action my_run my_action` will return the details of the action named `my_action` for the run `my_run`.
15
31
  """
16
32
 
17
33
 
@@ -20,7 +36,7 @@ def get():
20
36
  @click.pass_obj
21
37
  def project(cfg: common.CLIConfig, name: str | None = None):
22
38
  """
23
- Get the current project.
39
+ Get a list of all projects, or details of a specific project by name.
24
40
  """
25
41
  from flyte.remote import Project
26
42
 
@@ -39,7 +55,11 @@ def project(cfg: common.CLIConfig, name: str | None = None):
39
55
  @click.pass_obj
40
56
  def run(cfg: common.CLIConfig, name: str | None = None, project: str | None = None, domain: str | None = None):
41
57
  """
42
- Get the current run.
58
+ Get a list of all runs, or details of a specific run by name.
59
+
60
+ The run details will include information about the run, its status, but only the root action will be shown.
61
+
62
+ If you want to see the actions for a run, use `get action <run_name>`.
43
63
  """
44
64
  from flyte.remote import Run, RunDetails
45
65
 
@@ -65,7 +85,9 @@ def task(
65
85
  domain: str | None = None,
66
86
  ):
67
87
  """
68
- Get the current task.
88
+ Retrieve a list of all tasks, or details of a specific task by name and version.
89
+
90
+ Currently, both `name` and `version` are required to get a specific task.
69
91
  """
70
92
  from flyte.remote import Task
71
93
 
@@ -140,8 +162,25 @@ def logs(
140
162
  filter_system: bool = False,
141
163
  ):
142
164
  """
143
- Stream logs for the provided run or action. If the run is provided, only the logs for the parent action will be
144
- streamed.
165
+ Stream logs for the provided run or action.
166
+ If only the run is provided, only the logs for the parent action will be streamed:
167
+
168
+ ```bash
169
+ $ flyte get logs my_run
170
+ ```
171
+
172
+ If you want to see the logs for a specific action, you can provide the action name as well:
173
+
174
+ ```bash
175
+ $ flyte get logs my_run my_action
176
+ ```
177
+
178
+ By default, logs will be shown in the raw format and will scroll the terminal.
179
+ If automatic scrolling and only tailing `--lines` number of lines is desired, use the `--pretty` flag:
180
+
181
+ ```bash
182
+ $ flyte get logs my_run my_action --pretty --lines 50
183
+ ```
145
184
  """
146
185
  import flyte.remote as remote
147
186
 
@@ -175,7 +214,7 @@ def secret(
175
214
  domain: str | None = None,
176
215
  ):
177
216
  """
178
- Get the current secret.
217
+ Get a list of all secrets, or details of a specific secret by name.
179
218
  """
180
219
  import flyte.remote as remote
181
220
 
@@ -205,6 +244,19 @@ def io(
205
244
  ):
206
245
  """
207
246
  Get the inputs and outputs of a run or action.
247
+ If only the run name is provided, it will show the inputs and outputs of the root action of that run.
248
+ If an action name is provided, it will show the inputs and outputs for that action.
249
+ If `--inputs-only` or `--outputs-only` is specified, it will only show the inputs or outputs respectively.
250
+
251
+ Examples:
252
+
253
+ ```bash
254
+ $ flyte get io my_run
255
+ ```
256
+
257
+ ```bash
258
+ $ flyte get io my_run my_action
259
+ ```
208
260
  """
209
261
  if inputs_only and outputs_only:
210
262
  raise click.BadParameter("Cannot use both --inputs-only and --outputs-only")
@@ -249,7 +301,9 @@ def io(
249
301
  @click.pass_obj
250
302
  def config(cfg: common.CLIConfig):
251
303
  """
252
- Shows the automatically detected configuration to connect with remote Flyte services.
304
+ Shows the automatically detected configuration to connect with the remote backend.
305
+
306
+ The configuration will include the endpoint, organization, and other settings that are used by the CLI.
253
307
  """
254
308
  console = Console()
255
309
  console.print(cfg)
flyte/cli/_run.py CHANGED
@@ -204,11 +204,31 @@ class TaskFiles(common.FileGroup):
204
204
  filename=Path(filename),
205
205
  run_args=run_args,
206
206
  name=filename,
207
- help=f"Run, functions decorated `env.task` {filename}",
207
+ help=f"Run, functions decorated with `env.task` in {filename}",
208
208
  )
209
209
 
210
210
 
211
211
  run = TaskFiles(
212
212
  name="run",
213
- help="Run a task from a python file.",
213
+ help="""
214
+ Run a task from a python file.
215
+
216
+ Example usage:
217
+ ```bash
218
+ flyte run --name examples/basics/hello.py my_task --arg1 value1 --arg2 value2
219
+ ```
220
+ Note: all arguments for the run command are provided right after the `run` command and before the file name.
221
+
222
+ You can also specify the project and domain using the `--project` and `--domain` options, respectively. These
223
+ options can be set in the config file or passed as command line arguments.
224
+
225
+ Note: The arguments for the task are provided after the task name and can be retrieved using `--help`
226
+ Example:
227
+ ```bash
228
+ flyte run --name examples/basics/hello.py my_task --help
229
+ ```
230
+
231
+ To run a task locally, use the `--local` flag. This will run the task in the local environment instead of the remote
232
+ Flyte environment.
233
+ """,
214
234
  )
flyte/cli/main.py CHANGED
@@ -6,9 +6,32 @@ from ._abort import abort
6
6
  from ._common import CLIConfig
7
7
  from ._create import create
8
8
  from ._deploy import deploy
9
+ from ._gen import gen
9
10
  from ._get import get
10
11
  from ._run import run
11
12
 
13
+ click.rich_click.COMMAND_GROUPS = {
14
+ "flyte": [
15
+ {
16
+ "name": "Running workflows",
17
+ "commands": ["run", "abort"],
18
+ },
19
+ {
20
+ "name": "Management",
21
+ "commands": ["create", "deploy", "get"],
22
+ },
23
+ {
24
+ "name": "Documentation generation",
25
+ "commands": ["gen"],
26
+ },
27
+ ]
28
+ }
29
+
30
+ help_config = click.RichHelpConfiguration(
31
+ use_markdown=True,
32
+ use_markdown_emoji=True,
33
+ )
34
+
12
35
 
13
36
  def _verbosity_to_loglevel(verbosity: int) -> int | None:
14
37
  """
@@ -35,13 +58,13 @@ def _verbosity_to_loglevel(verbosity: int) -> int | None:
35
58
  "--endpoint",
36
59
  type=str,
37
60
  required=False,
38
- help="The endpoint to connect to, this will override any config and simply used pkce to connect.",
61
+ help="The endpoint to connect to. This will override any configuration file and simply use `pkce` to connect.",
39
62
  )
40
63
  @click.option(
41
- "--insecure/--secure",
64
+ "--insecure",
42
65
  is_flag=True,
43
66
  required=False,
44
- help="Use insecure connection to the endpoint. If secure is specified, the CLI will use TLS",
67
+ help="Use an insecure connection to the endpoint. If not specified, the CLI will use TLS.",
45
68
  type=bool,
46
69
  default=None,
47
70
  show_default=True,
@@ -50,7 +73,7 @@ def _verbosity_to_loglevel(verbosity: int) -> int | None:
50
73
  "-v",
51
74
  "--verbose",
52
75
  required=False,
53
- help="Show verbose messages and exception traces",
76
+ help="Show verbose messages and exception traces. Repeating multiple times increases the verbosity (e.g., -vvv).",
54
77
  count=True,
55
78
  default=0,
56
79
  type=int,
@@ -59,7 +82,7 @@ def _verbosity_to_loglevel(verbosity: int) -> int | None:
59
82
  "--org",
60
83
  type=str,
61
84
  required=False,
62
- help="Organization to use",
85
+ help="The organization to which the command applies.",
63
86
  )
64
87
  @click.option(
65
88
  "-c",
@@ -67,9 +90,9 @@ def _verbosity_to_loglevel(verbosity: int) -> int | None:
67
90
  "config_file",
68
91
  required=False,
69
92
  type=click.Path(exists=True),
70
- help="Path to config file (YAML format) to use for the CLI. If not specified,"
71
- " the default config file will be used.",
93
+ help="Path to the configuration file to use. If not specified, the default configuration file is used."
72
94
  )
95
+ @click.rich_config(help_config=help_config)
73
96
  @click.pass_context
74
97
  def main(
75
98
  ctx: click.Context,
@@ -80,14 +103,39 @@ def main(
80
103
  config_file: str | None,
81
104
  ):
82
105
  """
106
+ The Flyte CLI is the the command line interface for working with the Flyte SDK and backend.
107
+
108
+ It follows a simple verb/noun structure,
109
+ where the top-level commands are verbs that describe the action to be taken,
110
+ and the subcommands are nouns that describe the object of the action.
111
+
112
+ The root command can be used to configure the CLI for persistent settings,
113
+ such as the endpoint, organization, and verbosity level.
114
+
115
+ Set endpoint and organization:
116
+
117
+ ```bash
118
+ $ flyte --endpoint <endpoint> --org <org> get project <project_name>
119
+ ```
120
+
121
+ Increase verbosity level (This is useful for debugging,
122
+ this will show more logs and exception traces):
123
+
124
+ ```bash
125
+ $ flyte -vvv get logs <run-name>
126
+ ```
127
+
128
+ Override the default config file:
129
+
130
+ ```bash
131
+ $ flyte --config /path/to/config.yaml run ...
132
+ ```
83
133
 
84
- ____ __ _ _ ____ ____ _ _ ____ __
85
- ( __)( ) ( \\/ )(_ _)( __) / )( \\(___ \\ / \
86
- ) _) / (_/\\ ) / )( ) _) \\ \\/ / / __/ _( 0 )
87
- (__) \\____/(__/ (__) (____) \\__/ (____)(_)\\__/
134
+ * [Documentation](https://www.union.ai/docs/flyte/user-guide/)
135
+ * [GitHub](https://github.com/flyteorg/flyte): Please leave a star if you like Flyte!
136
+ * [Slack](https://slack.flyte.org): Join the community and ask questions.
137
+ * [Issues](https://github.com/flyteorg/flyte/issues)
88
138
 
89
- The flyte cli follows a simple verb based structure, where the top-level commands are verbs that describe the action
90
- to be taken, and the subcommands are nouns that describe the object of the action.
91
139
  """
92
140
  import flyte.config as config
93
141
 
@@ -108,6 +156,7 @@ to be taken, and the subcommands are nouns that describe the object of the actio
108
156
  insecure=final_insecure,
109
157
  org_override=org or cfg.task.org,
110
158
  config=cfg,
159
+ ctx=ctx,
111
160
  )
112
161
  logger.debug(f"Final materialized Cli config: {ctx.obj}")
113
162
 
@@ -117,3 +166,4 @@ main.add_command(deploy)
117
166
  main.add_command(get) # type: ignore
118
167
  main.add_command(create) # type: ignore
119
168
  main.add_command(abort) # type: ignore
169
+ main.add_command(gen) # type: ignore
@@ -215,7 +215,7 @@ class ContainerTask(TaskTemplate):
215
215
  output_dict[k] = self._convert_output_val_to_correct_type(output_val, output_type)
216
216
  return output_dict
217
217
 
218
- def execute(self, **kwargs) -> Any:
218
+ async def execute(self, **kwargs) -> Any:
219
219
  try:
220
220
  import docker
221
221
  except ImportError:
flyte/models.py CHANGED
@@ -5,7 +5,9 @@ import os
5
5
  import pathlib
6
6
  import tempfile
7
7
  from dataclasses import dataclass, field, replace
8
- from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Tuple, Type
8
+ from typing import TYPE_CHECKING, Any, Callable, Dict, Literal, Optional, Tuple, Type
9
+
10
+ import rich.repr
9
11
 
10
12
  from flyte._docstring import Docstring
11
13
  from flyte._interface import extract_return_annotation
@@ -27,6 +29,7 @@ def generate_random_name() -> str:
27
29
  return str(uuid4()) # Placeholder for actual random name generation logic
28
30
 
29
31
 
32
+ @rich.repr.auto
30
33
  @dataclass(frozen=True, kw_only=True)
31
34
  class ActionID:
32
35
  """
@@ -68,6 +71,7 @@ class ActionID:
68
71
  return self.new_sub_action(new_name)
69
72
 
70
73
 
74
+ @rich.repr.auto
71
75
  @dataclass(frozen=True, kw_only=True)
72
76
  class RawDataPath:
73
77
  """
@@ -133,11 +137,13 @@ class RawDataPath:
133
137
  return remote_path
134
138
 
135
139
 
140
+ @rich.repr.auto
136
141
  @dataclass(frozen=True)
137
142
  class GroupData:
138
143
  name: str
139
144
 
140
145
 
146
+ @rich.repr.auto
141
147
  @dataclass(frozen=True, kw_only=True)
142
148
  class TaskContext:
143
149
  """
@@ -160,6 +166,7 @@ class TaskContext:
160
166
  code_bundle: CodeBundle | None = None
161
167
  compiled_image_cache: ImageCache | None = None
162
168
  data: Dict[str, Any] = field(default_factory=dict)
169
+ mode: Literal["local", "remote", "hybrid"] = "remote"
163
170
 
164
171
  def replace(self, **kwargs) -> TaskContext:
165
172
  if "data" in kwargs:
@@ -177,6 +184,7 @@ class TaskContext:
177
184
  return self.data.get(key)
178
185
 
179
186
 
187
+ @rich.repr.auto
180
188
  @dataclass(frozen=True, kw_only=True)
181
189
  class CodeBundle:
182
190
  """
@@ -211,6 +219,7 @@ class CodeBundle:
211
219
  return replace(self, downloaded_path=path)
212
220
 
213
221
 
222
+ @rich.repr.auto
214
223
  @dataclass(frozen=True)
215
224
  class Checkpoints:
216
225
  """
flyte/remote/_run.py CHANGED
@@ -50,6 +50,7 @@ def _action_rich_repr(action: run_definition_pb2.Action) -> rich.repr.Result:
50
50
  """
51
51
  Rich representation of the action.
52
52
  """
53
+ yield "run", action.id.run.name
53
54
  if action.metadata.HasField("task"):
54
55
  yield "task", action.metadata.task.id.name
55
56
  yield "type", "task"
flyte/syncify/__init__.py CHANGED
@@ -1,3 +1,54 @@
1
+ """
2
+ # Syncify Module
3
+ This module provides the `syncify` decorator and the `Syncify` class.
4
+ The decorator can be used to convert asynchronous functions or methods into synchronous ones.
5
+ This is useful for integrating async code into synchronous contexts.
6
+
7
+ Every asynchronous function or method wrapped with `syncify` can be called synchronously using the
8
+ parenthesis `()` operator, or asynchronously using the `.aio()` method.
9
+
10
+ Example::
11
+
12
+ ```python
13
+ from flyte.syncify import syncify
14
+
15
+ @syncify
16
+ async def async_function(x: str) -> str:
17
+ return f"Hello, Async World {x}!"
18
+
19
+
20
+ # now you can call it synchronously
21
+ result = async_function("Async World") # Note: no .aio() needed for sync calls
22
+ print(result)
23
+ # Output: Hello, Async World Async World!
24
+
25
+ # or call it asynchronously
26
+ async def main():
27
+ result = await async_function.aio("World") # Note the use of .aio() for async calls
28
+ print(result)
29
+ ```
30
+
31
+ ## Creating a Syncify Instance
32
+ ```python
33
+ from flyte.syncify. import Syncify
34
+
35
+ syncer = Syncify("my_syncer")
36
+
37
+ # Now you can use `syncer` to decorate your async functions or methods
38
+
39
+ ```
40
+
41
+ ## How does it work?
42
+ The Syncify class wraps asynchronous functions, classmethods, instance methods, and static methods to
43
+ provide a synchronous interface. The wrapped methods are always executed in the context of a background loop,
44
+ whether they are called synchronously or asynchronously. This allows for seamless integration of async code, as
45
+ certain async libraries capture the event loop. An example is grpc.aio, which captures the event loop.
46
+ In such a case, the Syncify class ensures that the async function is executed in the context of the background loop.
47
+
48
+ To use it correctly with grpc.aio, you should wrap every grpc.aio channel creation, and client invocation
49
+ with the same `Syncify` instance. This ensures that the async code runs in the correct event loop context.
50
+ """
51
+
1
52
  from flyte.syncify._api import Syncify
2
53
 
3
54
  syncify = Syncify()