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
@@ -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)
@@ -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): ...