flyte 2.0.0b32__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of flyte might be problematic. Click here for more details.

Files changed (204) hide show
  1. flyte/__init__.py +108 -0
  2. flyte/_bin/__init__.py +0 -0
  3. flyte/_bin/debug.py +38 -0
  4. flyte/_bin/runtime.py +195 -0
  5. flyte/_bin/serve.py +178 -0
  6. flyte/_build.py +26 -0
  7. flyte/_cache/__init__.py +12 -0
  8. flyte/_cache/cache.py +147 -0
  9. flyte/_cache/defaults.py +9 -0
  10. flyte/_cache/local_cache.py +216 -0
  11. flyte/_cache/policy_function_body.py +42 -0
  12. flyte/_code_bundle/__init__.py +8 -0
  13. flyte/_code_bundle/_ignore.py +121 -0
  14. flyte/_code_bundle/_packaging.py +218 -0
  15. flyte/_code_bundle/_utils.py +347 -0
  16. flyte/_code_bundle/bundle.py +266 -0
  17. flyte/_constants.py +1 -0
  18. flyte/_context.py +155 -0
  19. flyte/_custom_context.py +73 -0
  20. flyte/_debug/__init__.py +0 -0
  21. flyte/_debug/constants.py +38 -0
  22. flyte/_debug/utils.py +17 -0
  23. flyte/_debug/vscode.py +307 -0
  24. flyte/_deploy.py +408 -0
  25. flyte/_deployer.py +109 -0
  26. flyte/_doc.py +29 -0
  27. flyte/_docstring.py +32 -0
  28. flyte/_environment.py +122 -0
  29. flyte/_excepthook.py +37 -0
  30. flyte/_group.py +32 -0
  31. flyte/_hash.py +8 -0
  32. flyte/_image.py +1055 -0
  33. flyte/_initialize.py +628 -0
  34. flyte/_interface.py +119 -0
  35. flyte/_internal/__init__.py +3 -0
  36. flyte/_internal/controllers/__init__.py +129 -0
  37. flyte/_internal/controllers/_local_controller.py +239 -0
  38. flyte/_internal/controllers/_trace.py +48 -0
  39. flyte/_internal/controllers/remote/__init__.py +58 -0
  40. flyte/_internal/controllers/remote/_action.py +211 -0
  41. flyte/_internal/controllers/remote/_client.py +47 -0
  42. flyte/_internal/controllers/remote/_controller.py +583 -0
  43. flyte/_internal/controllers/remote/_core.py +465 -0
  44. flyte/_internal/controllers/remote/_informer.py +381 -0
  45. flyte/_internal/controllers/remote/_service_protocol.py +50 -0
  46. flyte/_internal/imagebuild/__init__.py +3 -0
  47. flyte/_internal/imagebuild/docker_builder.py +706 -0
  48. flyte/_internal/imagebuild/image_builder.py +277 -0
  49. flyte/_internal/imagebuild/remote_builder.py +386 -0
  50. flyte/_internal/imagebuild/utils.py +78 -0
  51. flyte/_internal/resolvers/__init__.py +0 -0
  52. flyte/_internal/resolvers/_task_module.py +21 -0
  53. flyte/_internal/resolvers/common.py +31 -0
  54. flyte/_internal/resolvers/default.py +28 -0
  55. flyte/_internal/runtime/__init__.py +0 -0
  56. flyte/_internal/runtime/convert.py +486 -0
  57. flyte/_internal/runtime/entrypoints.py +204 -0
  58. flyte/_internal/runtime/io.py +188 -0
  59. flyte/_internal/runtime/resources_serde.py +152 -0
  60. flyte/_internal/runtime/reuse.py +125 -0
  61. flyte/_internal/runtime/rusty.py +193 -0
  62. flyte/_internal/runtime/task_serde.py +362 -0
  63. flyte/_internal/runtime/taskrunner.py +209 -0
  64. flyte/_internal/runtime/trigger_serde.py +160 -0
  65. flyte/_internal/runtime/types_serde.py +54 -0
  66. flyte/_keyring/__init__.py +0 -0
  67. flyte/_keyring/file.py +115 -0
  68. flyte/_logging.py +300 -0
  69. flyte/_map.py +312 -0
  70. flyte/_module.py +72 -0
  71. flyte/_pod.py +30 -0
  72. flyte/_resources.py +473 -0
  73. flyte/_retry.py +32 -0
  74. flyte/_reusable_environment.py +102 -0
  75. flyte/_run.py +724 -0
  76. flyte/_secret.py +96 -0
  77. flyte/_task.py +550 -0
  78. flyte/_task_environment.py +316 -0
  79. flyte/_task_plugins.py +47 -0
  80. flyte/_timeout.py +47 -0
  81. flyte/_tools.py +27 -0
  82. flyte/_trace.py +119 -0
  83. flyte/_trigger.py +1000 -0
  84. flyte/_utils/__init__.py +30 -0
  85. flyte/_utils/asyn.py +121 -0
  86. flyte/_utils/async_cache.py +139 -0
  87. flyte/_utils/coro_management.py +27 -0
  88. flyte/_utils/docker_credentials.py +173 -0
  89. flyte/_utils/file_handling.py +72 -0
  90. flyte/_utils/helpers.py +134 -0
  91. flyte/_utils/lazy_module.py +54 -0
  92. flyte/_utils/module_loader.py +104 -0
  93. flyte/_utils/org_discovery.py +57 -0
  94. flyte/_utils/uv_script_parser.py +49 -0
  95. flyte/_version.py +34 -0
  96. flyte/app/__init__.py +22 -0
  97. flyte/app/_app_environment.py +157 -0
  98. flyte/app/_deploy.py +125 -0
  99. flyte/app/_input.py +160 -0
  100. flyte/app/_runtime/__init__.py +3 -0
  101. flyte/app/_runtime/app_serde.py +347 -0
  102. flyte/app/_types.py +101 -0
  103. flyte/app/extras/__init__.py +3 -0
  104. flyte/app/extras/_fastapi.py +151 -0
  105. flyte/cli/__init__.py +12 -0
  106. flyte/cli/_abort.py +28 -0
  107. flyte/cli/_build.py +114 -0
  108. flyte/cli/_common.py +468 -0
  109. flyte/cli/_create.py +371 -0
  110. flyte/cli/_delete.py +45 -0
  111. flyte/cli/_deploy.py +293 -0
  112. flyte/cli/_gen.py +176 -0
  113. flyte/cli/_get.py +370 -0
  114. flyte/cli/_option.py +33 -0
  115. flyte/cli/_params.py +554 -0
  116. flyte/cli/_plugins.py +209 -0
  117. flyte/cli/_run.py +597 -0
  118. flyte/cli/_serve.py +64 -0
  119. flyte/cli/_update.py +37 -0
  120. flyte/cli/_user.py +17 -0
  121. flyte/cli/main.py +221 -0
  122. flyte/config/__init__.py +3 -0
  123. flyte/config/_config.py +248 -0
  124. flyte/config/_internal.py +73 -0
  125. flyte/config/_reader.py +225 -0
  126. flyte/connectors/__init__.py +11 -0
  127. flyte/connectors/_connector.py +270 -0
  128. flyte/connectors/_server.py +197 -0
  129. flyte/connectors/utils.py +135 -0
  130. flyte/errors.py +243 -0
  131. flyte/extend.py +19 -0
  132. flyte/extras/__init__.py +5 -0
  133. flyte/extras/_container.py +286 -0
  134. flyte/git/__init__.py +3 -0
  135. flyte/git/_config.py +21 -0
  136. flyte/io/__init__.py +29 -0
  137. flyte/io/_dataframe/__init__.py +131 -0
  138. flyte/io/_dataframe/basic_dfs.py +223 -0
  139. flyte/io/_dataframe/dataframe.py +1026 -0
  140. flyte/io/_dir.py +910 -0
  141. flyte/io/_file.py +914 -0
  142. flyte/io/_hashing_io.py +342 -0
  143. flyte/models.py +479 -0
  144. flyte/py.typed +0 -0
  145. flyte/remote/__init__.py +35 -0
  146. flyte/remote/_action.py +738 -0
  147. flyte/remote/_app.py +57 -0
  148. flyte/remote/_client/__init__.py +0 -0
  149. flyte/remote/_client/_protocols.py +189 -0
  150. flyte/remote/_client/auth/__init__.py +12 -0
  151. flyte/remote/_client/auth/_auth_utils.py +14 -0
  152. flyte/remote/_client/auth/_authenticators/__init__.py +0 -0
  153. flyte/remote/_client/auth/_authenticators/base.py +403 -0
  154. flyte/remote/_client/auth/_authenticators/client_credentials.py +73 -0
  155. flyte/remote/_client/auth/_authenticators/device_code.py +117 -0
  156. flyte/remote/_client/auth/_authenticators/external_command.py +79 -0
  157. flyte/remote/_client/auth/_authenticators/factory.py +200 -0
  158. flyte/remote/_client/auth/_authenticators/pkce.py +516 -0
  159. flyte/remote/_client/auth/_channel.py +213 -0
  160. flyte/remote/_client/auth/_client_config.py +85 -0
  161. flyte/remote/_client/auth/_default_html.py +32 -0
  162. flyte/remote/_client/auth/_grpc_utils/__init__.py +0 -0
  163. flyte/remote/_client/auth/_grpc_utils/auth_interceptor.py +288 -0
  164. flyte/remote/_client/auth/_grpc_utils/default_metadata_interceptor.py +151 -0
  165. flyte/remote/_client/auth/_keyring.py +152 -0
  166. flyte/remote/_client/auth/_token_client.py +260 -0
  167. flyte/remote/_client/auth/errors.py +16 -0
  168. flyte/remote/_client/controlplane.py +128 -0
  169. flyte/remote/_common.py +30 -0
  170. flyte/remote/_console.py +19 -0
  171. flyte/remote/_data.py +161 -0
  172. flyte/remote/_logs.py +185 -0
  173. flyte/remote/_project.py +88 -0
  174. flyte/remote/_run.py +386 -0
  175. flyte/remote/_secret.py +142 -0
  176. flyte/remote/_task.py +527 -0
  177. flyte/remote/_trigger.py +306 -0
  178. flyte/remote/_user.py +33 -0
  179. flyte/report/__init__.py +3 -0
  180. flyte/report/_report.py +182 -0
  181. flyte/report/_template.html +124 -0
  182. flyte/storage/__init__.py +36 -0
  183. flyte/storage/_config.py +237 -0
  184. flyte/storage/_parallel_reader.py +274 -0
  185. flyte/storage/_remote_fs.py +34 -0
  186. flyte/storage/_storage.py +456 -0
  187. flyte/storage/_utils.py +5 -0
  188. flyte/syncify/__init__.py +56 -0
  189. flyte/syncify/_api.py +375 -0
  190. flyte/types/__init__.py +52 -0
  191. flyte/types/_interface.py +40 -0
  192. flyte/types/_pickle.py +145 -0
  193. flyte/types/_renderer.py +162 -0
  194. flyte/types/_string_literals.py +119 -0
  195. flyte/types/_type_engine.py +2254 -0
  196. flyte/types/_utils.py +80 -0
  197. flyte-2.0.0b32.data/scripts/debug.py +38 -0
  198. flyte-2.0.0b32.data/scripts/runtime.py +195 -0
  199. flyte-2.0.0b32.dist-info/METADATA +351 -0
  200. flyte-2.0.0b32.dist-info/RECORD +204 -0
  201. flyte-2.0.0b32.dist-info/WHEEL +5 -0
  202. flyte-2.0.0b32.dist-info/entry_points.txt +7 -0
  203. flyte-2.0.0b32.dist-info/licenses/LICENSE +201 -0
  204. flyte-2.0.0b32.dist-info/top_level.txt +1 -0
