flyte 2.0.0b13__py3-none-any.whl → 2.0.0b30__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.
- flyte/__init__.py +18 -2
- flyte/_bin/debug.py +38 -0
- flyte/_bin/runtime.py +62 -8
- flyte/_cache/cache.py +4 -2
- flyte/_cache/local_cache.py +216 -0
- flyte/_code_bundle/_ignore.py +12 -4
- flyte/_code_bundle/_packaging.py +13 -9
- flyte/_code_bundle/_utils.py +18 -10
- flyte/_code_bundle/bundle.py +17 -9
- flyte/_constants.py +1 -0
- flyte/_context.py +4 -1
- flyte/_custom_context.py +73 -0
- flyte/_debug/constants.py +38 -0
- flyte/_debug/utils.py +17 -0
- flyte/_debug/vscode.py +307 -0
- flyte/_deploy.py +235 -61
- flyte/_environment.py +20 -6
- flyte/_excepthook.py +1 -1
- flyte/_hash.py +1 -16
- flyte/_image.py +178 -81
- flyte/_initialize.py +132 -51
- flyte/_interface.py +39 -2
- flyte/_internal/controllers/__init__.py +4 -5
- flyte/_internal/controllers/_local_controller.py +70 -29
- flyte/_internal/controllers/_trace.py +1 -1
- flyte/_internal/controllers/remote/__init__.py +0 -2
- flyte/_internal/controllers/remote/_action.py +14 -16
- flyte/_internal/controllers/remote/_client.py +1 -1
- flyte/_internal/controllers/remote/_controller.py +68 -70
- flyte/_internal/controllers/remote/_core.py +127 -99
- flyte/_internal/controllers/remote/_informer.py +19 -10
- flyte/_internal/controllers/remote/_service_protocol.py +7 -7
- flyte/_internal/imagebuild/docker_builder.py +181 -69
- flyte/_internal/imagebuild/image_builder.py +0 -5
- flyte/_internal/imagebuild/remote_builder.py +155 -64
- flyte/_internal/imagebuild/utils.py +51 -2
- flyte/_internal/resolvers/_task_module.py +5 -38
- flyte/_internal/resolvers/default.py +2 -2
- flyte/_internal/runtime/convert.py +110 -21
- flyte/_internal/runtime/entrypoints.py +27 -1
- flyte/_internal/runtime/io.py +21 -8
- flyte/_internal/runtime/resources_serde.py +20 -6
- flyte/_internal/runtime/reuse.py +1 -1
- flyte/_internal/runtime/rusty.py +20 -5
- flyte/_internal/runtime/task_serde.py +34 -19
- flyte/_internal/runtime/taskrunner.py +22 -4
- flyte/_internal/runtime/trigger_serde.py +160 -0
- flyte/_internal/runtime/types_serde.py +1 -1
- flyte/_keyring/__init__.py +0 -0
- flyte/_keyring/file.py +115 -0
- flyte/_logging.py +201 -39
- flyte/_map.py +111 -14
- flyte/_module.py +70 -0
- flyte/_pod.py +4 -3
- flyte/_resources.py +213 -31
- flyte/_run.py +110 -39
- flyte/_task.py +75 -16
- flyte/_task_environment.py +105 -29
- flyte/_task_plugins.py +4 -2
- flyte/_trace.py +5 -0
- flyte/_trigger.py +1000 -0
- flyte/_utils/__init__.py +2 -1
- flyte/_utils/asyn.py +3 -1
- flyte/_utils/coro_management.py +2 -1
- flyte/_utils/docker_credentials.py +173 -0
- flyte/_utils/module_loader.py +17 -2
- flyte/_version.py +3 -3
- flyte/cli/_abort.py +3 -3
- flyte/cli/_build.py +3 -6
- flyte/cli/_common.py +78 -7
- flyte/cli/_create.py +182 -4
- flyte/cli/_delete.py +23 -1
- flyte/cli/_deploy.py +63 -16
- flyte/cli/_get.py +79 -34
- flyte/cli/_params.py +26 -10
- flyte/cli/_plugins.py +209 -0
- flyte/cli/_run.py +151 -26
- flyte/cli/_serve.py +64 -0
- flyte/cli/_update.py +37 -0
- flyte/cli/_user.py +17 -0
- flyte/cli/main.py +30 -4
- flyte/config/_config.py +10 -6
- flyte/config/_internal.py +1 -0
- flyte/config/_reader.py +29 -8
- 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 +22 -2
- flyte/extend.py +8 -1
- flyte/extras/_container.py +6 -1
- flyte/git/__init__.py +3 -0
- flyte/git/_config.py +21 -0
- flyte/io/__init__.py +2 -0
- flyte/io/_dataframe/__init__.py +2 -0
- flyte/io/_dataframe/basic_dfs.py +17 -8
- flyte/io/_dataframe/dataframe.py +98 -132
- flyte/io/_dir.py +575 -113
- flyte/io/_file.py +582 -139
- flyte/io/_hashing_io.py +342 -0
- flyte/models.py +74 -15
- flyte/remote/__init__.py +6 -1
- flyte/remote/_action.py +34 -26
- flyte/remote/_client/_protocols.py +39 -4
- flyte/remote/_client/auth/_authenticators/device_code.py +4 -5
- flyte/remote/_client/auth/_authenticators/pkce.py +1 -1
- flyte/remote/_client/auth/_channel.py +10 -6
- flyte/remote/_client/controlplane.py +17 -5
- flyte/remote/_console.py +3 -2
- flyte/remote/_data.py +6 -6
- flyte/remote/_logs.py +3 -3
- flyte/remote/_run.py +64 -8
- flyte/remote/_secret.py +26 -17
- flyte/remote/_task.py +75 -33
- flyte/remote/_trigger.py +306 -0
- flyte/remote/_user.py +33 -0
- flyte/report/_report.py +1 -1
- flyte/storage/__init__.py +6 -1
- flyte/storage/_config.py +5 -1
- flyte/storage/_parallel_reader.py +274 -0
- flyte/storage/_storage.py +200 -103
- flyte/types/__init__.py +16 -0
- flyte/types/_interface.py +2 -2
- flyte/types/_pickle.py +35 -8
- flyte/types/_string_literals.py +8 -9
- flyte/types/_type_engine.py +40 -70
- flyte/types/_utils.py +1 -1
- flyte-2.0.0b30.data/scripts/debug.py +38 -0
- {flyte-2.0.0b13.data → flyte-2.0.0b30.data}/scripts/runtime.py +62 -8
- {flyte-2.0.0b13.dist-info → flyte-2.0.0b30.dist-info}/METADATA +11 -3
- flyte-2.0.0b30.dist-info/RECORD +192 -0
- {flyte-2.0.0b13.dist-info → flyte-2.0.0b30.dist-info}/entry_points.txt +3 -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 -93
- flyte/_protos/common/identifier_pb2.pyi +0 -110
- 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 -59
- flyte/_protos/imagebuilder/definition_pb2.pyi +0 -140
- 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/secret/secret_pb2_grpc_grpc.py +0 -198
- flyte/_protos/validate/validate/validate_pb2.py +0 -76
- flyte/_protos/workflow/common_pb2.py +0 -27
- flyte/_protos/workflow/common_pb2.pyi +0 -14
- 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 -109
- flyte/_protos/workflow/queue_service_pb2.pyi +0 -166
- flyte/_protos/workflow/queue_service_pb2_grpc.py +0 -172
- flyte/_protos/workflow/run_definition_pb2.py +0 -121
- flyte/_protos/workflow/run_definition_pb2.pyi +0 -327
- 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 -137
- flyte/_protos/workflow/run_service_pb2.pyi +0 -185
- flyte/_protos/workflow/run_service_pb2_grpc.py +0 -446
- 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 -79
- flyte/_protos/workflow/task_definition_pb2.pyi +0 -81
- flyte/_protos/workflow/task_definition_pb2_grpc.py +0 -4
- flyte/_protos/workflow/task_service_pb2.py +0 -60
- flyte/_protos/workflow/task_service_pb2.pyi +0 -59
- flyte/_protos/workflow/task_service_pb2_grpc.py +0 -138
- flyte-2.0.0b13.dist-info/RECORD +0 -239
- /flyte/{_protos → _debug}/__init__.py +0 -0
- {flyte-2.0.0b13.dist-info → flyte-2.0.0b30.dist-info}/WHEEL +0 -0
- {flyte-2.0.0b13.dist-info → flyte-2.0.0b30.dist-info}/licenses/LICENSE +0 -0
- {flyte-2.0.0b13.dist-info → flyte-2.0.0b30.dist-info}/top_level.txt +0 -0
flyte/_image.py
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import base64
|
|
4
3
|
import hashlib
|
|
5
4
|
import sys
|
|
6
5
|
import typing
|
|
7
6
|
from abc import abstractmethod
|
|
8
|
-
from dataclasses import
|
|
7
|
+
from dataclasses import dataclass, field
|
|
9
8
|
from functools import cached_property
|
|
10
9
|
from pathlib import Path
|
|
11
10
|
from typing import TYPE_CHECKING, ClassVar, Dict, List, Literal, Optional, Tuple, TypeVar, Union
|
|
@@ -56,7 +55,6 @@ class Layer:
|
|
|
56
55
|
|
|
57
56
|
:param hasher: The hash object to update with the layer's data.
|
|
58
57
|
"""
|
|
59
|
-
print("hash hash")
|
|
60
58
|
|
|
61
59
|
def validate(self):
|
|
62
60
|
"""
|
|
@@ -64,27 +62,6 @@ class Layer:
|
|
|
64
62
|
:return:
|
|
65
63
|
"""
|
|
66
64
|
|
|
67
|
-
def identifier(self) -> str:
|
|
68
|
-
"""
|
|
69
|
-
This method computes a unique identifier for the layer based on its properties.
|
|
70
|
-
It is used to identify the layer in the image cache.
|
|
71
|
-
|
|
72
|
-
It is also used to compute a unique identifier for the image itself, which is a combination of all the layers.
|
|
73
|
-
This identifier is used to look up previously built images in the image cache. So having a consistent
|
|
74
|
-
identifier is important for the image cache to work correctly.
|
|
75
|
-
|
|
76
|
-
:return: A unique identifier for the layer.
|
|
77
|
-
"""
|
|
78
|
-
ignore_fields: list[str] = []
|
|
79
|
-
for f in fields(self):
|
|
80
|
-
if f.metadata.get("identifier", True) is False:
|
|
81
|
-
ignore_fields.append(f.name)
|
|
82
|
-
d = asdict(self)
|
|
83
|
-
for v in ignore_fields:
|
|
84
|
-
d.pop(v)
|
|
85
|
-
|
|
86
|
-
return str(d)
|
|
87
|
-
|
|
88
65
|
|
|
89
66
|
@rich.repr.auto
|
|
90
67
|
@dataclass(kw_only=True, frozen=True, repr=True)
|
|
@@ -152,7 +129,7 @@ class PipPackages(PipOption, Layer):
|
|
|
152
129
|
@rich.repr.auto
|
|
153
130
|
@dataclass(kw_only=True, frozen=True, repr=True)
|
|
154
131
|
class PythonWheels(PipOption, Layer):
|
|
155
|
-
wheel_dir: Path
|
|
132
|
+
wheel_dir: Path
|
|
156
133
|
wheel_dir_name: str = field(init=False)
|
|
157
134
|
package_name: str
|
|
158
135
|
|
|
@@ -188,27 +165,72 @@ class Requirements(PipPackages):
|
|
|
188
165
|
class UVProject(PipOption, Layer):
|
|
189
166
|
pyproject: Path
|
|
190
167
|
uvlock: Path
|
|
168
|
+
project_install_mode: typing.Literal["dependencies_only", "install_project"] = "dependencies_only"
|
|
191
169
|
|
|
192
170
|
def validate(self):
|
|
193
171
|
if not self.pyproject.exists():
|
|
194
|
-
raise FileNotFoundError(f"pyproject.toml file {self.pyproject} does not exist")
|
|
172
|
+
raise FileNotFoundError(f"pyproject.toml file {self.pyproject.resolve()} does not exist")
|
|
195
173
|
if not self.pyproject.is_file():
|
|
196
|
-
raise ValueError(f"Pyproject file {self.pyproject} is not a file")
|
|
174
|
+
raise ValueError(f"Pyproject file {self.pyproject.resolve()} is not a file")
|
|
197
175
|
if not self.uvlock.exists():
|
|
198
|
-
raise ValueError(f"UVLock file {self.uvlock} does not exist")
|
|
176
|
+
raise ValueError(f"UVLock file {self.uvlock.resolve()} does not exist")
|
|
199
177
|
super().validate()
|
|
200
178
|
|
|
201
179
|
def update_hash(self, hasher: hashlib._Hash):
|
|
202
|
-
from ._utils import filehash_update
|
|
180
|
+
from ._utils import filehash_update, update_hasher_for_source
|
|
203
181
|
|
|
204
182
|
super().update_hash(hasher)
|
|
205
|
-
|
|
183
|
+
if self.project_install_mode == "dependencies_only":
|
|
184
|
+
filehash_update(self.uvlock, hasher)
|
|
185
|
+
filehash_update(self.pyproject, hasher)
|
|
186
|
+
else:
|
|
187
|
+
update_hasher_for_source(self.pyproject.parent, hasher)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@rich.repr.auto
|
|
191
|
+
@dataclass(frozen=True, repr=True)
|
|
192
|
+
class PoetryProject(Layer):
|
|
193
|
+
"""
|
|
194
|
+
Poetry does not use pip options, so the PoetryProject class do not inherits PipOption class
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
pyproject: Path
|
|
198
|
+
poetry_lock: Path
|
|
199
|
+
extra_args: Optional[str] = None
|
|
200
|
+
project_install_mode: typing.Literal["dependencies_only", "install_project"] = "dependencies_only"
|
|
201
|
+
secret_mounts: Optional[Tuple[str | Secret, ...]] = None
|
|
202
|
+
|
|
203
|
+
def validate(self):
|
|
204
|
+
if not self.pyproject.exists():
|
|
205
|
+
raise FileNotFoundError(f"pyproject.toml file {self.pyproject} does not exist")
|
|
206
|
+
if not self.pyproject.is_file():
|
|
207
|
+
raise ValueError(f"Pyproject file {self.pyproject} is not a file")
|
|
208
|
+
if not self.poetry_lock.exists():
|
|
209
|
+
raise ValueError(f"poetry.lock file {self.poetry_lock} does not exist")
|
|
210
|
+
super().validate()
|
|
211
|
+
|
|
212
|
+
def update_hash(self, hasher: hashlib._Hash):
|
|
213
|
+
from ._utils import filehash_update, update_hasher_for_source
|
|
214
|
+
|
|
215
|
+
hash_input = ""
|
|
216
|
+
if self.extra_args:
|
|
217
|
+
hash_input += self.extra_args
|
|
218
|
+
if self.secret_mounts:
|
|
219
|
+
for secret_mount in self.secret_mounts:
|
|
220
|
+
hash_input += str(secret_mount)
|
|
221
|
+
hasher.update(hash_input.encode("utf-8"))
|
|
222
|
+
|
|
223
|
+
if self.project_install_mode == "dependencies_only":
|
|
224
|
+
filehash_update(self.poetry_lock, hasher)
|
|
225
|
+
filehash_update(self.pyproject, hasher)
|
|
226
|
+
else:
|
|
227
|
+
update_hasher_for_source(self.pyproject.parent, hasher)
|
|
206
228
|
|
|
207
229
|
|
|
208
230
|
@rich.repr.auto
|
|
209
231
|
@dataclass(frozen=True, repr=True)
|
|
210
232
|
class UVScript(PipOption, Layer):
|
|
211
|
-
script: Path
|
|
233
|
+
script: Path
|
|
212
234
|
script_name: str = field(init=False)
|
|
213
235
|
|
|
214
236
|
def __post_init__(self):
|
|
@@ -252,9 +274,15 @@ class AptPackages(Layer):
|
|
|
252
274
|
@dataclass(frozen=True, repr=True)
|
|
253
275
|
class Commands(Layer):
|
|
254
276
|
commands: Tuple[str, ...]
|
|
277
|
+
secret_mounts: Optional[Tuple[str | Secret, ...]] = None
|
|
255
278
|
|
|
256
279
|
def update_hash(self, hasher: hashlib._Hash):
|
|
257
|
-
|
|
280
|
+
hash_input = "".join(self.commands)
|
|
281
|
+
|
|
282
|
+
if self.secret_mounts:
|
|
283
|
+
for secret_mount in self.secret_mounts:
|
|
284
|
+
hash_input += str(secret_mount)
|
|
285
|
+
hasher.update(hash_input.encode("utf-8"))
|
|
258
286
|
|
|
259
287
|
|
|
260
288
|
@rich.repr.auto
|
|
@@ -278,15 +306,13 @@ class DockerIgnore(Layer):
|
|
|
278
306
|
@rich.repr.auto
|
|
279
307
|
@dataclass(frozen=True, repr=True)
|
|
280
308
|
class CopyConfig(Layer):
|
|
281
|
-
path_type: CopyConfigType
|
|
282
|
-
src: Path
|
|
309
|
+
path_type: CopyConfigType
|
|
310
|
+
src: Path
|
|
283
311
|
dst: str
|
|
284
|
-
src_name: str = field(init=False)
|
|
285
312
|
|
|
286
313
|
def __post_init__(self):
|
|
287
314
|
if self.path_type not in (0, 1):
|
|
288
315
|
raise ValueError(f"Invalid path_type {self.path_type}, must be 0 (file) or 1 (directory)")
|
|
289
|
-
object.__setattr__(self, "src_name", self.src.name)
|
|
290
316
|
|
|
291
317
|
def validate(self):
|
|
292
318
|
if not self.src.exists():
|
|
@@ -374,9 +400,8 @@ class Image:
|
|
|
374
400
|
name: Optional[str] = field(default=None)
|
|
375
401
|
platform: Tuple[Architecture, ...] = field(default=("linux/amd64",))
|
|
376
402
|
python_version: Tuple[int, int] = field(default_factory=_detect_python_version)
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
_identifier_override: Optional[str] = field(default=None, init=False)
|
|
403
|
+
# Refer to the image_refs (name:image-uri) set in CLI or config
|
|
404
|
+
_ref_name: Optional[str] = field(default=None)
|
|
380
405
|
|
|
381
406
|
# Layers to be added to the image. In init, because frozen, but users shouldn't access, so underscore.
|
|
382
407
|
_layers: Tuple[Layer, ...] = field(default_factory=tuple)
|
|
@@ -394,6 +419,9 @@ class Image:
|
|
|
394
419
|
# class-level token not included in __init__
|
|
395
420
|
_token: ClassVar[object] = object()
|
|
396
421
|
|
|
422
|
+
# Underscore cuz we may rename in the future, don't expose for now,
|
|
423
|
+
_image_registry_secret: Optional[Secret] = None
|
|
424
|
+
|
|
397
425
|
# check for the guard that we put in place
|
|
398
426
|
def __post_init__(self):
|
|
399
427
|
if object.__getattribute__(self, "__dict__").pop("_guard", None) is not Image._token:
|
|
@@ -410,31 +438,6 @@ class Image:
|
|
|
410
438
|
cls.__init__(obj, **kwargs) # run dataclass generated __init__
|
|
411
439
|
return obj
|
|
412
440
|
|
|
413
|
-
@cached_property
|
|
414
|
-
def identifier(self) -> str:
|
|
415
|
-
"""
|
|
416
|
-
This identifier is a hash of the layers and properties of the image. It is used to look up previously built
|
|
417
|
-
images. Why is this useful? For example, if a user has Image.from_uv_base().with_source_file("a/local/file"),
|
|
418
|
-
it's not necessarily the case that that file exists within the image (further commands may have removed/changed
|
|
419
|
-
it), and certainly not the case that the path to the file, inside the image (which is used as part of the layer
|
|
420
|
-
hash computation), is the same. That is, inside the image when a task runs, as we come across the same Image
|
|
421
|
-
declaration, we need a way of identifying the image and its uri, without hashing all the layers again. This
|
|
422
|
-
is what this identifier is for. See the ImageCache object for additional information.
|
|
423
|
-
|
|
424
|
-
:return: A unique identifier of the Image
|
|
425
|
-
"""
|
|
426
|
-
if self._identifier_override:
|
|
427
|
-
return self._identifier_override
|
|
428
|
-
|
|
429
|
-
# Only get the non-None values in the Image to ensure the hash is consistent
|
|
430
|
-
# across different SDK versions.
|
|
431
|
-
# Layers can specify a _compute_identifier optionally, but the default will just stringify
|
|
432
|
-
image_dict = asdict(self, dict_factory=lambda x: {k: v for (k, v) in x if v is not None and k != "_layers"})
|
|
433
|
-
layers_str_repr = "".join([layer.identifier() for layer in self._layers])
|
|
434
|
-
image_dict["layers"] = layers_str_repr
|
|
435
|
-
spec_bytes = image_dict.__str__().encode("utf-8")
|
|
436
|
-
return base64.urlsafe_b64encode(hashlib.md5(spec_bytes).digest()).decode("ascii").rstrip("=")
|
|
437
|
-
|
|
438
441
|
def validate(self):
|
|
439
442
|
for layer in self._layers:
|
|
440
443
|
layer.validate()
|
|
@@ -497,9 +500,6 @@ class Image:
|
|
|
497
500
|
image = image.with_pip_packages(f"flyte=={flyte_version}")
|
|
498
501
|
if not dev_mode:
|
|
499
502
|
object.__setattr__(image, "_tag", preset_tag)
|
|
500
|
-
# Set this to auto for all auto images because the meaning of "auto" can change (based on logic inside
|
|
501
|
-
# _get_default_image_for, acts differently in a running task container) so let's make sure it stays auto.
|
|
502
|
-
object.__setattr__(image, "_identifier_override", "auto")
|
|
503
503
|
|
|
504
504
|
return image
|
|
505
505
|
|
|
@@ -510,6 +510,7 @@ class Image:
|
|
|
510
510
|
flyte_version: Optional[str] = None,
|
|
511
511
|
install_flyte: bool = True,
|
|
512
512
|
registry: Optional[str] = None,
|
|
513
|
+
registry_secret: Optional[str | Secret] = None,
|
|
513
514
|
name: Optional[str] = None,
|
|
514
515
|
platform: Optional[Tuple[Architecture, ...]] = None,
|
|
515
516
|
) -> Image:
|
|
@@ -521,6 +522,7 @@ class Image:
|
|
|
521
522
|
:param flyte_version: Union version to use
|
|
522
523
|
:param install_flyte: If True, will install the flyte library in the image
|
|
523
524
|
:param registry: Registry to use for the image
|
|
525
|
+
:param registry_secret: Secret to use to pull/push the private image.
|
|
524
526
|
:param name: Name of the image if you want to override the default name
|
|
525
527
|
:param platform: Platform to use for the image, default is linux/amd64, use tuple for multiple values
|
|
526
528
|
Example: ("linux/amd64", "linux/arm64")
|
|
@@ -538,11 +540,8 @@ class Image:
|
|
|
538
540
|
)
|
|
539
541
|
|
|
540
542
|
if registry or name:
|
|
541
|
-
return base_image.clone(registry=registry, name=name)
|
|
543
|
+
return base_image.clone(registry=registry, name=name, registry_secret=registry_secret)
|
|
542
544
|
|
|
543
|
-
# # Set this to auto for all auto images because the meaning of "auto" can change (based on logic inside
|
|
544
|
-
# # _get_default_image_for, acts differently in a running task container) so let's make sure it stays auto.
|
|
545
|
-
# object.__setattr__(base_image, "_identifier_override", "auto")
|
|
546
545
|
return base_image
|
|
547
546
|
|
|
548
547
|
@classmethod
|
|
@@ -556,6 +555,13 @@ class Image:
|
|
|
556
555
|
img = cls._new(base_image=image_uri)
|
|
557
556
|
return img
|
|
558
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
|
+
|
|
559
565
|
@classmethod
|
|
560
566
|
def from_uv_script(
|
|
561
567
|
cls,
|
|
@@ -563,6 +569,7 @@ class Image:
|
|
|
563
569
|
*,
|
|
564
570
|
name: str,
|
|
565
571
|
registry: str | None = None,
|
|
572
|
+
registry_secret: Optional[str | Secret] = None,
|
|
566
573
|
python_version: Optional[Tuple[int, int]] = None,
|
|
567
574
|
index_url: Optional[str] = None,
|
|
568
575
|
extra_index_urls: Union[str, List[str], Tuple[str, ...], None] = None,
|
|
@@ -591,6 +598,7 @@ class Image:
|
|
|
591
598
|
|
|
592
599
|
:param name: name of the image
|
|
593
600
|
:param registry: registry to use for the image
|
|
601
|
+
:param registry_secret: Secret to use to pull/push the private image.
|
|
594
602
|
:param python_version: Python version to use for the image, if not specified, will use the current Python
|
|
595
603
|
version
|
|
596
604
|
:param script: path to the uv script
|
|
@@ -616,14 +624,22 @@ class Image:
|
|
|
616
624
|
secret_mounts=_ensure_tuple(secret_mounts) if secret_mounts else None,
|
|
617
625
|
)
|
|
618
626
|
|
|
619
|
-
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
|
+
)
|
|
620
634
|
|
|
621
635
|
return img.clone(addl_layer=ll)
|
|
622
636
|
|
|
623
637
|
def clone(
|
|
624
638
|
self,
|
|
625
639
|
registry: Optional[str] = None,
|
|
640
|
+
registry_secret: Optional[str | Secret] = None,
|
|
626
641
|
name: Optional[str] = None,
|
|
642
|
+
base_image: Optional[str] = None,
|
|
627
643
|
python_version: Optional[Tuple[int, int]] = None,
|
|
628
644
|
addl_layer: Optional[Layer] = None,
|
|
629
645
|
) -> Image:
|
|
@@ -631,12 +647,14 @@ class Image:
|
|
|
631
647
|
Use this method to clone the current image and change the registry and name
|
|
632
648
|
|
|
633
649
|
:param registry: Registry to use for the image
|
|
650
|
+
:param registry_secret: Secret to use to pull/push the private image.
|
|
634
651
|
:param name: Name of the image
|
|
635
652
|
:param python_version: Python version for the image, if not specified, will use the current Python version
|
|
636
653
|
:param addl_layer: Additional layer to add to the image. This will be added to the end of the layers.
|
|
637
|
-
|
|
638
654
|
:return:
|
|
639
655
|
"""
|
|
656
|
+
from flyte import Secret
|
|
657
|
+
|
|
640
658
|
if addl_layer and self.dockerfile:
|
|
641
659
|
# We don't know how to inspect dockerfiles to know what kind it is (OS, python version, uv vs poetry, etc)
|
|
642
660
|
# so there's no guarantee any of the layering logic will work.
|
|
@@ -646,19 +664,23 @@ class Image:
|
|
|
646
664
|
)
|
|
647
665
|
registry = registry if registry else self.registry
|
|
648
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
|
|
649
669
|
if addl_layer and (not name):
|
|
650
670
|
raise ValueError(
|
|
651
671
|
f"Cannot add additional layer {addl_layer} to an image without name. Please first clone()."
|
|
652
672
|
)
|
|
653
673
|
new_layers = (*self._layers, addl_layer) if addl_layer else self._layers
|
|
654
674
|
img = Image._new(
|
|
655
|
-
base_image=
|
|
675
|
+
base_image=base_image,
|
|
656
676
|
dockerfile=self.dockerfile,
|
|
657
677
|
registry=registry,
|
|
658
678
|
name=name,
|
|
659
679
|
platform=self.platform,
|
|
660
680
|
python_version=python_version or self.python_version,
|
|
661
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,
|
|
662
684
|
)
|
|
663
685
|
|
|
664
686
|
return img
|
|
@@ -782,9 +804,24 @@ class Image:
|
|
|
782
804
|
|
|
783
805
|
Example:
|
|
784
806
|
```python
|
|
785
|
-
@flyte.task(image=(flyte.Image
|
|
786
|
-
|
|
787
|
-
|
|
807
|
+
@flyte.task(image=(flyte.Image.from_debian_base().with_pip_packages("requests", "numpy")))
|
|
808
|
+
def my_task(x: int) -> int:
|
|
809
|
+
import numpy as np
|
|
810
|
+
return np.sum([x, 1])
|
|
811
|
+
```
|
|
812
|
+
|
|
813
|
+
To mount secrets during the build process to download private packages, you can use the `secret_mounts`.
|
|
814
|
+
In the below example, "GITHUB_PAT" will be mounted as env var "GITHUB_PAT",
|
|
815
|
+
and "apt-secret" will be mounted at /etc/apt/apt-secret.
|
|
816
|
+
Example:
|
|
817
|
+
```python
|
|
818
|
+
private_package = "git+https://$GITHUB_PAT@github.com/flyteorg/flytex.git@2e20a2acebfc3877d84af643fdd768edea41d533"
|
|
819
|
+
@flyte.task(
|
|
820
|
+
image=(
|
|
821
|
+
flyte.Image.from_debian_base()
|
|
822
|
+
.with_pip_packages("private_package", secret_mounts=[Secret(key="GITHUB_PAT")])
|
|
823
|
+
.with_apt_packages("git", secret_mounts=[Secret(key="apt-secret", mount="/etc/apt/apt-secret")])
|
|
824
|
+
)
|
|
788
825
|
def my_task(x: int) -> int:
|
|
789
826
|
import numpy as np
|
|
790
827
|
return np.sum([x, 1])
|
|
@@ -824,15 +861,19 @@ class Image:
|
|
|
824
861
|
new_image = self.clone(addl_layer=Env.from_dict(env_vars))
|
|
825
862
|
return new_image
|
|
826
863
|
|
|
827
|
-
def with_source_folder(self, src: Path, dst: str = ".") -> Image:
|
|
864
|
+
def with_source_folder(self, src: Path, dst: str = ".", copy_contents_only: bool = False) -> Image:
|
|
828
865
|
"""
|
|
829
866
|
Use this method to create a new image with the specified local directory layered on top of the current image.
|
|
830
867
|
If dest is not specified, it will be copied to the working directory of the image
|
|
831
868
|
|
|
832
869
|
:param src: root folder of the source code from the build context to be copied
|
|
833
870
|
:param dst: destination folder in the image
|
|
871
|
+
:param copy_contents_only: If True, will copy the contents of the source folder to the destination folder,
|
|
872
|
+
instead of the folder itself. Default is False.
|
|
834
873
|
:return: Image
|
|
835
874
|
"""
|
|
875
|
+
if not copy_contents_only:
|
|
876
|
+
dst = str("./" + src.name) if dst == "." else dst
|
|
836
877
|
new_image = self.clone(addl_layer=CopyConfig(path_type=1, src=src, dst=dst))
|
|
837
878
|
return new_image
|
|
838
879
|
|
|
@@ -861,12 +902,17 @@ class Image:
|
|
|
861
902
|
pre: bool = False,
|
|
862
903
|
extra_args: Optional[str] = None,
|
|
863
904
|
secret_mounts: Optional[SecretRequest] = None,
|
|
905
|
+
project_install_mode: typing.Literal["dependencies_only", "install_project"] = "dependencies_only",
|
|
864
906
|
) -> Image:
|
|
865
907
|
"""
|
|
866
908
|
Use this method to create a new image with the specified uv.lock file layered on top of the current image
|
|
867
909
|
Must have a corresponding pyproject.toml file in the same directory
|
|
868
910
|
Cannot be used in conjunction with conda
|
|
869
|
-
|
|
911
|
+
|
|
912
|
+
By default, this method copies the pyproject.toml and uv.lock files into the image.
|
|
913
|
+
|
|
914
|
+
If `project_install_mode` is "install_project", it will also copy directory
|
|
915
|
+
where the pyproject.toml file is located into the image.
|
|
870
916
|
|
|
871
917
|
:param pyproject_file: path to the pyproject.toml file, needs to have a corresponding uv.lock file
|
|
872
918
|
:param uvlock: path to the uv.lock file, if not specified, will use the default uv.lock file in the same
|
|
@@ -876,6 +922,8 @@ class Image:
|
|
|
876
922
|
:param pre: whether to allow pre-release versions, default is False
|
|
877
923
|
:param extra_args: extra arguments to pass to pip install, default is None
|
|
878
924
|
:param secret_mounts: list of secret mounts to use for the build process.
|
|
925
|
+
:param project_install_mode: whether to install the project as a package or
|
|
926
|
+
only dependencies, default is "dependencies_only"
|
|
879
927
|
:return: Image
|
|
880
928
|
"""
|
|
881
929
|
if isinstance(pyproject_file, str):
|
|
@@ -889,6 +937,50 @@ class Image:
|
|
|
889
937
|
pre=pre,
|
|
890
938
|
extra_args=extra_args,
|
|
891
939
|
secret_mounts=_ensure_tuple(secret_mounts) if secret_mounts else None,
|
|
940
|
+
project_install_mode=project_install_mode,
|
|
941
|
+
)
|
|
942
|
+
)
|
|
943
|
+
return new_image
|
|
944
|
+
|
|
945
|
+
def with_poetry_project(
|
|
946
|
+
self,
|
|
947
|
+
pyproject_file: str | Path,
|
|
948
|
+
poetry_lock: Path | None = None,
|
|
949
|
+
extra_args: Optional[str] = None,
|
|
950
|
+
secret_mounts: Optional[SecretRequest] = None,
|
|
951
|
+
project_install_mode: typing.Literal["dependencies_only", "install_project"] = "dependencies_only",
|
|
952
|
+
):
|
|
953
|
+
"""
|
|
954
|
+
Use this method to create a new image with the specified pyproject.toml layered on top of the current image.
|
|
955
|
+
Must have a corresponding pyproject.toml file in the same directory.
|
|
956
|
+
Cannot be used in conjunction with conda.
|
|
957
|
+
|
|
958
|
+
By default, this method copies the entire project into the image,
|
|
959
|
+
including files such as pyproject.toml, poetry.lock, and the src/ directory.
|
|
960
|
+
|
|
961
|
+
If you prefer not to install the current project, you can pass through `extra_args`
|
|
962
|
+
`--no-root`. In this case, the image builder will only copy pyproject.toml and poetry.lock
|
|
963
|
+
into the image.
|
|
964
|
+
|
|
965
|
+
:param pyproject_file: Path to the pyproject.toml file. A poetry.lock file must exist in the same directory
|
|
966
|
+
unless `poetry_lock` is explicitly provided.
|
|
967
|
+
:param poetry_lock: Path to the poetry.lock file. If not specified, the default is the file named
|
|
968
|
+
'poetry.lock' in the same directory as `pyproject_file` (pyproject.parent / "poetry.lock").
|
|
969
|
+
:param extra_args: Extra arguments to pass through to the package installer/resolver, default is None.
|
|
970
|
+
:param secret_mounts: Secrets to make available during dependency resolution/build (e.g., private indexes).
|
|
971
|
+
:param project_install_mode: whether to install the project as a package or
|
|
972
|
+
only dependencies, default is "dependencies_only"
|
|
973
|
+
:return: Image
|
|
974
|
+
"""
|
|
975
|
+
if isinstance(pyproject_file, str):
|
|
976
|
+
pyproject_file = Path(pyproject_file)
|
|
977
|
+
new_image = self.clone(
|
|
978
|
+
addl_layer=PoetryProject(
|
|
979
|
+
pyproject=pyproject_file,
|
|
980
|
+
poetry_lock=poetry_lock or (pyproject_file.parent / "poetry.lock"),
|
|
981
|
+
extra_args=extra_args,
|
|
982
|
+
secret_mounts=_ensure_tuple(secret_mounts) if secret_mounts else None,
|
|
983
|
+
project_install_mode=project_install_mode,
|
|
892
984
|
)
|
|
893
985
|
)
|
|
894
986
|
return new_image
|
|
@@ -909,16 +1001,21 @@ class Image:
|
|
|
909
1001
|
)
|
|
910
1002
|
return new_image
|
|
911
1003
|
|
|
912
|
-
def with_commands(self, commands: List[str]) -> Image:
|
|
1004
|
+
def with_commands(self, commands: List[str], secret_mounts: Optional[SecretRequest] = None) -> Image:
|
|
913
1005
|
"""
|
|
914
1006
|
Use this method to create a new image with the specified commands layered on top of the current image
|
|
915
1007
|
Be sure not to use RUN in your command.
|
|
916
1008
|
|
|
917
1009
|
:param commands: list of commands to run
|
|
1010
|
+
:param secret_mounts: list of secret mounts to use for the build process.
|
|
918
1011
|
:return: Image
|
|
919
1012
|
"""
|
|
920
1013
|
new_commands: Tuple = _ensure_tuple(commands)
|
|
921
|
-
new_image = self.clone(
|
|
1014
|
+
new_image = self.clone(
|
|
1015
|
+
addl_layer=Commands(
|
|
1016
|
+
commands=new_commands, secret_mounts=_ensure_tuple(secret_mounts) if secret_mounts else None
|
|
1017
|
+
)
|
|
1018
|
+
)
|
|
922
1019
|
return new_image
|
|
923
1020
|
|
|
924
1021
|
def with_local_v2(self) -> Image:
|