flyte 2.0.0b13__py3-none-any.whl → 2.0.0b30__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 (211) hide show
  1. flyte/__init__.py +18 -2
  2. flyte/_bin/debug.py +38 -0
  3. flyte/_bin/runtime.py +62 -8
  4. flyte/_cache/cache.py +4 -2
  5. flyte/_cache/local_cache.py +216 -0
  6. flyte/_code_bundle/_ignore.py +12 -4
  7. flyte/_code_bundle/_packaging.py +13 -9
  8. flyte/_code_bundle/_utils.py +18 -10
  9. flyte/_code_bundle/bundle.py +17 -9
  10. flyte/_constants.py +1 -0
  11. flyte/_context.py +4 -1
  12. flyte/_custom_context.py +73 -0
  13. flyte/_debug/constants.py +38 -0
  14. flyte/_debug/utils.py +17 -0
  15. flyte/_debug/vscode.py +307 -0
  16. flyte/_deploy.py +235 -61
  17. flyte/_environment.py +20 -6
  18. flyte/_excepthook.py +1 -1
  19. flyte/_hash.py +1 -16
  20. flyte/_image.py +178 -81
  21. flyte/_initialize.py +132 -51
  22. flyte/_interface.py +39 -2
  23. flyte/_internal/controllers/__init__.py +4 -5
  24. flyte/_internal/controllers/_local_controller.py +70 -29
  25. flyte/_internal/controllers/_trace.py +1 -1
  26. flyte/_internal/controllers/remote/__init__.py +0 -2
  27. flyte/_internal/controllers/remote/_action.py +14 -16
  28. flyte/_internal/controllers/remote/_client.py +1 -1
  29. flyte/_internal/controllers/remote/_controller.py +68 -70
  30. flyte/_internal/controllers/remote/_core.py +127 -99
  31. flyte/_internal/controllers/remote/_informer.py +19 -10
  32. flyte/_internal/controllers/remote/_service_protocol.py +7 -7
  33. flyte/_internal/imagebuild/docker_builder.py +181 -69
  34. flyte/_internal/imagebuild/image_builder.py +0 -5
  35. flyte/_internal/imagebuild/remote_builder.py +155 -64
  36. flyte/_internal/imagebuild/utils.py +51 -2
  37. flyte/_internal/resolvers/_task_module.py +5 -38
  38. flyte/_internal/resolvers/default.py +2 -2
  39. flyte/_internal/runtime/convert.py +110 -21
  40. flyte/_internal/runtime/entrypoints.py +27 -1
  41. flyte/_internal/runtime/io.py +21 -8
  42. flyte/_internal/runtime/resources_serde.py +20 -6
  43. flyte/_internal/runtime/reuse.py +1 -1
  44. flyte/_internal/runtime/rusty.py +20 -5
  45. flyte/_internal/runtime/task_serde.py +34 -19
  46. flyte/_internal/runtime/taskrunner.py +22 -4
  47. flyte/_internal/runtime/trigger_serde.py +160 -0
  48. flyte/_internal/runtime/types_serde.py +1 -1
  49. flyte/_keyring/__init__.py +0 -0
  50. flyte/_keyring/file.py +115 -0
  51. flyte/_logging.py +201 -39
  52. flyte/_map.py +111 -14
  53. flyte/_module.py +70 -0
  54. flyte/_pod.py +4 -3
  55. flyte/_resources.py +213 -31
  56. flyte/_run.py +110 -39
  57. flyte/_task.py +75 -16
  58. flyte/_task_environment.py +105 -29
  59. flyte/_task_plugins.py +4 -2
  60. flyte/_trace.py +5 -0
  61. flyte/_trigger.py +1000 -0
  62. flyte/_utils/__init__.py +2 -1
  63. flyte/_utils/asyn.py +3 -1
  64. flyte/_utils/coro_management.py +2 -1
  65. flyte/_utils/docker_credentials.py +173 -0
  66. flyte/_utils/module_loader.py +17 -2
  67. flyte/_version.py +3 -3
  68. flyte/cli/_abort.py +3 -3
  69. flyte/cli/_build.py +3 -6
  70. flyte/cli/_common.py +78 -7
  71. flyte/cli/_create.py +182 -4
  72. flyte/cli/_delete.py +23 -1
  73. flyte/cli/_deploy.py +63 -16
  74. flyte/cli/_get.py +79 -34
  75. flyte/cli/_params.py +26 -10
  76. flyte/cli/_plugins.py +209 -0
  77. flyte/cli/_run.py +151 -26
  78. flyte/cli/_serve.py +64 -0
  79. flyte/cli/_update.py +37 -0
  80. flyte/cli/_user.py +17 -0
  81. flyte/cli/main.py +30 -4
  82. flyte/config/_config.py +10 -6
  83. flyte/config/_internal.py +1 -0
  84. flyte/config/_reader.py +29 -8
  85. flyte/connectors/__init__.py +11 -0
  86. flyte/connectors/_connector.py +270 -0
  87. flyte/connectors/_server.py +197 -0
  88. flyte/connectors/utils.py +135 -0
  89. flyte/errors.py +22 -2
  90. flyte/extend.py +8 -1
  91. flyte/extras/_container.py +6 -1
  92. flyte/git/__init__.py +3 -0
  93. flyte/git/_config.py +21 -0
  94. flyte/io/__init__.py +2 -0
  95. flyte/io/_dataframe/__init__.py +2 -0
  96. flyte/io/_dataframe/basic_dfs.py +17 -8
  97. flyte/io/_dataframe/dataframe.py +98 -132
  98. flyte/io/_dir.py +575 -113
  99. flyte/io/_file.py +582 -139
  100. flyte/io/_hashing_io.py +342 -0
  101. flyte/models.py +74 -15
  102. flyte/remote/__init__.py +6 -1
  103. flyte/remote/_action.py +34 -26
  104. flyte/remote/_client/_protocols.py +39 -4
  105. flyte/remote/_client/auth/_authenticators/device_code.py +4 -5
  106. flyte/remote/_client/auth/_authenticators/pkce.py +1 -1
  107. flyte/remote/_client/auth/_channel.py +10 -6
  108. flyte/remote/_client/controlplane.py +17 -5
  109. flyte/remote/_console.py +3 -2
  110. flyte/remote/_data.py +6 -6
  111. flyte/remote/_logs.py +3 -3
  112. flyte/remote/_run.py +64 -8
  113. flyte/remote/_secret.py +26 -17
  114. flyte/remote/_task.py +75 -33
  115. flyte/remote/_trigger.py +306 -0
  116. flyte/remote/_user.py +33 -0
  117. flyte/report/_report.py +1 -1
  118. flyte/storage/__init__.py +6 -1
  119. flyte/storage/_config.py +5 -1
  120. flyte/storage/_parallel_reader.py +274 -0
  121. flyte/storage/_storage.py +200 -103
  122. flyte/types/__init__.py +16 -0
  123. flyte/types/_interface.py +2 -2
  124. flyte/types/_pickle.py +35 -8
  125. flyte/types/_string_literals.py +8 -9
  126. flyte/types/_type_engine.py +40 -70
  127. flyte/types/_utils.py +1 -1
  128. flyte-2.0.0b30.data/scripts/debug.py +38 -0
  129. {flyte-2.0.0b13.data → flyte-2.0.0b30.data}/scripts/runtime.py +62 -8
  130. {flyte-2.0.0b13.dist-info → flyte-2.0.0b30.dist-info}/METADATA +11 -3
  131. flyte-2.0.0b30.dist-info/RECORD +192 -0
  132. {flyte-2.0.0b13.dist-info → flyte-2.0.0b30.dist-info}/entry_points.txt +3 -0
  133. flyte/_protos/common/authorization_pb2.py +0 -66
  134. flyte/_protos/common/authorization_pb2.pyi +0 -108
  135. flyte/_protos/common/authorization_pb2_grpc.py +0 -4
  136. flyte/_protos/common/identifier_pb2.py +0 -93
  137. flyte/_protos/common/identifier_pb2.pyi +0 -110
  138. flyte/_protos/common/identifier_pb2_grpc.py +0 -4
  139. flyte/_protos/common/identity_pb2.py +0 -48
  140. flyte/_protos/common/identity_pb2.pyi +0 -72
  141. flyte/_protos/common/identity_pb2_grpc.py +0 -4
  142. flyte/_protos/common/list_pb2.py +0 -36
  143. flyte/_protos/common/list_pb2.pyi +0 -71
  144. flyte/_protos/common/list_pb2_grpc.py +0 -4
  145. flyte/_protos/common/policy_pb2.py +0 -37
  146. flyte/_protos/common/policy_pb2.pyi +0 -27
  147. flyte/_protos/common/policy_pb2_grpc.py +0 -4
  148. flyte/_protos/common/role_pb2.py +0 -37
  149. flyte/_protos/common/role_pb2.pyi +0 -53
  150. flyte/_protos/common/role_pb2_grpc.py +0 -4
  151. flyte/_protos/common/runtime_version_pb2.py +0 -28
  152. flyte/_protos/common/runtime_version_pb2.pyi +0 -24
  153. flyte/_protos/common/runtime_version_pb2_grpc.py +0 -4
  154. flyte/_protos/imagebuilder/definition_pb2.py +0 -59
  155. flyte/_protos/imagebuilder/definition_pb2.pyi +0 -140
  156. flyte/_protos/imagebuilder/definition_pb2_grpc.py +0 -4
  157. flyte/_protos/imagebuilder/payload_pb2.py +0 -32
  158. flyte/_protos/imagebuilder/payload_pb2.pyi +0 -21
  159. flyte/_protos/imagebuilder/payload_pb2_grpc.py +0 -4
  160. flyte/_protos/imagebuilder/service_pb2.py +0 -29
  161. flyte/_protos/imagebuilder/service_pb2.pyi +0 -5
  162. flyte/_protos/imagebuilder/service_pb2_grpc.py +0 -66
  163. flyte/_protos/logs/dataplane/payload_pb2.py +0 -100
  164. flyte/_protos/logs/dataplane/payload_pb2.pyi +0 -177
  165. flyte/_protos/logs/dataplane/payload_pb2_grpc.py +0 -4
  166. flyte/_protos/secret/definition_pb2.py +0 -49
  167. flyte/_protos/secret/definition_pb2.pyi +0 -93
  168. flyte/_protos/secret/definition_pb2_grpc.py +0 -4
  169. flyte/_protos/secret/payload_pb2.py +0 -62
  170. flyte/_protos/secret/payload_pb2.pyi +0 -94
  171. flyte/_protos/secret/payload_pb2_grpc.py +0 -4
  172. flyte/_protos/secret/secret_pb2.py +0 -38
  173. flyte/_protos/secret/secret_pb2.pyi +0 -6
  174. flyte/_protos/secret/secret_pb2_grpc.py +0 -198
  175. flyte/_protos/secret/secret_pb2_grpc_grpc.py +0 -198
  176. flyte/_protos/validate/validate/validate_pb2.py +0 -76
  177. flyte/_protos/workflow/common_pb2.py +0 -27
  178. flyte/_protos/workflow/common_pb2.pyi +0 -14
  179. flyte/_protos/workflow/common_pb2_grpc.py +0 -4
  180. flyte/_protos/workflow/environment_pb2.py +0 -29
  181. flyte/_protos/workflow/environment_pb2.pyi +0 -12
  182. flyte/_protos/workflow/environment_pb2_grpc.py +0 -4
  183. flyte/_protos/workflow/node_execution_service_pb2.py +0 -26
  184. flyte/_protos/workflow/node_execution_service_pb2.pyi +0 -4
  185. flyte/_protos/workflow/node_execution_service_pb2_grpc.py +0 -32
  186. flyte/_protos/workflow/queue_service_pb2.py +0 -109
  187. flyte/_protos/workflow/queue_service_pb2.pyi +0 -166
  188. flyte/_protos/workflow/queue_service_pb2_grpc.py +0 -172
  189. flyte/_protos/workflow/run_definition_pb2.py +0 -121
  190. flyte/_protos/workflow/run_definition_pb2.pyi +0 -327
  191. flyte/_protos/workflow/run_definition_pb2_grpc.py +0 -4
  192. flyte/_protos/workflow/run_logs_service_pb2.py +0 -41
  193. flyte/_protos/workflow/run_logs_service_pb2.pyi +0 -28
  194. flyte/_protos/workflow/run_logs_service_pb2_grpc.py +0 -69
  195. flyte/_protos/workflow/run_service_pb2.py +0 -137
  196. flyte/_protos/workflow/run_service_pb2.pyi +0 -185
  197. flyte/_protos/workflow/run_service_pb2_grpc.py +0 -446
  198. flyte/_protos/workflow/state_service_pb2.py +0 -67
  199. flyte/_protos/workflow/state_service_pb2.pyi +0 -76
  200. flyte/_protos/workflow/state_service_pb2_grpc.py +0 -138
  201. flyte/_protos/workflow/task_definition_pb2.py +0 -79
  202. flyte/_protos/workflow/task_definition_pb2.pyi +0 -81
  203. flyte/_protos/workflow/task_definition_pb2_grpc.py +0 -4
  204. flyte/_protos/workflow/task_service_pb2.py +0 -60
  205. flyte/_protos/workflow/task_service_pb2.pyi +0 -59
  206. flyte/_protos/workflow/task_service_pb2_grpc.py +0 -138
  207. flyte-2.0.0b13.dist-info/RECORD +0 -239
  208. /flyte/{_protos → _debug}/__init__.py +0 -0
  209. {flyte-2.0.0b13.dist-info → flyte-2.0.0b30.dist-info}/WHEEL +0 -0
  210. {flyte-2.0.0b13.dist-info → flyte-2.0.0b30.dist-info}/licenses/LICENSE +0 -0
  211. {flyte-2.0.0b13.dist-info → flyte-2.0.0b30.dist-info}/top_level.txt +0 -0
