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.
Files changed (197) hide show
  1. flyte/__init__.py +18 -2
  2. flyte/_bin/runtime.py +43 -5
  3. flyte/_cache/cache.py +4 -2
  4. flyte/_cache/local_cache.py +216 -0
  5. flyte/_code_bundle/_ignore.py +1 -1
  6. flyte/_code_bundle/_packaging.py +4 -4
  7. flyte/_code_bundle/_utils.py +14 -8
  8. flyte/_code_bundle/bundle.py +13 -5
  9. flyte/_constants.py +1 -0
  10. flyte/_context.py +4 -1
  11. flyte/_custom_context.py +73 -0
  12. flyte/_debug/constants.py +0 -1
  13. flyte/_debug/vscode.py +6 -1
  14. flyte/_deploy.py +223 -59
  15. flyte/_environment.py +5 -0
  16. flyte/_excepthook.py +1 -1
  17. flyte/_image.py +144 -82
  18. flyte/_initialize.py +95 -12
  19. flyte/_interface.py +2 -0
  20. flyte/_internal/controllers/_local_controller.py +65 -24
  21. flyte/_internal/controllers/_trace.py +1 -1
  22. flyte/_internal/controllers/remote/_action.py +13 -11
  23. flyte/_internal/controllers/remote/_client.py +1 -1
  24. flyte/_internal/controllers/remote/_controller.py +9 -4
  25. flyte/_internal/controllers/remote/_core.py +16 -16
  26. flyte/_internal/controllers/remote/_informer.py +4 -4
  27. flyte/_internal/controllers/remote/_service_protocol.py +7 -7
  28. flyte/_internal/imagebuild/docker_builder.py +139 -84
  29. flyte/_internal/imagebuild/image_builder.py +7 -13
  30. flyte/_internal/imagebuild/remote_builder.py +65 -13
  31. flyte/_internal/imagebuild/utils.py +51 -3
  32. flyte/_internal/resolvers/_task_module.py +5 -38
  33. flyte/_internal/resolvers/default.py +2 -2
  34. flyte/_internal/runtime/convert.py +42 -20
  35. flyte/_internal/runtime/entrypoints.py +24 -1
  36. flyte/_internal/runtime/io.py +21 -8
  37. flyte/_internal/runtime/resources_serde.py +20 -6
  38. flyte/_internal/runtime/reuse.py +1 -1
  39. flyte/_internal/runtime/rusty.py +20 -5
  40. flyte/_internal/runtime/task_serde.py +33 -27
  41. flyte/_internal/runtime/taskrunner.py +10 -1
  42. flyte/_internal/runtime/trigger_serde.py +160 -0
  43. flyte/_internal/runtime/types_serde.py +1 -1
  44. flyte/_keyring/file.py +39 -9
  45. flyte/_logging.py +79 -12
  46. flyte/_map.py +31 -12
  47. flyte/_module.py +70 -0
  48. flyte/_pod.py +2 -2
  49. flyte/_resources.py +213 -31
  50. flyte/_run.py +107 -41
  51. flyte/_task.py +66 -10
  52. flyte/_task_environment.py +96 -24
  53. flyte/_task_plugins.py +4 -2
  54. flyte/_trigger.py +1000 -0
  55. flyte/_utils/__init__.py +2 -1
  56. flyte/_utils/asyn.py +3 -1
  57. flyte/_utils/docker_credentials.py +173 -0
  58. flyte/_utils/module_loader.py +17 -2
  59. flyte/_version.py +3 -3
  60. flyte/cli/_abort.py +3 -3
  61. flyte/cli/_build.py +1 -3
  62. flyte/cli/_common.py +78 -7
  63. flyte/cli/_create.py +178 -3
  64. flyte/cli/_delete.py +23 -1
  65. flyte/cli/_deploy.py +49 -11
  66. flyte/cli/_get.py +79 -34
  67. flyte/cli/_params.py +8 -6
  68. flyte/cli/_plugins.py +209 -0
  69. flyte/cli/_run.py +127 -11
  70. flyte/cli/_serve.py +64 -0
  71. flyte/cli/_update.py +37 -0
  72. flyte/cli/_user.py +17 -0
  73. flyte/cli/main.py +30 -4
  74. flyte/config/_config.py +2 -0
  75. flyte/config/_internal.py +1 -0
  76. flyte/config/_reader.py +3 -3
  77. flyte/connectors/__init__.py +11 -0
  78. flyte/connectors/_connector.py +270 -0
  79. flyte/connectors/_server.py +197 -0
  80. flyte/connectors/utils.py +135 -0
  81. flyte/errors.py +10 -1
  82. flyte/extend.py +8 -1
  83. flyte/extras/_container.py +6 -1
  84. flyte/git/_config.py +11 -9
  85. flyte/io/__init__.py +2 -0
  86. flyte/io/_dataframe/__init__.py +2 -0
  87. flyte/io/_dataframe/basic_dfs.py +1 -1
  88. flyte/io/_dataframe/dataframe.py +12 -8
  89. flyte/io/_dir.py +551 -120
  90. flyte/io/_file.py +538 -141
  91. flyte/models.py +57 -12
  92. flyte/remote/__init__.py +6 -1
  93. flyte/remote/_action.py +18 -16
  94. flyte/remote/_client/_protocols.py +39 -4
  95. flyte/remote/_client/auth/_channel.py +10 -6
  96. flyte/remote/_client/controlplane.py +17 -5
  97. flyte/remote/_console.py +3 -2
  98. flyte/remote/_data.py +4 -3
  99. flyte/remote/_logs.py +3 -3
  100. flyte/remote/_run.py +47 -7
  101. flyte/remote/_secret.py +26 -17
  102. flyte/remote/_task.py +21 -9
  103. flyte/remote/_trigger.py +306 -0
  104. flyte/remote/_user.py +33 -0
  105. flyte/storage/__init__.py +6 -1
  106. flyte/storage/_parallel_reader.py +274 -0
  107. flyte/storage/_storage.py +185 -103
  108. flyte/types/__init__.py +16 -0
  109. flyte/types/_interface.py +2 -2
  110. flyte/types/_pickle.py +17 -4
  111. flyte/types/_string_literals.py +8 -9
  112. flyte/types/_type_engine.py +26 -19
  113. flyte/types/_utils.py +1 -1
  114. {flyte-2.0.0b22.data → flyte-2.0.0b30.data}/scripts/runtime.py +43 -5
  115. {flyte-2.0.0b22.dist-info → flyte-2.0.0b30.dist-info}/METADATA +8 -1
  116. flyte-2.0.0b30.dist-info/RECORD +192 -0
  117. flyte/_protos/__init__.py +0 -0
  118. flyte/_protos/common/authorization_pb2.py +0 -66
  119. flyte/_protos/common/authorization_pb2.pyi +0 -108
  120. flyte/_protos/common/authorization_pb2_grpc.py +0 -4
  121. flyte/_protos/common/identifier_pb2.py +0 -99
  122. flyte/_protos/common/identifier_pb2.pyi +0 -120
  123. flyte/_protos/common/identifier_pb2_grpc.py +0 -4
  124. flyte/_protos/common/identity_pb2.py +0 -48
  125. flyte/_protos/common/identity_pb2.pyi +0 -72
  126. flyte/_protos/common/identity_pb2_grpc.py +0 -4
  127. flyte/_protos/common/list_pb2.py +0 -36
  128. flyte/_protos/common/list_pb2.pyi +0 -71
  129. flyte/_protos/common/list_pb2_grpc.py +0 -4
  130. flyte/_protos/common/policy_pb2.py +0 -37
  131. flyte/_protos/common/policy_pb2.pyi +0 -27
  132. flyte/_protos/common/policy_pb2_grpc.py +0 -4
  133. flyte/_protos/common/role_pb2.py +0 -37
  134. flyte/_protos/common/role_pb2.pyi +0 -53
  135. flyte/_protos/common/role_pb2_grpc.py +0 -4
  136. flyte/_protos/common/runtime_version_pb2.py +0 -28
  137. flyte/_protos/common/runtime_version_pb2.pyi +0 -24
  138. flyte/_protos/common/runtime_version_pb2_grpc.py +0 -4
  139. flyte/_protos/imagebuilder/definition_pb2.py +0 -60
  140. flyte/_protos/imagebuilder/definition_pb2.pyi +0 -153
  141. flyte/_protos/imagebuilder/definition_pb2_grpc.py +0 -4
  142. flyte/_protos/imagebuilder/payload_pb2.py +0 -32
  143. flyte/_protos/imagebuilder/payload_pb2.pyi +0 -21
  144. flyte/_protos/imagebuilder/payload_pb2_grpc.py +0 -4
  145. flyte/_protos/imagebuilder/service_pb2.py +0 -29
  146. flyte/_protos/imagebuilder/service_pb2.pyi +0 -5
  147. flyte/_protos/imagebuilder/service_pb2_grpc.py +0 -66
  148. flyte/_protos/logs/dataplane/payload_pb2.py +0 -100
  149. flyte/_protos/logs/dataplane/payload_pb2.pyi +0 -177
  150. flyte/_protos/logs/dataplane/payload_pb2_grpc.py +0 -4
  151. flyte/_protos/secret/definition_pb2.py +0 -49
  152. flyte/_protos/secret/definition_pb2.pyi +0 -93
  153. flyte/_protos/secret/definition_pb2_grpc.py +0 -4
  154. flyte/_protos/secret/payload_pb2.py +0 -62
  155. flyte/_protos/secret/payload_pb2.pyi +0 -94
  156. flyte/_protos/secret/payload_pb2_grpc.py +0 -4
  157. flyte/_protos/secret/secret_pb2.py +0 -38
  158. flyte/_protos/secret/secret_pb2.pyi +0 -6
  159. flyte/_protos/secret/secret_pb2_grpc.py +0 -198
  160. flyte/_protos/secret/secret_pb2_grpc_grpc.py +0 -198
  161. flyte/_protos/validate/validate/validate_pb2.py +0 -76
  162. flyte/_protos/workflow/common_pb2.py +0 -27
  163. flyte/_protos/workflow/common_pb2.pyi +0 -14
  164. flyte/_protos/workflow/common_pb2_grpc.py +0 -4
  165. flyte/_protos/workflow/environment_pb2.py +0 -29
  166. flyte/_protos/workflow/environment_pb2.pyi +0 -12
  167. flyte/_protos/workflow/environment_pb2_grpc.py +0 -4
  168. flyte/_protos/workflow/node_execution_service_pb2.py +0 -26
  169. flyte/_protos/workflow/node_execution_service_pb2.pyi +0 -4
  170. flyte/_protos/workflow/node_execution_service_pb2_grpc.py +0 -32
  171. flyte/_protos/workflow/queue_service_pb2.py +0 -111
  172. flyte/_protos/workflow/queue_service_pb2.pyi +0 -168
  173. flyte/_protos/workflow/queue_service_pb2_grpc.py +0 -172
  174. flyte/_protos/workflow/run_definition_pb2.py +0 -123
  175. flyte/_protos/workflow/run_definition_pb2.pyi +0 -352
  176. flyte/_protos/workflow/run_definition_pb2_grpc.py +0 -4
  177. flyte/_protos/workflow/run_logs_service_pb2.py +0 -41
  178. flyte/_protos/workflow/run_logs_service_pb2.pyi +0 -28
  179. flyte/_protos/workflow/run_logs_service_pb2_grpc.py +0 -69
  180. flyte/_protos/workflow/run_service_pb2.py +0 -137
  181. flyte/_protos/workflow/run_service_pb2.pyi +0 -185
  182. flyte/_protos/workflow/run_service_pb2_grpc.py +0 -446
  183. flyte/_protos/workflow/state_service_pb2.py +0 -67
  184. flyte/_protos/workflow/state_service_pb2.pyi +0 -76
  185. flyte/_protos/workflow/state_service_pb2_grpc.py +0 -138
  186. flyte/_protos/workflow/task_definition_pb2.py +0 -82
  187. flyte/_protos/workflow/task_definition_pb2.pyi +0 -88
  188. flyte/_protos/workflow/task_definition_pb2_grpc.py +0 -4
  189. flyte/_protos/workflow/task_service_pb2.py +0 -60
  190. flyte/_protos/workflow/task_service_pb2.pyi +0 -59
  191. flyte/_protos/workflow/task_service_pb2_grpc.py +0 -138
  192. flyte-2.0.0b22.dist-info/RECORD +0 -250
  193. {flyte-2.0.0b22.data → flyte-2.0.0b30.data}/scripts/debug.py +0 -0
  194. {flyte-2.0.0b22.dist-info → flyte-2.0.0b30.dist-info}/WHEEL +0 -0
  195. {flyte-2.0.0b22.dist-info → flyte-2.0.0b30.dist-info}/entry_points.txt +0 -0
  196. {flyte-2.0.0b22.dist-info → flyte-2.0.0b30.dist-info}/licenses/LICENSE +0 -0
  197. {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 asdict, dataclass, field, fields
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 = field(metadata={"identifier": False})
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
- filehash_update(self.uvlock, hasher)
206
- filehash_update(self.pyproject, hasher)
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 = field(metadata={"identifier": False})
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 = field(metadata={"identifier": True})
289
- src: Path = field(metadata={"identifier": False})
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
- # For .auto() images. Don't compute an actual identifier.
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(registry=registry, name=name, python_version=python_version, platform=platform)
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=self.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 entire project into the image,
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 you prefer not to install the current project, you can pass the extra argument --no-install-project.
899
- In this case, the image builder will only copy pyproject.toml and uv.lock into the image.
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
- root_dir = root_dir or get_cwd_editable_install() or Path.cwd()
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 get_common_config() -> CommonInit:
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