flyte 2.0.0b23__py3-none-any.whl → 2.0.0b25__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 +11 -2
- flyte/_cache/local_cache.py +4 -3
- flyte/_code_bundle/_utils.py +3 -3
- flyte/_code_bundle/bundle.py +12 -5
- flyte/_context.py +4 -1
- flyte/_custom_context.py +73 -0
- flyte/_deploy.py +31 -7
- flyte/_image.py +48 -16
- flyte/_initialize.py +69 -26
- flyte/_internal/controllers/_local_controller.py +1 -0
- flyte/_internal/controllers/_trace.py +1 -1
- flyte/_internal/controllers/remote/_action.py +9 -10
- flyte/_internal/controllers/remote/_client.py +1 -1
- flyte/_internal/controllers/remote/_controller.py +4 -2
- flyte/_internal/controllers/remote/_core.py +10 -13
- flyte/_internal/controllers/remote/_informer.py +3 -3
- flyte/_internal/controllers/remote/_service_protocol.py +7 -7
- flyte/_internal/imagebuild/docker_builder.py +45 -59
- flyte/_internal/imagebuild/remote_builder.py +51 -11
- flyte/_internal/imagebuild/utils.py +51 -3
- flyte/_internal/runtime/convert.py +39 -18
- flyte/_internal/runtime/io.py +8 -7
- flyte/_internal/runtime/resources_serde.py +20 -6
- flyte/_internal/runtime/reuse.py +1 -1
- flyte/_internal/runtime/task_serde.py +7 -10
- flyte/_internal/runtime/taskrunner.py +10 -1
- flyte/_internal/runtime/trigger_serde.py +13 -13
- flyte/_internal/runtime/types_serde.py +1 -1
- flyte/_keyring/file.py +2 -2
- flyte/_map.py +65 -13
- flyte/_pod.py +2 -2
- flyte/_resources.py +175 -31
- flyte/_run.py +37 -21
- flyte/_task.py +27 -6
- flyte/_task_environment.py +37 -10
- flyte/_utils/module_loader.py +2 -2
- flyte/_version.py +3 -3
- flyte/cli/_common.py +47 -5
- flyte/cli/_create.py +4 -0
- flyte/cli/_deploy.py +8 -0
- flyte/cli/_get.py +4 -0
- flyte/cli/_params.py +4 -4
- flyte/cli/_run.py +50 -7
- flyte/cli/_update.py +4 -3
- flyte/config/_config.py +2 -0
- flyte/config/_internal.py +1 -0
- flyte/config/_reader.py +3 -3
- flyte/errors.py +1 -1
- flyte/extend.py +4 -0
- flyte/extras/_container.py +6 -1
- flyte/git/_config.py +11 -9
- flyte/io/_dataframe/basic_dfs.py +1 -1
- flyte/io/_dataframe/dataframe.py +12 -8
- flyte/io/_dir.py +48 -15
- flyte/io/_file.py +48 -11
- flyte/models.py +12 -8
- flyte/remote/_action.py +18 -16
- flyte/remote/_client/_protocols.py +4 -3
- flyte/remote/_client/auth/_channel.py +1 -1
- flyte/remote/_client/controlplane.py +4 -8
- flyte/remote/_data.py +4 -3
- flyte/remote/_logs.py +3 -3
- flyte/remote/_run.py +5 -5
- flyte/remote/_secret.py +20 -13
- flyte/remote/_task.py +7 -8
- flyte/remote/_trigger.py +25 -27
- flyte/storage/_parallel_reader.py +274 -0
- flyte/storage/_storage.py +66 -2
- flyte/types/_interface.py +2 -2
- flyte/types/_pickle.py +1 -1
- flyte/types/_string_literals.py +8 -9
- flyte/types/_type_engine.py +25 -17
- flyte/types/_utils.py +1 -1
- {flyte-2.0.0b23.dist-info → flyte-2.0.0b25.dist-info}/METADATA +2 -1
- flyte-2.0.0b25.dist-info/RECORD +184 -0
- flyte/_protos/__init__.py +0 -0
- flyte/_protos/common/authorization_pb2.py +0 -66
- flyte/_protos/common/authorization_pb2.pyi +0 -108
- flyte/_protos/common/authorization_pb2_grpc.py +0 -4
- flyte/_protos/common/identifier_pb2.py +0 -117
- flyte/_protos/common/identifier_pb2.pyi +0 -142
- flyte/_protos/common/identifier_pb2_grpc.py +0 -4
- flyte/_protos/common/identity_pb2.py +0 -48
- flyte/_protos/common/identity_pb2.pyi +0 -72
- flyte/_protos/common/identity_pb2_grpc.py +0 -4
- flyte/_protos/common/list_pb2.py +0 -36
- flyte/_protos/common/list_pb2.pyi +0 -71
- flyte/_protos/common/list_pb2_grpc.py +0 -4
- flyte/_protos/common/policy_pb2.py +0 -37
- flyte/_protos/common/policy_pb2.pyi +0 -27
- flyte/_protos/common/policy_pb2_grpc.py +0 -4
- flyte/_protos/common/role_pb2.py +0 -37
- flyte/_protos/common/role_pb2.pyi +0 -53
- flyte/_protos/common/role_pb2_grpc.py +0 -4
- flyte/_protos/common/runtime_version_pb2.py +0 -28
- flyte/_protos/common/runtime_version_pb2.pyi +0 -24
- flyte/_protos/common/runtime_version_pb2_grpc.py +0 -4
- flyte/_protos/imagebuilder/definition_pb2.py +0 -60
- flyte/_protos/imagebuilder/definition_pb2.pyi +0 -153
- flyte/_protos/imagebuilder/definition_pb2_grpc.py +0 -4
- flyte/_protos/imagebuilder/payload_pb2.py +0 -32
- flyte/_protos/imagebuilder/payload_pb2.pyi +0 -21
- flyte/_protos/imagebuilder/payload_pb2_grpc.py +0 -4
- flyte/_protos/imagebuilder/service_pb2.py +0 -29
- flyte/_protos/imagebuilder/service_pb2.pyi +0 -5
- flyte/_protos/imagebuilder/service_pb2_grpc.py +0 -66
- flyte/_protos/logs/dataplane/payload_pb2.py +0 -100
- flyte/_protos/logs/dataplane/payload_pb2.pyi +0 -177
- flyte/_protos/logs/dataplane/payload_pb2_grpc.py +0 -4
- flyte/_protos/secret/definition_pb2.py +0 -49
- flyte/_protos/secret/definition_pb2.pyi +0 -93
- flyte/_protos/secret/definition_pb2_grpc.py +0 -4
- flyte/_protos/secret/payload_pb2.py +0 -62
- flyte/_protos/secret/payload_pb2.pyi +0 -94
- flyte/_protos/secret/payload_pb2_grpc.py +0 -4
- flyte/_protos/secret/secret_pb2.py +0 -38
- flyte/_protos/secret/secret_pb2.pyi +0 -6
- flyte/_protos/secret/secret_pb2_grpc.py +0 -198
- flyte/_protos/validate/validate/validate_pb2.py +0 -76
- flyte/_protos/workflow/common_pb2.py +0 -38
- flyte/_protos/workflow/common_pb2.pyi +0 -63
- flyte/_protos/workflow/common_pb2_grpc.py +0 -4
- flyte/_protos/workflow/environment_pb2.py +0 -29
- flyte/_protos/workflow/environment_pb2.pyi +0 -12
- flyte/_protos/workflow/environment_pb2_grpc.py +0 -4
- flyte/_protos/workflow/node_execution_service_pb2.py +0 -26
- flyte/_protos/workflow/node_execution_service_pb2.pyi +0 -4
- flyte/_protos/workflow/node_execution_service_pb2_grpc.py +0 -32
- flyte/_protos/workflow/queue_service_pb2.py +0 -117
- flyte/_protos/workflow/queue_service_pb2.pyi +0 -182
- flyte/_protos/workflow/queue_service_pb2_grpc.py +0 -206
- flyte/_protos/workflow/run_definition_pb2.py +0 -123
- flyte/_protos/workflow/run_definition_pb2.pyi +0 -354
- flyte/_protos/workflow/run_definition_pb2_grpc.py +0 -4
- flyte/_protos/workflow/run_logs_service_pb2.py +0 -41
- flyte/_protos/workflow/run_logs_service_pb2.pyi +0 -28
- flyte/_protos/workflow/run_logs_service_pb2_grpc.py +0 -69
- flyte/_protos/workflow/run_service_pb2.py +0 -147
- flyte/_protos/workflow/run_service_pb2.pyi +0 -203
- flyte/_protos/workflow/run_service_pb2_grpc.py +0 -480
- flyte/_protos/workflow/state_service_pb2.py +0 -67
- flyte/_protos/workflow/state_service_pb2.pyi +0 -76
- flyte/_protos/workflow/state_service_pb2_grpc.py +0 -138
- flyte/_protos/workflow/task_definition_pb2.py +0 -86
- flyte/_protos/workflow/task_definition_pb2.pyi +0 -105
- flyte/_protos/workflow/task_definition_pb2_grpc.py +0 -4
- flyte/_protos/workflow/task_service_pb2.py +0 -61
- flyte/_protos/workflow/task_service_pb2.pyi +0 -62
- flyte/_protos/workflow/task_service_pb2_grpc.py +0 -138
- flyte/_protos/workflow/trigger_definition_pb2.py +0 -66
- flyte/_protos/workflow/trigger_definition_pb2.pyi +0 -117
- flyte/_protos/workflow/trigger_definition_pb2_grpc.py +0 -4
- flyte/_protos/workflow/trigger_service_pb2.py +0 -96
- flyte/_protos/workflow/trigger_service_pb2.pyi +0 -110
- flyte/_protos/workflow/trigger_service_pb2_grpc.py +0 -281
- flyte-2.0.0b23.dist-info/RECORD +0 -262
- {flyte-2.0.0b23.data → flyte-2.0.0b25.data}/scripts/debug.py +0 -0
- {flyte-2.0.0b23.data → flyte-2.0.0b25.data}/scripts/runtime.py +0 -0
- {flyte-2.0.0b23.dist-info → flyte-2.0.0b25.dist-info}/WHEEL +0 -0
- {flyte-2.0.0b23.dist-info → flyte-2.0.0b25.dist-info}/entry_points.txt +0 -0
- {flyte-2.0.0b23.dist-info → flyte-2.0.0b25.dist-info}/licenses/LICENSE +0 -0
- {flyte-2.0.0b23.dist-info → flyte-2.0.0b25.dist-info}/top_level.txt +0 -0
flyte/__init__.py
CHANGED
|
@@ -9,15 +9,16 @@ import sys
|
|
|
9
9
|
from ._build import build
|
|
10
10
|
from ._cache import Cache, CachePolicy, CacheRequest
|
|
11
11
|
from ._context import ctx
|
|
12
|
+
from ._custom_context import custom_context, get_custom_context
|
|
12
13
|
from ._deploy import build_images, deploy
|
|
13
14
|
from ._environment import Environment
|
|
14
15
|
from ._excepthook import custom_excepthook
|
|
15
16
|
from ._group import group
|
|
16
17
|
from ._image import Image
|
|
17
|
-
from ._initialize import init, init_from_config
|
|
18
|
+
from ._initialize import current_domain, init, init_from_config
|
|
18
19
|
from ._map import map
|
|
19
20
|
from ._pod import PodTemplate
|
|
20
|
-
from ._resources import GPU, TPU, Device, Resources
|
|
21
|
+
from ._resources import AMD_GPU, GPU, HABANA_GAUDI, TPU, Device, DeviceClass, Neuron, Resources
|
|
21
22
|
from ._retry import RetryStrategy
|
|
22
23
|
from ._reusable_environment import ReusePolicy
|
|
23
24
|
from ._run import run, with_runcontext
|
|
@@ -60,16 +61,20 @@ def version() -> str:
|
|
|
60
61
|
|
|
61
62
|
|
|
62
63
|
__all__ = [
|
|
64
|
+
"AMD_GPU",
|
|
63
65
|
"GPU",
|
|
66
|
+
"HABANA_GAUDI",
|
|
64
67
|
"TPU",
|
|
65
68
|
"Cache",
|
|
66
69
|
"CachePolicy",
|
|
67
70
|
"CacheRequest",
|
|
68
71
|
"Cron",
|
|
69
72
|
"Device",
|
|
73
|
+
"DeviceClass",
|
|
70
74
|
"Environment",
|
|
71
75
|
"FixedRate",
|
|
72
76
|
"Image",
|
|
77
|
+
"Neuron",
|
|
73
78
|
"PodTemplate",
|
|
74
79
|
"Resources",
|
|
75
80
|
"RetryStrategy",
|
|
@@ -85,12 +90,16 @@ __all__ = [
|
|
|
85
90
|
"build",
|
|
86
91
|
"build_images",
|
|
87
92
|
"ctx",
|
|
93
|
+
"current_domain",
|
|
94
|
+
"custom_context",
|
|
88
95
|
"deploy",
|
|
96
|
+
"get_custom_context",
|
|
89
97
|
"group",
|
|
90
98
|
"init",
|
|
91
99
|
"init_from_config",
|
|
92
100
|
"map",
|
|
93
101
|
"run",
|
|
94
102
|
"trace",
|
|
103
|
+
"version",
|
|
95
104
|
"with_runcontext",
|
|
96
105
|
]
|
flyte/_cache/local_cache.py
CHANGED
|
@@ -8,9 +8,10 @@ try:
|
|
|
8
8
|
except ImportError:
|
|
9
9
|
HAS_AIOSQLITE = False
|
|
10
10
|
|
|
11
|
+
from flyteidl2.task import common_pb2
|
|
12
|
+
|
|
11
13
|
from flyte._internal.runtime import convert
|
|
12
14
|
from flyte._logging import logger
|
|
13
|
-
from flyte._protos.workflow import run_definition_pb2
|
|
14
15
|
from flyte.config import auto
|
|
15
16
|
|
|
16
17
|
DEFAULT_CACHE_DIR = "~/.flyte"
|
|
@@ -127,7 +128,7 @@ class LocalTaskCache(object):
|
|
|
127
128
|
row = await cursor.fetchone()
|
|
128
129
|
if row:
|
|
129
130
|
outputs_bytes = row[0]
|
|
130
|
-
outputs =
|
|
131
|
+
outputs = common_pb2.Outputs()
|
|
131
132
|
outputs.ParseFromString(outputs_bytes)
|
|
132
133
|
return convert.Outputs(proto_outputs=outputs)
|
|
133
134
|
return None
|
|
@@ -142,7 +143,7 @@ class LocalTaskCache(object):
|
|
|
142
143
|
row = cursor.fetchone()
|
|
143
144
|
if row:
|
|
144
145
|
outputs_bytes = row[0]
|
|
145
|
-
outputs =
|
|
146
|
+
outputs = common_pb2.Outputs()
|
|
146
147
|
outputs.ParseFromString(outputs_bytes)
|
|
147
148
|
return convert.Outputs(proto_outputs=outputs)
|
|
148
149
|
return None
|
flyte/_code_bundle/_utils.py
CHANGED
|
@@ -211,7 +211,7 @@ def list_imported_modules_as_files(source_path: str, modules: List[ModuleType])
|
|
|
211
211
|
import flyte
|
|
212
212
|
from flyte._utils.lazy_module import is_imported
|
|
213
213
|
|
|
214
|
-
files =
|
|
214
|
+
files = set()
|
|
215
215
|
flyte_root = os.path.dirname(flyte.__file__)
|
|
216
216
|
|
|
217
217
|
# These directories contain installed packages or modules from the Python standard library.
|
|
@@ -244,9 +244,9 @@ def list_imported_modules_as_files(source_path: str, modules: List[ModuleType])
|
|
|
244
244
|
logger.debug(f"{mod_file} is not in {source_path}")
|
|
245
245
|
continue
|
|
246
246
|
|
|
247
|
-
files.
|
|
247
|
+
files.add(mod_file)
|
|
248
248
|
|
|
249
|
-
return files
|
|
249
|
+
return list(files)
|
|
250
250
|
|
|
251
251
|
|
|
252
252
|
def add_imported_modules_from_source(source_path: str, destination: str, modules: List[ModuleType]):
|
flyte/_code_bundle/bundle.py
CHANGED
|
@@ -8,7 +8,7 @@ from pathlib import Path
|
|
|
8
8
|
from typing import ClassVar, Type
|
|
9
9
|
|
|
10
10
|
from async_lru import alru_cache
|
|
11
|
-
from
|
|
11
|
+
from flyteidl2.core.tasks_pb2 import TaskTemplate
|
|
12
12
|
|
|
13
13
|
from flyte._logging import log, logger
|
|
14
14
|
from flyte._utils import AsyncLRUCache
|
|
@@ -169,6 +169,8 @@ async def download_bundle(bundle: CodeBundle) -> pathlib.Path:
|
|
|
169
169
|
|
|
170
170
|
:return: The path to the downloaded code bundle.
|
|
171
171
|
"""
|
|
172
|
+
import sys
|
|
173
|
+
|
|
172
174
|
import flyte.storage as storage
|
|
173
175
|
|
|
174
176
|
dest = pathlib.Path(bundle.destination)
|
|
@@ -185,17 +187,22 @@ async def download_bundle(bundle: CodeBundle) -> pathlib.Path:
|
|
|
185
187
|
# NOTE the os.path.join(destination, ''). This is to ensure that the given path is in fact a directory and all
|
|
186
188
|
# downloaded data should be copied into this directory. We do this to account for a difference in behavior in
|
|
187
189
|
# fsspec, which requires a trailing slash in case of pre-existing directory.
|
|
188
|
-
|
|
189
|
-
"tar",
|
|
190
|
-
"--overwrite",
|
|
190
|
+
args = [
|
|
191
191
|
"-xvf",
|
|
192
192
|
str(downloaded_bundle),
|
|
193
193
|
"-C",
|
|
194
194
|
str(dest),
|
|
195
|
+
]
|
|
196
|
+
if sys.platform != "darwin":
|
|
197
|
+
args.insert(0, "--overwrite")
|
|
198
|
+
|
|
199
|
+
process = await asyncio.create_subprocess_exec(
|
|
200
|
+
"tar",
|
|
201
|
+
*args,
|
|
195
202
|
stdout=asyncio.subprocess.PIPE,
|
|
196
203
|
stderr=asyncio.subprocess.PIPE,
|
|
197
204
|
)
|
|
198
|
-
|
|
205
|
+
_stdout, stderr = await process.communicate()
|
|
199
206
|
|
|
200
207
|
if process.returncode != 0:
|
|
201
208
|
raise RuntimeError(stderr.decode())
|
flyte/_context.py
CHANGED
|
@@ -135,7 +135,10 @@ root_context_var = contextvars.ContextVar("root", default=Context(data=ContextDa
|
|
|
135
135
|
|
|
136
136
|
|
|
137
137
|
def ctx() -> Optional[TaskContext]:
|
|
138
|
-
"""
|
|
138
|
+
"""
|
|
139
|
+
Returns flyte.models.TaskContext if within a task context, else None
|
|
140
|
+
Note: Only use this in task code and not module level.
|
|
141
|
+
"""
|
|
139
142
|
return internal_ctx().data.task_context
|
|
140
143
|
|
|
141
144
|
|
flyte/_custom_context.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from contextlib import contextmanager
|
|
4
|
+
|
|
5
|
+
from flyte._context import ctx
|
|
6
|
+
|
|
7
|
+
from ._context import internal_ctx
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_custom_context() -> dict[str, str]:
|
|
11
|
+
"""
|
|
12
|
+
Get the current input context. This can be used within a task to retrieve
|
|
13
|
+
context metadata that was passed to the action.
|
|
14
|
+
|
|
15
|
+
Context will automatically propagate to sub-actions.
|
|
16
|
+
|
|
17
|
+
Example:
|
|
18
|
+
```python
|
|
19
|
+
import flyte
|
|
20
|
+
|
|
21
|
+
env = flyte.TaskEnvironment(name="...")
|
|
22
|
+
|
|
23
|
+
@env.task
|
|
24
|
+
def t1():
|
|
25
|
+
# context can be retrieved with `get_custom_context`
|
|
26
|
+
ctx = flyte.get_custom_context()
|
|
27
|
+
print(ctx) # {'project': '...', 'entity': '...'}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
:return: Dictionary of context key-value pairs
|
|
31
|
+
"""
|
|
32
|
+
tctx = ctx()
|
|
33
|
+
if tctx is None or tctx.custom_context is None:
|
|
34
|
+
return {}
|
|
35
|
+
return tctx.custom_context
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@contextmanager
|
|
39
|
+
def custom_context(**context: str):
|
|
40
|
+
"""
|
|
41
|
+
Synchronous context manager to set input context for tasks spawned within this block.
|
|
42
|
+
|
|
43
|
+
Example:
|
|
44
|
+
```python
|
|
45
|
+
import flyte
|
|
46
|
+
|
|
47
|
+
env = flyte.TaskEnvironment(name="...")
|
|
48
|
+
|
|
49
|
+
@env.task
|
|
50
|
+
def t1():
|
|
51
|
+
ctx = flyte.get_custom_context()
|
|
52
|
+
print(ctx)
|
|
53
|
+
|
|
54
|
+
@env.task
|
|
55
|
+
def main():
|
|
56
|
+
# context can be passed via a context manager
|
|
57
|
+
with flyte.custom_context(project="my-project"):
|
|
58
|
+
t1() # will have {'project': 'my-project'} as context
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
:param context: Key-value pairs to set as input context
|
|
62
|
+
"""
|
|
63
|
+
ctx = internal_ctx()
|
|
64
|
+
if ctx.data.task_context is None:
|
|
65
|
+
yield
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
tctx = ctx.data.task_context
|
|
69
|
+
new_tctx = tctx.replace(custom_context={**tctx.custom_context, **context})
|
|
70
|
+
|
|
71
|
+
with ctx.replace_task_context(new_tctx):
|
|
72
|
+
yield
|
|
73
|
+
# Exit the context and restore the previous context
|
flyte/_deploy.py
CHANGED
|
@@ -14,13 +14,14 @@ from flyte.syncify import syncify
|
|
|
14
14
|
|
|
15
15
|
from ._environment import Environment
|
|
16
16
|
from ._image import Image
|
|
17
|
-
from ._initialize import ensure_client, get_client,
|
|
17
|
+
from ._initialize import ensure_client, get_client, get_init_config, requires_initialization
|
|
18
18
|
from ._logging import logger
|
|
19
19
|
from ._task import TaskTemplate
|
|
20
20
|
from ._task_environment import TaskEnvironment
|
|
21
21
|
|
|
22
22
|
if TYPE_CHECKING:
|
|
23
|
-
from
|
|
23
|
+
from flyteidl2.task import task_definition_pb2
|
|
24
|
+
from flyteidl2.trigger import trigger_definition_pb2
|
|
24
25
|
|
|
25
26
|
from ._code_bundle import CopyFiles
|
|
26
27
|
from ._internal.imagebuild.image_builder import ImageCache
|
|
@@ -144,11 +145,11 @@ async def _deploy_task(
|
|
|
144
145
|
"""
|
|
145
146
|
ensure_client()
|
|
146
147
|
import grpc.aio
|
|
148
|
+
from flyteidl2.task import task_definition_pb2, task_service_pb2
|
|
147
149
|
|
|
148
150
|
from ._internal.runtime.convert import convert_upload_default_inputs
|
|
149
151
|
from ._internal.runtime.task_serde import translate_task_to_wire
|
|
150
152
|
from ._internal.runtime.trigger_serde import to_task_trigger
|
|
151
|
-
from ._protos.workflow import task_definition_pb2, task_service_pb2
|
|
152
153
|
|
|
153
154
|
image_uri = task.image.uri if isinstance(task.image, Image) else task.image
|
|
154
155
|
|
|
@@ -213,20 +214,41 @@ async def _build_image_bg(env_name: str, image: Image) -> Tuple[str, str]:
|
|
|
213
214
|
return env_name, await build.aio(image)
|
|
214
215
|
|
|
215
216
|
|
|
216
|
-
async def _build_images(deployment: DeploymentPlan) -> ImageCache:
|
|
217
|
+
async def _build_images(deployment: DeploymentPlan, image_refs: Dict[str, str] | None = None) -> ImageCache:
|
|
217
218
|
"""
|
|
218
219
|
Build the images for the given deployment plan and update the environment with the built image.
|
|
219
220
|
"""
|
|
220
221
|
from ._internal.imagebuild.image_builder import ImageCache
|
|
221
222
|
|
|
223
|
+
if image_refs is None:
|
|
224
|
+
image_refs = {}
|
|
225
|
+
|
|
222
226
|
images = []
|
|
223
227
|
image_identifier_map = {}
|
|
224
228
|
for env_name, env in deployment.envs.items():
|
|
225
229
|
if not isinstance(env.image, str):
|
|
230
|
+
if env.image._ref_name is not None:
|
|
231
|
+
if env.image._ref_name in image_refs:
|
|
232
|
+
# If the image is set in the config, set it as the base_image
|
|
233
|
+
image_uri = image_refs[env.image._ref_name]
|
|
234
|
+
env.image = env.image.clone(base_image=image_uri)
|
|
235
|
+
else:
|
|
236
|
+
raise ValueError(
|
|
237
|
+
f"Image name '{env.image._ref_name}' not found in config. Available: {list(image_refs.keys())}"
|
|
238
|
+
)
|
|
239
|
+
if not env.image._layers:
|
|
240
|
+
# No additional layers, use the base_image directly without building
|
|
241
|
+
image_identifier_map[env_name] = image_uri
|
|
242
|
+
continue
|
|
226
243
|
logger.debug(f"Building Image for environment {env_name}, image: {env.image}")
|
|
227
244
|
images.append(_build_image_bg(env_name, env.image))
|
|
228
245
|
|
|
229
246
|
elif env.image == "auto" and "auto" not in image_identifier_map:
|
|
247
|
+
if "default" in image_refs:
|
|
248
|
+
# If the default image is set through CLI, use it instead
|
|
249
|
+
image_uri = image_refs["default"]
|
|
250
|
+
image_identifier_map[env_name] = image_uri
|
|
251
|
+
continue
|
|
230
252
|
auto_image = Image.from_debian_base()
|
|
231
253
|
images.append(_build_image_bg(env_name, auto_image))
|
|
232
254
|
final_images = await asyncio.gather(*images)
|
|
@@ -312,9 +334,9 @@ def get_deployer(env_type: Type[Environment | TaskEnvironment]) -> Deployer:
|
|
|
312
334
|
async def apply(deployment_plan: DeploymentPlan, copy_style: CopyFiles, dryrun: bool = False) -> Deployment:
|
|
313
335
|
from ._code_bundle import build_code_bundle
|
|
314
336
|
|
|
315
|
-
cfg =
|
|
337
|
+
cfg = get_init_config()
|
|
316
338
|
|
|
317
|
-
image_cache = await _build_images(deployment_plan)
|
|
339
|
+
image_cache = await _build_images(deployment_plan, cfg.images)
|
|
318
340
|
|
|
319
341
|
if copy_style == "none" and not deployment_plan.version:
|
|
320
342
|
raise flyte.errors.DeploymentError("Version must be set when copy_style is none")
|
|
@@ -422,5 +444,7 @@ async def build_images(envs: Environment) -> ImageCache:
|
|
|
422
444
|
:param envs: Environment to build images for.
|
|
423
445
|
:return: ImageCache containing the built images.
|
|
424
446
|
"""
|
|
447
|
+
cfg = get_init_config()
|
|
448
|
+
images = cfg.images if cfg else {}
|
|
425
449
|
deployment = plan_deploy(envs)
|
|
426
|
-
return await _build_images(deployment[0])
|
|
450
|
+
return await _build_images(deployment[0], images)
|
flyte/_image.py
CHANGED
|
@@ -12,8 +12,6 @@ from typing import TYPE_CHECKING, ClassVar, Dict, List, Literal, Optional, Tuple
|
|
|
12
12
|
import rich.repr
|
|
13
13
|
from packaging.version import Version
|
|
14
14
|
|
|
15
|
-
from flyte._utils import update_hasher_for_source
|
|
16
|
-
|
|
17
15
|
if TYPE_CHECKING:
|
|
18
16
|
from flyte import Secret, SecretRequest
|
|
19
17
|
|
|
@@ -167,21 +165,22 @@ class Requirements(PipPackages):
|
|
|
167
165
|
class UVProject(PipOption, Layer):
|
|
168
166
|
pyproject: Path
|
|
169
167
|
uvlock: Path
|
|
168
|
+
project_install_mode: typing.Literal["dependencies_only", "install_project"] = "dependencies_only"
|
|
170
169
|
|
|
171
170
|
def validate(self):
|
|
172
171
|
if not self.pyproject.exists():
|
|
173
|
-
raise FileNotFoundError(f"pyproject.toml file {self.pyproject} does not exist")
|
|
172
|
+
raise FileNotFoundError(f"pyproject.toml file {self.pyproject.resolve()} does not exist")
|
|
174
173
|
if not self.pyproject.is_file():
|
|
175
|
-
raise ValueError(f"Pyproject file {self.pyproject} is not a file")
|
|
174
|
+
raise ValueError(f"Pyproject file {self.pyproject.resolve()} is not a file")
|
|
176
175
|
if not self.uvlock.exists():
|
|
177
|
-
raise ValueError(f"UVLock file {self.uvlock} does not exist")
|
|
176
|
+
raise ValueError(f"UVLock file {self.uvlock.resolve()} does not exist")
|
|
178
177
|
super().validate()
|
|
179
178
|
|
|
180
179
|
def update_hash(self, hasher: hashlib._Hash):
|
|
181
|
-
from ._utils import filehash_update
|
|
180
|
+
from ._utils import filehash_update, update_hasher_for_source
|
|
182
181
|
|
|
183
182
|
super().update_hash(hasher)
|
|
184
|
-
if self.
|
|
183
|
+
if self.project_install_mode == "dependencies_only":
|
|
185
184
|
filehash_update(self.uvlock, hasher)
|
|
186
185
|
filehash_update(self.pyproject, hasher)
|
|
187
186
|
else:
|
|
@@ -210,7 +209,7 @@ class PoetryProject(Layer):
|
|
|
210
209
|
super().validate()
|
|
211
210
|
|
|
212
211
|
def update_hash(self, hasher: hashlib._Hash):
|
|
213
|
-
from ._utils import filehash_update
|
|
212
|
+
from ._utils import filehash_update, update_hasher_for_source
|
|
214
213
|
|
|
215
214
|
hash_input = ""
|
|
216
215
|
if self.extra_args:
|
|
@@ -401,6 +400,8 @@ class Image:
|
|
|
401
400
|
name: Optional[str] = field(default=None)
|
|
402
401
|
platform: Tuple[Architecture, ...] = field(default=("linux/amd64",))
|
|
403
402
|
python_version: Tuple[int, int] = field(default_factory=_detect_python_version)
|
|
403
|
+
# Refer to the image_refs (name:image-uri) set in CLI or config
|
|
404
|
+
_ref_name: Optional[str] = field(default=None)
|
|
404
405
|
|
|
405
406
|
# Layers to be added to the image. In init, because frozen, but users shouldn't access, so underscore.
|
|
406
407
|
_layers: Tuple[Layer, ...] = field(default_factory=tuple)
|
|
@@ -418,6 +419,9 @@ class Image:
|
|
|
418
419
|
# class-level token not included in __init__
|
|
419
420
|
_token: ClassVar[object] = object()
|
|
420
421
|
|
|
422
|
+
# Underscore cuz we may rename in the future, don't expose for now,
|
|
423
|
+
_image_registry_secret: Optional[Secret] = None
|
|
424
|
+
|
|
421
425
|
# check for the guard that we put in place
|
|
422
426
|
def __post_init__(self):
|
|
423
427
|
if object.__getattribute__(self, "__dict__").pop("_guard", None) is not Image._token:
|
|
@@ -506,6 +510,7 @@ class Image:
|
|
|
506
510
|
flyte_version: Optional[str] = None,
|
|
507
511
|
install_flyte: bool = True,
|
|
508
512
|
registry: Optional[str] = None,
|
|
513
|
+
registry_secret: Optional[str | Secret] = None,
|
|
509
514
|
name: Optional[str] = None,
|
|
510
515
|
platform: Optional[Tuple[Architecture, ...]] = None,
|
|
511
516
|
) -> Image:
|
|
@@ -517,6 +522,7 @@ class Image:
|
|
|
517
522
|
:param flyte_version: Union version to use
|
|
518
523
|
:param install_flyte: If True, will install the flyte library in the image
|
|
519
524
|
:param registry: Registry to use for the image
|
|
525
|
+
:param registry_secret: Secret to use to pull/push the private image.
|
|
520
526
|
:param name: Name of the image if you want to override the default name
|
|
521
527
|
:param platform: Platform to use for the image, default is linux/amd64, use tuple for multiple values
|
|
522
528
|
Example: ("linux/amd64", "linux/arm64")
|
|
@@ -534,7 +540,7 @@ class Image:
|
|
|
534
540
|
)
|
|
535
541
|
|
|
536
542
|
if registry or name:
|
|
537
|
-
return base_image.clone(registry=registry, name=name)
|
|
543
|
+
return base_image.clone(registry=registry, name=name, registry_secret=registry_secret)
|
|
538
544
|
|
|
539
545
|
return base_image
|
|
540
546
|
|
|
@@ -549,6 +555,13 @@ class Image:
|
|
|
549
555
|
img = cls._new(base_image=image_uri)
|
|
550
556
|
return img
|
|
551
557
|
|
|
558
|
+
@classmethod
|
|
559
|
+
def from_ref_name(cls, name: str) -> Image:
|
|
560
|
+
# NOTE: set image name as _ref_name to enable adding additional layers.
|
|
561
|
+
# See: https://github.com/flyteorg/flyte-sdk/blob/14de802701aab7b8615ffb99c650a36305ef01f7/src/flyte/_image.py#L642
|
|
562
|
+
img = cls._new(name=name, _ref_name=name)
|
|
563
|
+
return img
|
|
564
|
+
|
|
552
565
|
@classmethod
|
|
553
566
|
def from_uv_script(
|
|
554
567
|
cls,
|
|
@@ -556,6 +569,7 @@ class Image:
|
|
|
556
569
|
*,
|
|
557
570
|
name: str,
|
|
558
571
|
registry: str | None = None,
|
|
572
|
+
registry_secret: Optional[str | Secret] = None,
|
|
559
573
|
python_version: Optional[Tuple[int, int]] = None,
|
|
560
574
|
index_url: Optional[str] = None,
|
|
561
575
|
extra_index_urls: Union[str, List[str], Tuple[str, ...], None] = None,
|
|
@@ -584,6 +598,7 @@ class Image:
|
|
|
584
598
|
|
|
585
599
|
:param name: name of the image
|
|
586
600
|
:param registry: registry to use for the image
|
|
601
|
+
:param registry_secret: Secret to use to pull/push the private image.
|
|
587
602
|
:param python_version: Python version to use for the image, if not specified, will use the current Python
|
|
588
603
|
version
|
|
589
604
|
:param script: path to the uv script
|
|
@@ -609,14 +624,22 @@ class Image:
|
|
|
609
624
|
secret_mounts=_ensure_tuple(secret_mounts) if secret_mounts else None,
|
|
610
625
|
)
|
|
611
626
|
|
|
612
|
-
img = cls.from_debian_base(
|
|
627
|
+
img = cls.from_debian_base(
|
|
628
|
+
registry=registry,
|
|
629
|
+
registry_secret=registry_secret,
|
|
630
|
+
name=name,
|
|
631
|
+
python_version=python_version,
|
|
632
|
+
platform=platform,
|
|
633
|
+
)
|
|
613
634
|
|
|
614
635
|
return img.clone(addl_layer=ll)
|
|
615
636
|
|
|
616
637
|
def clone(
|
|
617
638
|
self,
|
|
618
639
|
registry: Optional[str] = None,
|
|
640
|
+
registry_secret: Optional[str | Secret] = None,
|
|
619
641
|
name: Optional[str] = None,
|
|
642
|
+
base_image: Optional[str] = None,
|
|
620
643
|
python_version: Optional[Tuple[int, int]] = None,
|
|
621
644
|
addl_layer: Optional[Layer] = None,
|
|
622
645
|
) -> Image:
|
|
@@ -624,12 +647,14 @@ class Image:
|
|
|
624
647
|
Use this method to clone the current image and change the registry and name
|
|
625
648
|
|
|
626
649
|
:param registry: Registry to use for the image
|
|
650
|
+
:param registry_secret: Secret to use to pull/push the private image.
|
|
627
651
|
:param name: Name of the image
|
|
628
652
|
:param python_version: Python version for the image, if not specified, will use the current Python version
|
|
629
653
|
:param addl_layer: Additional layer to add to the image. This will be added to the end of the layers.
|
|
630
|
-
|
|
631
654
|
:return:
|
|
632
655
|
"""
|
|
656
|
+
from flyte import Secret
|
|
657
|
+
|
|
633
658
|
if addl_layer and self.dockerfile:
|
|
634
659
|
# We don't know how to inspect dockerfiles to know what kind it is (OS, python version, uv vs poetry, etc)
|
|
635
660
|
# so there's no guarantee any of the layering logic will work.
|
|
@@ -639,19 +664,23 @@ class Image:
|
|
|
639
664
|
)
|
|
640
665
|
registry = registry if registry else self.registry
|
|
641
666
|
name = name if name else self.name
|
|
667
|
+
registry_secret = registry_secret if registry_secret else self._image_registry_secret
|
|
668
|
+
base_image = base_image if base_image else self.base_image
|
|
642
669
|
if addl_layer and (not name):
|
|
643
670
|
raise ValueError(
|
|
644
671
|
f"Cannot add additional layer {addl_layer} to an image without name. Please first clone()."
|
|
645
672
|
)
|
|
646
673
|
new_layers = (*self._layers, addl_layer) if addl_layer else self._layers
|
|
647
674
|
img = Image._new(
|
|
648
|
-
base_image=
|
|
675
|
+
base_image=base_image,
|
|
649
676
|
dockerfile=self.dockerfile,
|
|
650
677
|
registry=registry,
|
|
651
678
|
name=name,
|
|
652
679
|
platform=self.platform,
|
|
653
680
|
python_version=python_version or self.python_version,
|
|
654
681
|
_layers=new_layers,
|
|
682
|
+
_image_registry_secret=Secret(key=registry_secret) if isinstance(registry_secret, str) else registry_secret,
|
|
683
|
+
_ref_name=self._ref_name,
|
|
655
684
|
)
|
|
656
685
|
|
|
657
686
|
return img
|
|
@@ -876,17 +905,17 @@ class Image:
|
|
|
876
905
|
pre: bool = False,
|
|
877
906
|
extra_args: Optional[str] = None,
|
|
878
907
|
secret_mounts: Optional[SecretRequest] = None,
|
|
908
|
+
project_install_mode: typing.Literal["dependencies_only", "install_project"] = "dependencies_only",
|
|
879
909
|
) -> Image:
|
|
880
910
|
"""
|
|
881
911
|
Use this method to create a new image with the specified uv.lock file layered on top of the current image
|
|
882
912
|
Must have a corresponding pyproject.toml file in the same directory
|
|
883
913
|
Cannot be used in conjunction with conda
|
|
884
914
|
|
|
885
|
-
By default, this method copies the
|
|
886
|
-
including files such as pyproject.toml, uv.lock, and the src/ directory.
|
|
915
|
+
By default, this method copies the pyproject.toml and uv.lock files into the image.
|
|
887
916
|
|
|
888
|
-
If
|
|
889
|
-
|
|
917
|
+
If `project_install_mode` is "install_project", it will also copy directory
|
|
918
|
+
where the pyproject.toml file is located into the image.
|
|
890
919
|
|
|
891
920
|
:param pyproject_file: path to the pyproject.toml file, needs to have a corresponding uv.lock file
|
|
892
921
|
:param uvlock: path to the uv.lock file, if not specified, will use the default uv.lock file in the same
|
|
@@ -896,6 +925,8 @@ class Image:
|
|
|
896
925
|
:param pre: whether to allow pre-release versions, default is False
|
|
897
926
|
:param extra_args: extra arguments to pass to pip install, default is None
|
|
898
927
|
:param secret_mounts: list of secret mounts to use for the build process.
|
|
928
|
+
:param project_install_mode: whether to install the project as a package or
|
|
929
|
+
only dependencies, default is "dependencies_only"
|
|
899
930
|
:return: Image
|
|
900
931
|
"""
|
|
901
932
|
if isinstance(pyproject_file, str):
|
|
@@ -909,6 +940,7 @@ class Image:
|
|
|
909
940
|
pre=pre,
|
|
910
941
|
extra_args=extra_args,
|
|
911
942
|
secret_mounts=_ensure_tuple(secret_mounts) if secret_mounts else None,
|
|
943
|
+
project_install_mode=project_install_mode,
|
|
912
944
|
)
|
|
913
945
|
)
|
|
914
946
|
return new_image
|