flyte/cli/_serve.py ADDED
@@ -0,0 +1,64 @@
1
+ from typing import List
2
+
3
+ import click
4
+
5
+
6
+ @click.group("serve")
7
+ @click.pass_context
8
+ def serve(_: click.Context):
9
+ """
10
+ Start the specific service. For example:
11
+
12
+ ```bash
13
+ flyte serve connector
14
+ ```
15
+ """
16
+
17
+
18
+ @serve.command()
19
+ @click.option(
20
+ "--port",
21
+ default="8000",
22
+ is_flag=False,
23
+ type=int,
24
+ help="Grpc port for the connector service",
25
+ )
26
+ @click.option(
27
+ "--prometheus_port",
28
+ default="9090",
29
+ is_flag=False,
30
+ type=int,
31
+ help="Prometheus port for the connector service",
32
+ )
33
+ @click.option(
34
+ "--worker",
35
+ default="10",
36
+ is_flag=False,
37
+ type=int,
38
+ help="Number of workers for the grpc server",
39
+ )
40
+ @click.option(
41
+ "--timeout",
42
+ default=None,
43
+ is_flag=False,
44
+ type=int,
45
+ help="It will wait for the specified number of seconds before shutting down grpc server. It should only be used "
46
+ "for testing.",
47
+ )
48
+ @click.option(
49
+ "--modules",
50
+ required=False,
51
+ multiple=True,
52
+ type=str,
53
+ help="List of additional files or module that defines the connector",
54
+ )
55
+ @click.pass_context
56
+ def connector(
57
+ _: click.Context, port: int, prometheus_port: int, worker: int, timeout: int | None, modules: List[str] | None
58
+ ):
59
+ """
60
+ Start a grpc server for the connector service.
61
+ """
62
+ from flyte.connectors import ConnectorService
63
+
64
+ ConnectorService.run(port, prometheus_port, worker, timeout, modules)
flyte/cli/_update.py ADDED
@@ -0,0 +1,37 @@
1
+ import rich_click as click
2
+
3
+ import flyte.remote as remote
4
+
5
+ from . import _common as common
6
+
7
+
8
+ @click.group(name="update")
9
+ def update():
10
+ """
11
+ Update various flyte entities.
12
+ """
13
+
14
+
15
+ @update.command("trigger", cls=common.CommandBase)
16
+ @click.argument("name", type=str)
17
+ @click.argument("task_name", type=str)
18
+ @click.option("--activate/--deactivate", required=True, help="Activate or deactivate the trigger.")
19
+ @click.pass_obj
20
+ def trigger(cfg: common.CLIConfig, name: str, task_name: str, activate: bool, project: str | None, domain: str | None):
21
+ """
22
+ Update a trigger.
23
+
24
+ \b
25
+ Example usage:
26
+
27
+ ```bash
28
+ flyte update trigger <trigger_name> <task_name> --activate | --deactivate
29
+ [--project <project_name> --domain <domain_name>]
30
+ ```
31
+ """
32
+ cfg.init(project, domain)
33
+ console = common.get_console()
34
+ to_state = "active" if activate else "deactivate"
35
+ with console.status(f"Updating trigger {name} for task {task_name} to {to_state}..."):
36
+ remote.Trigger.update(name, task_name, activate)
37
+ console.print(f"Trigger updated and is set to [fuchsia]{to_state}[/fuchsia]")
flyte/cli/_user.py ADDED
@@ -0,0 +1,17 @@
1
+ import rich_click as click
2
+
3
+ import flyte.remote as remote
4
+
5
+ from . import _common as common
6
+
7
+
8
+ @click.command()
9
+ @click.pass_obj
10
+ def whoami(
11
+ cfg: common.CLIConfig,
12
+ ):
13
+ """Display the current user information."""
14
+ cfg.init()
15
+ console = common.get_console()
16
+ user_info = remote.User.get()
17
+ console.print(user_info.to_json())
flyte/cli/main.py CHANGED
@@ -1,7 +1,7 @@
1
1
  import rich_click as click
