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.
- flyte/__init__.py +108 -0
- flyte/_bin/__init__.py +0 -0
- flyte/_bin/debug.py +38 -0
- flyte/_bin/runtime.py +195 -0
- flyte/_bin/serve.py +178 -0
- flyte/_build.py +26 -0
- flyte/_cache/__init__.py +12 -0
- flyte/_cache/cache.py +147 -0
- flyte/_cache/defaults.py +9 -0
- flyte/_cache/local_cache.py +216 -0
- flyte/_cache/policy_function_body.py +42 -0
- flyte/_code_bundle/__init__.py +8 -0
- flyte/_code_bundle/_ignore.py +121 -0
- flyte/_code_bundle/_packaging.py +218 -0
- flyte/_code_bundle/_utils.py +347 -0
- flyte/_code_bundle/bundle.py +266 -0
- flyte/_constants.py +1 -0
- flyte/_context.py +155 -0
- flyte/_custom_context.py +73 -0
- flyte/_debug/__init__.py +0 -0
- flyte/_debug/constants.py +38 -0
- flyte/_debug/utils.py +17 -0
- flyte/_debug/vscode.py +307 -0
- flyte/_deploy.py +408 -0
- flyte/_deployer.py +109 -0
- flyte/_doc.py +29 -0
- flyte/_docstring.py +32 -0
- flyte/_environment.py +122 -0
- flyte/_excepthook.py +37 -0
- flyte/_group.py +32 -0
- flyte/_hash.py +8 -0
- flyte/_image.py +1055 -0
- flyte/_initialize.py +628 -0
- flyte/_interface.py +119 -0
- flyte/_internal/__init__.py +3 -0
- flyte/_internal/controllers/__init__.py +129 -0
- flyte/_internal/controllers/_local_controller.py +239 -0
- flyte/_internal/controllers/_trace.py +48 -0
- flyte/_internal/controllers/remote/__init__.py +58 -0
- flyte/_internal/controllers/remote/_action.py +211 -0
- flyte/_internal/controllers/remote/_client.py +47 -0
- flyte/_internal/controllers/remote/_controller.py +583 -0
- flyte/_internal/controllers/remote/_core.py +465 -0
- flyte/_internal/controllers/remote/_informer.py +381 -0
- flyte/_internal/controllers/remote/_service_protocol.py +50 -0
- flyte/_internal/imagebuild/__init__.py +3 -0
- flyte/_internal/imagebuild/docker_builder.py +706 -0
- flyte/_internal/imagebuild/image_builder.py +277 -0
- flyte/_internal/imagebuild/remote_builder.py +386 -0
- flyte/_internal/imagebuild/utils.py +78 -0
- flyte/_internal/resolvers/__init__.py +0 -0
- flyte/_internal/resolvers/_task_module.py +21 -0
- flyte/_internal/resolvers/common.py +31 -0
- flyte/_internal/resolvers/default.py +28 -0
- flyte/_internal/runtime/__init__.py +0 -0
- flyte/_internal/runtime/convert.py +486 -0
- flyte/_internal/runtime/entrypoints.py +204 -0
- flyte/_internal/runtime/io.py +188 -0
- flyte/_internal/runtime/resources_serde.py +152 -0
- flyte/_internal/runtime/reuse.py +125 -0
- flyte/_internal/runtime/rusty.py +193 -0
- flyte/_internal/runtime/task_serde.py +362 -0
- flyte/_internal/runtime/taskrunner.py +209 -0
- flyte/_internal/runtime/trigger_serde.py +160 -0
- flyte/_internal/runtime/types_serde.py +54 -0
- flyte/_keyring/__init__.py +0 -0
- flyte/_keyring/file.py +115 -0
- flyte/_logging.py +300 -0
- flyte/_map.py +312 -0
- flyte/_module.py +72 -0
- flyte/_pod.py +30 -0
- flyte/_resources.py +473 -0
- flyte/_retry.py +32 -0
- flyte/_reusable_environment.py +102 -0
- flyte/_run.py +724 -0
- flyte/_secret.py +96 -0
- flyte/_task.py +550 -0
- flyte/_task_environment.py +316 -0
- flyte/_task_plugins.py +47 -0
- flyte/_timeout.py +47 -0
- flyte/_tools.py +27 -0
- flyte/_trace.py +119 -0
- flyte/_trigger.py +1000 -0
- flyte/_utils/__init__.py +30 -0
- flyte/_utils/asyn.py +121 -0
- flyte/_utils/async_cache.py +139 -0
- flyte/_utils/coro_management.py +27 -0
- flyte/_utils/docker_credentials.py +173 -0
- flyte/_utils/file_handling.py +72 -0
- flyte/_utils/helpers.py +134 -0
- flyte/_utils/lazy_module.py +54 -0
- flyte/_utils/module_loader.py +104 -0
- flyte/_utils/org_discovery.py +57 -0
- flyte/_utils/uv_script_parser.py +49 -0
- flyte/_version.py +34 -0
- flyte/app/__init__.py +22 -0
- flyte/app/_app_environment.py +157 -0
- flyte/app/_deploy.py +125 -0
- flyte/app/_input.py +160 -0
- flyte/app/_runtime/__init__.py +3 -0
- flyte/app/_runtime/app_serde.py +347 -0
- flyte/app/_types.py +101 -0
- flyte/app/extras/__init__.py +3 -0
- flyte/app/extras/_fastapi.py +151 -0
- flyte/cli/__init__.py +12 -0
- flyte/cli/_abort.py +28 -0
- flyte/cli/_build.py +114 -0
- flyte/cli/_common.py +468 -0
- flyte/cli/_create.py +371 -0
- flyte/cli/_delete.py +45 -0
- flyte/cli/_deploy.py +293 -0
- flyte/cli/_gen.py +176 -0
- flyte/cli/_get.py +370 -0
- flyte/cli/_option.py +33 -0
- flyte/cli/_params.py +554 -0
- flyte/cli/_plugins.py +209 -0
- flyte/cli/_run.py +597 -0
- flyte/cli/_serve.py +64 -0
- flyte/cli/_update.py +37 -0
- flyte/cli/_user.py +17 -0
- flyte/cli/main.py +221 -0
- flyte/config/__init__.py +3 -0
- flyte/config/_config.py +248 -0
- flyte/config/_internal.py +73 -0
- flyte/config/_reader.py +225 -0
- flyte/connectors/__init__.py +11 -0
- flyte/connectors/_connector.py +270 -0
- flyte/connectors/_server.py +197 -0
- flyte/connectors/utils.py +135 -0
- flyte/errors.py +243 -0
- flyte/extend.py +19 -0
- flyte/extras/__init__.py +5 -0
- flyte/extras/_container.py +286 -0
- flyte/git/__init__.py +3 -0
- flyte/git/_config.py +21 -0
- flyte/io/__init__.py +29 -0
- flyte/io/_dataframe/__init__.py +131 -0
- flyte/io/_dataframe/basic_dfs.py +223 -0
- flyte/io/_dataframe/dataframe.py +1026 -0
- flyte/io/_dir.py +910 -0
- flyte/io/_file.py +914 -0
- flyte/io/_hashing_io.py +342 -0
- flyte/models.py +479 -0
- flyte/py.typed +0 -0
- flyte/remote/__init__.py +35 -0
- flyte/remote/_action.py +738 -0
- flyte/remote/_app.py +57 -0
- flyte/remote/_client/__init__.py +0 -0
- flyte/remote/_client/_protocols.py +189 -0
- flyte/remote/_client/auth/__init__.py +12 -0
- flyte/remote/_client/auth/_auth_utils.py +14 -0
- flyte/remote/_client/auth/_authenticators/__init__.py +0 -0
- flyte/remote/_client/auth/_authenticators/base.py +403 -0
- flyte/remote/_client/auth/_authenticators/client_credentials.py +73 -0
- flyte/remote/_client/auth/_authenticators/device_code.py +117 -0
- flyte/remote/_client/auth/_authenticators/external_command.py +79 -0
- flyte/remote/_client/auth/_authenticators/factory.py +200 -0
- flyte/remote/_client/auth/_authenticators/pkce.py +516 -0
- flyte/remote/_client/auth/_channel.py +213 -0
- flyte/remote/_client/auth/_client_config.py +85 -0
- flyte/remote/_client/auth/_default_html.py +32 -0
- flyte/remote/_client/auth/_grpc_utils/__init__.py +0 -0
- flyte/remote/_client/auth/_grpc_utils/auth_interceptor.py +288 -0
- flyte/remote/_client/auth/_grpc_utils/default_metadata_interceptor.py +151 -0
- flyte/remote/_client/auth/_keyring.py +152 -0
- flyte/remote/_client/auth/_token_client.py +260 -0
- flyte/remote/_client/auth/errors.py +16 -0
- flyte/remote/_client/controlplane.py +128 -0
- flyte/remote/_common.py +30 -0
- flyte/remote/_console.py +19 -0
- flyte/remote/_data.py +161 -0
- flyte/remote/_logs.py +185 -0
- flyte/remote/_project.py +88 -0
- flyte/remote/_run.py +386 -0
- flyte/remote/_secret.py +142 -0
- flyte/remote/_task.py +527 -0
- flyte/remote/_trigger.py +306 -0
- flyte/remote/_user.py +33 -0
- flyte/report/__init__.py +3 -0
- flyte/report/_report.py +182 -0
- flyte/report/_template.html +124 -0
- flyte/storage/__init__.py +36 -0
- flyte/storage/_config.py +237 -0
- flyte/storage/_parallel_reader.py +274 -0
- flyte/storage/_remote_fs.py +34 -0
- flyte/storage/_storage.py +456 -0
- flyte/storage/_utils.py +5 -0
- flyte/syncify/__init__.py +56 -0
- flyte/syncify/_api.py +375 -0
- flyte/types/__init__.py +52 -0
- flyte/types/_interface.py +40 -0
- flyte/types/_pickle.py +145 -0
- flyte/types/_renderer.py +162 -0
- flyte/types/_string_literals.py +119 -0
- flyte/types/_type_engine.py +2254 -0
- flyte/types/_utils.py +80 -0
- flyte-2.0.0b32.data/scripts/debug.py +38 -0
- flyte-2.0.0b32.data/scripts/runtime.py +195 -0
- flyte-2.0.0b32.dist-info/METADATA +351 -0
- flyte-2.0.0b32.dist-info/RECORD +204 -0
- flyte-2.0.0b32.dist-info/WHEEL +5 -0
- flyte-2.0.0b32.dist-info/entry_points.txt +7 -0
- flyte-2.0.0b32.dist-info/licenses/LICENSE +201 -0
- 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)
|