flyte 2.0.0b22__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/runtime.py +43 -5
- flyte/_cache/cache.py +4 -2
- flyte/_cache/local_cache.py +216 -0
- flyte/_code_bundle/_ignore.py +1 -1
- flyte/_code_bundle/_packaging.py +4 -4
- flyte/_code_bundle/_utils.py +14 -8
- flyte/_code_bundle/bundle.py +13 -5
- flyte/_constants.py +1 -0
- flyte/_context.py +4 -1
- flyte/_custom_context.py +73 -0
- flyte/_debug/constants.py +0 -1
- flyte/_debug/vscode.py +6 -1
- flyte/_deploy.py +223 -59
- flyte/_environment.py +5 -0
- flyte/_excepthook.py +1 -1
- flyte/_image.py +144 -82
- flyte/_initialize.py +95 -12
- flyte/_interface.py +2 -0
- flyte/_internal/controllers/_local_controller.py +65 -24
- flyte/_internal/controllers/_trace.py +1 -1
- flyte/_internal/controllers/remote/_action.py +13 -11
- flyte/_internal/controllers/remote/_client.py +1 -1
- flyte/_internal/controllers/remote/_controller.py +9 -4
- flyte/_internal/controllers/remote/_core.py +16 -16
- flyte/_internal/controllers/remote/_informer.py +4 -4
- flyte/_internal/controllers/remote/_service_protocol.py +7 -7
- flyte/_internal/imagebuild/docker_builder.py +139 -84
- flyte/_internal/imagebuild/image_builder.py +7 -13
- flyte/_internal/imagebuild/remote_builder.py +65 -13
- flyte/_internal/imagebuild/utils.py +51 -3
- flyte/_internal/resolvers/_task_module.py +5 -38
- flyte/_internal/resolvers/default.py +2 -2
- flyte/_internal/runtime/convert.py +42 -20
- flyte/_internal/runtime/entrypoints.py +24 -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 +33 -27
- flyte/_internal/runtime/taskrunner.py +10 -1
- flyte/_internal/runtime/trigger_serde.py +160 -0
- flyte/_internal/runtime/types_serde.py +1 -1
- flyte/_keyring/file.py +39 -9
- flyte/_logging.py +79 -12
- flyte/_map.py +31 -12
- flyte/_module.py +70 -0
- flyte/_pod.py +2 -2
- flyte/_resources.py +213 -31
- flyte/_run.py +107 -41
- flyte/_task.py +66 -10
- flyte/_task_environment.py +96 -24
- flyte/_task_plugins.py +4 -2
- flyte/_trigger.py +1000 -0
- flyte/_utils/__init__.py +2 -1
- flyte/_utils/asyn.py +3 -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 +1 -3
- flyte/cli/_common.py +78 -7
- flyte/cli/_create.py +178 -3
- flyte/cli/_delete.py +23 -1
- flyte/cli/_deploy.py +49 -11
- flyte/cli/_get.py +79 -34
- flyte/cli/_params.py +8 -6
- flyte/cli/_plugins.py +209 -0
- flyte/cli/_run.py +127 -11
- 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 +2 -0
- flyte/config/_internal.py +1 -0
- flyte/config/_reader.py +3 -3
- 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 +10 -1
- flyte/extend.py +8 -1
- flyte/extras/_container.py +6 -1
- flyte/git/_config.py +11 -9
- flyte/io/__init__.py +2 -0
- flyte/io/_dataframe/__init__.py +2 -0
- flyte/io/_dataframe/basic_dfs.py +1 -1
- flyte/io/_dataframe/dataframe.py +12 -8
- flyte/io/_dir.py +551 -120
- flyte/io/_file.py +538 -141
- flyte/models.py +57 -12
- flyte/remote/__init__.py +6 -1
- flyte/remote/_action.py +18 -16
- flyte/remote/_client/_protocols.py +39 -4
- 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 +4 -3
- flyte/remote/_logs.py +3 -3
- flyte/remote/_run.py +47 -7
- flyte/remote/_secret.py +26 -17
- flyte/remote/_task.py +21 -9
- flyte/remote/_trigger.py +306 -0
- flyte/remote/_user.py +33 -0
- flyte/storage/__init__.py +6 -1
- flyte/storage/_parallel_reader.py +274 -0
- flyte/storage/_storage.py +185 -103
- flyte/types/__init__.py +16 -0
- flyte/types/_interface.py +2 -2
- flyte/types/_pickle.py +17 -4
- flyte/types/_string_literals.py +8 -9
- flyte/types/_type_engine.py +26 -19
- flyte/types/_utils.py +1 -1
- {flyte-2.0.0b22.data → flyte-2.0.0b30.data}/scripts/runtime.py +43 -5
- {flyte-2.0.0b22.dist-info → flyte-2.0.0b30.dist-info}/METADATA +8 -1
- flyte-2.0.0b30.dist-info/RECORD +192 -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 -99
- flyte/_protos/common/identifier_pb2.pyi +0 -120
- 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/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 -111
- flyte/_protos/workflow/queue_service_pb2.pyi +0 -168
- flyte/_protos/workflow/queue_service_pb2_grpc.py +0 -172
- flyte/_protos/workflow/run_definition_pb2.py +0 -123
- flyte/_protos/workflow/run_definition_pb2.pyi +0 -352
- 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 -82
- flyte/_protos/workflow/task_definition_pb2.pyi +0 -88
- 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.0b22.dist-info/RECORD +0 -250
- {flyte-2.0.0b22.data → flyte-2.0.0b30.data}/scripts/debug.py +0 -0
- {flyte-2.0.0b22.dist-info → flyte-2.0.0b30.dist-info}/WHEEL +0 -0
- {flyte-2.0.0b22.dist-info → flyte-2.0.0b30.dist-info}/entry_points.txt +0 -0
- {flyte-2.0.0b22.dist-info → flyte-2.0.0b30.dist-info}/licenses/LICENSE +0 -0
- {flyte-2.0.0b22.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,28 +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
|
-
|
|
206
|
-
|
|
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)
|
|
207
228
|
|
|
208
229
|
|
|
209
230
|
@rich.repr.auto
|
|
210
231
|
@dataclass(frozen=True, repr=True)
|
|
211
232
|
class UVScript(PipOption, Layer):
|
|
212
|
-
script: Path
|
|
233
|
+
script: Path
|
|
213
234
|
script_name: str = field(init=False)
|
|
214
235
|
|
|
215
236
|
def __post_init__(self):
|
|
@@ -285,15 +306,13 @@ class DockerIgnore(Layer):
|
|
|
285
306
|
@rich.repr.auto
|
|
286
307
|
@dataclass(frozen=True, repr=True)
|
|
287
308
|
class CopyConfig(Layer):
|
|
288
|
-
path_type: CopyConfigType
|
|
289
|
-
src: Path
|
|
309
|
+
path_type: CopyConfigType
|
|
310
|
+
src: Path
|
|
290
311
|
dst: str
|
|
291
|
-
src_name: str = field(init=False)
|
|
292
312
|
|
|
293
313
|
def __post_init__(self):
|
|
294
314
|
if self.path_type not in (0, 1):
|
|
295
315
|
raise ValueError(f"Invalid path_type {self.path_type}, must be 0 (file) or 1 (directory)")
|
|
296
|
-
object.__setattr__(self, "src_name", self.src.name)
|
|
297
316
|
|
|
298
317
|
def validate(self):
|
|
299
318
|
if not self.src.exists():
|
|
@@ -381,9 +400,8 @@ class Image:
|
|
|
381
400
|
name: Optional[str] = field(default=None)
|
|
382
401
|
platform: Tuple[Architecture, ...] = field(default=("linux/amd64",))
|
|
383
402
|
python_version: Tuple[int, int] = field(default_factory=_detect_python_version)
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
_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)
|
|
387
405
|
|
|
388
406
|
# Layers to be added to the image. In init, because frozen, but users shouldn't access, so underscore.
|
|
389
407
|
_layers: Tuple[Layer, ...] = field(default_factory=tuple)
|
|
@@ -401,6 +419,9 @@ class Image:
|
|
|
401
419
|
# class-level token not included in __init__
|
|
402
420
|
_token: ClassVar[object] = object()
|
|
403
421
|
|
|
422
|
+
# Underscore cuz we may rename in the future, don't expose for now,
|
|
423
|
+
_image_registry_secret: Optional[Secret] = None
|
|
424
|
+
|
|
404
425
|
# check for the guard that we put in place
|
|
405
426
|
def __post_init__(self):
|
|
406
427
|
if object.__getattribute__(self, "__dict__").pop("_guard", None) is not Image._token:
|
|
@@ -417,34 +438,6 @@ class Image:
|
|
|
417
438
|
cls.__init__(obj, **kwargs) # run dataclass generated __init__
|
|
418
439
|
return obj
|
|
419
440
|
|
|
420
|
-
@cached_property
|
|
421
|
-
def identifier(self) -> str:
|
|
422
|
-
"""
|
|
423
|
-
This identifier is a hash of the layers and properties of the image. It is used to look up previously built
|
|
424
|
-
images. Why is this useful? For example, if a user has Image.from_uv_base().with_source_file("a/local/file"),
|
|
425
|
-
it's not necessarily the case that that file exists within the image (further commands may have removed/changed
|
|
426
|
-
it), and certainly not the case that the path to the file, inside the image (which is used as part of the layer
|
|
427
|
-
hash computation), is the same. That is, inside the image when a task runs, as we come across the same Image
|
|
428
|
-
declaration, we need a way of identifying the image and its uri, without hashing all the layers again. This
|
|
429
|
-
is what this identifier is for. See the ImageCache object for additional information.
|
|
430
|
-
|
|
431
|
-
:return: A unique identifier of the Image
|
|
432
|
-
"""
|
|
433
|
-
if self._identifier_override:
|
|
434
|
-
return self._identifier_override
|
|
435
|
-
|
|
436
|
-
# Only get the non-None values in the Image to ensure the hash is consistent
|
|
437
|
-
# across different SDK versions.
|
|
438
|
-
# Layers can specify a _compute_identifier optionally, but the default will just stringify
|
|
439
|
-
image_dict = asdict(
|
|
440
|
-
self,
|
|
441
|
-
dict_factory=lambda x: {k: v for (k, v) in x if v is not None and k not in ("_layers", "python_version")},
|
|
442
|
-
)
|
|
443
|
-
layers_str_repr = "".join([layer.identifier() for layer in self._layers])
|
|
444
|
-
image_dict["layers"] = layers_str_repr
|
|
445
|
-
spec_bytes = image_dict.__str__().encode("utf-8")
|
|
446
|
-
return base64.urlsafe_b64encode(hashlib.md5(spec_bytes).digest()).decode("ascii").rstrip("=")
|
|
447
|
-
|
|
448
441
|
def validate(self):
|
|
449
442
|
for layer in self._layers:
|
|
450
443
|
layer.validate()
|
|
@@ -507,9 +500,6 @@ class Image:
|
|
|
507
500
|
image = image.with_pip_packages(f"flyte=={flyte_version}")
|
|
508
501
|
if not dev_mode:
|
|
509
502
|
object.__setattr__(image, "_tag", preset_tag)
|
|
510
|
-
# Set this to auto for all auto images because the meaning of "auto" can change (based on logic inside
|
|
511
|
-
# _get_default_image_for, acts differently in a running task container) so let's make sure it stays auto.
|
|
512
|
-
object.__setattr__(image, "_identifier_override", "auto")
|
|
513
503
|
|
|
514
504
|
return image
|
|
515
505
|
|
|
@@ -520,6 +510,7 @@ class Image:
|
|
|
520
510
|
flyte_version: Optional[str] = None,
|
|
521
511
|
install_flyte: bool = True,
|
|
522
512
|
registry: Optional[str] = None,
|
|
513
|
+
registry_secret: Optional[str | Secret] = None,
|
|
523
514
|
name: Optional[str] = None,
|
|
524
515
|
platform: Optional[Tuple[Architecture, ...]] = None,
|
|
525
516
|
) -> Image:
|
|
@@ -531,6 +522,7 @@ class Image:
|
|
|
531
522
|
:param flyte_version: Union version to use
|
|
532
523
|
:param install_flyte: If True, will install the flyte library in the image
|
|
533
524
|
:param registry: Registry to use for the image
|
|
525
|
+
:param registry_secret: Secret to use to pull/push the private image.
|
|
534
526
|
:param name: Name of the image if you want to override the default name
|
|
535
527
|
:param platform: Platform to use for the image, default is linux/amd64, use tuple for multiple values
|
|
536
528
|
Example: ("linux/amd64", "linux/arm64")
|
|
@@ -548,11 +540,8 @@ class Image:
|
|
|
548
540
|
)
|
|
549
541
|
|
|
550
542
|
if registry or name:
|
|
551
|
-
return base_image.clone(registry=registry, name=name)
|
|
543
|
+
return base_image.clone(registry=registry, name=name, registry_secret=registry_secret)
|
|
552
544
|
|
|
553
|
-
# # Set this to auto for all auto images because the meaning of "auto" can change (based on logic inside
|
|
554
|
-
# # _get_default_image_for, acts differently in a running task container) so let's make sure it stays auto.
|
|
555
|
-
# object.__setattr__(base_image, "_identifier_override", "auto")
|
|
556
545
|
return base_image
|
|
557
546
|
|
|
558
547
|
@classmethod
|
|
@@ -566,6 +555,13 @@ class Image:
|
|
|
566
555
|
img = cls._new(base_image=image_uri)
|
|
567
556
|
return img
|
|
568
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
|
+
|
|
569
565
|
@classmethod
|
|
570
566
|
def from_uv_script(
|
|
571
567
|
cls,
|
|
@@ -573,6 +569,7 @@ class Image:
|
|
|
573
569
|
*,
|
|
574
570
|
name: str,
|
|
575
571
|
registry: str | None = None,
|
|
572
|
+
registry_secret: Optional[str | Secret] = None,
|
|
576
573
|
python_version: Optional[Tuple[int, int]] = None,
|
|
577
574
|
index_url: Optional[str] = None,
|
|
578
575
|
extra_index_urls: Union[str, List[str], Tuple[str, ...], None] = None,
|
|
@@ -601,6 +598,7 @@ class Image:
|
|
|
601
598
|
|
|
602
599
|
:param name: name of the image
|
|
603
600
|
:param registry: registry to use for the image
|
|
601
|
+
:param registry_secret: Secret to use to pull/push the private image.
|
|
604
602
|
:param python_version: Python version to use for the image, if not specified, will use the current Python
|
|
605
603
|
version
|
|
606
604
|
:param script: path to the uv script
|
|
@@ -626,14 +624,22 @@ class Image:
|
|
|
626
624
|
secret_mounts=_ensure_tuple(secret_mounts) if secret_mounts else None,
|
|
627
625
|
)
|
|
628
626
|
|
|
629
|
-
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
|
+
)
|
|
630
634
|
|
|
631
635
|
return img.clone(addl_layer=ll)
|
|
632
636
|
|
|
633
637
|
def clone(
|
|
634
638
|
self,
|
|
635
639
|
registry: Optional[str] = None,
|
|
640
|
+
registry_secret: Optional[str | Secret] = None,
|
|
636
641
|
name: Optional[str] = None,
|
|
642
|
+
base_image: Optional[str] = None,
|
|
637
643
|
python_version: Optional[Tuple[int, int]] = None,
|
|
638
644
|
addl_layer: Optional[Layer] = None,
|
|
639
645
|
) -> Image:
|
|
@@ -641,12 +647,14 @@ class Image:
|
|
|
641
647
|
Use this method to clone the current image and change the registry and name
|
|
642
648
|
|
|
643
649
|
:param registry: Registry to use for the image
|
|
650
|
+
:param registry_secret: Secret to use to pull/push the private image.
|
|
644
651
|
:param name: Name of the image
|
|
645
652
|
:param python_version: Python version for the image, if not specified, will use the current Python version
|
|
646
653
|
:param addl_layer: Additional layer to add to the image. This will be added to the end of the layers.
|
|
647
|
-
|
|
648
654
|
:return:
|
|
649
655
|
"""
|
|
656
|
+
from flyte import Secret
|
|
657
|
+
|
|
650
658
|
if addl_layer and self.dockerfile:
|
|
651
659
|
# We don't know how to inspect dockerfiles to know what kind it is (OS, python version, uv vs poetry, etc)
|
|
652
660
|
# so there's no guarantee any of the layering logic will work.
|
|
@@ -656,19 +664,23 @@ class Image:
|
|
|
656
664
|
)
|
|
657
665
|
registry = registry if registry else self.registry
|
|
658
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
|
|
659
669
|
if addl_layer and (not name):
|
|
660
670
|
raise ValueError(
|
|
661
671
|
f"Cannot add additional layer {addl_layer} to an image without name. Please first clone()."
|
|
662
672
|
)
|
|
663
673
|
new_layers = (*self._layers, addl_layer) if addl_layer else self._layers
|
|
664
674
|
img = Image._new(
|
|
665
|
-
base_image=
|
|
675
|
+
base_image=base_image,
|
|
666
676
|
dockerfile=self.dockerfile,
|
|
667
677
|
registry=registry,
|
|
668
678
|
name=name,
|
|
669
679
|
platform=self.platform,
|
|
670
680
|
python_version=python_version or self.python_version,
|
|
671
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,
|
|
672
684
|
)
|
|
673
685
|
|
|
674
686
|
return img
|
|
@@ -849,15 +861,19 @@ class Image:
|
|
|
849
861
|
new_image = self.clone(addl_layer=Env.from_dict(env_vars))
|
|
850
862
|
return new_image
|
|
851
863
|
|
|
852
|
-
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:
|
|
853
865
|
"""
|
|
854
866
|
Use this method to create a new image with the specified local directory layered on top of the current image.
|
|
855
867
|
If dest is not specified, it will be copied to the working directory of the image
|
|
856
868
|
|
|
857
869
|
:param src: root folder of the source code from the build context to be copied
|
|
858
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.
|
|
859
873
|
:return: Image
|
|
860
874
|
"""
|
|
875
|
+
if not copy_contents_only:
|
|
876
|
+
dst = str("./" + src.name) if dst == "." else dst
|
|
861
877
|
new_image = self.clone(addl_layer=CopyConfig(path_type=1, src=src, dst=dst))
|
|
862
878
|
return new_image
|
|
863
879
|
|
|
@@ -886,17 +902,17 @@ class Image:
|
|
|
886
902
|
pre: bool = False,
|
|
887
903
|
extra_args: Optional[str] = None,
|
|
888
904
|
secret_mounts: Optional[SecretRequest] = None,
|
|
905
|
+
project_install_mode: typing.Literal["dependencies_only", "install_project"] = "dependencies_only",
|
|
889
906
|
) -> Image:
|
|
890
907
|
"""
|
|
891
908
|
Use this method to create a new image with the specified uv.lock file layered on top of the current image
|
|
892
909
|
Must have a corresponding pyproject.toml file in the same directory
|
|
893
910
|
Cannot be used in conjunction with conda
|
|
894
911
|
|
|
895
|
-
By default, this method copies the
|
|
896
|
-
including files such as pyproject.toml, uv.lock, and the src/ directory.
|
|
912
|
+
By default, this method copies the pyproject.toml and uv.lock files into the image.
|
|
897
913
|
|
|
898
|
-
If
|
|
899
|
-
|
|
914
|
+
If `project_install_mode` is "install_project", it will also copy directory
|
|
915
|
+
where the pyproject.toml file is located into the image.
|
|
900
916
|
|
|
901
917
|
:param pyproject_file: path to the pyproject.toml file, needs to have a corresponding uv.lock file
|
|
902
918
|
:param uvlock: path to the uv.lock file, if not specified, will use the default uv.lock file in the same
|
|
@@ -906,6 +922,8 @@ class Image:
|
|
|
906
922
|
:param pre: whether to allow pre-release versions, default is False
|
|
907
923
|
:param extra_args: extra arguments to pass to pip install, default is None
|
|
908
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"
|
|
909
927
|
:return: Image
|
|
910
928
|
"""
|
|
911
929
|
if isinstance(pyproject_file, str):
|
|
@@ -919,6 +937,50 @@ class Image:
|
|
|
919
937
|
pre=pre,
|
|
920
938
|
extra_args=extra_args,
|
|
921
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,
|
|
922
984
|
)
|
|
923
985
|
)
|
|
924
986
|
return new_image
|
flyte/_initialize.py
CHANGED
|
@@ -3,14 +3,14 @@ from __future__ import annotations
|
|
|
3
3
|
import functools
|
|
4
4
|
import threading
|
|
5
5
|
import typing
|
|
6
|
-
from dataclasses import dataclass, replace
|
|
6
|
+
from dataclasses import dataclass, field, replace
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import TYPE_CHECKING, Callable, List, Literal, Optional, TypeVar
|
|
9
9
|
|
|
10
10
|
from flyte.errors import InitializationError
|
|
11
11
|
from flyte.syncify import syncify
|
|
12
12
|
|
|
13
|
-
from ._logging import initialize_logger, logger
|
|
13
|
+
from ._logging import LogFormat, initialize_logger, logger
|
|
14
14
|
|
|
15
15
|
if TYPE_CHECKING:
|
|
16
16
|
from flyte._internal.imagebuild import ImageBuildEngine
|
|
@@ -34,6 +34,7 @@ class CommonInit:
|
|
|
34
34
|
domain: str | None = None
|
|
35
35
|
batch_size: int = 1000
|
|
36
36
|
source_config_path: Optional[Path] = None # Only used for documentation
|
|
37
|
+
sync_local_sys_paths: bool = True
|
|
37
38
|
|
|
38
39
|
|
|
39
40
|
@dataclass(init=True, kw_only=True, repr=True, eq=True, frozen=True)
|
|
@@ -41,6 +42,7 @@ class _InitConfig(CommonInit):
|
|
|
41
42
|
client: Optional[ClientSet] = None
|
|
42
43
|
storage: Optional[Storage] = None
|
|
43
44
|
image_builder: "ImageBuildEngine.ImageBuilderType" = "local"
|
|
45
|
+
images: typing.Dict[str, str] = field(default_factory=dict)
|
|
44
46
|
|
|
45
47
|
def replace(self, **kwargs) -> _InitConfig:
|
|
46
48
|
return replace(self, **kwargs)
|
|
@@ -111,10 +113,8 @@ async def _initialize_client(
|
|
|
111
113
|
)
|
|
112
114
|
|
|
113
115
|
|
|
114
|
-
def _initialize_logger(log_level: int | None = None):
|
|
115
|
-
initialize_logger(enable_rich=True)
|
|
116
|
-
if log_level:
|
|
117
|
-
initialize_logger(log_level=log_level, enable_rich=True)
|
|
116
|
+
def _initialize_logger(log_level: int | None = None, log_format: LogFormat | None = None) -> None:
|
|
117
|
+
initialize_logger(log_level=log_level, log_format=log_format, enable_rich=True)
|
|
118
118
|
|
|
119
119
|
|
|
120
120
|
@syncify
|
|
@@ -124,6 +124,7 @@ async def init(
|
|
|
124
124
|
domain: str | None = None,
|
|
125
125
|
root_dir: Path | None = None,
|
|
126
126
|
log_level: int | None = None,
|
|
127
|
+
log_format: LogFormat | None = None,
|
|
127
128
|
endpoint: str | None = None,
|
|
128
129
|
headless: bool = False,
|
|
129
130
|
insecure: bool = False,
|
|
@@ -141,7 +142,10 @@ async def init(
|
|
|
141
142
|
storage: Storage | None = None,
|
|
142
143
|
batch_size: int = 1000,
|
|
143
144
|
image_builder: ImageBuildEngine.ImageBuilderType = "local",
|
|
145
|
+
images: typing.Dict[str, str] | None = None,
|
|
144
146
|
source_config_path: Optional[Path] = None,
|
|
147
|
+
sync_local_sys_paths: bool = True,
|
|
148
|
+
load_plugin_type_transformers: bool = True,
|
|
145
149
|
) -> None:
|
|
146
150
|
"""
|
|
147
151
|
Initialize the Flyte system with the given configuration. This method should be called before any other Flyte
|
|
@@ -154,6 +158,7 @@ async def init(
|
|
|
154
158
|
also use to determine all the code that needs to be copied to the remote location.
|
|
155
159
|
defaults to the editable install directory if the cwd is in a Python editable install, else just the cwd.
|
|
156
160
|
:param log_level: Optional logging level for the logger, default is set using the default initialization policies
|
|
161
|
+
:param log_format: Optional logging format for the logger, default is "console"
|
|
157
162
|
:param api_key: Optional API key for authentication
|
|
158
163
|
:param endpoint: Optional API endpoint URL
|
|
159
164
|
:param headless: Optional Whether to run in headless mode
|
|
@@ -170,19 +175,26 @@ async def init(
|
|
|
170
175
|
:param ca_cert_file_path: [optional] str Root Cert to be loaded and used to verify admin
|
|
171
176
|
:param http_proxy_url: [optional] HTTP Proxy to be used for OAuth requests
|
|
172
177
|
:param rpc_retries: [optional] int Number of times to retry the platform calls
|
|
173
|
-
:param audience: oauth2 audience for the token request. This is used to validate the token
|
|
174
178
|
:param insecure: insecure flag for the client
|
|
175
179
|
:param storage: Optional blob store (S3, GCS, Azure) configuration if needed to access (i.e. using Minio)
|
|
176
180
|
:param org: Optional organization override for the client. Should be set by auth instead.
|
|
177
181
|
:param batch_size: Optional batch size for operations that use listings, defaults to 1000, so limit larger than
|
|
178
182
|
batch_size will be split into multiple requests.
|
|
179
183
|
:param image_builder: Optional image builder configuration, if not provided, the default image builder will be used.
|
|
184
|
+
:param images: Optional dict of images that can be used by referencing the image name.
|
|
180
185
|
:param source_config_path: Optional path to the source configuration file (This is only used for documentation)
|
|
186
|
+
:param sync_local_sys_paths: Whether to include and synchronize local sys.path entries under the root directory
|
|
187
|
+
into the remote container (default: True).
|
|
188
|
+
:param load_plugin_type_transformers: If enabled (default True), load the type transformer plugins registered under
|
|
189
|
+
the "flyte.plugins.types" entry point group.
|
|
181
190
|
:return: None
|
|
182
191
|
"""
|
|
183
192
|
from flyte._utils import get_cwd_editable_install, org_from_endpoint, sanitize_endpoint
|
|
193
|
+
from flyte.types import _load_custom_type_transformers
|
|
184
194
|
|
|
185
|
-
_initialize_logger(log_level=log_level)
|
|
195
|
+
_initialize_logger(log_level=log_level, log_format=log_format)
|
|
196
|
+
if load_plugin_type_transformers:
|
|
197
|
+
_load_custom_type_transformers()
|
|
186
198
|
|
|
187
199
|
global _init_config # noqa: PLW0603
|
|
188
200
|
|
|
@@ -216,7 +228,7 @@ async def init(
|
|
|
216
228
|
else:
|
|
217
229
|
logger.info("No editable install found, using current working directory as root directory.")
|
|
218
230
|
root_dir = Path.cwd()
|
|
219
|
-
|
|
231
|
+
|
|
220
232
|
_init_config = _InitConfig(
|
|
221
233
|
root_dir=root_dir,
|
|
222
234
|
project=project,
|
|
@@ -226,7 +238,9 @@ async def init(
|
|
|
226
238
|
org=org or org_from_endpoint(endpoint),
|
|
227
239
|
batch_size=batch_size,
|
|
228
240
|
image_builder=image_builder,
|
|
241
|
+
images=images or {},
|
|
229
242
|
source_config_path=source_config_path,
|
|
243
|
+
sync_local_sys_paths=sync_local_sys_paths,
|
|
230
244
|
)
|
|
231
245
|
|
|
232
246
|
|
|
@@ -235,7 +249,10 @@ async def init_from_config(
|
|
|
235
249
|
path_or_config: str | Path | Config | None = None,
|
|
236
250
|
root_dir: Path | None = None,
|
|
237
251
|
log_level: int | None = None,
|
|
252
|
+
log_format: LogFormat = "console",
|
|
238
253
|
storage: Storage | None = None,
|
|
254
|
+
images: tuple[str, ...] | None = None,
|
|
255
|
+
sync_local_sys_paths: bool = True,
|
|
239
256
|
) -> None:
|
|
240
257
|
"""
|
|
241
258
|
Initialize the Flyte system using a configuration file or Config object. This method should be called before any
|
|
@@ -248,12 +265,17 @@ async def init_from_config(
|
|
|
248
265
|
if not available, the current working directory.
|
|
249
266
|
:param log_level: Optional logging level for the framework logger,
|
|
250
267
|
default is set using the default initialization policies
|
|
268
|
+
:param log_format: Optional logging format for the logger, default is "console"
|
|
251
269
|
:param storage: Optional blob store (S3, GCS, Azure) configuration if needed to access (i.e. using Minio)
|
|
270
|
+
:param images: List of image strings in format "imagename=imageuri" or just "imageuri".
|
|
271
|
+
:param sync_local_sys_paths: Whether to include and synchronize local sys.path entries under the root directory
|
|
272
|
+
into the remote container (default: True).
|
|
252
273
|
:return: None
|
|
253
274
|
"""
|
|
254
275
|
from rich.highlighter import ReprHighlighter
|
|
255
276
|
|
|
256
277
|
import flyte.config as config
|
|
278
|
+
from flyte.cli._common import parse_images
|
|
257
279
|
|
|
258
280
|
cfg: config.Config
|
|
259
281
|
cfg_path: Optional[Path] = None
|
|
@@ -275,9 +297,11 @@ async def init_from_config(
|
|
|
275
297
|
else:
|
|
276
298
|
cfg = path_or_config
|
|
277
299
|
|
|
278
|
-
_initialize_logger(log_level=log_level)
|
|
279
|
-
|
|
280
300
|
logger.info(f"Flyte config initialized as {cfg}", extra={"highlighter": ReprHighlighter()})
|
|
301
|
+
|
|
302
|
+
# parse image, this will overwrite the image_refs set in the config file
|
|
303
|
+
parse_images(cfg, images)
|
|
304
|
+
|
|
281
305
|
await init.aio(
|
|
282
306
|
org=cfg.task.org,
|
|
283
307
|
project=cfg.task.project,
|
|
@@ -293,9 +317,12 @@ async def init_from_config(
|
|
|
293
317
|
client_credentials_secret=cfg.platform.client_credentials_secret,
|
|
294
318
|
root_dir=root_dir,
|
|
295
319
|
log_level=log_level,
|
|
320
|
+
log_format=log_format,
|
|
296
321
|
image_builder=cfg.image.builder,
|
|
322
|
+
images=cfg.image.image_refs,
|
|
297
323
|
storage=storage,
|
|
298
324
|
source_config_path=cfg_path,
|
|
325
|
+
sync_local_sys_paths=sync_local_sys_paths,
|
|
299
326
|
)
|
|
300
327
|
|
|
301
328
|
|
|
@@ -309,7 +336,7 @@ def _get_init_config() -> Optional[_InitConfig]:
|
|
|
309
336
|
return _init_config
|
|
310
337
|
|
|
311
338
|
|
|
312
|
-
def
|
|
339
|
+
def get_init_config() -> _InitConfig:
|
|
313
340
|
"""
|
|
314
341
|
Get the current initialization configuration. Thread-safe implementation.
|
|
315
342
|
|
|
@@ -467,6 +494,34 @@ def requires_initialization(func: T) -> T:
|
|
|
467
494
|
return typing.cast(T, wrapper)
|
|
468
495
|
|
|
469
496
|
|
|
497
|
+
def require_project_and_domain(func):
|
|
498
|
+
"""
|
|
499
|
+
Decorator that ensures the current Flyte configuration defines
|
|
500
|
+
both 'project' and 'domain'. Raises a clear error if not found.
|
|
501
|
+
"""
|
|
502
|
+
|
|
503
|
+
@functools.wraps(func)
|
|
504
|
+
def wrapper(*args, **kwargs):
|
|
505
|
+
cfg = get_init_config()
|
|
506
|
+
if cfg.project is None:
|
|
507
|
+
raise ValueError(
|
|
508
|
+
"Project must be provided to initialize the client. "
|
|
509
|
+
"Please set 'project' in the 'task' section of your config file, "
|
|
510
|
+
"or pass it directly to flyte.init(project='your-project-name')."
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
if cfg.domain is None:
|
|
514
|
+
raise ValueError(
|
|
515
|
+
"Domain must be provided to initialize the client. "
|
|
516
|
+
"Please set 'domain' in the 'task' section of your config file, "
|
|
517
|
+
"or pass it directly to flyte.init(domain='your-domain-name')."
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
return func(*args, **kwargs)
|
|
521
|
+
|
|
522
|
+
return wrapper
|
|
523
|
+
|
|
524
|
+
|
|
470
525
|
async def _init_for_testing(
|
|
471
526
|
project: str | None = None,
|
|
472
527
|
domain: str | None = None,
|
|
@@ -496,3 +551,31 @@ def replace_client(client):
|
|
|
496
551
|
|
|
497
552
|
with _init_lock:
|
|
498
553
|
_init_config = _init_config.replace(client=client)
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
def current_domain() -> str:
|
|
557
|
+
"""
|
|
558
|
+
Returns the current domain from Runtime environment (on the cluster) or from the initialized configuration.
|
|
559
|
+
This is safe to be used during `deploy`, `run` and within `task` code.
|
|
560
|
+
|
|
561
|
+
NOTE: This will not work if you deploy a task to a domain and then run it in another domain.
|
|
562
|
+
|
|
563
|
+
Raises InitializationError if the configuration is not initialized or domain is not set.
|
|
564
|
+
:return: The current domain
|
|
565
|
+
"""
|
|
566
|
+
from ._context import ctx
|
|
567
|
+
|
|
568
|
+
tctx = ctx()
|
|
569
|
+
if tctx is not None:
|
|
570
|
+
domain = tctx.action.domain
|
|
571
|
+
if domain is not None:
|
|
572
|
+
return domain
|
|
573
|
+
|
|
574
|
+
cfg = _get_init_config()
|
|
575
|
+
if cfg is None or cfg.domain is None:
|
|
576
|
+
raise InitializationError(
|
|
577
|
+
"DomainNotInitializedError",
|
|
578
|
+
"user",
|
|
579
|
+
"Domain has not been initialized. Call flyte.init() with a valid domain before using this function.",
|
|
580
|
+
)
|
|
581
|
+
return cfg.domain
|