2
2
  from typing_extensions import get_args
3
3
 
4
- from flyte._logging import initialize_logger, logger
4
+ from flyte._logging import LogFormat, initialize_logger, logger
5
5
 
6
6
  from . import _common as common
7
7
  from ._abort import abort
@@ -12,7 +12,11 @@ from ._delete import delete
12
12
  from ._deploy import deploy
13
13
  from ._gen import gen
14
14
  from ._get import get
15
+ from ._plugins import discover_and_register_plugins
15
16
  from ._run import run
17
+ from ._serve import serve
18
+ from ._update import update
19
+ from ._user import whoami
16
20
 
17
21
  help_config = click.RichHelpConfiguration(
18
22
  use_markdown=True,
@@ -25,7 +29,7 @@ help_config = click.RichHelpConfiguration(
25
29
  },
26
30
  {
27
31
  "name": "Management of various objects.",
28
- "commands": ["create", "get", "delete"],
32
+ "commands": ["create", "get", "delete", "update"],
29
33
  },
30
34
  {
31
35
  "name": "Build and deploy environments, tasks and images.",
@@ -35,6 +39,10 @@ help_config = click.RichHelpConfiguration(
35
39
  "name": "Documentation generation",
36
40
  "commands": ["gen"],
37
41
  },
42
+ {
43
+ "name": "User information",
44
+ "commands": ["whoami"],
45
+ },
38
46
  ]
39
47
  },
