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,316 @@
1
+ from __future__ import annotations
2
+
3
+ import inspect
4
+ import weakref
5
+ from dataclasses import dataclass, field, replace
6
+ from datetime import timedelta
7
+ from typing import (
8
+ TYPE_CHECKING,
9
+ Any,
10
+ Callable,
11
+ Dict,
12
+ List,
13
+ Literal,
14
+ Optional,
15
+ Tuple,
16
+ Union,
17
+ cast,
18
+ overload,
19
+ )
20
+
21
+ import rich.repr
22
+
23
+ from ._cache import Cache, CacheRequest
24
+ from ._doc import Documentation
25
+ from ._environment import Environment
26
+ from ._image import Image
27
+ from ._pod import PodTemplate
28
+ from ._resources import Resources
29
+ from ._retry import RetryStrategy
30
+ from ._reusable_environment import ReusePolicy
31
+ from ._secret import SecretRequest
32
+ from ._task import AsyncFunctionTaskTemplate, TaskTemplate
33
+ from ._trigger import Trigger
34
+ from .models import MAX_INLINE_IO_BYTES, NativeInterface
35
+
36
+ if TYPE_CHECKING:
37
+ from ._task import F, P, R
38
+
39
+
40
+ @rich.repr.auto
41
+ @dataclass(init=True, repr=True)
42
+ class TaskEnvironment(Environment):
43
+ """
44
+ Environment class to define a new environment for a set of tasks.
45
+
46
+ Example usage:
47
+ ```python
48
+ env = flyte.TaskEnvironment(name="my_env", image="my_image", resources=Resources(cpu="1", memory="1Gi"))
49
+
50
+ @env.task
51
+ async def my_task():
52
+ pass
53
+ ```
54
+
55
+ :param name: Name of the environment
56
+ :param image: Docker image to use for the environment. If set to "auto", will use the default image.
57
+ :param resources: Resources to allocate for the environment.
58
+ :param env_vars: Environment variables to set for the environment.
59
+ :param secrets: Secrets to inject into the environment.
60
+ :param depends_on: Environment dependencies to hint, so when you deploy the environment,
61
+ the dependencies are also deployed. This is useful when you have a set of environments
62
+ that depend on each other.
63
+ :param cache: Cache policy for the environment.
64
+ :param reusable: Reuse policy for the environment, if set, a python process may be reused for multiple tasks.
65
+ :param plugin_config: Optional plugin configuration for custom task types.
66
+ If set, all tasks in this environment will use the specified plugin configuration.
67
+ :param queue: Optional queue name to use for tasks in this environment.
68
+ If not set, the default queue will be used.
69
+ :param pod_template: Optional pod template to use for tasks in this environment.
70
+ If not set, the default pod template will be used.
71
+ """
72
+
73
+ cache: CacheRequest = "disable"
74
+ reusable: ReusePolicy | None = None
75
+ plugin_config: Optional[Any] = None
76
+ queue: Optional[str] = None
77
+
78
+ _tasks: Dict[str, TaskTemplate] = field(default_factory=dict, init=False)
79
+
80
+ def __post_init__(self) -> None:
81
+ super().__post_init__()
82
+ if self.reusable is not None and self.plugin_config is not None:
83
+ raise ValueError("Cannot set plugin_config when environment is reusable.")
84
+ if self.reusable and not isinstance(self.reusable, ReusePolicy):
85
+ raise TypeError(f"Expected reusable to be of type ReusePolicy, got {type(self.reusable)}")
86
+ if self.cache and not isinstance(self.cache, (str, Cache)):
87
+ raise TypeError(f"Expected cache to be of type str or Cache, got {type(self.cache)}")
88
+
89
+ def clone_with(
90
+ self,
91
+ name: str,
92
+ image: Optional[Union[str, Image, Literal["auto"]]] = None,
93
+ resources: Optional[Resources] = None,
94
+ env_vars: Optional[Dict[str, str]] = None,
95
+ secrets: Optional[SecretRequest] = None,
96
+ depends_on: Optional[List[Environment]] = None,
97
+ description: Optional[str] = None,
98
+ interruptible: Optional[bool] = None,
99
+ **kwargs: Any,
100
+ ) -> TaskEnvironment:
101
+ """
102
+ Clone the TaskEnvironment with new parameters.
103
+
104
+ Besides the base environment parameters, you can override kwargs like `cache`, `reusable`, etc.
105
+
106
+ :param name: The name of the environment.
107
+ :param image: The image to use for the environment.
108
+ :param resources: The resources to allocate for the environment.
109
+ :param env_vars: The environment variables to set for the environment.
110
+ :param secrets: The secrets to inject into the environment.
111
+ :param depends_on: The environment dependencies to hint, so when you deploy the environment,
112
+ the dependencies are also deployed. This is useful when you have a set of environments
113
+ that depend on each other.
114
+ :param queue: The queue name to use for tasks in this environment.
115
+ :param pod_template: The pod template to use for tasks in this environment.
116
+ :param description: The description of the environment.
117
+ :param interruptible: Whether the environment is interruptible and can be scheduled on spot/preemptible
118
+ instances.
119
+ :param kwargs: Additional parameters to override the environment (e.g., cache, reusable, plugin_config).
120
+ """
121
+ cache = kwargs.pop("cache", None)
122
+ reusable = None
123
+ reusable_set = False
124
+ if "reusable" in kwargs:
125
+ reusable_set = True
126
+ reusable = kwargs.pop("reusable", None)
127
+
128
+ # validate unknown kwargs if needed
129
+ if kwargs:
130
+ raise TypeError(f"Unexpected keyword arguments: {list(kwargs.keys())}")
131
+
132
+ kwargs = self._get_kwargs()
133
+ kwargs["name"] = name
134
+ if image is not None:
135
+ kwargs["image"] = image
136
+ if resources is not None:
137
+ kwargs["resources"] = resources
138
+ if cache is not None:
139
+ kwargs["cache"] = cache
140
+ if env_vars is not None:
141
+ kwargs["env_vars"] = env_vars
142
+ if reusable_set:
143
+ kwargs["reusable"] = reusable
144
+ if secrets is not None:
145
+ kwargs["secrets"] = secrets
146
+ if depends_on is not None:
147
+ kwargs["depends_on"] = depends_on
148
+ if description is not None:
149
+ kwargs["description"] = description
150
+ if interruptible is not None:
151
+ kwargs["interruptible"] = interruptible
152
+ return replace(self, **kwargs)
153
+
154
+ @overload
155
+ def task(
156
+ self,
157
+ *,
158
+ short_name: Optional[str] = None,
159
+ cache: CacheRequest | None = None,
160
+ retries: Union[int, RetryStrategy] = 0,
161
+ timeout: Union[timedelta, int] = 0,
162
+ docs: Optional[Documentation] = None,
163
+ pod_template: Optional[Union[str, PodTemplate]] = None,
164
+ report: bool = False,
165
+ interruptible: bool | None = None,
166
+ max_inline_io_bytes: int = MAX_INLINE_IO_BYTES,
167
+ queue: Optional[str] = None,
168
+ triggers: Tuple[Trigger, ...] | Trigger = (),
169
+ ) -> Callable[[Callable[P, R]], AsyncFunctionTaskTemplate[P, R, Callable[P, R]]]: ...
170
+
171
+ @overload
172
+ def task(
173
+ self,
174
+ _func: Callable[P, R],
175
+ /,
176
+ ) -> AsyncFunctionTaskTemplate[P, R, Callable[P, R]]: ...
177
+
178
+ def task(
179
+ self,
180
+ _func: F | None = None,
181
+ *,
182
+ short_name: Optional[str] = None,
183
+ cache: CacheRequest | None = None,
184
+ retries: Union[int, RetryStrategy] = 0,
185
+ timeout: Union[timedelta, int] = 0,
186
+ docs: Optional[Documentation] = None,
187
+ pod_template: Optional[Union[str, PodTemplate]] = None,
188
+ report: bool = False,
189
+ interruptible: bool | None = None,
190
+ max_inline_io_bytes: int = MAX_INLINE_IO_BYTES,
191
+ queue: Optional[str] = None,
192
+ triggers: Tuple[Trigger, ...] | Trigger = (),
193
+ ) -> Callable[[F], AsyncFunctionTaskTemplate[P, R, F]] | AsyncFunctionTaskTemplate[P, R, F]:
194
+ """
195
+ Decorate a function to be a task.
196
+
197
+ :param _func: Optional The function to decorate. If not provided, the decorator will return a callable that
198
+ accepts a function to be decorated.
199
+ :param short_name: Optional A friendly name for the task (defaults to the function name)
200
+ :param cache: Optional The cache policy for the task, defaults to auto, which will cache the results of the
201
+ task.
202
+ :param retries: Optional The number of retries for the task, defaults to 0, which means no retries.
203
+ :param docs: Optional The documentation for the task, if not provided the function docstring will be used.
204
+ :param timeout: Optional The timeout for the task.
205
+ :param pod_template: Optional The pod template for the task, if not provided the default pod template will be
206
+ used.
207
+ :param report: Optional Whether to generate the html report for the task, defaults to False.
208
+ :param max_inline_io_bytes: Maximum allowed size (in bytes) for all inputs and outputs passed directly to the
209
+ task (e.g., primitives, strings, dicts). Does not apply to files, directories, or dataframes.
210
+ :param triggers: Optional A tuple of triggers to associate with the task. This allows the task to be run on a
211
+ schedule or in response to events. Triggers can be defined using the `flyte.trigger` module.
212
+ :param interruptible: Optional Whether the task is interruptible, defaults to environment setting.
213
+ :param queue: Optional queue name to use for this task. If not set, the environment's queue will be used.
214
+
215
+ :return: A TaskTemplate that can be used to deploy the task.
216
+ """
217
+ from ._task import F, P, R
218
+
219
+ if self.reusable is not None:
220
+ if pod_template is not None:
221
+ raise ValueError("Cannot set pod_template when environment is reusable.")
222
+
223
+ def decorator(func: F) -> AsyncFunctionTaskTemplate[P, R, F]:
224
+ short = short_name or func.__name__
225
+ task_name = self.name + "." + func.__name__
226
+
227
+ if not inspect.iscoroutinefunction(func) and self.reusable is not None:
228
+ if self.reusable.concurrency > 1:
229
+ raise ValueError(
230
+ "Reusable environments with concurrency greater than 1 are only supported for async tasks. "
231
+ "Please use an async function or set concurrency to 1."
232
+ )
233
+
234
+ if self.plugin_config is not None:
235
+ from flyte.extend import TaskPluginRegistry
236
+
237
+ task_template_class: type[AsyncFunctionTaskTemplate[P, R, F]] | None = TaskPluginRegistry.find(
238
+ config_type=type(self.plugin_config)
239
+ )
240
+ if task_template_class is None:
241
+ raise ValueError(
242
+ f"No task plugin found for config type {type(self.plugin_config)}. "
243
+ f"Please register a plugin using flyte.extend.TaskPluginRegistry.register() api."
244
+ )
245
+ else:
246
+ task_template_class = AsyncFunctionTaskTemplate[P, R, F]
247
+
248
+ task_template_class = cast(type[AsyncFunctionTaskTemplate[P, R, F]], task_template_class)
249
+ tmpl = task_template_class(
250
+ func=func,
251
+ name=task_name,
252
+ image=self.image,
253
+ resources=self.resources,
254
+ cache=cache or self.cache,
255
+ retries=retries,
256
+ timeout=timeout,
257
+ reusable=self.reusable,
258
+ docs=docs,
259
+ env_vars=self.env_vars,
260
+ secrets=self.secrets,
261
+ pod_template=pod_template or self.pod_template,
262
+ parent_env=weakref.ref(self),
263
+ parent_env_name=self.name,
264
+ interface=NativeInterface.from_callable(func),
265
+ report=report,
266
+ short_name=short,
267
+ plugin_config=self.plugin_config,
268
+ max_inline_io_bytes=max_inline_io_bytes,
269
+ queue=queue or self.queue,
270
+ interruptible=interruptible if interruptible is not None else self.interruptible,
271
+ triggers=triggers if isinstance(triggers, tuple) else (triggers,),
272
+ )
273
+ self._tasks[task_name] = tmpl
274
+ return tmpl
275
+
276
+ if _func is None:
277
+ return cast(Callable[[F], AsyncFunctionTaskTemplate[P, R, F]], decorator)
278
+ return cast(AsyncFunctionTaskTemplate[P, R, F], decorator(_func))
279
+
280
+ @property
281
+ def tasks(self) -> Dict[str, TaskTemplate]:
282
+ """
283
+ Get all tasks defined in the environment.
284
+ """
285
+ return self._tasks
286
+
287
+ @classmethod
288
+ def from_task(cls, name: str, *tasks: TaskTemplate) -> TaskEnvironment:
289
+ """
290
+ Create a TaskEnvironment from a list of tasks. All tasks should have the same image or no Image defined.
291
+ Similarity of Image is determined by the python reference, not by value.
292
+
293
+ If images are different, an error is raised. If no image is defined, the image is set to "auto".
294
+
295
+ For any other tasks that need to be use these tasks, the returned environment can be used in the `depends_on`
296
+ attribute of the other TaskEnvironment.
297
+
298
+ :param name: The name of the environment.
299
+ :param tasks: The list of tasks to create the environment from.
300
+
301
+ :raises ValueError: If tasks are assigned to multiple environments or have different images.
302
+ :return: The created TaskEnvironment.
303
+ """
304
+ envs = [t.parent_env() for t in tasks if t.parent_env and t.parent_env() is not None]
305
+ if envs:
306
+ raise ValueError("Tasks cannot assigned to multiple environments.")
307
+ images = {t.image for t in tasks}
308
+ if len(images) > 1:
309
+ raise ValueError("Tasks must have the same image to be in the same environment.")
310
+ image: Union[str, Image] = images.pop() if images else "auto"
311
+ env = cls(name, image=image)
312
+ for t in tasks:
313
+ env._tasks[t.name] = t
314
+ t.parent_env = weakref.ref(env)
315
+ t.parent_env_name = name
316
+ return env
flyte/_task_plugins.py ADDED
@@ -0,0 +1,47 @@
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+ from typing import Type
5
+
6
+ import rich.repr
7
+
8
+ if typing.TYPE_CHECKING:
9
+ from ._task import AsyncFunctionTaskTemplate
10
+
11
+ T = typing.TypeVar("T", bound="AsyncFunctionTaskTemplate")
12
+
13
+
14
+ class _Registry:
15
+ """
16
+ A registry for task plugins.
17
+ """
18
+
19
+ def __init__(self: _Registry):
20
+ self._plugins: typing.Dict[Type, Type[typing.Any]] = {}
21
+
22
+ def register(self, config_type: Type, plugin: Type[T]):
23
+ """
24
+ Register a plugin.
25
+ """
26
+ self._plugins[config_type] = plugin
27
+
28
+ def find(self, config_type: Type) -> typing.Optional[Type[T]]:
29
+ """
30
+ Get a plugin by name.
31
+ """
32
+ return self._plugins.get(config_type)
33
+
34
+ def list_plugins(self):
35
+ """
36
+ List all registered plugins.
37
+ """
38
+ return list(self._plugins.keys())
39
+
40
+ def __rich_repr__(self) -> "rich.repr.Result":
41
+ yield from (("Name", i) for i in self.list_plugins())
42
+
43
+ def __repr__(self):
44
+ return f"TaskPluginRegistry(plugins={self.list_plugins()})"
45
+
46
+
47
+ TaskPluginRegistry = _Registry()
flyte/_timeout.py ADDED
@@ -0,0 +1,47 @@
1
+ from dataclasses import dataclass
2
+ from datetime import timedelta
3
+
4
+
5
+ @dataclass
6
+ class Timeout:
7
+ """
8
+ Timeout class to define a timeout for a task.
9
+ The task timeout can be set to a maximum runtime and a maximum queued time.
10
+ Maximum runtime is the maximum time the task can run for (in one attempt).
11
+ Maximum queued time is the maximum time the task can stay in the queue before it starts executing.
12
+
13
+ Example usage:
14
+ ```python
15
+ timeout = Timeout(max_runtime=timedelta(minutes=5), max_queued_time=timedelta(minutes=10))
16
+ @env.task(timeout=timeout)
17
+ async def my_task():
18
+ pass
19
+ ```
20
+ :param max_runtime: timedelta or int - Maximum runtime for the task. If specified int, it will be converted to
21
+ timedelta as seconds.
22
+ :param max_queued_time: optional, timedelta or int - Maximum queued time for the task. If specified int,
23
+ it will be converted to timedelta as seconds. Defaults to None.
24
+
25
+ """
26
+
27
+ max_runtime: timedelta | int
28
+ max_queued_time: timedelta | int | None = None
29
+
30
+
31
+ TimeoutType = Timeout | int | timedelta
32
+
33
+
34
+ def timeout_from_request(timeout: TimeoutType) -> Timeout:
35
+ """
36
+ Converts a timeout request into a Timeout object.
37
+ """
38
+ if isinstance(timeout, Timeout):
39
+ return timeout
40
+ else:
41
+ if isinstance(timeout, int):
42
+ timeout = timedelta(seconds=timeout)
43
+ elif isinstance(timeout, timedelta):
44
+ pass
45
+ else:
46
+ raise ValueError("Timeout must be an instance of Timeout, int, or timedelta.")
47
+ return Timeout(max_runtime=timeout)
flyte/_tools.py ADDED
@@ -0,0 +1,27 @@
1
+ def ipython_check() -> bool:
2
+ """
3
+ Check if interface is launching from iPython (not colab)
4
+ :return is_ipython (bool): True or False
5
+ """
6
+ is_ipython = False
7
+ try: # Check if running interactively using ipython.
8
+ from IPython import get_ipython
9
+
10
+ if get_ipython() is not None:
11
+ is_ipython = True
12
+ except (ImportError, NameError):
13
+ pass
14
+ return is_ipython
15
+
16
+
17
+ def ipywidgets_check() -> bool:
18
+ """
19
+ Check if the interface is running in IPython with ipywidgets support.
20
+ :return: True if running in IPython with ipywidgets support, False otherwise.
21
+ """
22
+ try:
23
+ import ipywidgets # noqa: F401
24
+
25
+ return True
26
+ except (ImportError, NameError):
27
+ return False
flyte/_trace.py ADDED
@@ -0,0 +1,119 @@
1
+ import functools
2
+ import inspect
3
+ import time
4
+ from typing import Any, AsyncGenerator, AsyncIterator, Awaitable, Callable, TypeGuard, TypeVar, Union, cast
5
+
6
+ from flyte._logging import logger
7
+ from flyte.models import NativeInterface
8
+
9
+ T = TypeVar("T")
10
+
11
+
12
+ def trace(func: Callable[..., T]) -> Callable[..., T]:
13
+ """
14
+ A decorator that traces function execution with timing information.
15
+ Works with regular functions, async functions, and async generators/iterators.
16
+ """
17
+
18
+ @functools.wraps(func)
19
+ def wrapper_sync(*args: Any, **kwargs: Any) -> Any:
20
+ raise NotImplementedError
21
+
22
+ @functools.wraps(func)
23
+ async def wrapper_async(*args: Any, **kwargs: Any) -> Any:
24
+ from flyte._context import internal_ctx
25
+
26
+ ctx = internal_ctx()
27
+ if ctx.is_task_context():
28
+ # If we are in a task context, that implies we are executing a Run.
29
+ # In this scenario, we should submit the task to the controller.
30
+ # We will also check if we are not initialized, It is not expected to be not initialized
31
+ from ._internal.controllers import get_controller
32
+
33
+ controller = get_controller()
34
+ iface = NativeInterface.from_callable(func)
35
+ info, ok = await controller.get_action_outputs(iface, func, *args, **kwargs)
36
+ if ok:
37
+ logger.info(f"Found existing trace info for {func}, {info}")
38
+ if info.output:
39
+ return info.output
40
+ elif info.error:
41
+ raise info.error
42
+ else:
43
+ logger.debug(f"No existing trace info found for {func}, proceeding to execute.")
44
+ start_time = time.time()
45
+ try:
46
+ # Cast to Awaitable to satisfy mypy
47
+ coroutine_result = cast(Awaitable[Any], func(*args, **kwargs))
48
+ results = await coroutine_result
49
+ info.add_outputs(results, start_time=start_time, end_time=time.time())
50
+ await controller.record_trace(info)
51
+ logger.debug(f"Finished trace for {func}, {info}")
52
+ return results
53
+ except Exception as e:
54
+ # If there is an error, we need to record it
55
+ info.add_error(e, start_time=start_time, end_time=time.time())
56
+ await controller.record_trace(info)
57
+ raise e
58
+ else:
59
+ # If we are not in a task context, we can just call the function normally
60
+ # Cast to Awaitable to satisfy mypy
61
+ coroutine_result = cast(Awaitable[Any], func(*args, **kwargs))
62
+ return await coroutine_result
63
+
64
+ def is_async_iterable(obj: Any) -> TypeGuard[Union[AsyncGenerator, AsyncIterator]]:
65
+ return hasattr(obj, "__aiter__")
66
+
67
+ @functools.wraps(func)
68
+ async def wrapper_async_iterator(*args: Any, **kwargs: Any) -> AsyncIterator[Any]:
69
+ from flyte._context import internal_ctx
70
+
71
+ ctx = internal_ctx()
72
+ if ctx.is_task_context():
73
+ # If we are in a task context, that implies we are executing a Run.
74
+ # In this scenario, we should submit the task to the controller.
75
+ # We will also check if we are not initialized, It is not expected to be not initialized
76
+ from ._internal.controllers import get_controller
77
+
78
+ controller = get_controller()
79
+ iface = NativeInterface.from_callable(func)
80
+ info, ok = await controller.get_action_outputs(iface, func, *args, **kwargs)
81
+ if ok:
82
+ if info.output:
83
+ for item in info.output:
84
+ yield item
85
+ elif info.error:
86
+ raise info.error
87
+ start_time = time.time()
88
+ try:
89
+ items = []
90
+ result = func(*args, **kwargs)
91
+ # TODO ideally we should use streaming into the type-engine so that it stream uploads large blocks
92
+ if inspect.isasyncgen(result) or is_async_iterable(result):
93
+ # If it's directly an async generator
94
+ async_iter = result
95
+ async for item in async_iter:
96
+ items.append(item)
97
+ yield item
98
+ info.add_outputs(items, start_time=start_time, end_time=time.time())
99
+ await controller.record_trace(info)
100
+ return
101
+ except Exception as e:
102
+ info.add_error(e, start_time=start_time, end_time=time.time())
103
+ await controller.record_trace(info)
104
+ raise e
105
+ else:
106
+ result = func(*args, **kwargs)
107
+ if is_async_iterable(result):
108
+ async for item in result:
109
+ yield item
110
+
111
+ # Choose the appropriate wrapper based on the function type
112
+ if inspect.iscoroutinefunction(func):
113
+ # This handles async functions that return normal values
114
+ return cast(Callable[..., T], wrapper_async)
115
+ elif inspect.isasyncgenfunction(func):
116
+ return cast(Callable[..., T], wrapper_async_iterator)
117
+ else:
118
+ # For regular sync functions
119
+ return cast(Callable[..., T], wrapper_sync)