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.
- flyte/__init__.py +83 -30
- flyte/_bin/connect.py +61 -0
- flyte/_bin/debug.py +38 -0
- flyte/_bin/runtime.py +87 -19
- flyte/_bin/serve.py +351 -0
- flyte/_build.py +3 -2
- flyte/_cache/cache.py +6 -5
- flyte/_cache/local_cache.py +216 -0
- flyte/_code_bundle/_ignore.py +31 -5
- flyte/_code_bundle/_packaging.py +42 -11
- flyte/_code_bundle/_utils.py +57 -34
- flyte/_code_bundle/bundle.py +130 -27
- flyte/_constants.py +1 -0
- flyte/_context.py +21 -5
- flyte/_custom_context.py +73 -0
- flyte/_debug/constants.py +37 -0
- flyte/_debug/utils.py +17 -0
- flyte/_debug/vscode.py +315 -0
- flyte/_deploy.py +396 -75
- flyte/_deployer.py +109 -0
- flyte/_environment.py +94 -11
- flyte/_excepthook.py +37 -0
- flyte/_group.py +2 -1
- flyte/_hash.py +1 -16
- flyte/_image.py +544 -231
- flyte/_initialize.py +456 -316
- flyte/_interface.py +40 -5
- flyte/_internal/controllers/__init__.py +22 -8
- flyte/_internal/controllers/_local_controller.py +159 -35
- flyte/_internal/controllers/_trace.py +18 -10
- flyte/_internal/controllers/remote/__init__.py +38 -9
- flyte/_internal/controllers/remote/_action.py +82 -12
- flyte/_internal/controllers/remote/_client.py +6 -2
- flyte/_internal/controllers/remote/_controller.py +290 -64
- flyte/_internal/controllers/remote/_core.py +155 -95
- flyte/_internal/controllers/remote/_informer.py +40 -20
- flyte/_internal/controllers/remote/_service_protocol.py +2 -2
- flyte/_internal/imagebuild/__init__.py +2 -10
- flyte/_internal/imagebuild/docker_builder.py +391 -84
- flyte/_internal/imagebuild/image_builder.py +111 -55
- flyte/_internal/imagebuild/remote_builder.py +409 -0
- flyte/_internal/imagebuild/utils.py +79 -0
- flyte/_internal/resolvers/_app_env_module.py +92 -0
- flyte/_internal/resolvers/_task_module.py +5 -38
- flyte/_internal/resolvers/app_env.py +26 -0
- flyte/_internal/resolvers/common.py +8 -1
- flyte/_internal/resolvers/default.py +2 -2
- flyte/_internal/runtime/convert.py +319 -36
- flyte/_internal/runtime/entrypoints.py +106 -18
- flyte/_internal/runtime/io.py +71 -23
- flyte/_internal/runtime/resources_serde.py +21 -7
- flyte/_internal/runtime/reuse.py +125 -0
- flyte/_internal/runtime/rusty.py +196 -0
- flyte/_internal/runtime/task_serde.py +239 -66
- flyte/_internal/runtime/taskrunner.py +48 -8
- flyte/_internal/runtime/trigger_serde.py +162 -0
- flyte/_internal/runtime/types_serde.py +7 -16
- flyte/_keyring/file.py +115 -0
- flyte/_link.py +30 -0
- flyte/_logging.py +241 -42
- flyte/_map.py +312 -0
- flyte/_metrics.py +59 -0
- flyte/_module.py +74 -0
- flyte/_pod.py +30 -0
- flyte/_resources.py +296 -33
- flyte/_retry.py +1 -7
- flyte/_reusable_environment.py +72 -7
- flyte/_run.py +462 -132
- flyte/_secret.py +47 -11
- flyte/_serve.py +333 -0
- flyte/_task.py +245 -56
- flyte/_task_environment.py +219 -97
- flyte/_task_plugins.py +47 -0
- flyte/_tools.py +8 -8
- flyte/_trace.py +15 -24
- flyte/_trigger.py +1027 -0
- flyte/_utils/__init__.py +12 -1
- flyte/_utils/asyn.py +3 -1
- flyte/_utils/async_cache.py +139 -0
- flyte/_utils/coro_management.py +5 -4
- flyte/_utils/description_parser.py +19 -0
- flyte/_utils/docker_credentials.py +173 -0
- flyte/_utils/helpers.py +45 -19
- flyte/_utils/module_loader.py +123 -0
- flyte/_utils/org_discovery.py +57 -0
- flyte/_utils/uv_script_parser.py +8 -1
- flyte/_version.py +16 -3
- flyte/app/__init__.py +27 -0
- flyte/app/_app_environment.py +362 -0
- flyte/app/_connector_environment.py +40 -0
- flyte/app/_deploy.py +130 -0
- flyte/app/_parameter.py +343 -0
- flyte/app/_runtime/__init__.py +3 -0
- flyte/app/_runtime/app_serde.py +383 -0
- flyte/app/_types.py +113 -0
- flyte/app/extras/__init__.py +9 -0
- flyte/app/extras/_auth_middleware.py +217 -0
- flyte/app/extras/_fastapi.py +93 -0
- flyte/app/extras/_model_loader/__init__.py +3 -0
- flyte/app/extras/_model_loader/config.py +7 -0
- flyte/app/extras/_model_loader/loader.py +288 -0
- flyte/cli/__init__.py +12 -0
- flyte/cli/_abort.py +28 -0
- flyte/cli/_build.py +114 -0
- flyte/cli/_common.py +493 -0
- flyte/cli/_create.py +371 -0
- flyte/cli/_delete.py +45 -0
- flyte/cli/_deploy.py +401 -0
- flyte/cli/_gen.py +316 -0
- flyte/cli/_get.py +446 -0
- flyte/cli/_option.py +33 -0
- flyte/{_cli → cli}/_params.py +57 -17
- flyte/cli/_plugins.py +209 -0
- flyte/cli/_prefetch.py +292 -0
- flyte/cli/_run.py +690 -0
- flyte/cli/_serve.py +338 -0
- flyte/cli/_update.py +86 -0
- flyte/cli/_user.py +20 -0
- flyte/cli/main.py +246 -0
- flyte/config/__init__.py +2 -167
- flyte/config/_config.py +215 -163
- flyte/config/_internal.py +10 -1
- flyte/config/_reader.py +225 -0
- flyte/connectors/__init__.py +11 -0
- flyte/connectors/_connector.py +330 -0
- flyte/connectors/_server.py +194 -0
- flyte/connectors/utils.py +159 -0
- flyte/errors.py +134 -2
- flyte/extend.py +24 -0
- flyte/extras/_container.py +69 -56
- flyte/git/__init__.py +3 -0
- flyte/git/_config.py +279 -0
- flyte/io/__init__.py +8 -1
- flyte/io/{structured_dataset → _dataframe}/__init__.py +32 -30
- flyte/io/{structured_dataset → _dataframe}/basic_dfs.py +75 -68
- flyte/io/{structured_dataset/structured_dataset.py → _dataframe/dataframe.py} +207 -242
- flyte/io/_dir.py +575 -113
- flyte/io/_file.py +587 -141
- flyte/io/_hashing_io.py +342 -0
- flyte/io/extend.py +7 -0
- flyte/models.py +635 -0
- flyte/prefetch/__init__.py +22 -0
- flyte/prefetch/_hf_model.py +563 -0
- flyte/remote/__init__.py +14 -3
- flyte/remote/_action.py +879 -0
- flyte/remote/_app.py +346 -0
- flyte/remote/_auth_metadata.py +42 -0
- flyte/remote/_client/_protocols.py +62 -4
- flyte/remote/_client/auth/_auth_utils.py +19 -0
- flyte/remote/_client/auth/_authenticators/base.py +8 -2
- flyte/remote/_client/auth/_authenticators/device_code.py +4 -5
- flyte/remote/_client/auth/_authenticators/factory.py +4 -0
- flyte/remote/_client/auth/_authenticators/passthrough.py +79 -0
- flyte/remote/_client/auth/_authenticators/pkce.py +17 -18
- flyte/remote/_client/auth/_channel.py +47 -18
- flyte/remote/_client/auth/_client_config.py +5 -3
- flyte/remote/_client/auth/_keyring.py +15 -2
- flyte/remote/_client/auth/_token_client.py +3 -3
- flyte/remote/_client/controlplane.py +206 -18
- flyte/remote/_common.py +66 -0
- flyte/remote/_data.py +107 -22
- flyte/remote/_logs.py +116 -33
- flyte/remote/_project.py +21 -19
- flyte/remote/_run.py +164 -631
- flyte/remote/_secret.py +72 -29
- flyte/remote/_task.py +387 -46
- flyte/remote/_trigger.py +368 -0
- flyte/remote/_user.py +43 -0
- flyte/report/_report.py +10 -6
- flyte/storage/__init__.py +13 -1
- flyte/storage/_config.py +237 -0
- flyte/storage/_parallel_reader.py +289 -0
- flyte/storage/_storage.py +268 -59
- flyte/syncify/__init__.py +56 -0
- flyte/syncify/_api.py +414 -0
- flyte/types/__init__.py +39 -0
- flyte/types/_interface.py +22 -7
- flyte/{io/pickle/transformer.py → types/_pickle.py} +37 -9
- flyte/types/_string_literals.py +8 -9
- flyte/types/_type_engine.py +226 -126
- flyte/types/_utils.py +1 -1
- flyte-2.0.0b46.data/scripts/debug.py +38 -0
- flyte-2.0.0b46.data/scripts/runtime.py +194 -0
- flyte-2.0.0b46.dist-info/METADATA +352 -0
- flyte-2.0.0b46.dist-info/RECORD +221 -0
- flyte-2.0.0b46.dist-info/entry_points.txt +8 -0
- flyte-2.0.0b46.dist-info/licenses/LICENSE +201 -0
- flyte/_api_commons.py +0 -3
- flyte/_cli/_common.py +0 -299
- flyte/_cli/_create.py +0 -42
- flyte/_cli/_delete.py +0 -23
- flyte/_cli/_deploy.py +0 -140
- flyte/_cli/_get.py +0 -235
- flyte/_cli/_run.py +0 -174
- flyte/_cli/main.py +0 -98
- flyte/_datastructures.py +0 -342
- flyte/_internal/controllers/pbhash.py +0 -39
- flyte/_protos/common/authorization_pb2.py +0 -66
- flyte/_protos/common/authorization_pb2.pyi +0 -108
- flyte/_protos/common/authorization_pb2_grpc.py +0 -4
- flyte/_protos/common/identifier_pb2.py +0 -71
- flyte/_protos/common/identifier_pb2.pyi +0 -82
- flyte/_protos/common/identifier_pb2_grpc.py +0 -4
- flyte/_protos/common/identity_pb2.py +0 -48
- flyte/_protos/common/identity_pb2.pyi +0 -72
- flyte/_protos/common/identity_pb2_grpc.py +0 -4
- flyte/_protos/common/list_pb2.py +0 -36
- flyte/_protos/common/list_pb2.pyi +0 -69
- flyte/_protos/common/list_pb2_grpc.py +0 -4
- flyte/_protos/common/policy_pb2.py +0 -37
- flyte/_protos/common/policy_pb2.pyi +0 -27
- flyte/_protos/common/policy_pb2_grpc.py +0 -4
- flyte/_protos/common/role_pb2.py +0 -37
- flyte/_protos/common/role_pb2.pyi +0 -53
- flyte/_protos/common/role_pb2_grpc.py +0 -4
- flyte/_protos/common/runtime_version_pb2.py +0 -28
- flyte/_protos/common/runtime_version_pb2.pyi +0 -24
- flyte/_protos/common/runtime_version_pb2_grpc.py +0 -4
- flyte/_protos/logs/dataplane/payload_pb2.py +0 -96
- flyte/_protos/logs/dataplane/payload_pb2.pyi +0 -168
- flyte/_protos/logs/dataplane/payload_pb2_grpc.py +0 -4
- flyte/_protos/secret/definition_pb2.py +0 -49
- flyte/_protos/secret/definition_pb2.pyi +0 -93
- flyte/_protos/secret/definition_pb2_grpc.py +0 -4
- flyte/_protos/secret/payload_pb2.py +0 -62
- flyte/_protos/secret/payload_pb2.pyi +0 -94
- flyte/_protos/secret/payload_pb2_grpc.py +0 -4
- flyte/_protos/secret/secret_pb2.py +0 -38
- flyte/_protos/secret/secret_pb2.pyi +0 -6
- flyte/_protos/secret/secret_pb2_grpc.py +0 -198
- flyte/_protos/secret/secret_pb2_grpc_grpc.py +0 -198
- flyte/_protos/validate/validate/validate_pb2.py +0 -76
- flyte/_protos/workflow/node_execution_service_pb2.py +0 -26
- flyte/_protos/workflow/node_execution_service_pb2.pyi +0 -4
- flyte/_protos/workflow/node_execution_service_pb2_grpc.py +0 -32
- flyte/_protos/workflow/queue_service_pb2.py +0 -106
- flyte/_protos/workflow/queue_service_pb2.pyi +0 -141
- flyte/_protos/workflow/queue_service_pb2_grpc.py +0 -172
- flyte/_protos/workflow/run_definition_pb2.py +0 -128
- flyte/_protos/workflow/run_definition_pb2.pyi +0 -310
- flyte/_protos/workflow/run_definition_pb2_grpc.py +0 -4
- flyte/_protos/workflow/run_logs_service_pb2.py +0 -41
- flyte/_protos/workflow/run_logs_service_pb2.pyi +0 -28
- flyte/_protos/workflow/run_logs_service_pb2_grpc.py +0 -69
- flyte/_protos/workflow/run_service_pb2.py +0 -133
- flyte/_protos/workflow/run_service_pb2.pyi +0 -175
- flyte/_protos/workflow/run_service_pb2_grpc.py +0 -412
- flyte/_protos/workflow/state_service_pb2.py +0 -58
- flyte/_protos/workflow/state_service_pb2.pyi +0 -71
- flyte/_protos/workflow/state_service_pb2_grpc.py +0 -138
- flyte/_protos/workflow/task_definition_pb2.py +0 -72
- flyte/_protos/workflow/task_definition_pb2.pyi +0 -65
- flyte/_protos/workflow/task_definition_pb2_grpc.py +0 -4
- flyte/_protos/workflow/task_service_pb2.py +0 -44
- flyte/_protos/workflow/task_service_pb2.pyi +0 -31
- flyte/_protos/workflow/task_service_pb2_grpc.py +0 -104
- flyte/io/_dataframe.py +0 -0
- flyte/io/pickle/__init__.py +0 -0
- flyte/remote/_console.py +0 -18
- flyte-0.2.0b1.dist-info/METADATA +0 -179
- flyte-0.2.0b1.dist-info/RECORD +0 -204
- flyte-0.2.0b1.dist-info/entry_points.txt +0 -3
- /flyte/{_cli → _debug}/__init__.py +0 -0
- /flyte/{_protos → _keyring}/__init__.py +0 -0
- {flyte-0.2.0b1.dist-info → flyte-2.0.0b46.dist-info}/WHEEL +0 -0
- {flyte-0.2.0b1.dist-info → flyte-2.0.0b46.dist-info}/top_level.txt +0 -0
flyte/config/_reader.py
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
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 | pathlib.Path) -> 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 _config_path_from_git_root() -> pathlib.Path | None:
|
|
139
|
+
from flyte.git import config_from_root
|
|
140
|
+
|
|
141
|
+
config = config_from_root()
|
|
142
|
+
if config is None:
|
|
143
|
+
return None
|
|
144
|
+
return config.source
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def resolve_config_path() -> pathlib.Path | None:
|
|
148
|
+
"""
|
|
149
|
+
Config is read from the following locations in order of precedence:
|
|
150
|
+
1. ./config.yaml if it exists
|
|
151
|
+
2. ./.flyte/config.yaml if it exists
|
|
152
|
+
3. <git_root>/.flyte/config.yaml if it exists
|
|
153
|
+
4. `UCTL_CONFIG` environment variable
|
|
154
|
+
5. `FLYTECTL_CONFIG` environment variable
|
|
155
|
+
6. ~/.union/config.yaml if it exists
|
|
156
|
+
7. ~/.flyte/config.yaml if it exists
|
|
157
|
+
"""
|
|
158
|
+
current_location_config = Path("config.yaml")
|
|
159
|
+
if current_location_config.exists():
|
|
160
|
+
return current_location_config
|
|
161
|
+
logger.debug("No ./config.yaml found")
|
|
162
|
+
|
|
163
|
+
dot_flyte_config = Path(".flyte", "config.yaml")
|
|
164
|
+
if dot_flyte_config.exists():
|
|
165
|
+
return dot_flyte_config
|
|
166
|
+
logger.debug("No ./.flyte/config.yaml found")
|
|
167
|
+
|
|
168
|
+
git_root_config = _config_path_from_git_root()
|
|
169
|
+
if git_root_config:
|
|
170
|
+
return git_root_config
|
|
171
|
+
logger.debug("No .flyte/config.yaml found in git repo root")
|
|
172
|
+
|
|
173
|
+
uctl_path_from_env = getenv(UCTL_CONFIG_ENV_VAR, None)
|
|
174
|
+
if uctl_path_from_env:
|
|
175
|
+
return pathlib.Path(uctl_path_from_env)
|
|
176
|
+
logger.debug("No UCTL_CONFIG environment variable found, checking FLYTECTL_CONFIG")
|
|
177
|
+
|
|
178
|
+
flytectl_path_from_env = getenv(FLYTECTL_CONFIG_ENV_VAR, None)
|
|
179
|
+
if flytectl_path_from_env:
|
|
180
|
+
return pathlib.Path(flytectl_path_from_env)
|
|
181
|
+
logger.debug("No FLYTECTL_CONFIG environment variable found, checking default locations")
|
|
182
|
+
|
|
183
|
+
home_dir_union_config = Path(Path.home(), ".union", "config.yaml")
|
|
184
|
+
if home_dir_union_config.exists():
|
|
185
|
+
return home_dir_union_config
|
|
186
|
+
logger.debug("No ~/.union/config.yaml found, checking current directory")
|
|
187
|
+
|
|
188
|
+
home_dir_flytectl_config = Path(Path.home(), ".flyte", "config.yaml")
|
|
189
|
+
if home_dir_flytectl_config.exists():
|
|
190
|
+
return home_dir_flytectl_config
|
|
191
|
+
logger.debug("No ~/.flyte/config.yaml found, checking current directory")
|
|
192
|
+
|
|
193
|
+
return None
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@lru_cache
|
|
197
|
+
def get_config_file(c: typing.Union[str, pathlib.Path, ConfigFile, None]) -> ConfigFile | None:
|
|
198
|
+
"""
|
|
199
|
+
Checks if the given argument is a file or a configFile and returns a loaded configFile else returns None
|
|
200
|
+
"""
|
|
201
|
+
if isinstance(c, (str, pathlib.Path)):
|
|
202
|
+
logger.debug(f"Using specified config file at {c}")
|
|
203
|
+
return ConfigFile(str(c))
|
|
204
|
+
elif isinstance(c, ConfigFile):
|
|
205
|
+
return c
|
|
206
|
+
config_path = resolve_config_path()
|
|
207
|
+
if config_path:
|
|
208
|
+
return ConfigFile(str(config_path))
|
|
209
|
+
return None
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def read_file_if_exists(filename: typing.Optional[str], encoding=None) -> typing.Optional[str]:
|
|
213
|
+
"""
|
|
214
|
+
Reads the contents of the file if passed a path. Otherwise, returns None.
|
|
215
|
+
|
|
216
|
+
:param filename: The file path to load
|
|
217
|
+
:param encoding: The encoding to use when reading the file.
|
|
218
|
+
:return: The contents of the file as a string or None.
|
|
219
|
+
"""
|
|
220
|
+
if not filename:
|
|
221
|
+
return None
|
|
222
|
+
|
|
223
|
+
file = pathlib.Path(filename)
|
|
224
|
+
logger.debug(f"Reading file contents from [{file}] with current directory [{os.getcwd()}].")
|
|
225
|
+
return file.read_text(encoding=encoding)
|
flyte/connectors/__init__.py
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from ._connector import AsyncConnector, AsyncConnectorExecutorMixin, ConnectorRegistry, Resource, ResourceMeta
|
|
2
|
+
from ._server import ConnectorService
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"AsyncConnector",
|
|
6
|
+
"AsyncConnectorExecutorMixin",
|
|
7
|
+
"ConnectorRegistry",
|
|
8
|
+
"ConnectorService",
|
|
9
|
+
"Resource",
|
|
10
|
+
"ResourceMeta",
|
|
11
|
+
]
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import typing
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from dataclasses import asdict, dataclass
|
|
7
|
+
from typing import Any, Dict, List, Optional
|
|
8
|
+
|
|
9
|
+
from flyteidl2.connector import connector_pb2
|
|
10
|
+
from flyteidl2.connector.connector_pb2 import Connector as ConnectorProto
|
|
11
|
+
from flyteidl2.connector.connector_pb2 import (
|
|
12
|
+
GetTaskLogsResponse,
|
|
13
|
+
GetTaskMetricsResponse,
|
|
14
|
+
TaskCategory,
|
|
15
|
+
TaskExecutionMetadata,
|
|
16
|
+
)
|
|
17
|
+
from flyteidl2.core import tasks_pb2
|
|
18
|
+
from flyteidl2.core.execution_pb2 import TaskExecution, TaskLog
|
|
19
|
+
from google.protobuf import json_format
|
|
20
|
+
from google.protobuf.struct_pb2 import Struct
|
|
21
|
+
|
|
22
|
+
import flyte.storage as storage
|
|
23
|
+
from flyte import Secret
|
|
24
|
+
from flyte._code_bundle import build_code_bundle
|
|
25
|
+
from flyte._context import internal_ctx
|
|
26
|
+
from flyte._deploy import build_images
|
|
27
|
+
from flyte._initialize import get_init_config
|
|
28
|
+
from flyte._internal.runtime import convert, io
|
|
29
|
+
from flyte._internal.runtime.convert import convert_from_native_to_inputs, convert_from_native_to_outputs
|
|
30
|
+
from flyte._internal.runtime.io import upload_inputs
|
|
31
|
+
from flyte._internal.runtime.task_serde import get_proto_task
|
|
32
|
+
from flyte._logging import logger
|
|
33
|
+
from flyte._task import AsyncFunctionTaskTemplate, TaskTemplate
|
|
34
|
+
from flyte.connectors.utils import _render_task_template, is_terminal_phase
|
|
35
|
+
from flyte.models import CodeBundle, NativeInterface, SerializationContext
|
|
36
|
+
from flyte.types._type_engine import dataclass_from_dict
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True)
|
|
40
|
+
class ConnectorRegistryKey:
|
|
41
|
+
task_type_name: str
|
|
42
|
+
task_type_version: int
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class ResourceMeta:
|
|
47
|
+
"""
|
|
48
|
+
This is the metadata for the job. For example, the id of the job.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def encode(self) -> bytes:
|
|
52
|
+
"""
|
|
53
|
+
Encode the resource meta to bytes.
|
|
54
|
+
"""
|
|
55
|
+
return json.dumps(asdict(self)).encode("utf-8")
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def decode(cls, data: bytes) -> "ResourceMeta":
|
|
59
|
+
"""
|
|
60
|
+
Decode the resource meta from bytes.
|
|
61
|
+
"""
|
|
62
|
+
return dataclass_from_dict(cls, json.loads(data.decode("utf-8")))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class Resource:
|
|
67
|
+
"""
|
|
68
|
+
This is the output resource of the job.
|
|
69
|
+
|
|
70
|
+
Attributes
|
|
71
|
+
----------
|
|
72
|
+
phase : TaskExecution.Phase
|
|
73
|
+
The phase of the job.
|
|
74
|
+
message : Optional[str]
|
|
75
|
+
The return message from the job.
|
|
76
|
+
log_links : Optional[List[TaskLog]]
|
|
77
|
+
The log links of the job. For example, the link to the BigQuery Console.
|
|
78
|
+
outputs : Optional[Union[LiteralMap, typing.Dict[str, Any]]]
|
|
79
|
+
The outputs of the job. If return python native types, the agent will convert them to flyte literals.
|
|
80
|
+
custom_info : Optional[typing.Dict[str, Any]]
|
|
81
|
+
The custom info of the job. For example, the job config.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
phase: TaskExecution.Phase
|
|
85
|
+
message: Optional[str] = None
|
|
86
|
+
log_links: Optional[List[TaskLog]] = None
|
|
87
|
+
outputs: Optional[Dict[str, Any]] = None
|
|
88
|
+
custom_info: Optional[typing.Dict[str, Any]] = None
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class AsyncConnector(ABC):
|
|
92
|
+
"""
|
|
93
|
+
This is the base class for all async connectors, and it defines the interface that all connectors must implement.
|
|
94
|
+
The connector service is responsible for invoking connectors.
|
|
95
|
+
The executor will communicate with the connector service to create tasks, get the status of tasks, and delete tasks.
|
|
96
|
+
|
|
97
|
+
All the connectors should be registered in the ConnectorRegistry.
|
|
98
|
+
Connector Service will look up the connector based on the task type and version.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
name = "Async Connector"
|
|
102
|
+
task_type_name: str
|
|
103
|
+
task_type_version: int = 0
|
|
104
|
+
metadata_type: ResourceMeta
|
|
105
|
+
|
|
106
|
+
@abstractmethod
|
|
107
|
+
async def create(
|
|
108
|
+
self,
|
|
109
|
+
task_template: tasks_pb2.TaskTemplate,
|
|
110
|
+
output_prefix: str,
|
|
111
|
+
inputs: Optional[Dict[str, typing.Any]] = None,
|
|
112
|
+
task_execution_metadata: Optional[TaskExecutionMetadata] = None,
|
|
113
|
+
**kwargs,
|
|
114
|
+
) -> ResourceMeta:
|
|
115
|
+
"""
|
|
116
|
+
Return a resource meta that can be used to get the status of the task.
|
|
117
|
+
"""
|
|
118
|
+
raise NotImplementedError
|
|
119
|
+
|
|
120
|
+
@abstractmethod
|
|
121
|
+
async def get(self, resource_meta: ResourceMeta, **kwargs) -> Resource:
|
|
122
|
+
"""
|
|
123
|
+
Return the status of the task, and return the outputs in some cases. For example, bigquery job
|
|
124
|
+
can't write the structured dataset to the output location, so it returns the output literals to the propeller,
|
|
125
|
+
and the propeller will write the structured dataset to the blob store.
|
|
126
|
+
"""
|
|
127
|
+
raise NotImplementedError
|
|
128
|
+
|
|
129
|
+
@abstractmethod
|
|
130
|
+
async def delete(self, resource_meta: ResourceMeta, **kwargs):
|
|
131
|
+
"""
|
|
132
|
+
Delete the task. This call should be idempotent. It should raise an error if fails to delete the task.
|
|
133
|
+
"""
|
|
134
|
+
raise NotImplementedError
|
|
135
|
+
|
|
136
|
+
async def get_metrics(self, resource_meta: ResourceMeta, **kwargs) -> GetTaskMetricsResponse:
|
|
137
|
+
"""
|
|
138
|
+
Return the metrics for the task.
|
|
139
|
+
"""
|
|
140
|
+
raise NotImplementedError
|
|
141
|
+
|
|
142
|
+
async def get_logs(self, resource_meta: ResourceMeta, **kwargs) -> GetTaskLogsResponse:
|
|
143
|
+
"""
|
|
144
|
+
Return the metrics for the task.
|
|
145
|
+
"""
|
|
146
|
+
raise NotImplementedError
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class ConnectorRegistry(object):
|
|
150
|
+
"""
|
|
151
|
+
This is the registry for all connectors.
|
|
152
|
+
The connector service will look up the connector registry based on the task type and version.
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
_REGISTRY: typing.ClassVar[Dict[ConnectorRegistryKey, AsyncConnector]] = {}
|
|
156
|
+
_METADATA: typing.ClassVar[Dict[str, ConnectorProto]] = {}
|
|
157
|
+
|
|
158
|
+
@staticmethod
|
|
159
|
+
def register(connector: AsyncConnector, override: bool = False):
|
|
160
|
+
key = ConnectorRegistryKey(
|
|
161
|
+
task_type_name=connector.task_type_name, task_type_version=connector.task_type_version
|
|
162
|
+
)
|
|
163
|
+
if key in ConnectorRegistry._REGISTRY and override is False:
|
|
164
|
+
raise ValueError(
|
|
165
|
+
f"Duplicate connector for task type: {connector.task_type_name}"
|
|
166
|
+
f" and version: {connector.task_type_version}"
|
|
167
|
+
)
|
|
168
|
+
ConnectorRegistry._REGISTRY[key] = connector
|
|
169
|
+
|
|
170
|
+
task_category = TaskCategory(name=connector.task_type_name, version=connector.task_type_version)
|
|
171
|
+
|
|
172
|
+
if connector.name in ConnectorRegistry._METADATA:
|
|
173
|
+
connector_metadata = ConnectorRegistry._get_connector_metadata(connector.name)
|
|
174
|
+
connector_metadata.supported_task_categories.append(task_category)
|
|
175
|
+
else:
|
|
176
|
+
connector_metadata = ConnectorProto(
|
|
177
|
+
name=connector.name,
|
|
178
|
+
supported_task_categories=[task_category],
|
|
179
|
+
)
|
|
180
|
+
ConnectorRegistry._METADATA[connector.name] = connector_metadata
|
|
181
|
+
|
|
182
|
+
@staticmethod
|
|
183
|
+
def get_connector(task_type_name: str, task_type_version: int = 0) -> AsyncConnector:
|
|
184
|
+
key = ConnectorRegistryKey(task_type_name=task_type_name, task_type_version=task_type_version)
|
|
185
|
+
if key not in ConnectorRegistry._REGISTRY:
|
|
186
|
+
raise FlyteConnectorNotFound(
|
|
187
|
+
f"Cannot find connector for task type: {task_type_name} and version: {task_type_version}"
|
|
188
|
+
)
|
|
189
|
+
return ConnectorRegistry._REGISTRY[key]
|
|
190
|
+
|
|
191
|
+
@staticmethod
|
|
192
|
+
def _list_connectors() -> List[ConnectorProto]:
|
|
193
|
+
return list(ConnectorRegistry._METADATA.values())
|
|
194
|
+
|
|
195
|
+
@staticmethod
|
|
196
|
+
def _get_connector_metadata(name: str) -> ConnectorProto:
|
|
197
|
+
if name not in ConnectorRegistry._METADATA:
|
|
198
|
+
raise FlyteConnectorNotFound(f"Cannot find connector for name: {name}.")
|
|
199
|
+
return ConnectorRegistry._METADATA[name]
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class ConnectorSecretsMixin:
|
|
203
|
+
def __init__(self, secrets: Dict[str, str]):
|
|
204
|
+
# Key is the id of the secret, value is the secret name.
|
|
205
|
+
self._secrets = secrets
|
|
206
|
+
|
|
207
|
+
@property
|
|
208
|
+
def secrets(self) -> List[Secret]:
|
|
209
|
+
return [Secret(key=k, as_env_var=v) for k, v in self._secrets.items()]
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class AsyncConnectorExecutorMixin:
|
|
213
|
+
"""
|
|
214
|
+
This mixin class is used to run the connector task locally, and it's only used for local execution.
|
|
215
|
+
Task should inherit from this class if the task can be run in the connector.
|
|
216
|
+
"""
|
|
217
|
+
|
|
218
|
+
async def execute(self, **kwargs) -> Any:
|
|
219
|
+
task = typing.cast(TaskTemplate, self)
|
|
220
|
+
connector = ConnectorRegistry.get_connector(task.task_type, task.task_type_version)
|
|
221
|
+
|
|
222
|
+
ctx = internal_ctx()
|
|
223
|
+
tctx = internal_ctx().data.task_context
|
|
224
|
+
cfg = get_init_config()
|
|
225
|
+
|
|
226
|
+
if tctx is None:
|
|
227
|
+
raise RuntimeError("Task context is not set.")
|
|
228
|
+
|
|
229
|
+
if tctx.mode == "remote" and isinstance(self, AsyncFunctionTaskTemplate):
|
|
230
|
+
return await AsyncFunctionTaskTemplate.execute(self, **kwargs)
|
|
231
|
+
|
|
232
|
+
prefix = tctx.raw_data_path.get_random_remote_path()
|
|
233
|
+
if isinstance(self, AsyncFunctionTaskTemplate):
|
|
234
|
+
if not storage.is_remote(tctx.raw_data_path.path):
|
|
235
|
+
return await TaskTemplate.execute(self, **kwargs)
|
|
236
|
+
else:
|
|
237
|
+
local_code_bundle = await build_code_bundle(
|
|
238
|
+
from_dir=cfg.root_dir,
|
|
239
|
+
dryrun=True,
|
|
240
|
+
)
|
|
241
|
+
if local_code_bundle.tgz is None:
|
|
242
|
+
raise RuntimeError("no tgz found in code bundle")
|
|
243
|
+
remote_code_path = await storage.put(
|
|
244
|
+
local_code_bundle.tgz, prefix + "/code_bundle/" + os.path.basename(local_code_bundle.tgz)
|
|
245
|
+
)
|
|
246
|
+
sc = SerializationContext(
|
|
247
|
+
project=tctx.action.project,
|
|
248
|
+
domain=tctx.action.domain,
|
|
249
|
+
org=tctx.action.org,
|
|
250
|
+
code_bundle=CodeBundle(
|
|
251
|
+
tgz=remote_code_path,
|
|
252
|
+
computed_version=local_code_bundle.computed_version,
|
|
253
|
+
destination="/opt/flyte/",
|
|
254
|
+
),
|
|
255
|
+
version=tctx.version,
|
|
256
|
+
image_cache=await build_images.aio(task.parent_env()) if task.parent_env else None,
|
|
257
|
+
root_dir=cfg.root_dir,
|
|
258
|
+
)
|
|
259
|
+
tt = get_proto_task(task, sc)
|
|
260
|
+
|
|
261
|
+
tt = _render_task_template(tt, prefix)
|
|
262
|
+
inputs = await convert_from_native_to_inputs(task.native_interface, **kwargs)
|
|
263
|
+
inputs_uri = io.inputs_path(prefix)
|
|
264
|
+
await upload_inputs(inputs, inputs_uri)
|
|
265
|
+
else:
|
|
266
|
+
sc = SerializationContext(
|
|
267
|
+
project=tctx.action.project,
|
|
268
|
+
domain=tctx.action.domain,
|
|
269
|
+
org=tctx.action.org,
|
|
270
|
+
code_bundle=tctx.code_bundle,
|
|
271
|
+
version=tctx.version,
|
|
272
|
+
image_cache=tctx.compiled_image_cache,
|
|
273
|
+
root_dir=cfg.root_dir,
|
|
274
|
+
)
|
|
275
|
+
tt = get_proto_task(task, sc)
|
|
276
|
+
|
|
277
|
+
custom = json_format.MessageToDict(tt.custom)
|
|
278
|
+
secrets = custom["secrets"] if "secrets" in custom else {}
|
|
279
|
+
for k, v in secrets.items():
|
|
280
|
+
env_var = os.getenv(v)
|
|
281
|
+
if env_var is None:
|
|
282
|
+
raise ValueError(f"Secret {v} not found in environment.")
|
|
283
|
+
secrets[k] = env_var
|
|
284
|
+
resource_meta = await connector.create(
|
|
285
|
+
task_template=tt, output_prefix=ctx.raw_data.path, inputs=kwargs, **secrets
|
|
286
|
+
)
|
|
287
|
+
resource = Resource(phase=TaskExecution.RUNNING)
|
|
288
|
+
|
|
289
|
+
while not is_terminal_phase(resource.phase):
|
|
290
|
+
resource = await connector.get(resource_meta=resource_meta, **secrets)
|
|
291
|
+
|
|
292
|
+
if resource.log_links:
|
|
293
|
+
for link in resource.log_links:
|
|
294
|
+
logger.info(f"{link.name}: {link.uri}")
|
|
295
|
+
await asyncio.sleep(3)
|
|
296
|
+
|
|
297
|
+
if resource.phase != TaskExecution.SUCCEEDED:
|
|
298
|
+
raise RuntimeError(f"Failed to run the task {task.name} with error: {resource.message}")
|
|
299
|
+
|
|
300
|
+
# TODO: Support abort
|
|
301
|
+
if (
|
|
302
|
+
isinstance(self, AsyncFunctionTaskTemplate)
|
|
303
|
+
and storage.is_remote(tctx.raw_data_path.path)
|
|
304
|
+
and await storage.exists(io.outputs_path(prefix))
|
|
305
|
+
):
|
|
306
|
+
outputs = await io.load_outputs(io.outputs_path(prefix))
|
|
307
|
+
return await convert.convert_outputs_to_native(task.interface, outputs)
|
|
308
|
+
|
|
309
|
+
if resource.outputs is None:
|
|
310
|
+
return None
|
|
311
|
+
return tuple(resource.outputs.values())
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
async def get_resource_proto(resource: Resource) -> connector_pb2.Resource:
|
|
315
|
+
if resource.outputs:
|
|
316
|
+
interface = NativeInterface.from_types(inputs={}, outputs={k: type(v) for k, v in resource.outputs.items()})
|
|
317
|
+
outputs = (await convert_from_native_to_outputs(tuple(resource.outputs.values()), interface)).proto_outputs
|
|
318
|
+
else:
|
|
319
|
+
outputs = None
|
|
320
|
+
|
|
321
|
+
return connector_pb2.Resource(
|
|
322
|
+
phase=resource.phase,
|
|
323
|
+
message=resource.message,
|
|
324
|
+
log_links=resource.log_links,
|
|
325
|
+
outputs=outputs,
|
|
326
|
+
custom_info=(json_format.Parse(json.dumps(resource.custom_info), Struct()) if resource.custom_info else None),
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
class FlyteConnectorNotFound(ValueError): ...
|