@@ -0,0 +1,266 @@
1
+ import asyncio
2
+ import gzip
3
+ import logging
4
+ import os
5
+ import pathlib
6
+ import tempfile
7
+ from pathlib import Path
8
+ from typing import ClassVar, Type
9
+
10
+ from async_lru import alru_cache
11
+ from flyteidl2.core.tasks_pb2 import TaskTemplate
12
+
13
+ from flyte._logging import log, logger
14
+ from flyte._utils import AsyncLRUCache
15
+ from flyte.models import CodeBundle
16
+
17
+ from ._ignore import GitIgnore, Ignore, StandardIgnore
18
+ from ._packaging import create_bundle, list_files_to_bundle, list_relative_files_to_bundle, print_ls_tree
19
+ from ._utils import CopyFiles, hash_file
20
+
21
+ _pickled_file_extension = ".pkl.gz"
22
+ _tar_file_extension = ".tar.gz"
23
+
24
+
25
+ class _PklCache:
26
+ _pkl_cache: ClassVar[AsyncLRUCache[str, str]] = AsyncLRUCache[str, str](maxsize=100)
27
+
28
+ @classmethod
29
+ async def put(cls, digest: str, upload_to_path: str, from_path: pathlib.Path) -> str:
30
+ """
31
+ Get the pickled code bundle from the cache or build it if not present.
32
+
33
+ :param digest: The hash digest of the task template.
34
+ :param upload_to_path: The path to upload the pickled file to.
35
+ :param from_path: The path to read the pickled file from.
36
+ :return: CodeBundle object containing the pickled file path and the computed version.
37
+ """
38
+ import flyte.storage as storage
39
+
40
+ async def put_data() -> str:
41
+ return await storage.put(str(from_path), to_path=str(upload_to_path))
42
+
43
+ return await cls._pkl_cache.get(
44
+ key=digest,
45
+ value_func=put_data,
46
+ )
47
+
48
+
49
+ async def build_pkl_bundle(
50
+ o: TaskTemplate,
51
+ upload_to_controlplane: bool = True,
52
+ upload_from_dataplane_base_path: str | None = None,
53
+ copy_bundle_to: pathlib.Path | None = None,
54
+ ) -> CodeBundle:
55
+ """
56
+ Build a Pickled for the given task.
57
+
58
+ TODO We can optimize this by having an LRU cache for the function, this is so that if the same task is being
59
+ pickled multiple times, we can avoid the overhead of pickling it multiple times, by copying to a common place
60
+ and reusing based on task hash.
61
+
62
+ :param o: Object to be pickled. This is the task template.
63
+ :param upload_to_controlplane: Whether to upload the pickled file to the control plane or not
64
+ :param upload_from_dataplane_base_path: If we are on the dataplane, this is the path where the
65
+ pickled file should be uploaded to. upload_to_controlplane has to be False in this case.
66
+ :param copy_bundle_to: If set, the bundle will be copied to this path. This is used for testing purposes.
67
+ :return: CodeBundle object containing the pickled file path and the computed version.
68
+ """
69
+ import cloudpickle
70
+
71
+ if upload_to_controlplane and upload_from_dataplane_base_path:
72
+ raise ValueError("Cannot upload to control plane and upload from dataplane path at the same time.")
73
+
74
+ logger.debug("Building pickled code bundle.")
75
+ with tempfile.TemporaryDirectory() as tmp_dir:
76
+ dest = pathlib.Path(tmp_dir) / f"code_bundle{_pickled_file_extension}"
77
+ with gzip.GzipFile(filename=dest, mode="wb", mtime=0) as gzipped:
78
+ cloudpickle.dump(o, gzipped)
79
+
80
+ if upload_to_controlplane:
81
+ logger.debug("Uploading pickled code bundle to control plane.")
82
+ from flyte.remote import upload_file
83
+
84
+ hash_digest, remote_path = await upload_file.aio(dest)
85
+ return CodeBundle(pkl=remote_path, computed_version=hash_digest)
86
+
87
+ elif upload_from_dataplane_base_path:
88
+ from flyte._internal.runtime import io
89
+
90
+ _, str_digest, _ = hash_file(file_path=dest)
91
+ upload_path = io.pkl_path(upload_from_dataplane_base_path, str_digest)
92
+ logger.debug(f"Uploading pickled code bundle to dataplane path {upload_path}.")
93
+ final_path = await _PklCache.put(
94
+ digest=str_digest,
95
+ upload_to_path=upload_path,
96
+ from_path=dest,
97
+ )
98
+ return CodeBundle(pkl=final_path, computed_version=str_digest)
99
+
100
+ else:
101
+ logger.debug("Dryrun enabled, not uploading pickled code bundle.")
102
+ _, str_digest, _ = hash_file(file_path=dest)
103
+ if copy_bundle_to:
104
+ import shutil
105
+
106
+ # Copy the bundle to the given path
107
+ shutil.copy(dest, copy_bundle_to, follow_symlinks=True)
108
+ local_path = copy_bundle_to / dest.name
109
+ return CodeBundle(pkl=str(local_path), computed_version=str_digest)
110
+ return CodeBundle(pkl=str(dest), computed_version=str_digest)
111
+
112
+
113
+ @alru_cache
114
+ async def build_code_bundle(
115
+ from_dir: Path,
116
+ *ignore: Type[Ignore],
117
+ extract_dir: str = ".",
118
+ dryrun: bool = False,
119
+ copy_bundle_to: pathlib.Path | None = None,
120
+ copy_style: CopyFiles = "loaded_modules",
121
+ ) -> CodeBundle:
122
+ """
123
+ Build the code bundle for the current environment.
124
+ :param from_dir: The directory of the code to bundle. This is the root directory for the source.
125
+ :param extract_dir: The directory to extract the code bundle to, when in the container. It defaults to the current
126
+ working directory.
127
+ :param ignore: The list of ignores to apply. This is a list of Ignore classes.
128
+ :param dryrun: If dryrun is enabled, files will not be uploaded to the control plane.
129
+ :param copy_bundle_to: If set, the bundle will be copied to this path. This is used for testing purposes.
130
+ :param copy_style: What to put into the tarball. (either all, or loaded_modules. if none, skip this function)
131
+
132
+ :return: The code bundle, which contains the path where the code was zipped to.
133
+ """
134
+ logger.debug("Building code bundle.")
135
+ from flyte.remote import upload_file
136
+
137
+ if not ignore:
138
+ ignore = (StandardIgnore, GitIgnore)
139
+
140
+ logger.debug(f"Finding files to bundle, ignoring as configured by: {ignore}")
141
+ files, digest = list_files_to_bundle(from_dir, True, *ignore, copy_style=copy_style)
142
+ if logger.getEffectiveLevel() <= logging.INFO:
143
+ print_ls_tree(from_dir, files)
144
+
145
+ logger.debug("Building code bundle.")
146
+ with tempfile.TemporaryDirectory() as tmp_dir:
147
+ bundle_path, tar_size, archive_size = create_bundle(from_dir, pathlib.Path(tmp_dir), files, digest)
148
+ logger.info(f"Code bundle created at {bundle_path}, size: {tar_size} MB, archive size: {archive_size} MB")
149
+ if not dryrun:
150
+ hash_digest, remote_path = await upload_file.aio(bundle_path)
151
+ logger.debug(f"Code bundle uploaded to {remote_path}")
152
+ else:
153
+ remote_path = "na"
154
+ if copy_bundle_to:
155
+ import shutil
156
+
157
+ # Copy the bundle to the given path
158
+ shutil.copy(bundle_path, copy_bundle_to)
159
+ remote_path = str(copy_bundle_to / bundle_path.name)
160
+ _, hash_digest, _ = hash_file(file_path=bundle_path)
161
+ return CodeBundle(tgz=remote_path, destination=extract_dir, computed_version=hash_digest)
162
+
163
+
164
+ @alru_cache
165
+ async def build_code_bundle_from_relative_paths(
166
+ relative_paths: tuple[str, ...],
167
+ from_dir: Path,
168
+ extract_dir: str = ".",
169
+ dryrun: bool = False,
170
+ copy_bundle_to: pathlib.Path | None = None,
171
+ ) -> CodeBundle:
172
+ """
173
+ Build a code bundle from a list of relative paths.
174
+ :param relative_paths: The list of relative paths to bundle.
175
+ :param from_dir: The directory of the code to bundle. This is the root directory for the source.
176
+ :param ignore: The list of ignores to apply. This is a list of Ignore classes.
177
+ :param extract_dir: The directory to extract the code bundle to, when in the container. It defaults to the current
178
+ working directory.
179
+ :param dryrun: If dryrun is enabled, files will not be uploaded to the control plane.
180
+ :param copy_bundle_to: If set, the bundle will be copied to this path. This is used for testing purposes.
181
+ :param copy_style: What to put into the tarball. (either all, or loaded_modules. if none, skip this function)
182
+ :return: The code bundle, which contains the path where the code was zipped to.
183
+ """
184
+ logger.debug("Building code bundle from relative paths.")
185
+ from flyte.remote import upload_file
186
+
187
+ logger.debug("Finding files to bundle")
188
+ files, digest = list_relative_files_to_bundle(relative_paths, from_dir)
189
+ if logger.getEffectiveLevel() <= logging.INFO:
190
+ print_ls_tree(from_dir, files)
191
+
192
+ logger.debug("Building code bundle.")
193
+ with tempfile.TemporaryDirectory() as tmp_dir:
194
+ bundle_path, tar_size, archive_size = create_bundle(from_dir, pathlib.Path(tmp_dir), files, digest)
195
+ logger.info(f"Code bundle created at {bundle_path}, size: {tar_size} MB, archive size: {archive_size} MB")
196
+ if not dryrun:
197
+ hash_digest, remote_path = await upload_file.aio(bundle_path)
198
+ logger.debug(f"Code bundle uploaded to {remote_path}")
199
+ else:
200
+ remote_path = "na"
201
+ if copy_bundle_to:
202
+ import shutil
203
+
204
+ # Copy the bundle to the given path
205
+ shutil.copy(bundle_path, copy_bundle_to)
206
+ remote_path = str(copy_bundle_to / bundle_path.name)
207
+ _, hash_digest, _ = hash_file(file_path=bundle_path)
208
+ return CodeBundle(tgz=remote_path, destination=extract_dir, computed_version=hash_digest)
209
+
210
+
211
+ @log(level=logging.INFO)
212
+ async def download_bundle(bundle: CodeBundle) -> pathlib.Path:
213
+ """
214
+ Downloads a code bundle (tgz | pkl) to the local destination path.
215
+ :param bundle: The code bundle to download.
216
+
217
+ :return: The path to the downloaded code bundle.
218
+ """
219
+ import sys
220
+
221
+ import flyte.storage as storage
222
+
223
+ dest = pathlib.Path(bundle.destination)
224
+ if not dest.is_dir():
225
+ raise ValueError(f"Destination path should be a directory, found {dest}, {dest.stat()}")
226
+
227
+ # TODO make storage apis better to accept pathlib.Path
228
+ if bundle.tgz:
229
+ downloaded_bundle = dest / os.path.basename(bundle.tgz)
230
+ if downloaded_bundle.exists():
231
+ return downloaded_bundle.absolute()
232
+ # Download the tgz file
233
+ await storage.get(bundle.tgz, str(downloaded_bundle.absolute()))
234
+ # NOTE the os.path.join(destination, ''). This is to ensure that the given path is in fact a directory and all
235
+ # downloaded data should be copied into this directory. We do this to account for a difference in behavior in
236
+ # fsspec, which requires a trailing slash in case of pre-existing directory.
237
+ args = [
238
+ "-xvf",
239
+ str(downloaded_bundle),
240
+ "-C",
241
+ str(dest),
242
+ ]
243
+ if sys.platform != "darwin":
244
+ args.insert(0, "--overwrite")
245
+
246
+ process = await asyncio.create_subprocess_exec(
247
+ "tar",
248
+ *args,
249
+ stdout=asyncio.subprocess.PIPE,
250
+ stderr=asyncio.subprocess.PIPE,
251
+ )
252
+ _stdout, stderr = await process.communicate()
253
+
254
+ if process.returncode != 0:
255
+ raise RuntimeError(stderr.decode())
256
+ return downloaded_bundle.absolute()
257
+
258
+ elif bundle.pkl:
259
+ # Lets gunzip the pkl file
260
+
261
+ downloaded_bundle = dest / os.path.basename(bundle.pkl)
262
+ # Download the tgz file
263
+ await storage.get(bundle.pkl, str(downloaded_bundle.absolute()))
264
+ return downloaded_bundle.absolute()
265
+ else:
266
+ raise ValueError("Code bundle should be either tgz or pkl, found neither.")
flyte/_constants.py ADDED
@@ -0,0 +1 @@
1
+ FLYTE_SYS_PATH = "_F_SYS_PATH" # The paths that will be appended to sys.path at runtime
flyte/_context.py ADDED
@@ -0,0 +1,155 @@
1
+ from __future__ import annotations
2
+
3
+ import contextvars
4
+ from dataclasses import dataclass, replace
5
+ from typing import TYPE_CHECKING, Awaitable, Callable, Optional, ParamSpec, TypeVar
6
+
7
+ from flyte._logging import logger
8
+ from flyte.models import GroupData, RawDataPath, TaskContext
9
+
10
+ if TYPE_CHECKING:
11
+ from flyte.report import Report
12
+
13
+ P = ParamSpec("P") # capture the function's parameters
14
+ R = TypeVar("R") # return type
15
+
16
+
17
+ @dataclass(frozen=True, kw_only=True)
18
+ class ContextData:
19
+ """
20
+ A ContextData cannot be created without an execution. Even for local execution's there should be an execution ID
21
+
22
+ :param: action The action ID of the current execution. This is always set, within a run.
23
+ :param: group_data If nested in a group the current group information
24
+ :param: task_context The context of the current task execution, this is what is available to the user, it is set
25
+ when the task is executed through `run` methods. If the Task is executed as regular python methods, this
26
+ will be None.
27
+ """
28
+
29
+ group_data: Optional[GroupData] = None
30
+ task_context: Optional[TaskContext] = None
31
+ raw_data_path: Optional[RawDataPath] = None
32
+
33
+ def replace(self, **kwargs) -> ContextData:
34
+ return replace(self, **kwargs)
35
+
36
+
37
+ class Context:
38
+ """
39
+ A context class to hold the current execution context.
40
+ This is not coroutine safe, it assumes that the context is set in a single thread.
41
+ You should use the `contextual_run` function to run a function in a new context tree.
42
+
43
+ A context tree is defined as a tree of contexts, where under the root, all coroutines that were started in
44
+ this context tree can access the context mutations, but no coroutine, created outside of the context tree can access
45
+ the context mutations.
46
+ """
47
+
48
+ def __init__(self, data: ContextData):
49
+ if data is None:
50
+ raise ValueError("Cannot create a new context without contextdata.")
51
+ self._data = data
52
+ self._id = id(self) # Immutable unique identifier
53
+ self._token = None # Context variable token to restore the previous context
54
+
55
+ @property
56
+ def data(self) -> ContextData:
57
+ """Viewable data."""
58
+ return self._data
59
+
60
+ @property
61
+ def raw_data(self) -> RawDataPath:
62
+ """
63
+ Get the raw data prefix for the current context first by looking up the task context, then the raw data path
64
+ """
65
+ if self.data and self.data.task_context and self.data.task_context.raw_data_path:
66
+ return self.data.task_context.raw_data_path
67
+ if self.data and self.data.raw_data_path:
68
+ return self.data.raw_data_path
69
+ raise ValueError("Raw data path has not been set in the context.")
70
+
71
+ @property
72
+ def id(self) -> int:
73
+ """Viewable ID."""
74
+ return self._id
75
+
76
+ def replace_task_context(self, tctx: TaskContext) -> Context:
77
+ """
78
+ Replace the task context in the current context.
79
+ """
80
+ return Context(self.data.replace(task_context=tctx))
81
+
82
+ def new_raw_data_path(self, raw_data_path: RawDataPath) -> Context:
83
+ """
84
+ Return a copy of the context with the given raw data path object
85
+ """
86
+ return Context(self.data.replace(raw_data_path=raw_data_path))
87
+
88
+ def get_report(self) -> Optional[Report]:
89
+ """
90
+ Returns a report if within a task context, else a None
91
+ :return:
92
+ """
93
+ if self.data.task_context:
94
+ return self.data.task_context.report
95
+ return None
96
+
97
+ def is_task_context(self) -> bool:
98
+ """
99
+ Returns true if the context is a task context
100
+ :return: bool
101
+ """
102
+ return self.data.task_context is not None
103
+
104
+ def __enter__(self):
105
+ """Enter the context, setting it as the current context."""
106
+ self._token = root_context_var.set(self)
107
+ return self
108
+
109
+ def __exit__(self, exc_type, exc_val, exc_tb):
110
+ """Exit the context, restoring the previous context."""
111
+ try:
112
+ root_context_var.reset(self._token)
113
+ except Exception as e:
114
+ logger.warn(f"Failed to reset context: {e}")
115
+ raise e
116
+
117
+ async def __aenter__(self):
118
+ """Async version of context entry."""
119
+ self._token = root_context_var.set(self)
120
+ return self
121
+
122
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
123
+ """Async version of context exit."""
124
+ root_context_var.reset(self._token)
125
+
126
+ def __repr__(self):
127
+ return f"{self.data}"
128
+
129
+ def __str__(self):
130
+ return self.__repr__()
131
+
132
+
133
+ # Global context variable to hold the current context
134
+ root_context_var = contextvars.ContextVar("root", default=Context(data=ContextData()))
135
+
136
+
137
+ def ctx() -> Optional[TaskContext]:
138
+ """
139
+ Returns flyte.models.TaskContext if within a task context, else None
140
+ Note: Only use this in task code and not module level.
141
+ """
142
+ return internal_ctx().data.task_context
143
+
144
+
145
+ def internal_ctx() -> Context:
146
+ """Retrieve the current context from the context variable."""
147
+ return root_context_var.get()
148
+
149
+
150
+ async def contextual_run(func: Callable[P, Awaitable[R]], *args: P.args, **kwargs: P.kwargs) -> R:
151
+ """
152
+ Run a function with a new context subtree.
153
+ """
154
+ _ctx = contextvars.copy_context()
155
+ return await _ctx.run(func, *args, **kwargs)
@@ -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
File without changes
@@ -0,0 +1,38 @@
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.18.0/code-server-4.18.0-linux-amd64.tar.gz",
11
+ "arm64": "https://github.com/coder/code-server/releases/download/v4.18.0/code-server-4.18.0-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
+ MAX_IDLE_SECONDS = 180
20
+
21
+ # The path is hardcoded by code-server
22
+ # https://coder.com/docs/code-server/latest/FAQ#what-is-the-heartbeat-file
23
+ HEARTBEAT_PATH = os.path.expanduser("~/.local/share/code-server/heartbeat")
24
+
25
+ INTERACTIVE_DEBUGGING_FILE_NAME = "flyteinteractive_interactive_entrypoint.py"
26
+ RESUME_TASK_FILE_NAME = "flyteinteractive_resume_task.py"
27
+ # Config keys to store in task template
28
+ VSCODE_TYPE_KEY = "flyteinteractive_type"
29
+ VSCODE_PORT_KEY = "flyteinteractive_port"
30
+
31
+ TASK_FUNCTION_SOURCE_PATH = "TASK_FUNCTION_SOURCE_PATH"
32
+
33
+ # Default max idle seconds to terminate the flyteinteractive server
34
+ HOURS_TO_SECONDS = 60 * 60
35
+ MAX_IDLE_SECONDS = 10 * HOURS_TO_SECONDS # 10 hours
36
+
37
+ # Subprocess constants
38
+ 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}")