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