40
48
  )
@@ -116,6 +124,16 @@ def _verbosity_to_loglevel(verbosity: int) -> int | None:
116
124
  show_default=True,
117
125
  required=False,
118
126
  )
127
+ @click.option(
128
+ "--log-format",
129
+ type=click.Choice(get_args(LogFormat), case_sensitive=False),
130
+ envvar="LOG_FORMAT",
131
+ default="console",
132
+ help="Formatting for logs, defaults to 'console' which is meant to be human readable."
133
+ " 'json' is meant for machine parsing.",
134
+ show_default=True,
135
+ required=False,
136
+ )
119
137
  @click.rich_config(help_config=help_config)
120
138
  @click.pass_context
121
139
  def main(
@@ -123,6 +141,7 @@ def main(
123
141
  endpoint: str | None,
124
142
  insecure: bool,
125
143
  verbose: int,
144
+ log_format: LogFormat,
126
145
  org: str | None,
127
146
  config_file: str | None,
128
147
  auth_type: str | None = None,
@@ -166,8 +185,8 @@ def main(
166
185
  import flyte.config as config
167
186
 
168
187
  log_level = _verbosity_to_loglevel(verbose)
169
- if log_level is not None:
170
- initialize_logger(log_level)
188
+ if log_level is not None or log_format != "console":
189
+ initialize_logger(log_level=log_level, log_format=log_format)
171
190
 
172
191
  cfg = config.auto(config_file=config_file)
173
192
  if cfg.source:
@@ -175,6 +194,7 @@ def main(
175
194
 
176
195
  ctx.obj = CLIConfig(
177
196
  log_level=log_level,
197
+ log_format=log_format,
178
198
  endpoint=endpoint,
179
199
  insecure=insecure,
180
200
  org=org,
@@ -193,3 +213,9 @@ main.add_command(abort) # type: ignore
193
213
  main.add_command(gen) # type: ignore
194
214
  main.add_command(delete) # type: ignore
195
215
  main.add_command(build)
216
+ main.add_command(whoami) # type: ignore
217
+ main.add_command(update) # type: ignore
218
+ main.add_command(serve) # type: ignore
219
+
220
+ # Discover and register CLI plugins from installed packages
221
+ discover_and_register_plugins(main)
flyte/config/_config.py CHANGED
@@ -148,6 +148,7 @@ class ImageConfig(object):
148
148
  """
149
149
 
150
150
  builder: str | None = None
151
+ image_refs: typing.Dict[str, str] = field(default_factory=dict)
151
152
 
152
153
  @classmethod
153
154
  def auto(cls, config_file: typing.Optional[typing.Union[str, ConfigFile]] = None) -> "ImageConfig":
@@ -159,6 +160,7 @@ class ImageConfig(object):
159
160
  config_file = get_config_file(config_file)
160
161
  kwargs: typing.Dict[str, typing.Any] = {}
161
162
  kwargs = set_if_exists(kwargs, "builder", _internal.Image.BUILDER.read(config_file))
163
+ kwargs = set_if_exists(kwargs, "image_refs", _internal.Image.IMAGE_REFS.read(config_file))
162
164
  return ImageConfig(**kwargs)
163
165
 
164
166
 
@@ -192,7 +194,7 @@ class Config(object):
192
194
  )
193
195
 
194
196
  @classmethod
195
- def auto(cls, config_file: typing.Union[str, ConfigFile, None] = None) -> "Config":
197
+ def auto(cls, config_file: typing.Union[str, pathlib.Path, ConfigFile, None] = None) -> "Config":
196
198
  """
197
199
  Automatically constructs the Config Object. The order of precedence is as follows
198
200
  1. first try to find any env vars that match the config vars specified in the FLYTE_CONFIG format.
@@ -225,16 +227,18 @@ def set_if_exists(d: dict, k: str, val: typing.Any) -> dict:
225
227
  return d
226
228
 
227
229
 
228
- def auto(config_file: typing.Union[str, ConfigFile, None] = None) -> Config:
230
+ def auto(config_file: typing.Union[str, pathlib.Path, ConfigFile, None] = None) -> Config:
229
231
  """
230
232
  Automatically constructs the Config Object. The order of precedence is as follows
231
233
  1. If specified, read the config from the provided file path.
232
234
  2. If not specified, the config file is searched in the default locations.
233
235
  a. ./config.yaml if it exists (current working directory)
234
- b. `UCTL_CONFIG` environment variable
235
- c. `FLYTECTL_CONFIG` environment variable
236
- d. ~/.union/config.yaml if it exists
237
- e. ~/.flyte/config.yaml if it exists
236
+ b. ./.flyte/config.yaml if it exists (current working directory)
237
+ c. <git_root>/.flyte/config.yaml if it exists
238
+ d. `UCTL_CONFIG` environment variable
239
+ e. `FLYTECTL_CONFIG` environment variable
240
+ f. ~/.union/config.yaml if it exists
241
+ g. ~/.flyte/config.yaml if it exists
238
242
  3. If any value is not found in the config file, the default value is used.
239
243
  4. For any value there are environment variables that match the config variable names, those will override
240
244
 
flyte/config/_internal.py CHANGED
@@ -70,3 +70,4 @@ class Image(object):
70
70
  """
71
71
 
72
72
  BUILDER = ConfigEntry(YamlConfigEntry("image.builder"))
73
+ IMAGE_REFS = ConfigEntry(YamlConfigEntry("image.image_refs"))
flyte/config/_reader.py CHANGED
@@ -108,7 +108,7 @@ class ConfigFile(object):
108
108
  return pathlib.Path(self._location)
109
109
 
110
110
  @staticmethod
111
- def _read_yaml_config(location: str) -> typing.Optional[typing.Dict[str, typing.Any]]:
111
+ def _read_yaml_config(location: str | pathlib.Path) -> typing.Optional[typing.Dict[str, typing.Any]]:
112
112
  with open(location, "r") as fh:
113
113
  try:
114
114
  yaml_contents = yaml.safe_load(fh)
@@ -135,20 +135,41 @@ class ConfigFile(object):
135
135
  return self._yaml_config
136
136
 
137
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
+
138
147
  def resolve_config_path() -> pathlib.Path | None:
139
148
  """
140
149
  Config is read from the following locations in order of precedence:
141
150
  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
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
146
157
  """
147
158
  current_location_config = Path("config.yaml")
148
159
  if current_location_config.exists():
149
160
  return current_location_config
150
161
  logger.debug("No ./config.yaml found")
151
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
+
152
173
  uctl_path_from_env = getenv(UCTL_CONFIG_ENV_VAR, None)
153
174
  if uctl_path_from_env:
154
175
  return pathlib.Path(uctl_path_from_env)
@@ -173,13 +194,13 @@ def resolve_config_path() -> pathlib.Path | None:
173
194
 
174
195
 
175
196
  @lru_cache
176
- def get_config_file(c: typing.Union[str, ConfigFile, None]) -> ConfigFile | None:
197
+ def get_config_file(c: typing.Union[str, pathlib.Path, ConfigFile, None]) -> ConfigFile | None:
177
198
  """
178
199
  Checks if the given argument is a file or a configFile and returns a loaded configFile else returns None
179
200
  """
180
- if isinstance(c, str):
201
+ if isinstance(c, (str, pathlib.Path)):
181
202
  logger.debug(f"Using specified config file at {c}")
182
- return ConfigFile(c)
203
+ return ConfigFile(str(c))
183
204
  elif isinstance(c, ConfigFile):
184
205
  return c
185
206
  config_path = resolve_config_path()
@@ -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,270 @@
1
+ import asyncio
2
+ import json
3
+ import typing
4
+ from abc import ABC, abstractmethod
5
+ from dataclasses import asdict, dataclass
6
+ from typing import Any, Dict, List, Optional
7
+
8
+ from flyteidl2.core import tasks_pb2
9
+ from flyteidl2.core.execution_pb2 import TaskExecution, TaskLog
10
+ from flyteidl2.plugins import connector_pb2
11
+ from flyteidl2.plugins.connector_pb2 import Connector as ConnectorProto
12
+ from flyteidl2.plugins.connector_pb2 import (
13
+ GetTaskLogsResponse,
14
+ GetTaskMetricsResponse,
15
+ TaskCategory,
16
+ TaskExecutionMetadata,
17
+ )
18
+ from google.protobuf import json_format
19
+ from google.protobuf.struct_pb2 import Struct
20
+
21
+ from flyte import Secret
22
+ from flyte._context import internal_ctx
23
+ from flyte._initialize import get_init_config
24
+ from flyte._internal.runtime.convert import convert_from_native_to_outputs
25
+ from flyte._internal.runtime.task_serde import get_proto_task
26
+ from flyte._logging import logger
27
+ from flyte._task import TaskTemplate
28
+ from flyte.connectors.utils import is_terminal_phase
29
+ from flyte.models import NativeInterface, SerializationContext
30
+ from flyte.types._type_engine import dataclass_from_dict
31
+
32
+
33
+ @dataclass(frozen=True)
34
+ class ConnectorRegistryKey:
35
+ task_type_name: str
36
+ task_type_version: int
37
+
38
+
39
+ @dataclass
40
+ class ResourceMeta:
41
+ """
42
+ This is the metadata for the job. For example, the id of the job.
43
+ """
44
+
45
+ def encode(self) -> bytes:
46
+ """
47
+ Encode the resource meta to bytes.
48
+ """
49
+ return json.dumps(asdict(self)).encode("utf-8")
50
+
51
+ @classmethod
52
+ def decode(cls, data: bytes) -> "ResourceMeta":
53
+ """
54
+ Decode the resource meta from bytes.
55
+ """
56
+ return dataclass_from_dict(cls, json.loads(data.decode("utf-8")))
57
+
58
+
59
+ @dataclass
60
+ class Resource:
61
+ """
62
+ This is the output resource of the job.
63
+
64
+ Attributes
65
+ ----------
66
+ phase : TaskExecution.Phase
67
+ The phase of the job.
68
+ message : Optional[str]
69
+ The return message from the job.
70
+ log_links : Optional[List[TaskLog]]
71
+ The log links of the job. For example, the link to the BigQuery Console.
72
+ outputs : Optional[Union[LiteralMap, typing.Dict[str, Any]]]
73
+ The outputs of the job. If return python native types, the agent will convert them to flyte literals.
74
+ custom_info : Optional[typing.Dict[str, Any]]
75
+ The custom info of the job. For example, the job config.
76
+ """
77
+
78
+ phase: TaskExecution.Phase
79
+ message: Optional[str] = None
80
+ log_links: Optional[List[TaskLog]] = None
81
+ outputs: Optional[Dict[str, Any]] = None
82
+ custom_info: Optional[typing.Dict[str, Any]] = None
83
+
84
+
85
+ class AsyncConnector(ABC):
86
+ """
87
+ This is the base class for all async connectors, and it defines the interface that all connectors must implement.
88
+ The connector service is responsible for invoking connectors.
89
+ The executor will communicate with the connector service to create tasks, get the status of tasks, and delete tasks.
90
+
91
+ All the connectors should be registered in the ConnectorRegistry.
92
+ Connector Service will look up the connector based on the task type and version.
93
+ """
94
+
95
+ name = "Async Connector"
96
+ task_type_name: str
97
+ task_type_version: int = 0
98
+ metadata_type: ResourceMeta
99
+
100
+ @abstractmethod
101
+ async def create(
102
+ self,
103
+ task_template: tasks_pb2.TaskTemplate,
104
+ output_prefix: str,
105
+ inputs: Optional[Dict[str, typing.Any]] = None,
106
+ task_execution_metadata: Optional[TaskExecutionMetadata] = None,
107
+ **kwargs,
108
+ ) -> ResourceMeta:
109
+ """
110
+ Return a resource meta that can be used to get the status of the task.
111
+ """
112
+ raise NotImplementedError
113
+
114
+ @abstractmethod
115
+ async def get(self, resource_meta: ResourceMeta, **kwargs) -> Resource:
116
+ """
117
+ Return the status of the task, and return the outputs in some cases. For example, bigquery job
118
+ can't write the structured dataset to the output location, so it returns the output literals to the propeller,
119
+ and the propeller will write the structured dataset to the blob store.
120
+ """
121
+ raise NotImplementedError
122
+
123
+ @abstractmethod
124
+ async def delete(self, resource_meta: ResourceMeta, **kwargs):
125
+ """
126
+ Delete the task. This call should be idempotent. It should raise an error if fails to delete the task.
127
+ """
128
+ raise NotImplementedError
129
+
130
+ async def get_metrics(self, resource_meta: ResourceMeta, **kwargs) -> GetTaskMetricsResponse:
131
+ """
132
+ Return the metrics for the task.
133
+ """
134
+ raise NotImplementedError
135
+
136
+ async def get_logs(self, resource_meta: ResourceMeta, **kwargs) -> GetTaskLogsResponse:
137
+ """
138
+ Return the metrics for the task.
139
+ """
140
+ raise NotImplementedError
141
+
142
+
143
+ class ConnectorRegistry(object):
144
+ """
145
+ This is the registry for all connectors.
146
+ The connector service will look up the connector registry based on the task type and version.
147
+ """
148
+
149
+ _REGISTRY: typing.ClassVar[Dict[ConnectorRegistryKey, AsyncConnector]] = {}
150
+ _METADATA: typing.ClassVar[Dict[str, ConnectorProto]] = {}
151
+
152
+ @staticmethod
153
+ def register(connector: AsyncConnector, override: bool = False):
154
+ key = ConnectorRegistryKey(
155
+ task_type_name=connector.task_type_name, task_type_version=connector.task_type_version
156
+ )
157
+ if key in ConnectorRegistry._REGISTRY and override is False:
158
+ raise ValueError(
159
+ f"Duplicate connector for task type: {connector.task_type_name}"
160
+ f" and version: {connector.task_type_version}"
161
+ )
162
+ ConnectorRegistry._REGISTRY[key] = connector
163
+
164
+ task_category = TaskCategory(name=connector.task_type_name, version=connector.task_type_version)
165
+
166
+ if connector.name in ConnectorRegistry._METADATA:
167
+ connector_metadata = ConnectorRegistry._get_connector_metadata(connector.name)
168
+ connector_metadata.supported_task_categories.append(task_category)
169
+ else:
170
+ connector_metadata = ConnectorProto(
171
+ name=connector.name,
172
+ supported_task_categories=[task_category],
173
+ )
174
+ ConnectorRegistry._METADATA[connector.name] = connector_metadata
175
+
176
+ @staticmethod
177
+ def get_connector(task_type_name: str, task_type_version: int = 0) -> AsyncConnector:
178
+ key = ConnectorRegistryKey(task_type_name=task_type_name, task_type_version=task_type_version)
179
+ if key not in ConnectorRegistry._REGISTRY:
180
+ raise FlyteConnectorNotFound(
181
+ f"Cannot find connector for task type: {task_type_name} and version: {task_type_version}"
182
+ )
183
+ return ConnectorRegistry._REGISTRY[key]
184
+
185
+ @staticmethod
186
+ def _list_connectors() -> List[ConnectorProto]:
187
+ return list(ConnectorRegistry._METADATA.values())
188
+
189
+ @staticmethod
190
+ def _get_connector_metadata(name: str) -> ConnectorProto:
191
+ if name not in ConnectorRegistry._METADATA:
192
+ raise FlyteConnectorNotFound(f"Cannot find connector for name: {name}.")
193
+ return ConnectorRegistry._METADATA[name]
194
+
195
+
196
+ class ConnectorSecretsMixin:
197
+ def __init__(self, secrets: Dict[str, str]):
198
+ # Key is the id of the secret, value is the secret name.
199
+ self._secrets = secrets
200
+
201
+ @property
202
+ def secrets(self) -> List[Secret]:
203
+ return [Secret(key=k, as_env_var=v) for k, v in self._secrets.items()]
204
+
205
+
206
+ class AsyncConnectorExecutorMixin:
207
+ """
208
+ This mixin class is used to run the connector task locally, and it's only used for local execution.
209
+ Task should inherit from this class if the task can be run in the connector.
210
+ """
211
+
212
+ async def execute(self, **kwargs) -> Any:
213
+ task = typing.cast(TaskTemplate, self)
214
+ connector = ConnectorRegistry.get_connector(task.task_type, task.task_type_version)
215
+
216
+ ctx = internal_ctx()
217
+ tctx = internal_ctx().data.task_context
218
+ cfg = get_init_config()
219
+
220
+ if tctx is None:
221
+ raise RuntimeError("Task context is not set.")
222
+
223
+ sc = SerializationContext(
224
+ project=tctx.action.project,
225
+ domain=tctx.action.domain,
226
+ org=tctx.action.org,
227
+ code_bundle=tctx.code_bundle,
228
+ version=tctx.version,
229
+ image_cache=tctx.compiled_image_cache,
230
+ root_dir=cfg.root_dir,
231
+ )
232
+ tt = get_proto_task(task, sc)
233
+ resource_meta = await connector.create(task_template=tt, output_prefix=ctx.raw_data.path, inputs=kwargs)
234
+ resource = Resource(phase=TaskExecution.RUNNING)
235
+
236
+ while not is_terminal_phase(resource.phase):
237
+ resource = await connector.get(resource_meta=resource_meta)
238
+
239
+ if resource.log_links:
240
+ for link in resource.log_links:
241
+ logger.info(f"{link.name}: {link.uri}")
242
+ await asyncio.sleep(1)
243
+
244
+ if resource.phase != TaskExecution.SUCCEEDED:
245
+ raise RuntimeError(f"Failed to run the task {task.name} with error: {resource.message}")
246
+
247
+ # TODO: Support abort
248
+
249
+ if resource.outputs is None:
250
+ return None
251
+ return tuple(resource.outputs.values())
252
+
253
+
254
+ async def get_resource_proto(resource: Resource) -> connector_pb2.Resource:
255
+ if resource.outputs:
256
+ interface = NativeInterface.from_types(inputs={}, outputs={k: type(v) for k, v in resource.outputs.items()})
257
+ outputs = await convert_from_native_to_outputs(tuple(resource.outputs.values()), interface)
258
+ else:
259
+ outputs = None
260
+
261
+ return connector_pb2.Resource(
262
+ phase=resource.phase,
263
+ message=resource.message,
264
+ log_links=resource.log_links,
265
+ outputs=outputs,
266
+ custom_info=(json_format.Parse(json.dumps(resource.custom_info), Struct()) if resource.custom_info else None),
267
+ )
268
+
269
+
270
+ class FlyteConnectorNotFound(ValueError): ...