flyte 0.1.0__py3-none-any.whl → 0.2.0a0__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 (219) hide show
  1. flyte/__init__.py +78 -2
  2. flyte/_bin/__init__.py +0 -0
  3. flyte/_bin/runtime.py +152 -0
  4. flyte/_build.py +26 -0
  5. flyte/_cache/__init__.py +12 -0
  6. flyte/_cache/cache.py +145 -0
  7. flyte/_cache/defaults.py +9 -0
  8. flyte/_cache/policy_function_body.py +42 -0
  9. flyte/_code_bundle/__init__.py +8 -0
  10. flyte/_code_bundle/_ignore.py +113 -0
  11. flyte/_code_bundle/_packaging.py +187 -0
  12. flyte/_code_bundle/_utils.py +323 -0
  13. flyte/_code_bundle/bundle.py +209 -0
  14. flyte/_context.py +152 -0
  15. flyte/_deploy.py +243 -0
  16. flyte/_doc.py +29 -0
  17. flyte/_docstring.py +32 -0
  18. flyte/_environment.py +84 -0
  19. flyte/_excepthook.py +37 -0
  20. flyte/_group.py +32 -0
  21. flyte/_hash.py +23 -0
  22. flyte/_image.py +762 -0
  23. flyte/_initialize.py +492 -0
  24. flyte/_interface.py +84 -0
  25. flyte/_internal/__init__.py +3 -0
  26. flyte/_internal/controllers/__init__.py +128 -0
  27. flyte/_internal/controllers/_local_controller.py +193 -0
  28. flyte/_internal/controllers/_trace.py +41 -0
  29. flyte/_internal/controllers/remote/__init__.py +60 -0
  30. flyte/_internal/controllers/remote/_action.py +146 -0
  31. flyte/_internal/controllers/remote/_client.py +47 -0
  32. flyte/_internal/controllers/remote/_controller.py +494 -0
  33. flyte/_internal/controllers/remote/_core.py +410 -0
  34. flyte/_internal/controllers/remote/_informer.py +361 -0
  35. flyte/_internal/controllers/remote/_service_protocol.py +50 -0
  36. flyte/_internal/imagebuild/__init__.py +11 -0
  37. flyte/_internal/imagebuild/docker_builder.py +427 -0
  38. flyte/_internal/imagebuild/image_builder.py +246 -0
  39. flyte/_internal/imagebuild/remote_builder.py +0 -0
  40. flyte/_internal/resolvers/__init__.py +0 -0
  41. flyte/_internal/resolvers/_task_module.py +54 -0
  42. flyte/_internal/resolvers/common.py +31 -0
  43. flyte/_internal/resolvers/default.py +28 -0
  44. flyte/_internal/runtime/__init__.py +0 -0
  45. flyte/_internal/runtime/convert.py +342 -0
  46. flyte/_internal/runtime/entrypoints.py +135 -0
  47. flyte/_internal/runtime/io.py +136 -0
  48. flyte/_internal/runtime/resources_serde.py +138 -0
  49. flyte/_internal/runtime/task_serde.py +330 -0
  50. flyte/_internal/runtime/taskrunner.py +191 -0
  51. flyte/_internal/runtime/types_serde.py +54 -0
  52. flyte/_logging.py +135 -0
  53. flyte/_map.py +215 -0
  54. flyte/_pod.py +19 -0
  55. flyte/_protos/__init__.py +0 -0
  56. flyte/_protos/common/authorization_pb2.py +66 -0
  57. flyte/_protos/common/authorization_pb2.pyi +108 -0
  58. flyte/_protos/common/authorization_pb2_grpc.py +4 -0
  59. flyte/_protos/common/identifier_pb2.py +71 -0
  60. flyte/_protos/common/identifier_pb2.pyi +82 -0
  61. flyte/_protos/common/identifier_pb2_grpc.py +4 -0
  62. flyte/_protos/common/identity_pb2.py +48 -0
  63. flyte/_protos/common/identity_pb2.pyi +72 -0
  64. flyte/_protos/common/identity_pb2_grpc.py +4 -0
  65. flyte/_protos/common/list_pb2.py +36 -0
  66. flyte/_protos/common/list_pb2.pyi +71 -0
  67. flyte/_protos/common/list_pb2_grpc.py +4 -0
  68. flyte/_protos/common/policy_pb2.py +37 -0
  69. flyte/_protos/common/policy_pb2.pyi +27 -0
  70. flyte/_protos/common/policy_pb2_grpc.py +4 -0
  71. flyte/_protos/common/role_pb2.py +37 -0
  72. flyte/_protos/common/role_pb2.pyi +53 -0
  73. flyte/_protos/common/role_pb2_grpc.py +4 -0
  74. flyte/_protos/common/runtime_version_pb2.py +28 -0
  75. flyte/_protos/common/runtime_version_pb2.pyi +24 -0
  76. flyte/_protos/common/runtime_version_pb2_grpc.py +4 -0
  77. flyte/_protos/logs/dataplane/payload_pb2.py +100 -0
  78. flyte/_protos/logs/dataplane/payload_pb2.pyi +177 -0
  79. flyte/_protos/logs/dataplane/payload_pb2_grpc.py +4 -0
  80. flyte/_protos/secret/definition_pb2.py +49 -0
  81. flyte/_protos/secret/definition_pb2.pyi +93 -0
  82. flyte/_protos/secret/definition_pb2_grpc.py +4 -0
  83. flyte/_protos/secret/payload_pb2.py +62 -0
  84. flyte/_protos/secret/payload_pb2.pyi +94 -0
  85. flyte/_protos/secret/payload_pb2_grpc.py +4 -0
  86. flyte/_protos/secret/secret_pb2.py +38 -0
  87. flyte/_protos/secret/secret_pb2.pyi +6 -0
  88. flyte/_protos/secret/secret_pb2_grpc.py +198 -0
  89. flyte/_protos/secret/secret_pb2_grpc_grpc.py +198 -0
  90. flyte/_protos/validate/validate/validate_pb2.py +76 -0
  91. flyte/_protos/workflow/common_pb2.py +27 -0
  92. flyte/_protos/workflow/common_pb2.pyi +14 -0
  93. flyte/_protos/workflow/common_pb2_grpc.py +4 -0
  94. flyte/_protos/workflow/environment_pb2.py +29 -0
  95. flyte/_protos/workflow/environment_pb2.pyi +12 -0
  96. flyte/_protos/workflow/environment_pb2_grpc.py +4 -0
  97. flyte/_protos/workflow/node_execution_service_pb2.py +26 -0
  98. flyte/_protos/workflow/node_execution_service_pb2.pyi +4 -0
  99. flyte/_protos/workflow/node_execution_service_pb2_grpc.py +32 -0
  100. flyte/_protos/workflow/queue_service_pb2.py +105 -0
  101. flyte/_protos/workflow/queue_service_pb2.pyi +146 -0
  102. flyte/_protos/workflow/queue_service_pb2_grpc.py +172 -0
  103. flyte/_protos/workflow/run_definition_pb2.py +128 -0
  104. flyte/_protos/workflow/run_definition_pb2.pyi +314 -0
  105. flyte/_protos/workflow/run_definition_pb2_grpc.py +4 -0
  106. flyte/_protos/workflow/run_logs_service_pb2.py +41 -0
  107. flyte/_protos/workflow/run_logs_service_pb2.pyi +28 -0
  108. flyte/_protos/workflow/run_logs_service_pb2_grpc.py +69 -0
  109. flyte/_protos/workflow/run_service_pb2.py +129 -0
  110. flyte/_protos/workflow/run_service_pb2.pyi +171 -0
  111. flyte/_protos/workflow/run_service_pb2_grpc.py +412 -0
  112. flyte/_protos/workflow/state_service_pb2.py +66 -0
  113. flyte/_protos/workflow/state_service_pb2.pyi +75 -0
  114. flyte/_protos/workflow/state_service_pb2_grpc.py +138 -0
  115. flyte/_protos/workflow/task_definition_pb2.py +79 -0
  116. flyte/_protos/workflow/task_definition_pb2.pyi +81 -0
  117. flyte/_protos/workflow/task_definition_pb2_grpc.py +4 -0
  118. flyte/_protos/workflow/task_service_pb2.py +60 -0
  119. flyte/_protos/workflow/task_service_pb2.pyi +59 -0
  120. flyte/_protos/workflow/task_service_pb2_grpc.py +138 -0
  121. flyte/_resources.py +226 -0
  122. flyte/_retry.py +32 -0
  123. flyte/_reusable_environment.py +25 -0
  124. flyte/_run.py +482 -0
  125. flyte/_secret.py +61 -0
  126. flyte/_task.py +449 -0
  127. flyte/_task_environment.py +183 -0
  128. flyte/_timeout.py +47 -0
  129. flyte/_tools.py +27 -0
  130. flyte/_trace.py +120 -0
  131. flyte/_utils/__init__.py +26 -0
  132. flyte/_utils/asyn.py +119 -0
  133. flyte/_utils/async_cache.py +139 -0
  134. flyte/_utils/coro_management.py +23 -0
  135. flyte/_utils/file_handling.py +72 -0
  136. flyte/_utils/helpers.py +134 -0
  137. flyte/_utils/lazy_module.py +54 -0
  138. flyte/_utils/org_discovery.py +57 -0
  139. flyte/_utils/uv_script_parser.py +49 -0
  140. flyte/_version.py +21 -0
  141. flyte/cli/__init__.py +3 -0
  142. flyte/cli/_abort.py +28 -0
  143. flyte/cli/_common.py +337 -0
  144. flyte/cli/_create.py +145 -0
  145. flyte/cli/_delete.py +23 -0
  146. flyte/cli/_deploy.py +152 -0
  147. flyte/cli/_gen.py +163 -0
  148. flyte/cli/_get.py +310 -0
  149. flyte/cli/_params.py +538 -0
  150. flyte/cli/_run.py +231 -0
  151. flyte/cli/main.py +166 -0
  152. flyte/config/__init__.py +3 -0
  153. flyte/config/_config.py +216 -0
  154. flyte/config/_internal.py +64 -0
  155. flyte/config/_reader.py +207 -0
  156. flyte/connectors/__init__.py +0 -0
  157. flyte/errors.py +172 -0
  158. flyte/extras/__init__.py +5 -0
  159. flyte/extras/_container.py +263 -0
  160. flyte/io/__init__.py +27 -0
  161. flyte/io/_dir.py +448 -0
  162. flyte/io/_file.py +467 -0
  163. flyte/io/_structured_dataset/__init__.py +129 -0
  164. flyte/io/_structured_dataset/basic_dfs.py +219 -0
  165. flyte/io/_structured_dataset/structured_dataset.py +1061 -0
  166. flyte/models.py +391 -0
  167. flyte/remote/__init__.py +26 -0
  168. flyte/remote/_client/__init__.py +0 -0
  169. flyte/remote/_client/_protocols.py +133 -0
  170. flyte/remote/_client/auth/__init__.py +12 -0
  171. flyte/remote/_client/auth/_auth_utils.py +14 -0
  172. flyte/remote/_client/auth/_authenticators/__init__.py +0 -0
  173. flyte/remote/_client/auth/_authenticators/base.py +397 -0
  174. flyte/remote/_client/auth/_authenticators/client_credentials.py +73 -0
  175. flyte/remote/_client/auth/_authenticators/device_code.py +118 -0
  176. flyte/remote/_client/auth/_authenticators/external_command.py +79 -0
  177. flyte/remote/_client/auth/_authenticators/factory.py +200 -0
  178. flyte/remote/_client/auth/_authenticators/pkce.py +516 -0
  179. flyte/remote/_client/auth/_channel.py +215 -0
  180. flyte/remote/_client/auth/_client_config.py +83 -0
  181. flyte/remote/_client/auth/_default_html.py +32 -0
  182. flyte/remote/_client/auth/_grpc_utils/__init__.py +0 -0
  183. flyte/remote/_client/auth/_grpc_utils/auth_interceptor.py +288 -0
  184. flyte/remote/_client/auth/_grpc_utils/default_metadata_interceptor.py +151 -0
  185. flyte/remote/_client/auth/_keyring.py +143 -0
  186. flyte/remote/_client/auth/_token_client.py +260 -0
  187. flyte/remote/_client/auth/errors.py +16 -0
  188. flyte/remote/_client/controlplane.py +95 -0
  189. flyte/remote/_console.py +18 -0
  190. flyte/remote/_data.py +159 -0
  191. flyte/remote/_logs.py +176 -0
  192. flyte/remote/_project.py +85 -0
  193. flyte/remote/_run.py +970 -0
  194. flyte/remote/_secret.py +132 -0
  195. flyte/remote/_task.py +391 -0
  196. flyte/report/__init__.py +3 -0
  197. flyte/report/_report.py +178 -0
  198. flyte/report/_template.html +124 -0
  199. flyte/storage/__init__.py +29 -0
  200. flyte/storage/_config.py +233 -0
  201. flyte/storage/_remote_fs.py +34 -0
  202. flyte/storage/_storage.py +271 -0
  203. flyte/storage/_utils.py +5 -0
  204. flyte/syncify/__init__.py +56 -0
  205. flyte/syncify/_api.py +371 -0
  206. flyte/types/__init__.py +36 -0
  207. flyte/types/_interface.py +40 -0
  208. flyte/types/_pickle.py +118 -0
  209. flyte/types/_renderer.py +162 -0
  210. flyte/types/_string_literals.py +120 -0
  211. flyte/types/_type_engine.py +2287 -0
  212. flyte/types/_utils.py +80 -0
  213. flyte-0.2.0a0.dist-info/METADATA +249 -0
  214. flyte-0.2.0a0.dist-info/RECORD +218 -0
  215. {flyte-0.1.0.dist-info → flyte-0.2.0a0.dist-info}/WHEEL +2 -1
  216. flyte-0.2.0a0.dist-info/entry_points.txt +3 -0
  217. flyte-0.2.0a0.dist-info/top_level.txt +1 -0
  218. flyte-0.1.0.dist-info/METADATA +0 -6
  219. flyte-0.1.0.dist-info/RECORD +0 -5
