flyte 0.2.0b7__py3-none-any.whl → 0.2.0b9__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 +2 -0
- flyte/_context.py +7 -1
- flyte/_environment.py +42 -1
- flyte/_group.py +1 -0
- flyte/_image.py +1 -2
- flyte/_internal/controllers/__init__.py +14 -1
- flyte/_internal/controllers/_local_controller.py +66 -1
- flyte/_internal/controllers/remote/_controller.py +48 -0
- flyte/_internal/controllers/remote/_informer.py +3 -3
- flyte/_internal/runtime/taskrunner.py +2 -1
- flyte/_map.py +215 -0
- flyte/_run.py +26 -26
- flyte/_task.py +101 -13
- flyte/_task_environment.py +48 -66
- flyte/_utils/coro_management.py +0 -2
- flyte/_utils/helpers.py +15 -0
- flyte/_version.py +2 -2
- flyte/cli/__init__.py +0 -7
- flyte/cli/_abort.py +1 -1
- flyte/cli/_common.py +4 -3
- flyte/cli/_create.py +69 -23
- flyte/cli/_delete.py +2 -2
- flyte/cli/_deploy.py +7 -4
- flyte/cli/_gen.py +163 -0
- flyte/cli/_get.py +62 -8
- flyte/cli/_run.py +22 -2
- flyte/cli/main.py +63 -13
- flyte/extras/_container.py +1 -1
- flyte/models.py +10 -1
- flyte/remote/_run.py +1 -0
- flyte/syncify/__init__.py +51 -0
- flyte/syncify/_api.py +48 -21
- {flyte-0.2.0b7.dist-info → flyte-0.2.0b9.dist-info}/METADATA +30 -4
- {flyte-0.2.0b7.dist-info → flyte-0.2.0b9.dist-info}/RECORD +37 -35
- {flyte-0.2.0b7.dist-info → flyte-0.2.0b9.dist-info}/WHEEL +0 -0
- {flyte-0.2.0b7.dist-info → flyte-0.2.0b9.dist-info}/entry_points.txt +0 -0
- {flyte-0.2.0b7.dist-info → flyte-0.2.0b9.dist-info}/top_level.txt +0 -0
flyte/_task.py
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import asyncio
|
|
3
4
|
import weakref
|
|
4
5
|
from dataclasses import dataclass, field, replace
|
|
5
6
|
from functools import cached_property
|
|
7
|
+
from inspect import iscoroutinefunction
|
|
6
8
|
from typing import (
|
|
7
9
|
TYPE_CHECKING,
|
|
8
10
|
Any,
|
|
9
|
-
Awaitable,
|
|
10
11
|
Callable,
|
|
11
12
|
Coroutine,
|
|
12
13
|
Dict,
|
|
@@ -15,6 +16,7 @@ from typing import (
|
|
|
15
16
|
Literal,
|
|
16
17
|
Optional,
|
|
17
18
|
ParamSpec,
|
|
19
|
+
TypeAlias,
|
|
18
20
|
TypeVar,
|
|
19
21
|
Union,
|
|
20
22
|
)
|
|
@@ -42,6 +44,10 @@ if TYPE_CHECKING:
|
|
|
42
44
|
P = ParamSpec("P") # capture the function's parameters
|
|
43
45
|
R = TypeVar("R") # return type
|
|
44
46
|
|
|
47
|
+
AsyncFunctionType: TypeAlias = Callable[P, Coroutine[Any, Any, R]]
|
|
48
|
+
SyncFunctionType: TypeAlias = Callable[P, R]
|
|
49
|
+
FunctionTypes: TypeAlias = Union[AsyncFunctionType, SyncFunctionType]
|
|
50
|
+
|
|
45
51
|
|
|
46
52
|
@dataclass(kw_only=True)
|
|
47
53
|
class TaskTemplate(Generic[P, R]):
|
|
@@ -98,6 +104,9 @@ class TaskTemplate(Generic[P, R]):
|
|
|
98
104
|
local: bool = field(default=False, init=False)
|
|
99
105
|
ref: bool = field(default=False, init=False, repr=False, compare=False)
|
|
100
106
|
|
|
107
|
+
# Only used in python 3.10 and 3.11, where we cannot use markcoroutinefunction
|
|
108
|
+
_call_as_synchronous: bool = False
|
|
109
|
+
|
|
101
110
|
def __post_init__(self):
|
|
102
111
|
# If pod_template is set to a pod, verify
|
|
103
112
|
if self.pod_template is not None and not isinstance(self.pod_template, str):
|
|
@@ -208,13 +217,60 @@ class TaskTemplate(Generic[P, R]):
|
|
|
208
217
|
def native_interface(self) -> NativeInterface:
|
|
209
218
|
return self.interface
|
|
210
219
|
|
|
211
|
-
async def
|
|
220
|
+
async def aio(self, *args: P.args, **kwargs: P.kwargs) -> Coroutine[Any, Any, R] | R:
|
|
221
|
+
"""
|
|
222
|
+
The aio function allows executing "sync" tasks, in an async context. This helps with migrating v1 defined sync
|
|
223
|
+
tasks to be used within an asyncio parent task.
|
|
224
|
+
This function will also re-raise exceptions from the underlying task.
|
|
225
|
+
|
|
226
|
+
Example:
|
|
227
|
+
```python
|
|
228
|
+
@env.task
|
|
229
|
+
def my_legacy_task(x: int) -> int:
|
|
230
|
+
return x
|
|
231
|
+
|
|
232
|
+
@env.task
|
|
233
|
+
async def my_new_parent_task(n: int) -> List[int]:
|
|
234
|
+
collect = []
|
|
235
|
+
for x in range(n):
|
|
236
|
+
collect.append(my_legacy_task.aio(x))
|
|
237
|
+
return asyncio.gather(*collect)
|
|
238
|
+
```
|
|
239
|
+
:param args:
|
|
240
|
+
:param kwargs:
|
|
241
|
+
:return:
|
|
242
|
+
"""
|
|
243
|
+
|
|
244
|
+
ctx = internal_ctx()
|
|
245
|
+
if ctx.is_task_context():
|
|
246
|
+
from ._internal.controllers import get_controller
|
|
247
|
+
|
|
248
|
+
# If we are in a task context, that implies we are executing a Run.
|
|
249
|
+
# In this scenario, we should submit the task to the controller.
|
|
250
|
+
controller = get_controller()
|
|
251
|
+
if controller:
|
|
252
|
+
if self._call_as_synchronous:
|
|
253
|
+
fut = controller.submit_sync(self, *args, **kwargs)
|
|
254
|
+
asyncio_future = asyncio.wrap_future(fut) # Wrap the future to make it awaitable
|
|
255
|
+
return await asyncio_future
|
|
256
|
+
else:
|
|
257
|
+
return await controller.submit(self, *args, **kwargs)
|
|
258
|
+
else:
|
|
259
|
+
raise RuntimeSystemError("BadContext", "Controller is not initialized.")
|
|
260
|
+
else:
|
|
261
|
+
# Local execute, just stay out of the way, but because .aio is used, we want to return an awaitable,
|
|
262
|
+
# even for synchronous tasks. This is to support migration.
|
|
263
|
+
return self.forward(*args, **kwargs)
|
|
264
|
+
|
|
265
|
+
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> Coroutine[Any, Any, R] | R:
|
|
212
266
|
"""
|
|
213
267
|
This is the entrypoint for an async function task at runtime. It will be called during an execution.
|
|
214
268
|
Please do not override this method, if you simply want to modify the execution behavior, override the
|
|
215
269
|
execute method.
|
|
216
270
|
|
|
217
|
-
|
|
271
|
+
This needs to be overridable to maybe be async.
|
|
272
|
+
The returned thing from here needs to be an awaitable if the underlying task is async, and a regular object
|
|
273
|
+
if the task is not.
|
|
218
274
|
"""
|
|
219
275
|
try:
|
|
220
276
|
ctx = internal_ctx()
|
|
@@ -225,9 +281,18 @@ class TaskTemplate(Generic[P, R]):
|
|
|
225
281
|
from ._internal.controllers import get_controller
|
|
226
282
|
|
|
227
283
|
controller = get_controller()
|
|
228
|
-
if controller:
|
|
229
|
-
|
|
230
|
-
|
|
284
|
+
if not controller:
|
|
285
|
+
raise RuntimeSystemError("BadContext", "Controller is not initialized.")
|
|
286
|
+
|
|
287
|
+
if self._call_as_synchronous:
|
|
288
|
+
fut = controller.submit_sync(self, *args, **kwargs)
|
|
289
|
+
x = fut.result(None)
|
|
290
|
+
return x
|
|
291
|
+
else:
|
|
292
|
+
return controller.submit(self, *args, **kwargs)
|
|
293
|
+
else:
|
|
294
|
+
# If not in task context, purely function run, stay out of the way
|
|
295
|
+
return self.forward(*args, **kwargs)
|
|
231
296
|
except RuntimeSystemError:
|
|
232
297
|
raise
|
|
233
298
|
except RuntimeUserError:
|
|
@@ -235,6 +300,17 @@ class TaskTemplate(Generic[P, R]):
|
|
|
235
300
|
except Exception as e:
|
|
236
301
|
raise RuntimeUserError(type(e).__name__, str(e)) from e
|
|
237
302
|
|
|
303
|
+
def forward(self, *args: P.args, **kwargs: P.kwargs) -> Coroutine[Any, Any, R] | R:
|
|
304
|
+
"""
|
|
305
|
+
Think of this as a local execute method for your task. This function will be invoked by the __call__ method
|
|
306
|
+
when not in a Flyte task execution context. See the implementation below for an example.
|
|
307
|
+
|
|
308
|
+
:param args:
|
|
309
|
+
:param kwargs:
|
|
310
|
+
:return:
|
|
311
|
+
"""
|
|
312
|
+
raise NotImplementedError
|
|
313
|
+
|
|
238
314
|
def override(
|
|
239
315
|
self,
|
|
240
316
|
*,
|
|
@@ -290,26 +366,38 @@ class AsyncFunctionTaskTemplate(TaskTemplate[P, R]):
|
|
|
290
366
|
is decorated with the task decorator.
|
|
291
367
|
"""
|
|
292
368
|
|
|
293
|
-
func:
|
|
369
|
+
func: FunctionTypes
|
|
370
|
+
|
|
371
|
+
def __post_init__(self):
|
|
372
|
+
super().__post_init__()
|
|
373
|
+
if not iscoroutinefunction(self.func):
|
|
374
|
+
self._call_as_synchronous = True
|
|
294
375
|
|
|
295
376
|
@cached_property
|
|
296
377
|
def native_interface(self) -> NativeInterface:
|
|
297
378
|
return NativeInterface.from_callable(self.func)
|
|
298
379
|
|
|
380
|
+
def forward(self, *args: P.args, **kwargs: P.kwargs) -> Coroutine[Any, Any, R] | R:
|
|
381
|
+
# In local execution, we want to just call the function. Note we're not awaiting anything here.
|
|
382
|
+
# If the function was a coroutine function, the coroutine is returned and the await that the caller has
|
|
383
|
+
# in front of the task invocation will handle the awaiting.
|
|
384
|
+
return self.func(*args, **kwargs)
|
|
385
|
+
|
|
299
386
|
async def execute(self, *args: P.args, **kwargs: P.kwargs) -> R:
|
|
300
387
|
"""
|
|
301
388
|
This is the execute method that will be called when the task is invoked. It will call the actual function.
|
|
302
389
|
# TODO We may need to keep this as the bare func execute, and need a pre and post execute some other func.
|
|
303
390
|
"""
|
|
391
|
+
|
|
304
392
|
ctx = internal_ctx()
|
|
393
|
+
assert ctx.data.task_context is not None, "Function should have already returned if not in a task context"
|
|
305
394
|
ctx_data = await self.pre(*args, **kwargs)
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
395
|
+
tctx = ctx.data.task_context.replace(data=ctx_data)
|
|
396
|
+
with ctx.replace_task_context(tctx):
|
|
397
|
+
if iscoroutinefunction(self.func):
|
|
309
398
|
v = await self.func(*args, **kwargs)
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
v = await self.func(*args, **kwargs)
|
|
399
|
+
else:
|
|
400
|
+
v = self.func(*args, **kwargs)
|
|
313
401
|
await self.post(v)
|
|
314
402
|
return v
|
|
315
403
|
|
flyte/_task_environment.py
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import asyncio
|
|
4
3
|
import weakref
|
|
5
4
|
from dataclasses import dataclass, field, replace
|
|
6
5
|
from datetime import timedelta
|
|
7
|
-
from
|
|
8
|
-
|
|
6
|
+
from typing import (
|
|
7
|
+
TYPE_CHECKING,
|
|
8
|
+
Any,
|
|
9
|
+
Callable,
|
|
10
|
+
Dict,
|
|
11
|
+
List,
|
|
12
|
+
Literal,
|
|
13
|
+
Optional,
|
|
14
|
+
Union,
|
|
15
|
+
cast,
|
|
16
|
+
)
|
|
9
17
|
|
|
10
18
|
import rich.repr
|
|
11
19
|
|
|
@@ -23,8 +31,7 @@ from .models import NativeInterface
|
|
|
23
31
|
if TYPE_CHECKING:
|
|
24
32
|
from kubernetes.client import V1PodTemplate
|
|
25
33
|
|
|
26
|
-
|
|
27
|
-
R = TypeVar("R") # return type
|
|
34
|
+
from ._task import FunctionTypes, P, R
|
|
28
35
|
|
|
29
36
|
|
|
30
37
|
@rich.repr.auto
|
|
@@ -54,7 +61,7 @@ class TaskEnvironment(Environment):
|
|
|
54
61
|
"""
|
|
55
62
|
|
|
56
63
|
cache: Union[CacheRequest] = "auto"
|
|
57
|
-
reusable:
|
|
64
|
+
reusable: ReusePolicy | None = None
|
|
58
65
|
# TODO Shall we make this union of string or env? This way we can lookup the env by module/file:name
|
|
59
66
|
# TODO also we could add list of files that are used by this environment
|
|
60
67
|
|
|
@@ -65,32 +72,42 @@ class TaskEnvironment(Environment):
|
|
|
65
72
|
name: str,
|
|
66
73
|
image: Optional[Union[str, Image, Literal["auto"]]] = None,
|
|
67
74
|
resources: Optional[Resources] = None,
|
|
68
|
-
cache: Union[CacheRequest, None] = None,
|
|
69
75
|
env: Optional[Dict[str, str]] = None,
|
|
70
|
-
reusable: Union[ReusePolicy, None] = None,
|
|
71
76
|
secrets: Optional[SecretRequest] = None,
|
|
72
77
|
env_dep_hints: Optional[List[Environment]] = None,
|
|
78
|
+
**kwargs: Any,
|
|
73
79
|
) -> TaskEnvironment:
|
|
74
80
|
"""
|
|
75
|
-
Clone the
|
|
81
|
+
Clone the TaskEnvironment with new parameters.
|
|
82
|
+
besides the base environment parameters, you can override, kwargs like `cache`, `reusable`, etc.
|
|
83
|
+
|
|
76
84
|
"""
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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 env_dep_hints is not None:
|
|
107
|
+
kwargs["env_dep_hints"] = env_dep_hints
|
|
108
|
+
return replace(self, **kwargs)
|
|
109
|
+
|
|
110
|
+
def task(
|
|
94
111
|
self,
|
|
95
112
|
_func=None,
|
|
96
113
|
*,
|
|
@@ -104,6 +121,7 @@ class TaskEnvironment(Environment):
|
|
|
104
121
|
report: bool = False,
|
|
105
122
|
) -> Union[AsyncFunctionTaskTemplate, Callable[P, R]]:
|
|
106
123
|
"""
|
|
124
|
+
:param _func: Optional The function to decorate. If not provided, the decorator will return a callable that
|
|
107
125
|
:param name: Optional The name of the task (defaults to the function name)
|
|
108
126
|
:param cache: Optional The cache policy for the task, defaults to auto, which will cache the results of the
|
|
109
127
|
task.
|
|
@@ -119,25 +137,12 @@ class TaskEnvironment(Environment):
|
|
|
119
137
|
if pod_template is not None:
|
|
120
138
|
raise ValueError("Cannot set pod_template when environment is reusable.")
|
|
121
139
|
|
|
122
|
-
def decorator(func:
|
|
140
|
+
def decorator(func: FunctionTypes) -> AsyncFunctionTaskTemplate[P, R]:
|
|
123
141
|
task_name = name or func.__name__
|
|
124
142
|
task_name = self.name + "." + task_name
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
@wraps(func)
|
|
130
|
-
async def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
131
|
-
return await func(*args, **kwargs)
|
|
132
|
-
|
|
133
|
-
if not asyncio.iscoroutinefunction(func):
|
|
134
|
-
raise TypeError(
|
|
135
|
-
f"Function {func.__name__} is not a coroutine function. Use @env.task decorator for async tasks."
|
|
136
|
-
f"You can simply mark your function as async def {func.__name__} to make it a coroutine function, "
|
|
137
|
-
f"it is ok to write sync code in async functions, but not the other way around."
|
|
138
|
-
)
|
|
139
|
-
tmpl = AsyncFunctionTaskTemplate(
|
|
140
|
-
func=wrapper,
|
|
143
|
+
|
|
144
|
+
tmpl: AsyncFunctionTaskTemplate = AsyncFunctionTaskTemplate(
|
|
145
|
+
func=func,
|
|
141
146
|
name=task_name,
|
|
142
147
|
image=self.image,
|
|
143
148
|
resources=self.resources,
|
|
@@ -160,29 +165,6 @@ class TaskEnvironment(Environment):
|
|
|
160
165
|
return cast(AsyncFunctionTaskTemplate, decorator)
|
|
161
166
|
return cast(AsyncFunctionTaskTemplate, decorator(_func))
|
|
162
167
|
|
|
163
|
-
@property
|
|
164
|
-
def task(self) -> Callable:
|
|
165
|
-
"""
|
|
166
|
-
Decorator to create a new task with the environment settings.
|
|
167
|
-
The task will be executed in its own container with the specified image, resources, and environment variables,
|
|
168
|
-
unless reusePolicy is set, in which case the same container will be reused for all tasks with the same
|
|
169
|
-
environment settings.
|
|
170
|
-
|
|
171
|
-
:param name: Optional The name of the task (defaults to the function name)
|
|
172
|
-
:param cache: Optional The cache policy for the task, defaults to auto, which will cache the results of the
|
|
173
|
-
task.
|
|
174
|
-
:param retries: Optional The number of retries for the task, defaults to 0, which means no retries.
|
|
175
|
-
:param docs: Optional The documentation for the task, if not provided the function docstring will be used.
|
|
176
|
-
:param secrets: Optional The secrets that will be injected into the task at runtime.
|
|
177
|
-
:param timeout: Optional The timeout for the task.
|
|
178
|
-
:param pod_template: Optional The pod template for the task, if not provided the default pod template will be
|
|
179
|
-
used.
|
|
180
|
-
:param report: Optional Whether to generate the html report for the task, defaults to False.
|
|
181
|
-
|
|
182
|
-
:return: New Task instance or Task decorator
|
|
183
|
-
"""
|
|
184
|
-
return self._task
|
|
185
|
-
|
|
186
168
|
@property
|
|
187
169
|
def tasks(self) -> Dict[str, TaskTemplate]:
|
|
188
170
|
"""
|
flyte/_utils/coro_management.py
CHANGED
flyte/_utils/helpers.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import string
|
|
3
3
|
import typing
|
|
4
|
+
from contextlib import contextmanager
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
|
|
6
7
|
|
|
@@ -106,3 +107,17 @@ def get_cwd_editable_install() -> typing.Optional[Path]:
|
|
|
106
107
|
return install # note we want the install folder, not the parent
|
|
107
108
|
|
|
108
109
|
return None
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@contextmanager
|
|
113
|
+
def _selector_policy():
|
|
114
|
+
import asyncio
|
|
115
|
+
|
|
116
|
+
original_policy = asyncio.get_event_loop_policy()
|
|
117
|
+
try:
|
|
118
|
+
if os.name == "nt" and hasattr(asyncio, "WindowsSelectorEventLoopPolicy"):
|
|
119
|
+
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
|
120
|
+
|
|
121
|
+
yield
|
|
122
|
+
finally:
|
|
123
|
+
asyncio.set_event_loop_policy(original_policy)
|
flyte/_version.py
CHANGED
|
@@ -17,5 +17,5 @@ __version__: str
|
|
|
17
17
|
__version_tuple__: VERSION_TUPLE
|
|
18
18
|
version_tuple: VERSION_TUPLE
|
|
19
19
|
|
|
20
|
-
__version__ = version = '0.2.
|
|
21
|
-
__version_tuple__ = version_tuple = (0, 2, 0, '
|
|
20
|
+
__version__ = version = '0.2.0b9'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 2, 0, 'b9')
|
flyte/cli/__init__.py
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
"""
|
|
2
|
-
# CLI for Flyte
|
|
3
|
-
|
|
4
|
-
The flyte cli follows a simple verb based structure, where the top-level commands are verbs that describe the action
|
|
5
|
-
to be taken, and the subcommands are nouns that describe the object of the action.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
1
|
from flyte.cli.main import main
|
|
9
2
|
|
|
10
3
|
__all__ = ["main"]
|
flyte/cli/_abort.py
CHANGED
flyte/cli/_common.py
CHANGED
|
@@ -58,7 +58,7 @@ DRY_RUN_OPTION = click.Option(
|
|
|
58
58
|
|
|
59
59
|
def _common_options() -> List[click.Option]:
|
|
60
60
|
"""
|
|
61
|
-
Common options
|
|
61
|
+
Common options that will be added to all commands and groups that inherit from CommandBase or GroupBase.
|
|
62
62
|
"""
|
|
63
63
|
return [PROJECT_OPTION, DOMAIN_OPTION]
|
|
64
64
|
|
|
@@ -74,6 +74,7 @@ class CLIConfig:
|
|
|
74
74
|
"""
|
|
75
75
|
|
|
76
76
|
config: Config
|
|
77
|
+
ctx: click.Context
|
|
77
78
|
log_level: int | None = logging.ERROR
|
|
78
79
|
endpoint: str | None = None
|
|
79
80
|
insecure: bool = False
|
|
@@ -109,8 +110,8 @@ class CLIConfig:
|
|
|
109
110
|
|
|
110
111
|
class InvokeBaseMixin:
|
|
111
112
|
"""
|
|
112
|
-
Mixin to catch grpc.RpcError, flyte.RpcError, other errors and other exceptions
|
|
113
|
-
|
|
113
|
+
Mixin to catch grpc.RpcError, flyte.RpcError, other errors and other exceptions
|
|
114
|
+
and raise them as gclick.ClickException.
|
|
114
115
|
"""
|
|
115
116
|
|
|
116
117
|
def invoke(self, ctx):
|
flyte/cli/_create.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
-
from typing import get_args
|
|
2
|
+
from typing import Any, Dict, get_args
|
|
3
3
|
|
|
4
4
|
import rich_click as click
|
|
5
5
|
|
|
@@ -10,7 +10,7 @@ from flyte.remote._secret import SecretTypes
|
|
|
10
10
|
@click.group(name="create")
|
|
11
11
|
def create():
|
|
12
12
|
"""
|
|
13
|
-
Create
|
|
13
|
+
Create resources in a Flyte deployment.
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
16
|
|
|
@@ -32,7 +32,27 @@ def secret(
|
|
|
32
32
|
domain: str | None = None,
|
|
33
33
|
):
|
|
34
34
|
"""
|
|
35
|
-
Create a new secret.
|
|
35
|
+
Create a new secret. The name of the secret is required. For example:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
$ flyte create secret my_secret --value my_value
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
If `--from-file` is specified, the value will be read from the file instead of being provided directly:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
$ flyte create secret my_secret --from-file /path/to/secret_file
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The `--type` option can be used to create specific types of secrets.
|
|
48
|
+
Either `regular` or `image_pull` can be specified.
|
|
49
|
+
Secrets intended to access container images should be specified as `image_pull`.
|
|
50
|
+
Other secrets should be specified as `regular`.
|
|
51
|
+
If no type is specified, `regular` is assumed.
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
$ flyte create secret my_secret --type image_pull
|
|
55
|
+
```
|
|
36
56
|
"""
|
|
37
57
|
from flyte.remote import Secret
|
|
38
58
|
|
|
@@ -45,19 +65,29 @@ def secret(
|
|
|
45
65
|
|
|
46
66
|
@create.command(cls=common.CommandBase)
|
|
47
67
|
@click.option("--endpoint", type=str, help="Endpoint of the Flyte backend.")
|
|
48
|
-
@click.option("--insecure", is_flag=True, help="Use insecure connection to the Flyte backend.")
|
|
68
|
+
@click.option("--insecure", is_flag=True, help="Use an insecure connection to the Flyte backend.")
|
|
49
69
|
@click.option(
|
|
50
70
|
"--org",
|
|
51
71
|
type=str,
|
|
52
72
|
required=False,
|
|
53
|
-
help="Organization to use, this will override the organization in the
|
|
73
|
+
help="Organization to use, this will override the organization in the configusraion file.",
|
|
54
74
|
)
|
|
55
75
|
@click.option(
|
|
56
76
|
"-o",
|
|
57
77
|
"--output",
|
|
58
|
-
type=click.Path(),
|
|
59
|
-
default=Path.cwd(),
|
|
60
|
-
help="Path to the output
|
|
78
|
+
type=click.Path(exists=False, writable=True),
|
|
79
|
+
default=Path.cwd() / "config.yaml",
|
|
80
|
+
help="Path to the output directory where the configuration will be saved. Defaults to current directory.",
|
|
81
|
+
show_default=True,
|
|
82
|
+
)
|
|
83
|
+
@click.option(
|
|
84
|
+
"--force",
|
|
85
|
+
is_flag=True,
|
|
86
|
+
default=False,
|
|
87
|
+
help="Force overwrite of the configuration file if it already exists.",
|
|
88
|
+
show_default=True,
|
|
89
|
+
prompt="Are you sure you want to overwrite the configuration file?",
|
|
90
|
+
confirmation_prompt=True,
|
|
61
91
|
)
|
|
62
92
|
def config(
|
|
63
93
|
output: Path,
|
|
@@ -66,25 +96,41 @@ def config(
|
|
|
66
96
|
org: str | None = None,
|
|
67
97
|
project: str | None = None,
|
|
68
98
|
domain: str | None = None,
|
|
99
|
+
force: bool = False,
|
|
69
100
|
):
|
|
70
101
|
"""
|
|
71
|
-
|
|
102
|
+
Creates a configuration file for Flyte CLI.
|
|
103
|
+
If the `--output` option is not specified, it will create a file named `config.yaml` in the current directory.
|
|
104
|
+
If the file already exists, it will raise an error unless the `--force` option is used.
|
|
72
105
|
"""
|
|
73
106
|
import yaml
|
|
74
107
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
108
|
+
if output.exists() and not force:
|
|
109
|
+
raise click.BadParameter(f"Output file {output} already exists. Use --force to overwrite.")
|
|
110
|
+
|
|
111
|
+
admin: Dict[str, Any] = {}
|
|
112
|
+
if endpoint:
|
|
113
|
+
admin["endpoint"] = endpoint
|
|
114
|
+
if insecure:
|
|
115
|
+
admin["insecure"] = insecure
|
|
116
|
+
|
|
117
|
+
task: Dict[str, str] = {}
|
|
118
|
+
if org:
|
|
119
|
+
task["org"] = org
|
|
120
|
+
if project:
|
|
121
|
+
task["project"] = project
|
|
122
|
+
if domain:
|
|
123
|
+
task["domain"] = domain
|
|
124
|
+
|
|
125
|
+
if not admin and not task:
|
|
126
|
+
raise click.BadParameter("At least one of --endpoint or --org must be provided.")
|
|
127
|
+
|
|
128
|
+
with open(output, "w") as f:
|
|
129
|
+
d: Dict[str, Any] = {}
|
|
130
|
+
if admin:
|
|
131
|
+
d["admin"] = admin
|
|
132
|
+
if task:
|
|
133
|
+
d["task"] = task
|
|
88
134
|
yaml.dump(d, f)
|
|
89
135
|
|
|
90
|
-
click.echo(f"Config file created at {
|
|
136
|
+
click.echo(f"Config file created at {output}")
|
flyte/cli/_delete.py
CHANGED
|
@@ -6,7 +6,7 @@ import flyte.cli._common as common
|
|
|
6
6
|
@click.group(name="delete")
|
|
7
7
|
def delete():
|
|
8
8
|
"""
|
|
9
|
-
|
|
9
|
+
Remove resources from a Flyte deployment.
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
|
|
@@ -15,7 +15,7 @@ def delete():
|
|
|
15
15
|
@click.pass_obj
|
|
16
16
|
def secret(cfg: common.CLIConfig, name: str, project: str | None = None, domain: str | None = None):
|
|
17
17
|
"""
|
|
18
|
-
Delete a secret.
|
|
18
|
+
Delete a secret. The name of the secret is required.
|
|
19
19
|
"""
|
|
20
20
|
from flyte.remote import Secret
|
|
21
21
|
|
flyte/cli/_deploy.py
CHANGED
|
@@ -61,7 +61,7 @@ class DeployArguments:
|
|
|
61
61
|
@classmethod
|
|
62
62
|
def options(cls) -> List[click.Option]:
|
|
63
63
|
"""
|
|
64
|
-
Return the set of base parameters added to every
|
|
64
|
+
Return the set of base parameters added to every flyte run workflow subcommand.
|
|
65
65
|
"""
|
|
66
66
|
return [common.get_option_from_metadata(f.metadata) for f in fields(cls) if f.metadata]
|
|
67
67
|
|
|
@@ -87,7 +87,7 @@ class DeployEnvCommand(click.Command):
|
|
|
87
87
|
|
|
88
88
|
class EnvPerFileGroup(common.ObjectsPerFileGroup):
|
|
89
89
|
"""
|
|
90
|
-
Group that creates a command for each task in the current directory that is not __init__.py
|
|
90
|
+
Group that creates a command for each task in the current directory that is not `__init__.py`.
|
|
91
91
|
"""
|
|
92
92
|
|
|
93
93
|
def __init__(self, filename: Path, deploy_args: DeployArguments, *args, **kwargs):
|
|
@@ -111,7 +111,7 @@ class EnvPerFileGroup(common.ObjectsPerFileGroup):
|
|
|
111
111
|
|
|
112
112
|
class EnvFiles(common.FileGroup):
|
|
113
113
|
"""
|
|
114
|
-
Group that creates a command for each file in the current directory that is not __init__.py
|
|
114
|
+
Group that creates a command for each file in the current directory that is not `__init__.py`.
|
|
115
115
|
"""
|
|
116
116
|
|
|
117
117
|
common_options_enabled = False
|
|
@@ -138,5 +138,8 @@ class EnvFiles(common.FileGroup):
|
|
|
138
138
|
|
|
139
139
|
deploy = EnvFiles(
|
|
140
140
|
name="deploy",
|
|
141
|
-
help="
|
|
141
|
+
help="""
|
|
142
|
+
Deploy one or more environments from a python file.
|
|
143
|
+
The deploy command will create or update environments in the Flyte system.
|
|
144
|
+
""",
|
|
142
145
|
)
|