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,73 @@
1
+ from __future__ import annotations
2
+
3
+ from contextlib import contextmanager
4
+
5
+ from flyte._context import ctx
6
+
7
+ from ._context import internal_ctx
8
+
9
+
10
+ def get_custom_context() -> dict[str, str]:
11
+ """
12
+ Get the current input context. This can be used within a task to retrieve
13
+ context metadata that was passed to the action.
14
+
15
+ Context will automatically propagate to sub-actions.
16
+
17
+ Example:
18
+ ```python
19
+ import flyte
20
+
21
+ env = flyte.TaskEnvironment(name="...")
22
+
23
+ @env.task
24
+ def t1():
25
+ # context can be retrieved with `get_custom_context`
26
+ ctx = flyte.get_custom_context()
27
+ print(ctx) # {'project': '...', 'entity': '...'}
28
+ ```
29
+
30
+ :return: Dictionary of context key-value pairs
31
+ """
32
+ tctx = ctx()
33
+ if tctx is None or tctx.custom_context is None:
34
+ return {}
35
+ return tctx.custom_context
36
+
37
+
38
+ @contextmanager
39
+ def custom_context(**context: str):
40
+ """
41
+ Synchronous context manager to set input context for tasks spawned within this block.
42
+
43
+ Example:
44
+ ```python
45
+ import flyte
46
+
47
+ env = flyte.TaskEnvironment(name="...")
48
+
49
+ @env.task
50
+ def t1():
51
+ ctx = flyte.get_custom_context()
52
+ print(ctx)
53
+
54
+ @env.task
55
+ def main():
56
+ # context can be passed via a context manager
57
+ with flyte.custom_context(project="my-project"):
58
+ t1() # will have {'project': 'my-project'} as context
59
+ ```
60
+
61
+ :param context: Key-value pairs to set as input context
62
+ """
63
+ ctx = internal_ctx()
64
+ if ctx.data.task_context is None:
65
+ yield
66
+ return
67
+
68
+ tctx = ctx.data.task_context
69
+ new_tctx = tctx.replace(custom_context={**tctx.custom_context, **context})
70
+
71
+ with ctx.replace_task_context(new_tctx):
72
+ yield
73
+ # Exit the context and restore the previous context
@@ -0,0 +1,37 @@
1
+ import os
2
+ from pathlib import Path
3
+
4
+ # Where the code-server tar and plugins are downloaded to
5
+ EXECUTABLE_NAME = "code-server"
6
+ DOWNLOAD_DIR = Path.home() / ".code-server"
7
+ HOURS_TO_SECONDS = 60 * 60
8
+ DEFAULT_UP_SECONDS = 10 * HOURS_TO_SECONDS # 10 hours
9
+ DEFAULT_CODE_SERVER_REMOTE_PATHS = {
10
+ "amd64": "https://github.com/coder/code-server/releases/download/v4.106.3/code-server-4.106.3-linux-amd64.tar.gz",
11
+ "arm64": "https://github.com/coder/code-server/releases/download/v4.106.3/code-server-4.106.3-linux-arm64.tar.gz",
12
+ }
13
+ DEFAULT_CODE_SERVER_EXTENSIONS = [
14
+ "https://raw.githubusercontent.com/flyteorg/flytetools/master/flytekitplugins/flyin/ms-python.python-2023.20.0.vsix",
15
+ ]
16
+
17
+ # Duration to pause the checking of the heartbeat file until the next one
18
+ HEARTBEAT_CHECK_SECONDS = 60
19
+
20
+ # The path is hardcoded by code-server
21
+ # https://coder.com/docs/code-server/latest/FAQ#what-is-the-heartbeat-file
22
+ HEARTBEAT_PATH = os.path.expanduser("~/.local/share/code-server/heartbeat")
23
+
24
+ INTERACTIVE_DEBUGGING_FILE_NAME = "flyteinteractive_interactive_entrypoint.py"
25
+ RESUME_TASK_FILE_NAME = "flyteinteractive_resume_task.py"
26
+ # Config keys to store in task template
27
+ VSCODE_TYPE_KEY = "flyteinteractive_type"
28
+ VSCODE_PORT_KEY = "flyteinteractive_port"
29
+
30
+ TASK_FUNCTION_SOURCE_PATH = "TASK_FUNCTION_SOURCE_PATH"
31
+
32
+ # Default max idle seconds to terminate the flyteinteractive server
33
+ HOURS_TO_SECONDS = 60 * 60
34
+ MAX_IDLE_SECONDS = 10 * HOURS_TO_SECONDS # 10 hours
35
+
36
+ # Subprocess constants
37
+ EXIT_CODE_SUCCESS = 0
flyte/_debug/utils.py ADDED
@@ -0,0 +1,17 @@
1
+ import asyncio
2
+
3
+ from flyte._debug.constants import EXIT_CODE_SUCCESS
4
+ from flyte._logging import logger
5
+
6
+
7
+ async def execute_command(cmd: str):
8
+ """
9
+ Execute a command in the shell.
10
+ """
11
+ process = await asyncio.create_subprocess_shell(cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
12
+ logger.info(f"cmd: {cmd}")
13
+ stdout, stderr = await process.communicate()
14
+ if process.returncode != EXIT_CODE_SUCCESS:
15
+ raise RuntimeError(f"Command {cmd} failed with error: {stderr!r}")
16
+ logger.info(f"stdout: {stdout!r}")
17
+ logger.info(f"stderr: {stderr!r}")
flyte/_debug/vscode.py ADDED
@@ -0,0 +1,315 @@
1
+ import asyncio
2
+ import json
3
+ import multiprocessing
4
+ import os
5
+ import platform
6
+ import shutil
7
+ import subprocess
8
+ import sys
9
+ import tarfile
10
+ import time
11
+ from pathlib import Path
12
+ from typing import List
13
+
14
+ import aiofiles
15
+ import click
16
+ import httpx
17
+
18
+ from flyte import storage
19
+ from flyte._debug.constants import (
20
+ DEFAULT_CODE_SERVER_EXTENSIONS,
21
+ DEFAULT_CODE_SERVER_REMOTE_PATHS,
22
+ DOWNLOAD_DIR,
23
+ EXECUTABLE_NAME,
24
+ EXIT_CODE_SUCCESS,
25
+ HEARTBEAT_PATH,
26
+ MAX_IDLE_SECONDS,
27
+ )
28
+ from flyte._debug.utils import (
29
+ execute_command,
30
+ )
31
+ from flyte._internal.runtime.rusty import download_tgz
32
+ from flyte._logging import logger
33
+
34
+
35
+ async def download_file(url: str, target_dir: str) -> str:
36
+ """
37
+ Downloads a file from a given URL using HTTPX and saves it locally.
38
+
39
+ Args:
40
+ url (str): The URL of the file to download.
41
+ target_dir (str): The directory where the file should be saved. Defaults to current directory.
42
+ """
43
+ try:
44
+ filename = os.path.join(target_dir, os.path.basename(url))
45
+ if url.startswith("http"):
46
+ response = httpx.get(url, follow_redirects=True)
47
+ response.raise_for_status() # Raise an exception for bad status codes (4xx or 5xx)
48
+ async with aiofiles.open(filename, "wb") as f:
49
+ await f.write(response.content)
50
+ else:
51
+ await storage.get(url, filename)
52
+ logger.info(f"File '{filename}' downloaded successfully from '{url}'.")
53
+ return filename
54
+
55
+ except httpx.RequestError as e:
56
+ raise RuntimeError(f"An error occurred while requesting '{url}': {e}")
57
+ except httpx.HTTPStatusError as e:
58
+ raise RuntimeError(f"HTTP error occurred: {e.response.status_code} - {e.response.text}")
59
+ except Exception as e:
60
+ raise RuntimeError(f"An unexpected error occurred: {e}")
61
+
62
+
63
+ def get_default_extensions() -> List[str]:
64
+ extensions = os.getenv("_F_CS_E")
65
+ if extensions is not None:
66
+ return extensions.split(",")
67
+ return DEFAULT_CODE_SERVER_EXTENSIONS
68
+
69
+
70
+ def get_code_server_info() -> str:
71
+ """
72
+ Returns the code server information based on the system's architecture.
73
+
74
+ This function checks the system's architecture and returns the corresponding
75
+ code server information from the provided dictionary. The function currently
76
+ supports AMD64 and ARM64 architectures.
77
+
78
+ Returns:
79
+ str: The code server information corresponding to the system's architecture.
80
+
81
+ Raises:
82
+ ValueError: If the system's architecture is not AMD64 or ARM64.
83
+ """
84
+ code_server_path = os.getenv("_F_CS_RP")
85
+ if code_server_path is not None:
86
+ return code_server_path
87
+
88
+ machine_info = platform.machine()
89
+ logger.info(f"machine type: {machine_info}")
90
+ code_server_info_dict = DEFAULT_CODE_SERVER_REMOTE_PATHS
91
+
92
+ if "aarch64" == machine_info:
93
+ return code_server_info_dict["arm64"]
94
+ elif "x86_64" == machine_info:
95
+ return code_server_info_dict["amd64"]
96
+ else:
97
+ raise ValueError(
98
+ "Automatic download is only supported on AMD64 and ARM64 architectures."
99
+ " If you are using a different architecture, please visit the code-server official website to"
100
+ " manually download the appropriate version for your image."
101
+ )
102
+
103
+
104
+ def get_installed_extensions() -> List[str]:
105
+ """
106
+ Get the list of installed extensions.
107
+
108
+ Returns:
109
+ List[str]: The list of installed extensions.
110
+ """
111
+ installed_extensions = subprocess.run(
112
+ ["code-server", "--list-extensions"], check=False, capture_output=True, text=True
113
+ )
114
+ if installed_extensions.returncode != EXIT_CODE_SUCCESS:
115
+ logger.info(f"Command code-server --list-extensions failed with error: {installed_extensions.stderr}")
116
+ return []
117
+
118
+ return installed_extensions.stdout.splitlines()
119
+
120
+
121
+ def is_extension_installed(extension: str, installed_extensions: List[str]) -> bool:
122
+ return any(installed_extension in extension for installed_extension in installed_extensions)
123
+
124
+
125
+ async def download_vscode():
126
+ """
127
+ Download vscode server and extension from remote to local and add the directory of binary executable to $PATH.
128
+ """
129
+ # If the code server already exists in the container, skip downloading
130
+ executable_path = shutil.which(EXECUTABLE_NAME)
131
+ if executable_path is not None or os.path.exists(DOWNLOAD_DIR):
132
+ logger.info(f"Code server binary already exists at {executable_path}")
133
+ logger.info("Skipping downloading code server...")
134
+ else:
135
+ logger.info("Code server is not in $PATH, start downloading code server...")
136
+ # Create DOWNLOAD_DIR if not exist
137
+ logger.info(f"DOWNLOAD_DIR: {DOWNLOAD_DIR}")
138
+ os.makedirs(DOWNLOAD_DIR)
139
+
140
+ logger.info(f"Start downloading files to {DOWNLOAD_DIR}")
141
+ # Download remote file to local
142
+ code_server_remote_path = get_code_server_info()
143
+ code_server_tar_path = await download_file(code_server_remote_path, str(DOWNLOAD_DIR))
144
+
145
+ # Extract the tarball
146
+ with tarfile.open(code_server_tar_path, "r:gz") as tar:
147
+ tar.extractall(path=DOWNLOAD_DIR)
148
+
149
+ if os.path.exists(DOWNLOAD_DIR):
150
+ code_server_dir_name = os.path.basename(get_code_server_info()).removesuffix(".tar.gz")
151
+ code_server_bin_dir = os.path.join(DOWNLOAD_DIR, code_server_dir_name, "bin")
152
+ # Add the directory of code-server binary to $PATH
153
+ os.environ["PATH"] = code_server_bin_dir + os.pathsep + os.environ["PATH"]
154
+
155
+ # If the extension already exists in the container, skip downloading
156
+ installed_extensions = get_installed_extensions()
157
+ coros = []
158
+
159
+ for extension in get_default_extensions():
160
+ if not is_extension_installed(extension, installed_extensions):
161
+ coros.append(download_file(extension, str(DOWNLOAD_DIR)))
162
+ extension_paths = await asyncio.gather(*coros)
163
+
164
+ coros = []
165
+ for p in extension_paths:
166
+ logger.info(f"Execute extension installation command to install extension {p}")
167
+ coros.append(execute_command(f"code-server --install-extension {p}"))
168
+
169
+ await asyncio.gather(*coros)
170
+
171
+
172
+ def prepare_launch_json(ctx: click.Context, pid: int):
173
+ """
174
+ Generate the launch.json and settings.json for users to easily launch interactive debugging and task resumption.
175
+ """
176
+
177
+ virtual_venv = os.getenv("VIRTUAL_ENV", str(Path(sys.executable).parent.parent))
178
+ if virtual_venv is None:
179
+ raise RuntimeError("VIRTUAL_ENV is not found in environment variables.")
180
+
181
+ run_name = ctx.params["run_name"]
182
+ name = ctx.params["name"]
183
+ # TODO: Executor should pass correct name.
184
+ if run_name.startswith("{{"):
185
+ run_name = os.getenv("RUN_NAME", "")
186
+ if name.startswith("{{"):
187
+ name = os.getenv("ACTION_NAME", "")
188
+
189
+ launch_json = {
190
+ "version": "0.2.0",
191
+ "configurations": [
192
+ {
193
+ "name": "Interactive Debugging",
194
+ "type": "python",
195
+ "request": "launch",
196
+ "program": f"{virtual_venv}/bin/runtime.py",
197
+ "console": "integratedTerminal",
198
+ "justMyCode": True,
199
+ "args": [
200
+ "a0",
201
+ "--inputs",
202
+ ctx.params["inputs"],
203
+ "--outputs-path",
204
+ ctx.params["outputs_path"],
205
+ "--version",
206
+ ctx.params["version"],
207
+ "--run-base-dir",
208
+ ctx.params["run_base_dir"],
209
+ "--name",
210
+ name,
211
+ "--run-name",
212
+ run_name,
213
+ "--project",
214
+ ctx.params["project"],
215
+ "--domain",
216
+ ctx.params["domain"],
217
+ "--org",
218
+ ctx.params["org"],
219
+ "--image-cache",
220
+ ctx.params["image_cache"],
221
+ "--debug",
222
+ "False",
223
+ "--interactive-mode",
224
+ "True",
225
+ "--tgz",
226
+ ctx.params["tgz"],
227
+ "--dest",
228
+ ctx.params["dest"],
229
+ "--resolver",
230
+ ctx.params["resolver"],
231
+ *ctx.params["resolver_args"],
232
+ ],
233
+ },
234
+ {
235
+ "name": "Resume Task",
236
+ "type": "python",
237
+ "request": "launch",
238
+ "program": f"{virtual_venv}/bin/debug.py",
239
+ "console": "integratedTerminal",
240
+ "justMyCode": True,
241
+ "args": ["resume", "--pid", str(pid)],
242
+ },
243
+ ],
244
+ }
245
+
246
+ vscode_directory = os.path.join(os.getcwd(), ".vscode")
247
+ if not os.path.exists(vscode_directory):
248
+ os.makedirs(vscode_directory)
249
+
250
+ with open(os.path.join(vscode_directory, "launch.json"), "w") as file:
251
+ json.dump(launch_json, file, indent=4)
252
+
253
+ settings_json = {
254
+ "python.defaultInterpreterPath": sys.executable,
255
+ "remote.autoForwardPorts": False,
256
+ "remote.autoForwardPortsFallback": 0,
257
+ }
258
+ with open(os.path.join(vscode_directory, "settings.json"), "w") as file:
259
+ json.dump(settings_json, file, indent=4)
260
+
261
+
262
+ async def _start_vscode_server(ctx: click.Context):
263
+ if ctx.params["tgz"] is None:
264
+ await download_vscode()
265
+ else:
266
+ await asyncio.gather(
267
+ download_tgz(ctx.params["dest"], ctx.params["version"], ctx.params["tgz"]), download_vscode()
268
+ )
269
+ code_server_idle_timeout_seconds = os.getenv("CODE_SERVER_IDLE_TIMEOUT_SECONDS", str(MAX_IDLE_SECONDS))
270
+ child_process = multiprocessing.Process(
271
+ target=lambda cmd: asyncio.run(asyncio.run(execute_command(cmd))),
272
+ kwargs={
273
+ "cmd": f"code-server --bind-addr 0.0.0.0:6060 --idle-timeout-seconds {code_server_idle_timeout_seconds}"
274
+ f" --disable-workspace-trust --auth none {os.getcwd()}"
275
+ },
276
+ )
277
+ child_process.start()
278
+ if child_process.pid is None:
279
+ raise RuntimeError("Failed to start vscode server.")
280
+
281
+ prepare_launch_json(ctx, child_process.pid)
282
+
283
+ start_time = time.time()
284
+ check_interval = 60 # Interval for heartbeat checking in seconds
285
+ last_heartbeat_check = time.time() - check_interval
286
+
287
+ def terminate_process():
288
+ if child_process.is_alive():
289
+ child_process.terminate()
290
+ child_process.join()
291
+
292
+ logger.info("waiting for task to resume...")
293
+ while child_process.is_alive():
294
+ current_time = time.time()
295
+ if current_time - last_heartbeat_check >= check_interval:
296
+ last_heartbeat_check = current_time
297
+ if not os.path.exists(HEARTBEAT_PATH):
298
+ delta = current_time - start_time
299
+ logger.info(f"Code server has not been connected since {delta} seconds ago.")
300
+ logger.info("Please open the browser to connect to the running server.")
301
+ else:
302
+ delta = current_time - os.path.getmtime(HEARTBEAT_PATH)
303
+ logger.info(f"The latest activity on code server is {delta} seconds ago.")
304
+
305
+ # If the time from last connection is longer than max idle seconds, terminate the vscode server.
306
+ if delta > MAX_IDLE_SECONDS:
307
+ logger.info(f"VSCode server is idle for more than {MAX_IDLE_SECONDS} seconds. Terminating...")
308
+ terminate_process()
309
+ sys.exit()
310
+
311
+ await asyncio.sleep(1)
312
+
313
+ logger.info("User has resumed the task.")
314
+ terminate_process()
315
+ return