flyte 0.2.0b5__py3-none-any.whl → 0.2.0b8__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/__init__.py +2 -1
- flyte/_code_bundle/_utils.py +0 -16
- flyte/_code_bundle/bundle.py +1 -1
- flyte/_environment.py +42 -1
- flyte/_image.py +1 -2
- flyte/_initialize.py +52 -23
- flyte/_internal/controllers/__init__.py +2 -0
- flyte/_internal/controllers/_local_controller.py +3 -0
- flyte/_internal/controllers/remote/_controller.py +3 -0
- flyte/_internal/controllers/remote/_core.py +1 -1
- flyte/_internal/controllers/remote/_informer.py +3 -3
- flyte/_task.py +51 -12
- flyte/_task_environment.py +48 -66
- flyte/_utils/coro_management.py +0 -2
- flyte/_version.py +2 -2
- flyte/cli/_common.py +24 -15
- flyte/cli/_create.py +39 -8
- flyte/cli/_delete.py +2 -2
- flyte/cli/_deploy.py +4 -1
- flyte/cli/_gen.py +155 -0
- flyte/cli/_get.py +53 -7
- flyte/cli/_run.py +34 -8
- flyte/cli/main.py +69 -16
- flyte/config/__init__.py +2 -189
- flyte/config/_config.py +181 -172
- flyte/config/_internal.py +1 -1
- flyte/config/_reader.py +207 -0
- flyte/extras/_container.py +1 -1
- flyte/remote/_logs.py +9 -2
- flyte/remote/_run.py +26 -17
- flyte/syncify/__init__.py +51 -0
- flyte/syncify/_api.py +5 -6
- flyte-0.2.0b8.dist-info/METADATA +180 -0
- {flyte-0.2.0b5.dist-info → flyte-0.2.0b8.dist-info}/RECORD +37 -35
- flyte-0.2.0b5.dist-info/METADATA +0 -178
- {flyte-0.2.0b5.dist-info → flyte-0.2.0b8.dist-info}/WHEEL +0 -0
- {flyte-0.2.0b5.dist-info → flyte-0.2.0b8.dist-info}/entry_points.txt +0 -0
- {flyte-0.2.0b5.dist-info → flyte-0.2.0b8.dist-info}/top_level.txt +0 -0
flyte/config/_reader.py
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pathlib
|
|
3
|
+
import typing
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from functools import lru_cache
|
|
6
|
+
from os import getenv
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import yaml
|
|
10
|
+
|
|
11
|
+
from flyte._logging import logger
|
|
12
|
+
|
|
13
|
+
# This is the default config file name for flyte
|
|
14
|
+
FLYTECTL_CONFIG_ENV_VAR = "FLYTECTL_CONFIG"
|
|
15
|
+
UCTL_CONFIG_ENV_VAR = "UCTL_CONFIG"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class YamlConfigEntry(object):
|
|
20
|
+
"""
|
|
21
|
+
Creates a record for the config entry.
|
|
22
|
+
Args:
|
|
23
|
+
switch: dot-delimited string that should match flytectl args. Leaving it as dot-delimited instead of a list
|
|
24
|
+
of strings because it's easier to maintain alignment with flytectl.
|
|
25
|
+
config_value_type: Expected type of the value
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
switch: str
|
|
29
|
+
config_value_type: typing.Type = str
|
|
30
|
+
|
|
31
|
+
def get_env_name(self) -> str:
|
|
32
|
+
var_name = self.switch.upper().replace(".", "_")
|
|
33
|
+
return f"FLYTE_{var_name}"
|
|
34
|
+
|
|
35
|
+
def read_from_env(self, transform: typing.Optional[typing.Callable] = None) -> typing.Optional[typing.Any]:
|
|
36
|
+
"""
|
|
37
|
+
Reads the config entry from environment variable, the structure of the env var is current
|
|
38
|
+
``FLYTE_{SECTION}_{OPTION}`` all upper cased. We will change this in the future.
|
|
39
|
+
:return:
|
|
40
|
+
"""
|
|
41
|
+
env = self.get_env_name()
|
|
42
|
+
v = os.environ.get(env, None)
|
|
43
|
+
if v is None:
|
|
44
|
+
return None
|
|
45
|
+
return transform(v) if transform else v
|
|
46
|
+
|
|
47
|
+
def read_from_file(
|
|
48
|
+
self, cfg: "ConfigFile", transform: typing.Optional[typing.Callable] = None
|
|
49
|
+
) -> typing.Optional[typing.Any]:
|
|
50
|
+
if not cfg:
|
|
51
|
+
return None
|
|
52
|
+
try:
|
|
53
|
+
v = cfg.get(self)
|
|
54
|
+
if isinstance(v, bool) or bool(v is not None and v):
|
|
55
|
+
return transform(v) if transform else v
|
|
56
|
+
except Exception:
|
|
57
|
+
...
|
|
58
|
+
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass
|
|
63
|
+
class ConfigEntry(object):
|
|
64
|
+
"""
|
|
65
|
+
A top level Config entry holder, that holds multiple different representations of the config.
|
|
66
|
+
Legacy means the INI style config files. YAML support is for the flytectl config file, which is there by default
|
|
67
|
+
when flytectl starts a sandbox
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
yaml_entry: YamlConfigEntry
|
|
71
|
+
transform: typing.Optional[typing.Callable[[str], typing.Any]] = None
|
|
72
|
+
|
|
73
|
+
def read(self, cfg: typing.Optional["ConfigFile"] = None) -> typing.Optional[typing.Any]:
|
|
74
|
+
"""
|
|
75
|
+
Reads the config Entry from the various sources in the following order,
|
|
76
|
+
#. First try to read from the relevant environment variable,
|
|
77
|
+
#. If missing, then try to read from the legacy config file, if one was parsed.
|
|
78
|
+
#. If missing, then try to read from the yaml file.
|
|
79
|
+
|
|
80
|
+
The constructor for ConfigFile currently does not allow specification of both the ini and yaml style formats.
|
|
81
|
+
|
|
82
|
+
:param cfg:
|
|
83
|
+
:return:
|
|
84
|
+
"""
|
|
85
|
+
from_env = self.yaml_entry.read_from_env(self.transform)
|
|
86
|
+
if from_env is not None:
|
|
87
|
+
return from_env
|
|
88
|
+
if cfg and cfg.yaml_config and self.yaml_entry:
|
|
89
|
+
return self.yaml_entry.read_from_file(cfg, self.transform)
|
|
90
|
+
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class ConfigFile(object):
|
|
95
|
+
def __init__(self, location: str):
|
|
96
|
+
"""
|
|
97
|
+
Load the config from this location
|
|
98
|
+
"""
|
|
99
|
+
self._location = location
|
|
100
|
+
self._yaml_config = self._read_yaml_config(location)
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def path(self) -> pathlib.Path:
|
|
104
|
+
"""
|
|
105
|
+
Returns the path to the config file.
|
|
106
|
+
:return: Path to the config file
|
|
107
|
+
"""
|
|
108
|
+
return pathlib.Path(self._location)
|
|
109
|
+
|
|
110
|
+
@staticmethod
|
|
111
|
+
def _read_yaml_config(location: str) -> typing.Optional[typing.Dict[str, typing.Any]]:
|
|
112
|
+
with open(location, "r") as fh:
|
|
113
|
+
try:
|
|
114
|
+
yaml_contents = yaml.safe_load(fh)
|
|
115
|
+
return yaml_contents
|
|
116
|
+
except yaml.YAMLError as exc:
|
|
117
|
+
logger.warning(f"Error {exc} reading yaml config file at {location}, ignoring...")
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
def _get_from_yaml(self, c: YamlConfigEntry) -> typing.Any:
|
|
121
|
+
keys = c.switch.split(".") # flytectl switches are dot delimited
|
|
122
|
+
d = typing.cast(typing.Dict[str, typing.Any], self.yaml_config)
|
|
123
|
+
try:
|
|
124
|
+
for k in keys:
|
|
125
|
+
d = d[k]
|
|
126
|
+
return d
|
|
127
|
+
except KeyError:
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
def get(self, c: YamlConfigEntry) -> typing.Any:
|
|
131
|
+
return self._get_from_yaml(c)
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def yaml_config(self) -> typing.Dict[str, typing.Any] | None:
|
|
135
|
+
return self._yaml_config
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def resolve_config_path() -> pathlib.Path | None:
|
|
139
|
+
"""
|
|
140
|
+
Config is read from the following locations in order of precedence:
|
|
141
|
+
1. ./config.yaml if it exists
|
|
142
|
+
2. `UCTL_CONFIG` environment variable
|
|
143
|
+
3. `FLYTECTL_CONFIG` environment variable
|
|
144
|
+
4. ~/.union/config.yaml if it exists
|
|
145
|
+
5. ~/.flyte/config.yaml if it exists
|
|
146
|
+
"""
|
|
147
|
+
current_location_config = Path("config.yaml")
|
|
148
|
+
if current_location_config.exists():
|
|
149
|
+
return current_location_config
|
|
150
|
+
logger.debug("No ./config.yaml found, returning None")
|
|
151
|
+
|
|
152
|
+
uctl_path_from_env = getenv(UCTL_CONFIG_ENV_VAR, None)
|
|
153
|
+
if uctl_path_from_env:
|
|
154
|
+
return pathlib.Path(uctl_path_from_env)
|
|
155
|
+
logger.debug("No UCTL_CONFIG environment variable found, checking FLYTECTL_CONFIG")
|
|
156
|
+
|
|
157
|
+
flytectl_path_from_env = getenv(FLYTECTL_CONFIG_ENV_VAR, None)
|
|
158
|
+
if flytectl_path_from_env:
|
|
159
|
+
return pathlib.Path(flytectl_path_from_env)
|
|
160
|
+
logger.debug("No FLYTECTL_CONFIG environment variable found, checking default locations")
|
|
161
|
+
|
|
162
|
+
home_dir_union_config = Path(Path.home(), ".union", "config.yaml")
|
|
163
|
+
if home_dir_union_config.exists():
|
|
164
|
+
return home_dir_union_config
|
|
165
|
+
logger.debug("No ~/.union/config.yaml found, checking current directory")
|
|
166
|
+
|
|
167
|
+
home_dir_flytectl_config = Path(Path.home(), ".flyte", "config.yaml")
|
|
168
|
+
if home_dir_flytectl_config.exists():
|
|
169
|
+
return home_dir_flytectl_config
|
|
170
|
+
logger.debug("No ~/.flyte/config.yaml found, checking current directory")
|
|
171
|
+
|
|
172
|
+
return None
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@lru_cache
|
|
176
|
+
def get_config_file(c: typing.Union[str, ConfigFile, None]) -> ConfigFile | None:
|
|
177
|
+
"""
|
|
178
|
+
Checks if the given argument is a file or a configFile and returns a loaded configFile else returns None
|
|
179
|
+
"""
|
|
180
|
+
if "PYTEST_VERSION" in os.environ:
|
|
181
|
+
# Use default local config in the pytest environment
|
|
182
|
+
return None
|
|
183
|
+
if isinstance(c, str):
|
|
184
|
+
logger.debug(f"Using specified config file at {c}")
|
|
185
|
+
return ConfigFile(c)
|
|
186
|
+
elif isinstance(c, ConfigFile):
|
|
187
|
+
return c
|
|
188
|
+
config_path = resolve_config_path()
|
|
189
|
+
if config_path:
|
|
190
|
+
return ConfigFile(str(config_path))
|
|
191
|
+
return None
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def read_file_if_exists(filename: typing.Optional[str], encoding=None) -> typing.Optional[str]:
|
|
195
|
+
"""
|
|
196
|
+
Reads the contents of the file if passed a path. Otherwise, returns None.
|
|
197
|
+
|
|
198
|
+
:param filename: The file path to load
|
|
199
|
+
:param encoding: The encoding to use when reading the file.
|
|
200
|
+
:return: The contents of the file as a string or None.
|
|
201
|
+
"""
|
|
202
|
+
if not filename:
|
|
203
|
+
return None
|
|
204
|
+
|
|
205
|
+
file = pathlib.Path(filename)
|
|
206
|
+
logger.debug(f"Reading file contents from [{file}] with current directory [{os.getcwd()}].")
|
|
207
|
+
return file.read_text(encoding=encoding)
|
flyte/extras/_container.py
CHANGED
|
@@ -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/remote/_logs.py
CHANGED
|
@@ -49,6 +49,7 @@ class AsyncLogViewer:
|
|
|
49
49
|
name: str = "Logs",
|
|
50
50
|
show_ts: bool = False,
|
|
51
51
|
filter_system: bool = False,
|
|
52
|
+
panel: bool = False,
|
|
52
53
|
):
|
|
53
54
|
self.console = Console()
|
|
54
55
|
self.log_source = log_source
|
|
@@ -58,12 +59,15 @@ class AsyncLogViewer:
|
|
|
58
59
|
self.show_ts = show_ts
|
|
59
60
|
self.total_lines = 0
|
|
60
61
|
self.filter_flyte = filter_system
|
|
62
|
+
self.panel = panel
|
|
61
63
|
|
|
62
|
-
def _render(self) -> Panel:
|
|
64
|
+
def _render(self) -> Panel | Text:
|
|
63
65
|
log_text = Text()
|
|
64
66
|
for line in self.lines:
|
|
65
67
|
log_text.append(line)
|
|
66
|
-
|
|
68
|
+
if self.panel:
|
|
69
|
+
return Panel(log_text, title=self.name, border_style="yellow")
|
|
70
|
+
return log_text
|
|
67
71
|
|
|
68
72
|
async def run(self):
|
|
69
73
|
with Live(self._render(), refresh_per_second=20, console=self.console) as live:
|
|
@@ -139,6 +143,7 @@ class Logs:
|
|
|
139
143
|
show_ts: bool = False,
|
|
140
144
|
raw: bool = False,
|
|
141
145
|
filter_system: bool = False,
|
|
146
|
+
panel: bool = False,
|
|
142
147
|
):
|
|
143
148
|
"""
|
|
144
149
|
Create a log viewer for a given action ID and attempt.
|
|
@@ -149,6 +154,7 @@ class Logs:
|
|
|
149
154
|
:param show_ts: Whether to show timestamps in the logs.
|
|
150
155
|
:param raw: if True, return the raw log lines instead of a viewer.
|
|
151
156
|
:param filter_system: Whether to filter log lines based on system logs.
|
|
157
|
+
:param panel: Whether to use a panel for the log viewer. only applicable if raw is False.
|
|
152
158
|
"""
|
|
153
159
|
if attempt < 1:
|
|
154
160
|
raise ValueError("Attempt number must be greater than 0.")
|
|
@@ -165,5 +171,6 @@ class Logs:
|
|
|
165
171
|
show_ts=show_ts,
|
|
166
172
|
name=f"{action_id.run.name}:{action_id.name} ({attempt})",
|
|
167
173
|
filter_system=filter_system,
|
|
174
|
+
panel=panel,
|
|
168
175
|
)
|
|
169
176
|
await viewer.run()
|
flyte/remote/_run.py
CHANGED
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import asyncio
|
|
4
4
|
from dataclasses import dataclass, field
|
|
5
5
|
from datetime import datetime, timedelta, timezone
|
|
6
|
-
from typing import AsyncGenerator, AsyncIterator, Iterator, Literal, Tuple, Union, cast
|
|
6
|
+
from typing import AsyncGenerator, AsyncIterator, Iterator, List, Literal, Tuple, Union, cast
|
|
7
7
|
|
|
8
8
|
import grpc
|
|
9
9
|
import rich.repr
|
|
@@ -46,33 +46,42 @@ def _action_time_phase(action: run_definition_pb2.Action | run_definition_pb2.Ac
|
|
|
46
46
|
yield "error", action.error_info if action.HasField("error_info") else "NA"
|
|
47
47
|
|
|
48
48
|
|
|
49
|
-
def _action_rich_repr(action: run_definition_pb2.Action
|
|
49
|
+
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
|
|
53
|
+
yield "run", action.id.run.name
|
|
54
|
+
if action.metadata.HasField("task"):
|
|
55
|
+
yield "task", action.metadata.task.id.name
|
|
56
|
+
yield "type", "task"
|
|
54
57
|
yield "name", action.id.name
|
|
55
58
|
yield from _action_time_phase(action)
|
|
56
|
-
yield "
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
yield "parent", action.metadata.parent
|
|
59
|
+
yield "group", action.metadata.group
|
|
60
|
+
yield "parent", action.metadata.parent
|
|
61
|
+
yield "attempts", action.status.attempts
|
|
60
62
|
|
|
61
63
|
|
|
62
|
-
def
|
|
64
|
+
def _attempt_rich_repr(action: List[run_definition_pb2.ActionAttempt]) -> rich.repr.Result:
|
|
65
|
+
for attempt in action:
|
|
66
|
+
yield "attempt", attempt.attempt
|
|
67
|
+
yield "phase", run_definition_pb2.Phase.Name(attempt.phase)
|
|
68
|
+
yield "logs_available", attempt.logs_available
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _action_details_rich_repr(action: run_definition_pb2.ActionDetails) -> rich.repr.Result:
|
|
63
72
|
"""
|
|
64
73
|
Rich representation of the action details.
|
|
65
74
|
"""
|
|
66
75
|
yield "name", action.id.run.name
|
|
67
76
|
yield from _action_time_phase(action)
|
|
68
|
-
# yield "task", action.metadata.task.id.name
|
|
69
77
|
yield "task", action.resolved_task_spec.task_template.id.name
|
|
70
78
|
yield "task_type", action.resolved_task_spec.task_template.type
|
|
71
79
|
yield "task_version", action.resolved_task_spec.task_template.id.version
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
80
|
+
yield "attempts", action.attempts
|
|
81
|
+
yield "error_info", action.error_info if action.HasField("error_info") else "NA"
|
|
82
|
+
yield "phase", run_definition_pb2.Phase.Name(action.status.phase)
|
|
83
|
+
yield "group", action.metadata.group
|
|
84
|
+
yield "parent", action.metadata.parent
|
|
76
85
|
|
|
77
86
|
|
|
78
87
|
def _action_done_check(phase: run_definition_pb2.Phase) -> bool:
|
|
@@ -270,7 +279,7 @@ class Run:
|
|
|
270
279
|
"""
|
|
271
280
|
Rich representation of the Run object.
|
|
272
281
|
"""
|
|
273
|
-
yield from _action_rich_repr(self.pb2.action
|
|
282
|
+
yield from _action_rich_repr(self.pb2.action)
|
|
274
283
|
|
|
275
284
|
def __repr__(self) -> str:
|
|
276
285
|
"""
|
|
@@ -375,7 +384,7 @@ class RunDetails:
|
|
|
375
384
|
"""
|
|
376
385
|
Rich representation of the Run object.
|
|
377
386
|
"""
|
|
378
|
-
yield from _action_details_rich_repr(self.pb2.action
|
|
387
|
+
yield from _action_details_rich_repr(self.pb2.action)
|
|
379
388
|
|
|
380
389
|
def __repr__(self) -> str:
|
|
381
390
|
"""
|
|
@@ -657,7 +666,7 @@ class Action:
|
|
|
657
666
|
"""
|
|
658
667
|
Rich representation of the Action object.
|
|
659
668
|
"""
|
|
660
|
-
yield from _action_rich_repr(self.pb2
|
|
669
|
+
yield from _action_rich_repr(self.pb2)
|
|
661
670
|
|
|
662
671
|
def __repr__(self) -> str:
|
|
663
672
|
"""
|
|
@@ -911,7 +920,7 @@ class ActionDetails:
|
|
|
911
920
|
"""
|
|
912
921
|
Rich representation of the Action object.
|
|
913
922
|
"""
|
|
914
|
-
yield from _action_details_rich_repr(self.pb2
|
|
923
|
+
yield from _action_details_rich_repr(self.pb2)
|
|
915
924
|
|
|
916
925
|
def __repr__(self) -> str:
|
|
917
926
|
"""
|
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()
|
flyte/syncify/_api.py
CHANGED
|
@@ -54,14 +54,13 @@ class _BackgroundLoop:
|
|
|
54
54
|
functions or methods synchronously.
|
|
55
55
|
"""
|
|
56
56
|
|
|
57
|
-
def __init__(self):
|
|
58
|
-
self.loop =
|
|
59
|
-
self.thread = threading.Thread(name=
|
|
57
|
+
def __init__(self, name: str):
|
|
58
|
+
self.loop = asyncio.new_event_loop()
|
|
59
|
+
self.thread = threading.Thread(name=name, target=self._run, daemon=True)
|
|
60
60
|
self.thread.start()
|
|
61
61
|
atexit.register(self.stop)
|
|
62
62
|
|
|
63
63
|
def _run(self):
|
|
64
|
-
self.loop = asyncio.new_event_loop()
|
|
65
64
|
asyncio.set_event_loop(self.loop)
|
|
66
65
|
self.loop.run_forever()
|
|
67
66
|
|
|
@@ -242,8 +241,8 @@ class Syncify:
|
|
|
242
241
|
|
|
243
242
|
"""
|
|
244
243
|
|
|
245
|
-
def __init__(self):
|
|
246
|
-
self._bg_loop = _BackgroundLoop()
|
|
244
|
+
def __init__(self, name: str = "flyte_syncify"):
|
|
245
|
+
self._bg_loop = _BackgroundLoop(name=name)
|
|
247
246
|
|
|
248
247
|
@overload
|
|
249
248
|
def __call__(self, func: Callable[P, Awaitable[R_co]]) -> Any: ...
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: flyte
|
|
3
|
+
Version: 0.2.0b8
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Author-email: Ketan Umare <kumare3@users.noreply.github.com>
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: aiofiles>=24.1.0
|
|
9
|
+
Requires-Dist: click>=8.2.1
|
|
10
|
+
Requires-Dist: flyteidl==1.15.4b0
|
|
11
|
+
Requires-Dist: cloudpickle>=3.1.1
|
|
12
|
+
Requires-Dist: fsspec>=2025.3.0
|
|
13
|
+
Requires-Dist: grpcio>=1.71.0
|
|
14
|
+
Requires-Dist: obstore>=0.6.0
|
|
15
|
+
Requires-Dist: protobuf>=6.30.1
|
|
16
|
+
Requires-Dist: pydantic>=2.10.6
|
|
17
|
+
Requires-Dist: pyyaml>=6.0.2
|
|
18
|
+
Requires-Dist: rich-click>=1.8.9
|
|
19
|
+
Requires-Dist: httpx>=0.28.1
|
|
20
|
+
Requires-Dist: keyring>=25.6.0
|
|
21
|
+
Requires-Dist: msgpack>=1.1.0
|
|
22
|
+
Requires-Dist: toml>=0.10.2
|
|
23
|
+
Requires-Dist: async-lru>=2.0.5
|
|
24
|
+
Requires-Dist: mashumaro
|
|
25
|
+
Requires-Dist: dataclasses_json
|
|
26
|
+
|
|
27
|
+
# Flyte 2 SDK
|
|
28
|
+
|
|
29
|
+
The next-generation SDK for Flyte.
|
|
30
|
+
|
|
31
|
+
## Quickstart
|
|
32
|
+
1. Clone this repo and set `unionv2` to working directory.
|
|
33
|
+
2. Run `uv venv`, and `source .venv/bin/activate` to create a new virtualenv
|
|
34
|
+
3. Install the latest version of the SDK by running the following:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
uv pip install --no-cache --prerelease=allow --upgrade flyte
|
|
38
|
+
```
|
|
39
|
+
4. Create the config and point it to the Dogfood GCP cluster by running the following:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
flyte create config --endpoint dns:///dogfood-gcp.cloud-staging.union.ai --org dogfood-gcp --project andrew --domain development
|
|
43
|
+
```
|
|
44
|
+
This will create a `config.yaml` file in the current directory which will be referenced ahead of any other `config.yaml`s found in your system.
|
|
45
|
+
|
|
46
|
+
5. Now you can run stuff with the CLI:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
flyte run --follow examples/basics/devbox_one.py say_hello_nested
|
|
50
|
+
```
|
|
51
|
+
Note that the `--follow` command is optional. Use this to stream updates to the terminal!
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
## Hello World Example
|
|
56
|
+
|
|
57
|
+
1. Only async tasks are supported right now. Style recommended, `import flyte` and then `flyte.TaskEnvironment` etc
|
|
58
|
+
2. You have to create environment for even a single task
|
|
59
|
+
3. look at examples/... for various examples.
|
|
60
|
+
4. For a single script recommend using uv run scripts with metadata headers.
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
import flyte
|
|
65
|
+
|
|
66
|
+
env = flyte.TaskEnvironment(name="hello_world")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@env.task
|
|
70
|
+
async def say_hello(data: str) -> str:
|
|
71
|
+
return f"Hello {data}"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@env.task
|
|
75
|
+
async def say_hello_nested(data: str) -> str:
|
|
76
|
+
return await say_hello.override(resources=flyte.Resources(gpu="A100 80G:4")).execute(data)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
if __name__ == "__main__":
|
|
80
|
+
import asyncio
|
|
81
|
+
|
|
82
|
+
# to run pure python - the SDK is not invoked at all
|
|
83
|
+
asyncio.run(say_hello_nested("test"))
|
|
84
|
+
|
|
85
|
+
# To run locally, but run through type system etc
|
|
86
|
+
flyte.init()
|
|
87
|
+
flyte.run(say_hello_nested, "World")
|
|
88
|
+
|
|
89
|
+
# To run remote
|
|
90
|
+
flyte.init(endpoint="dns:///localhost:8090", insecure=True)
|
|
91
|
+
flyte.run(say_hello_nested, "World")
|
|
92
|
+
# It is possible to switch local and remote, but keeping init to have and endpoint, but , changing context during run
|
|
93
|
+
flyte.with_runcontext(mode="local").run(...) # this will run locally only
|
|
94
|
+
|
|
95
|
+
# To run remote with a config
|
|
96
|
+
flyte.init_auto_from_config("config.yaml")
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
# CLI
|
|
100
|
+
All commands can be run from any root directory.
|
|
101
|
+
For examples, it is not needed to have `__init__.py` in the directory. If you run from a directory, the
|
|
102
|
+
code will automatically package and upload all modules that are imported. You can change the behaviour by using --copy-style flag.
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
flyte run examples/basics/devbox_one.py say_hello --data "World"
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
To Follow the logs for the a0 action, you can use the `--follow` flag:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
flyte run --follow examples/basics/devbox_one.py say_hello --data "World"
|
|
112
|
+
```
|
|
113
|
+
Note follow has to be used with `run` command
|
|
114
|
+
|
|
115
|
+
Change copy style
|
|
116
|
+
```bash
|
|
117
|
+
flyte run --copy-style examples/basics/devbox_one.py say_hello_nested --data "World"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
# Building Images
|
|
121
|
+
```python
|
|
122
|
+
|
|
123
|
+
import flyte
|
|
124
|
+
|
|
125
|
+
env = flyte.TaskEnvironment(
|
|
126
|
+
name="hello_world",
|
|
127
|
+
image=flyte.Image.auto().with_apt_packages(...).with_pip_packages(...),
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Deploy
|
|
133
|
+
```bash
|
|
134
|
+
flyte deploy examples/basics/devbox_one.py say_hello_nested
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
CLI shortcuts
|
|
138
|
+
|
|
139
|
+
Get runs
|
|
140
|
+
```bash
|
|
141
|
+
flyte get run
|
|
142
|
+
```
|
|
143
|
+
Get specific run
|
|
144
|
+
```bash
|
|
145
|
+
flyte get run "run-name"
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Get run actions
|
|
149
|
+
```bash
|
|
150
|
+
flyte get actions "run-name"
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Get specific action
|
|
154
|
+
```bash
|
|
155
|
+
flyte get action "run-name" "action-name"
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Get action logs
|
|
159
|
+
```bash
|
|
160
|
+
flyte get logs "run-name" ["action-name"]
|
|
161
|
+
```
|
|
162
|
+
defaults to root action if no action name is provided
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
you can run any python script directly within the script module using __main__:
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
if __name__ == "__main__":
|
|
169
|
+
import flyte
|
|
170
|
+
flyte.init()
|
|
171
|
+
flyte.run(say_hello_nested, "World")
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
You can also run from cli
|
|
175
|
+
|
|
176
|
+
you can Also run a uv script with metadata headers:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
uv run scripts / hello_world.py
|
|
180
|
+
```
|