flyte/_task.py ADDED
@@ -0,0 +1,449 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import weakref
5
+ from dataclasses import dataclass, field, replace
6
+ from functools import cached_property
7
+ from inspect import iscoroutinefunction
8
+ from typing import (
9
+ TYPE_CHECKING,
10
+ Any,
11
+ Callable,
12
+ Coroutine,
13
+ Dict,
14
+ Generic,
15
+ List,
16
+ Literal,
17
+ Optional,
18
+ ParamSpec,
19
+ TypeAlias,
20
+ TypeVar,
21
+ Union,
22
+ )
23
+
24
+ from flyteidl.core.tasks_pb2 import DataLoadingConfig
25
+
26
+ from flyte._pod import PodTemplate
27
+ from flyte.errors import RuntimeSystemError, RuntimeUserError
28
+
29
+ from ._cache import Cache, CacheRequest
30
+ from ._context import internal_ctx
31
+ from ._doc import Documentation
32
+ from ._image import Image
33
+ from ._resources import Resources
34
+ from ._retry import RetryStrategy
35
+ from ._reusable_environment import ReusePolicy
36
+ from ._secret import SecretRequest
37
+ from ._timeout import TimeoutType
38
+ from .models import NativeInterface, SerializationContext
39
+
40
+ if TYPE_CHECKING:
41
+ from ._task_environment import TaskEnvironment
42
+
43
+ P = ParamSpec("P") # capture the function's parameters
44
+ R = TypeVar("R") # return type
45
+
46
+ AsyncFunctionType: TypeAlias = Callable[P, Coroutine[Any, Any, R]]
47
+ SyncFunctionType: TypeAlias = Callable[P, R]
48
+ FunctionTypes: TypeAlias = Union[AsyncFunctionType, SyncFunctionType]
49
+
50
+
51
+ @dataclass(kw_only=True)
52
+ class TaskTemplate(Generic[P, R]):
53
+ """
54
+ Task template is a template for a task that can be executed. It defines various parameters for the task, which
55
+ can be defined statically at the time of task definition or dynamically at the time of task invocation using
56
+ the override method.
57
+
58
+ Example usage:
59
+ ```python
60
+ @task(name="my_task", image="my_image", resources=Resources(cpu="1", memory="1Gi"))
61
+ def my_task():
62
+ pass
63
+ ```
64
+
65
+ :param name: Optional The name of the task (defaults to the function name)
66
+ :param task_type: Router type for the task, this is used to determine how the task will be executed.
67
+ This is usually set to match with th execution plugin.
68
+ :param image: Optional The image to use for the task, if set to "auto" will use the default image for the python
69
+ version with flyte installed
70
+ :param resources: Optional The resources to use for the task
71
+ :param cache: Optional The cache policy for the task, defaults to auto, which will cache the results of the task.
72
+ :param interruptable: Optional The interruptable policy for the task, defaults to False, which means the task
73
+ will not be scheduled on interruptable nodes. If set to True, the task will be scheduled on interruptable nodes,
74
+ and the code should handle interruptions and resumptions.
75
+ :param retries: Optional The number of retries for the task, defaults to 0, which means no retries.
76
+ :param reusable: Optional The reusability policy for the task, defaults to None, which means the task environment
77
+ will not be reused across task invocations.
78
+ :param docs: Optional The documentation for the task, if not provided the function docstring will be used.
79
+ :param env: Optional The environment variables to set for the task.
80
+ :param secrets: Optional The secrets that will be injected into the task at runtime.
81
+ :param timeout: Optional The timeout for the task.
82
+ """
83
+
84
+ name: str
85
+ interface: NativeInterface
86
+ friendly_name: str = ""
87
+ task_type: str = "python"
88
+ task_type_version: int = 0
89
+ image: Union[str, Image, Literal["auto"]] = "auto"
90
+ resources: Optional[Resources] = None
91
+ cache: CacheRequest = "auto"
92
+ interruptable: bool = False
93
+ retries: Union[int, RetryStrategy] = 0
94
+ reusable: Union[ReusePolicy, Literal["auto"], None] = None
95
+ docs: Optional[Documentation] = None
96
+ env: Optional[Dict[str, str]] = None
97
+ secrets: Optional[SecretRequest] = None
98
+ timeout: Optional[TimeoutType] = None
99
+ pod_template: Optional[Union[str, PodTemplate]] = None
100
+ report: bool = False
101
+
102
+ parent_env: Optional[weakref.ReferenceType[TaskEnvironment]] = None
103
+ local: bool = field(default=False, init=False)
104
+ ref: bool = field(default=False, init=False, repr=False, compare=False)
105
+
106
+ # Only used in python 3.10 and 3.11, where we cannot use markcoroutinefunction
107
+ _call_as_synchronous: bool = False
108
+
109
+ def __post_init__(self):
110
+ # Auto set the image based on the image request
111
+ if self.image == "auto":
112
+ self.image = Image.auto()
113
+ elif isinstance(self.image, str):
114
+ self.image = Image.from_prebuilt(str(self.image))
115
+
116
+ # Auto set cache based on the cache request
117
+ if isinstance(self.cache, str):
118
+ match self.cache:
119
+ case "auto":
120
+ self.cache = Cache(behavior="auto")
121
+ case "override":
122
+ self.cache = Cache(behavior="override")
123
+ case "disable":
124
+ self.cache = Cache(behavior="disable")
125
+
126
+ # if retries is set to int, convert to RetryStrategy
127
+ if isinstance(self.retries, int):
128
+ self.retries = RetryStrategy(count=self.retries)
129
+
130
+ if self.friendly_name == "":
131
+ # If friendly_name is not set, use the name of the task
132
+ self.friendly_name = self.name
133
+
134
+ def __getstate__(self):
135
+ """
136
+ This method is called when the object is pickled. We need to remove the parent_env reference
137
+ to avoid circular references.
138
+ """
139
+ state = self.__dict__.copy()
140
+ state.pop("parent_env", None)
141
+ return state
142
+
143
+ def __setstate__(self, state):
144
+ """
145
+ This method is called when the object is unpickled. We need to set the parent_env reference
146
+ to the environment that created the task.
147
+ """
148
+ self.__dict__.update(state)
149
+ self.parent_env = None
150
+
151
+ async def pre(self, *args, **kwargs) -> Dict[str, Any]:
152
+ """
153
+ This is the preexecute function that will be
154
+ called before the task is executed
155
+ """
156
+ return {}
157
+
158
+ async def execute(self, *args, **kwargs) -> Any:
159
+ """
160
+ This is the pure python function that will be executed when the task is called.
161
+ """
162
+ raise NotImplementedError
163
+
164
+ async def post(self, return_vals: Any) -> Any:
165
+ """
166
+ This is the postexecute function that will be
167
+ called after the task is executed
168
+ """
169
+ return return_vals
170
+
171
+ # ---- Extension points ----
172
+ def config(self, sctx: SerializationContext) -> Dict[str, str]:
173
+ """
174
+ Returns additional configuration for the task. This is a set of key-value pairs that can be used to
175
+ configure the task execution environment at runtime. This is usually used by plugins.
176
+ """
177
+ return {}
178
+
179
+ def custom_config(self, sctx: SerializationContext) -> Dict[str, str]:
180
+ """
181
+ Returns additional configuration for the task. This is a set of key-value pairs that can be used to
182
+ configure the task execution environment at runtime. This is usually used by plugins.
183
+ """
184
+ return {}
185
+
186
+ def data_loading_config(self, sctx: SerializationContext) -> DataLoadingConfig:
187
+ """
188
+ This configuration allows executing raw containers in Flyte using the Flyte CoPilot system
189
+ Flyte CoPilot, eliminates the needs of sdk inside the container. Any inputs required by the users container
190
+ are side-loaded in the input_path
191
+ Any outputs generated by the user container - within output_path are automatically uploaded
192
+ """
193
+
194
+ def container_args(self, sctx: SerializationContext) -> List[str]:
195
+ """
196
+ Returns the container args for the task. This is a set of key-value pairs that can be used to
197
+ configure the task execution environment at runtime. This is usually used by plugins.
198
+ """
199
+ return []
200
+
201
+ def sql(self, sctx: SerializationContext) -> Optional[str]:
202
+ """
203
+ Returns the SQL for the task. This is a set of key-value pairs that can be used to
204
+ configure the task execution environment at runtime. This is usually used by plugins.
205
+ """
206
+ return None
207
+
208
+ # ---- Extension points ----
209
+
210
+ @property
211
+ def native_interface(self) -> NativeInterface:
212
+ return self.interface
213
+
214
+ async def aio(self, *args: P.args, **kwargs: P.kwargs) -> Coroutine[Any, Any, R] | R:
215
+ """
216
+ The aio function allows executing "sync" tasks, in an async context. This helps with migrating v1 defined sync
217
+ tasks to be used within an asyncio parent task.
218
+ This function will also re-raise exceptions from the underlying task.
219
+
220
+ Example:
221
+ ```python
222
+ @env.task
223
+ def my_legacy_task(x: int) -> int:
224
+ return x
225
+
226
+ @env.task
227
+ async def my_new_parent_task(n: int) -> List[int]:
228
+ collect = []
229
+ for x in range(n):
230
+ collect.append(my_legacy_task.aio(x))
231
+ return asyncio.gather(*collect)
232
+ ```
233
+ :param args:
234
+ :param kwargs:
235
+ :return:
236
+ """
237
+
238
+ ctx = internal_ctx()
239
+ if ctx.is_task_context():
240
+ from ._internal.controllers import get_controller
241
+
242
+ # If we are in a task context, that implies we are executing a Run.
243
+ # In this scenario, we should submit the task to the controller.
244
+ controller = get_controller()
245
+ if controller:
246
+ if self._call_as_synchronous:
247
+ fut = controller.submit_sync(self, *args, **kwargs)
248
+ asyncio_future = asyncio.wrap_future(fut) # Wrap the future to make it awaitable
249
+ return await asyncio_future
250
+ else:
251
+ return await controller.submit(self, *args, **kwargs)
252
+ else:
253
+ raise RuntimeSystemError("BadContext", "Controller is not initialized.")
254
+ else:
255
+ # Local execute, just stay out of the way, but because .aio is used, we want to return an awaitable,
256
+ # even for synchronous tasks. This is to support migration.
257
+ return self.forward(*args, **kwargs)
258
+
259
+ def __call__(self, *args: P.args, **kwargs: P.kwargs) -> Coroutine[Any, Any, R] | R:
260
+ """
261
+ This is the entrypoint for an async function task at runtime. It will be called during an execution.
262
+ Please do not override this method, if you simply want to modify the execution behavior, override the
263
+ execute method.
264
+
265
+ This needs to be overridable to maybe be async.
266
+ The returned thing from here needs to be an awaitable if the underlying task is async, and a regular object
267
+ if the task is not.
268
+ """
269
+ try:
270
+ ctx = internal_ctx()
271
+ if ctx.is_task_context():
272
+ # If we are in a task context, that implies we are executing a Run.
273
+ # In this scenario, we should submit the task to the controller.
274
+ # We will also check if we are not initialized, It is not expected to be not initialized
275
+ from ._internal.controllers import get_controller
276
+
277
+ controller = get_controller()
278
+ if not controller:
279
+ raise RuntimeSystemError("BadContext", "Controller is not initialized.")
280
+
281
+ if self._call_as_synchronous:
282
+ fut = controller.submit_sync(self, *args, **kwargs)
283
+ x = fut.result(None)
284
+ return x
285
+ else:
286
+ return controller.submit(self, *args, **kwargs)
287
+ else:
288
+ # If not in task context, purely function run, stay out of the way
289
+ return self.forward(*args, **kwargs)
290
+ except RuntimeSystemError:
291
+ raise
292
+ except RuntimeUserError:
293
+ raise
294
+ except Exception as e:
295
+ raise RuntimeUserError(type(e).__name__, str(e)) from e
296
+
297
+ def forward(self, *args: P.args, **kwargs: P.kwargs) -> Coroutine[Any, Any, R] | R:
298
+ """
299
+ Think of this as a local execute method for your task. This function will be invoked by the __call__ method
300
+ when not in a Flyte task execution context. See the implementation below for an example.
301
+
302
+ :param args:
303
+ :param kwargs:
304
+ :return:
305
+ """
306
+ raise NotImplementedError
307
+
308
+ def override(
309
+ self,
310
+ *,
311
+ local: Optional[bool] = None,
312
+ ref: Optional[bool] = None,
313
+ resources: Optional[Resources] = None,
314
+ cache: CacheRequest = "auto",
315
+ retries: Union[int, RetryStrategy] = 0,
316
+ timeout: Optional[TimeoutType] = None,
317
+ reusable: Union[ReusePolicy, Literal["auto"], None] = None,
318
+ env: Optional[Dict[str, str]] = None,
319
+ secrets: Optional[SecretRequest] = None,
320
+ **kwargs: Any,
321
+ ) -> TaskTemplate:
322
+ """
323
+ Override various parameters of the task template. This allows for dynamic configuration of the task
324
+ when it is called, such as changing the image, resources, cache policy, etc.
325
+ """
326
+ resources = resources or self.resources
327
+ cache = cache or self.cache
328
+ retries = retries or self.retries
329
+ timeout = timeout or self.timeout
330
+ reusable = reusable or self.reusable
331
+ env = env or self.env
332
+ secrets = secrets or self.secrets
333
+ local = local or self.local
334
+ ref = ref or self.ref
335
+ for k, v in kwargs.items():
336
+ if k == "name":
337
+ raise ValueError("Name cannot be overridden")
338
+ if k == "image":
339
+ raise ValueError("Image cannot be overridden")
340
+ if k == "docs":
341
+ raise ValueError("Docs cannot be overridden")
342
+ if k == "interface":
343
+ raise ValueError("Interface cannot be overridden")
344
+ return replace(
345
+ self,
346
+ resources=resources,
347
+ cache=cache,
348
+ retries=retries,
349
+ timeout=timeout,
350
+ reusable=reusable,
351
+ env=env,
352
+ secrets=secrets,
353
+ )
354
+
355
+
356
+ @dataclass(kw_only=True)
357
+ class AsyncFunctionTaskTemplate(TaskTemplate[P, R]):
358
+ """
359
+ A task template that wraps an asynchronous functions. This is automatically created when an asynchronous function
360
+ is decorated with the task decorator.
361
+ """
362
+
363
+ func: FunctionTypes
364
+
365
+ def __post_init__(self):
366
+ super().__post_init__()
367
+ if not iscoroutinefunction(self.func):
368
+ self._call_as_synchronous = True
369
+
370
+ @cached_property
371
+ def native_interface(self) -> NativeInterface:
372
+ return NativeInterface.from_callable(self.func)
373
+
374
+ def forward(self, *args: P.args, **kwargs: P.kwargs) -> Coroutine[Any, Any, R] | R:
375
+ # In local execution, we want to just call the function. Note we're not awaiting anything here.
376
+ # If the function was a coroutine function, the coroutine is returned and the await that the caller has
377
+ # in front of the task invocation will handle the awaiting.
378
+ return self.func(*args, **kwargs)
379
+
380
+ async def execute(self, *args: P.args, **kwargs: P.kwargs) -> R:
381
+ """
382
+ This is the execute method that will be called when the task is invoked. It will call the actual function.
383
+ # TODO We may need to keep this as the bare func execute, and need a pre and post execute some other func.
384
+ """
385
+
386
+ ctx = internal_ctx()
387
+ assert ctx.data.task_context is not None, "Function should have already returned if not in a task context"
388
+ ctx_data = await self.pre(*args, **kwargs)
389
+ tctx = ctx.data.task_context.replace(data=ctx_data)
390
+ with ctx.replace_task_context(tctx):
391
+ if iscoroutinefunction(self.func):
392
+ v = await self.func(*args, **kwargs)
393
+ else:
394
+ v = self.func(*args, **kwargs)
395
+ await self.post(v)
396
+ return v
397
+
398
+ def container_args(self, serialize_context: SerializationContext) -> List[str]:
399
+ args = [
400
+ "a0",
401
+ "--inputs",
402
+ serialize_context.input_path,
403
+ "--outputs-path",
404
+ serialize_context.output_path,
405
+ "--version",
406
+ serialize_context.version, # pr: should this be serialize_context.version or code_bundle.version?
407
+ "--raw-data-path",
408
+ "{{.rawOutputDataPrefix}}",
409
+ "--checkpoint-path",
410
+ "{{.checkpointOutputPrefix}}",
411
+ "--prev-checkpoint",
412
+ "{{.prevCheckpointPrefix}}",
413
+ "--run-name",
414
+ "{{.runName}}",
415
+ "--name",
416
+ "{{.actionName}}",
417
+ ]
418
+ # Add on all the known images
419
+ if serialize_context.image_cache and serialize_context.image_cache.serialized_form:
420
+ args = [*args, "--image-cache", serialize_context.image_cache.serialized_form]
421
+ else:
422
+ if serialize_context.image_cache:
423
+ args = [*args, "--image-cache", serialize_context.image_cache.to_transport]
424
+
425
+ if serialize_context.code_bundle:
426
+ if serialize_context.code_bundle.tgz:
427
+ args = [*args, *["--tgz", f"{serialize_context.code_bundle.tgz}"]]
428
+ elif serialize_context.code_bundle.pkl:
429
+ args = [*args, *["--pkl", f"{serialize_context.code_bundle.pkl}"]]
430
+ args = [*args, *["--dest", f"{serialize_context.code_bundle.destination or '.'}"]]
431
+
432
+ if not serialize_context.code_bundle or not serialize_context.code_bundle.pkl:
433
+ # If we do not have a code bundle, or if we have one, but it is not a pkl, we need to add the resolver
434
+
435
+ from flyte._internal.resolvers.default import DefaultTaskResolver
436
+
437
+ _task_resolver = DefaultTaskResolver()
438
+ args = [
439
+ *args,
440
+ *[
441
+ "--resolver",
442
+ _task_resolver.import_path,
443
+ *_task_resolver.loader_args(task=self, root_dir=serialize_context.root_dir),
444
+ ],
445
+ ]
446
+
447
+ assert all(isinstance(item, str) for item in args), f"All args should be strings, non string item = {args}"
448
+
449
+ return args
@@ -0,0 +1,183 @@
1
+ from __future__ import annotations
2
+
3
+ import weakref
4
+ from dataclasses import dataclass, field, replace
5
+ from datetime import timedelta
6
+ from typing import (
7
+ TYPE_CHECKING,
8
+ Any,
9
+ Callable,
10
+ Dict,
11
+ List,
12
+ Literal,
13
+ Optional,
14
+ Union,
15
+ cast,
16
+ )
17
+
18
+ import rich.repr
19
+
20
+ from ._cache import CacheRequest
21
+ from ._doc import Documentation
22
+ from ._environment import Environment
23
+ from ._image import Image
24
+ from ._resources import Resources
25
+ from ._retry import RetryStrategy
26
+ from ._reusable_environment import ReusePolicy
27
+ from ._secret import SecretRequest
28
+ from ._task import AsyncFunctionTaskTemplate, TaskTemplate
29
+ from .models import NativeInterface
30
+
31
+ if TYPE_CHECKING:
32
+ from kubernetes.client import V1PodTemplate
33
+
34
+ from ._task import FunctionTypes, P, R
35
+
36
+
37
+ @rich.repr.auto
38
+ @dataclass(init=True, repr=True)
39
+ class TaskEnvironment(Environment):
40
+ """
41
+ Environment class to define a new environment for a set of tasks.
42
+
43
+ Example usage:
44
+ ```python
45
+ env = flyte.TaskEnvironment(name="my_env", image="my_image", resources=Resources(cpu="1", memory="1Gi"))
46
+
47
+ @env.task
48
+ async def my_task():
49
+ pass
50
+ ```
51
+
52
+ :param name: Name of the environment
53
+ :param image: Docker image to use for the environment. If set to "auto", will use the default image.
54
+ :param resources: Resources to allocate for the environment.
55
+ :param env: Environment variables to set for the environment.
56
+ :param secrets: Secrets to inject into the environment.
57
+ :param depends_on: Environment dependencies to hint, so when you deploy the environment, the dependencies are
58
+ also deployed. This is useful when you have a set of environments that depend on each other.
59
+ :param cache: Cache policy for the environment.
60
+ :param reusable: Reuse policy for the environment, if set, a python process may be reused for multiple tasks.
61
+ """
62
+
63
+ cache: Union[CacheRequest] = "auto"
64
+ reusable: ReusePolicy | None = None
65
+ # TODO Shall we make this union of string or env? This way we can lookup the env by module/file:name
66
+ # TODO also we could add list of files that are used by this environment
67
+
68
+ _tasks: Dict[str, TaskTemplate] = field(default_factory=dict, init=False)
69
+
70
+ def clone_with(
71
+ self,
72
+ name: str,
73
+ image: Optional[Union[str, Image, Literal["auto"]]] = None,
74
+ resources: Optional[Resources] = None,
75
+ env: Optional[Dict[str, str]] = None,
76
+ secrets: Optional[SecretRequest] = None,
77
+ depends_on: Optional[List[Environment]] = None,
78
+ **kwargs: Any,
79
+ ) -> TaskEnvironment:
80
+ """
81
+ Clone the TaskEnvironment with new parameters.
82
+ besides the base environment parameters, you can override, kwargs like `cache`, `reusable`, etc.
83
+
84
+ """
85
+ cache = kwargs.pop("cache", None)
86
+ reusable = kwargs.pop("reusable", None)
87
+
88
+ # validate unknown kwargs if needed
89
+ if kwargs:
90
+ raise TypeError(f"Unexpected keyword arguments: {list(kwargs.keys())}")
91
+
92
+ kwargs = self._get_kwargs()
93
+ kwargs["name"] = name
94
+ if image is not None:
95
+ kwargs["image"] = image
96
+ if resources is not None:
97
+ kwargs["resources"] = resources
98
+ if cache is not None:
99
+ kwargs["cache"] = cache
100
+ if env is not None:
101
+ kwargs["env"] = env
102
+ if reusable is not None:
103
+ kwargs["reusable"] = reusable
104
+ if secrets is not None:
105
+ kwargs["secrets"] = secrets
106
+ if depends_on is not None:
107
+ kwargs["depends_on"] = depends_on
108
+ return replace(self, **kwargs)
109
+
110
+ def task(
111
+ self,
112
+ _func=None,
113
+ *,
114
+ name: Optional[str] = None,
115
+ cache: Union[CacheRequest] | None = None,
116
+ retries: Union[int, RetryStrategy] = 0,
117
+ timeout: Union[timedelta, int] = 0,
118
+ docs: Optional[Documentation] = None,
119
+ secrets: Optional[SecretRequest] = None,
120
+ pod_template: Optional[Union[str, "V1PodTemplate"]] = None,
121
+ report: bool = False,
122
+ ) -> Union[AsyncFunctionTaskTemplate, Callable[P, R]]:
123
+ """
124
+ :param _func: Optional The function to decorate. If not provided, the decorator will return a callable that
125
+ :param name: Optional A friendly name for the task (defaults to the function name)
126
+ :param cache: Optional The cache policy for the task, defaults to auto, which will cache the results of the
127
+ task.
128
+ :param retries: Optional The number of retries for the task, defaults to 0, which means no retries.
129
+ :param docs: Optional The documentation for the task, if not provided the function docstring will be used.
130
+ :param secrets: Optional The secrets that will be injected into the task at runtime.
131
+ :param timeout: Optional The timeout for the task.
132
+ :param pod_template: Optional The pod template for the task, if not provided the default pod template will be
133
+ used.
134
+ :param report: Optional Whether to generate the html report for the task, defaults to False.
135
+ """
136
+ if self.reusable is not None:
137
+ if pod_template is not None:
138
+ raise ValueError("Cannot set pod_template when environment is reusable.")
139
+
140
+ def decorator(func: FunctionTypes) -> AsyncFunctionTaskTemplate[P, R]:
141
+ friendly_name = name or func.__name__
142
+ task_name = self.name + "." + func.__name__
143
+
144
+ tmpl: AsyncFunctionTaskTemplate = AsyncFunctionTaskTemplate(
145
+ func=func,
146
+ name=task_name,
147
+ image=self.image,
148
+ resources=self.resources,
149
+ cache=cache or self.cache,
150
+ retries=retries,
151
+ timeout=timeout,
152
+ reusable=self.reusable,
153
+ docs=docs,
154
+ env=self.env,
155
+ secrets=secrets or self.secrets,
156
+ pod_template=pod_template or self.pod_template,
157
+ parent_env=weakref.ref(self),
158
+ interface=NativeInterface.from_callable(func),
159
+ report=report,
160
+ friendly_name=friendly_name,
161
+ )
162
+ self._tasks[task_name] = tmpl
163
+ return tmpl
164
+
165
+ if _func is None:
166
+ return cast(AsyncFunctionTaskTemplate, decorator)
167
+ return cast(AsyncFunctionTaskTemplate, decorator(_func))
168
+
169
+ @property
170
+ def tasks(self) -> Dict[str, TaskTemplate]:
171
+ """
172
+ Get all tasks defined in the environment.
173
+ """
174
+ return self._tasks
175
+
176
+ def add_task(self, task: TaskTemplate) -> TaskTemplate:
177
+ """
178
+ Add a task to the environment.
179
+ """
180
+ if task.name in self._tasks:
181
+ raise ValueError(f"Task {task.name} already exists in the environment. Task names should be unique.")
182
+ self._tasks[task.name] = task
183
+ return task
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)