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
@@ -6,7 +6,7 @@ import tempfile
6
6
  import typing
7
7
  from pathlib import Path
8
8
  from string import Template
9
- from typing import ClassVar, List, Optional, Protocol, cast
9
+ from typing import ClassVar, Optional, Protocol, cast
10
10
 
11
11
  import aiofiles
12
12
  import click
@@ -22,6 +22,7 @@ from flyte._image import (
22
22
  Layer,
23
23
  PipOption,
24
24
  PipPackages,
25
+ PoetryProject,
25
26
  PythonWheels,
26
27
  Requirements,
27
28
  UVProject,
@@ -37,7 +38,7 @@ from flyte._internal.imagebuild.image_builder import (
37
38
  LocalDockerCommandImageChecker,
38
39
  LocalPodmanCommandImageChecker,
39
40
  )
40
- from flyte._internal.imagebuild.utils import copy_files_to_context
41
+ from flyte._internal.imagebuild.utils import copy_files_to_context, get_and_list_dockerignore
41
42
  from flyte._logging import logger
42
43
 
43
44
  _F_IMG_ID = "_F_IMG_ID"
@@ -46,44 +47,71 @@ FLYTE_DOCKER_BUILDER_CACHE_TO = "FLYTE_DOCKER_BUILDER_CACHE_TO"
46
47
 
47
48
  UV_LOCK_WITHOUT_PROJECT_INSTALL_TEMPLATE = Template("""\
48
49
  RUN --mount=type=cache,sharing=locked,mode=0777,target=/root/.cache/uv,id=uv \
49
- --mount=type=bind,target=uv.lock,src=$UV_LOCK_PATH \
50
- --mount=type=bind,target=pyproject.toml,src=$PYPROJECT_PATH \
51
- $SECRET_MOUNT \
52
- uv sync --active $PIP_INSTALL_ARGS
50
+ --mount=type=bind,target=uv.lock,src=$UV_LOCK_PATH,rw \
51
+ --mount=type=bind,target=pyproject.toml,src=$PYPROJECT_PATH \
52
+ $SECRET_MOUNT \
53
+ uv sync --active --inexact $PIP_INSTALL_ARGS
53
54
  """)
54
55
 
55
56
  UV_LOCK_INSTALL_TEMPLATE = Template("""\
56
- COPY $PYPROJECT_PATH $PYPROJECT_PATH
57
57
  RUN --mount=type=cache,sharing=locked,mode=0777,target=/root/.cache/uv,id=uv \
58
- $SECRET_MOUNT \
59
- uv sync --active $PIP_INSTALL_ARGS --project $PYPROJECT_PATH
58
+ --mount=type=bind,target=/root/.flyte/$PYPROJECT_PATH,src=$PYPROJECT_PATH,rw \
59
+ $SECRET_MOUNT \
60
+ uv sync --active --inexact --no-editable $PIP_INSTALL_ARGS --project /root/.flyte/$PYPROJECT_PATH
61
+ """)
62
+
63
+ POETRY_LOCK_WITHOUT_PROJECT_INSTALL_TEMPLATE = Template("""\
64
+ RUN --mount=type=cache,sharing=locked,mode=0777,target=/root/.cache/uv,id=uv \
65
+ uv pip install poetry
66
+
67
+ ENV POETRY_CACHE_DIR=/tmp/poetry_cache \
68
+ POETRY_VIRTUALENVS_IN_PROJECT=true
69
+
70
+ RUN --mount=type=cache,sharing=locked,mode=0777,target=/tmp/poetry_cache,id=poetry \
71
+ --mount=type=bind,target=poetry.lock,src=$POETRY_LOCK_PATH \
72
+ --mount=type=bind,target=pyproject.toml,src=$PYPROJECT_PATH \
73
+ $SECRET_MOUNT \
74
+ poetry install $POETRY_INSTALL_ARGS
75
+ """)
76
+
77
+ POETRY_LOCK_INSTALL_TEMPLATE = Template("""\
78
+ RUN --mount=type=cache,sharing=locked,mode=0777,target=/root/.cache/uv,id=uv \
79
+ uv pip install poetry
80
+
81
+ ENV POETRY_CACHE_DIR=/tmp/poetry_cache \
82
+ POETRY_VIRTUALENVS_IN_PROJECT=true
83
+
84
+ RUN --mount=type=cache,sharing=locked,mode=0777,target=/tmp/poetry_cache,id=poetry \
85
+ --mount=type=bind,target=/root/.flyte/$PYPROJECT_PATH,src=$PYPROJECT_PATH,rw \
86
+ $SECRET_MOUNT \
87
+ poetry install $POETRY_INSTALL_ARGS -C /root/.flyte/$PYPROJECT_PATH
60
88
  """)
61
89
 
62
90
  UV_PACKAGE_INSTALL_COMMAND_TEMPLATE = Template("""\
63
91
  RUN --mount=type=cache,sharing=locked,mode=0777,target=/root/.cache/uv,id=uv \
64
- $REQUIREMENTS_MOUNT \
65
- $SECRET_MOUNT \
66
- uv pip install --python $$UV_PYTHON $PIP_INSTALL_ARGS
92
+ $REQUIREMENTS_MOUNT \
93
+ $SECRET_MOUNT \
94
+ uv pip install --python $$UV_PYTHON $PIP_INSTALL_ARGS
67
95
  """)
68
96
 
69
97
  UV_WHEEL_INSTALL_COMMAND_TEMPLATE = Template("""\
70
98
  RUN --mount=type=cache,sharing=locked,mode=0777,target=/root/.cache/uv,id=wheel \
71
- --mount=source=/dist,target=/dist,type=bind \
72
- $SECRET_MOUNT \
73
- uv pip install --python $$UV_PYTHON $PIP_INSTALL_ARGS
99
+ --mount=source=/dist,target=/dist,type=bind \
100
+ $SECRET_MOUNT \
101
+ uv pip install --python $$UV_PYTHON $PIP_INSTALL_ARGS
74
102
  """)
75
103
 
76
104
  APT_INSTALL_COMMAND_TEMPLATE = Template("""\
77
105
  RUN --mount=type=cache,sharing=locked,mode=0777,target=/var/cache/apt,id=apt \
78
- $SECRET_MOUNT \
79
- apt-get update && apt-get install -y --no-install-recommends \
80
- $APT_PACKAGES
106
+ $SECRET_MOUNT \
107
+ apt-get update && apt-get install -y --no-install-recommends \
108
+ $APT_PACKAGES
81
109
  """)
82
110
 
83
111
  UV_PYTHON_INSTALL_COMMAND = Template("""\
84
112
  RUN --mount=type=cache,sharing=locked,mode=0777,target=/root/.cache/uv,id=uv \
85
- $SECRET_MOUNT \
86
- uv pip install $PIP_INSTALL_ARGS
113
+ $SECRET_MOUNT \
114
+ uv pip install $PIP_INSTALL_ARGS
87
115
  """)
88
116
 
89
117
  # uv pip install --python /root/env/bin/python
@@ -93,24 +121,29 @@ DOCKER_FILE_UV_BASE_TEMPLATE = Template("""\
93
121
  FROM ghcr.io/astral-sh/uv:0.8.13 AS uv
94
122
  FROM $BASE_IMAGE
95
123
 
124
+
96
125
  USER root
97
126
 
127
+
98
128
  # Copy in uv so that later commands don't have to mount it in
99
129
  COPY --from=uv /uv /usr/bin/uv
100
130
 
131
+
101
132
  # Configure default envs
102
133
  ENV UV_COMPILE_BYTECODE=1 \
103
- UV_LINK_MODE=copy \
104
- VIRTUALENV=/opt/venv \
105
- UV_PYTHON=/opt/venv/bin/python \
106
- PATH="/opt/venv/bin:$$PATH"
134
+ UV_LINK_MODE=copy \
135
+ VIRTUALENV=/opt/venv \
136
+ UV_PYTHON=/opt/venv/bin/python \
137
+ PATH="/opt/venv/bin:$$PATH"
138
+
107
139
 
108
140
  # Create a virtualenv with the user specified python version
109
141
  RUN uv venv $$VIRTUALENV --python=$PYTHON_VERSION
110
142
 
143
+
111
144
  # Adds nvidia just in case it exists
112
145
  ENV PATH="$$PATH:/usr/local/nvidia/bin:/usr/local/cuda/bin" \
113
- LD_LIBRARY_PATH="/usr/local/nvidia/lib64"
146
+ LD_LIBRARY_PATH="/usr/local/nvidia/lib64"
114
147
  """)
115
148
 
116
149
  # This gets added on to the end of the dockerfile
@@ -230,24 +263,37 @@ class AptPackagesHandler:
230
263
 
231
264
  class UVProjectHandler:
232
265
  @staticmethod
233
- async def handle(layer: UVProject, context_path: Path, dockerfile: str) -> str:
266
+ async def handle(
267
+ layer: UVProject, context_path: Path, dockerfile: str, docker_ignore_patterns: list[str] = []
268
+ ) -> str:
234
269
  secret_mounts = _get_secret_mounts_layer(layer.secret_mounts)
235
- if layer.extra_index_urls and "--no-install-project" in layer.extra_index_urls:
270
+ if layer.project_install_mode == "dependencies_only":
271
+ pip_install_args = " ".join(layer.get_pip_install_args())
272
+ if "--no-install-project" not in pip_install_args:
273
+ pip_install_args += " --no-install-project"
274
+ if "--no-sources" not in pip_install_args:
275
+ pip_install_args += " --no-sources"
236
276
  # Only Copy pyproject.yaml and uv.lock.
237
277
  pyproject_dst = copy_files_to_context(layer.pyproject, context_path)
238
278
  uvlock_dst = copy_files_to_context(layer.uvlock, context_path)
239
279
  delta = UV_LOCK_WITHOUT_PROJECT_INSTALL_TEMPLATE.substitute(
240
280
  UV_LOCK_PATH=uvlock_dst.relative_to(context_path),
241
281
  PYPROJECT_PATH=pyproject_dst.relative_to(context_path),
242
- PIP_INSTALL_ARGS=" ".join(layer.get_pip_install_args()),
282
+ PIP_INSTALL_ARGS=pip_install_args,
243
283
  SECRET_MOUNT=secret_mounts,
244
284
  )
245
285
  else:
246
286
  # Copy the entire project.
247
- pyproject_dst = copy_files_to_context(layer.pyproject.parent, context_path)
248
- if layer.uvlock:
249
- # Sometimes the uv.lock file is in a different folder, if it's specified, let's copy it there explicitly
287
+ pyproject_dst = copy_files_to_context(layer.pyproject.parent, context_path, docker_ignore_patterns)
288
+
289
+ # Make sure pyproject.toml and uv.lock files are not removed by docker ignore
290
+ uv_lock_context_path = pyproject_dst / "uv.lock"
291
+ pyproject_context_path = pyproject_dst / "pyproject.toml"
292
+ if not uv_lock_context_path.exists():
250
293
  shutil.copy(layer.uvlock, pyproject_dst)
294
+ if not pyproject_context_path.exists():
295
+ shutil.copy(layer.pyproject, pyproject_dst)
296
+
251
297
  delta = UV_LOCK_INSTALL_TEMPLATE.substitute(
252
298
  PYPROJECT_PATH=pyproject_dst.relative_to(context_path),
253
299
  PIP_INSTALL_ARGS=" ".join(layer.get_pip_install_args()),
@@ -258,6 +304,46 @@ class UVProjectHandler:
258
304
  return dockerfile
259
305
 
260
306
 
307
+ class PoetryProjectHandler:
308
+ @staticmethod
309
+ async def handel(
310
+ layer: PoetryProject, context_path: Path, dockerfile: str, docker_ignore_patterns: list[str] = []
311
+ ) -> str:
312
+ secret_mounts = _get_secret_mounts_layer(layer.secret_mounts)
313
+ extra_args = layer.extra_args or ""
314
+ if layer.project_install_mode == "dependencies_only":
315
+ # Only Copy pyproject.yaml and poetry.lock.
316
+ pyproject_dst = copy_files_to_context(layer.pyproject, context_path)
317
+ poetry_lock_dst = copy_files_to_context(layer.poetry_lock, context_path)
318
+ if "--no-root" not in extra_args:
319
+ extra_args += " --no-root"
320
+ delta = POETRY_LOCK_WITHOUT_PROJECT_INSTALL_TEMPLATE.substitute(
321
+ POETRY_LOCK_PATH=poetry_lock_dst.relative_to(context_path),
322
+ PYPROJECT_PATH=pyproject_dst.relative_to(context_path),
323
+ POETRY_INSTALL_ARGS=extra_args,
324
+ SECRET_MOUNT=secret_mounts,
325
+ )
326
+ else:
327
+ # Copy the entire project.
328
+ pyproject_dst = copy_files_to_context(layer.pyproject.parent, context_path, docker_ignore_patterns)
329
+
330
+ # Make sure pyproject.toml and poetry.lock files are not removed by docker ignore
331
+ poetry_lock_context_path = pyproject_dst / "poetry.lock"
332
+ pyproject_context_path = pyproject_dst / "pyproject.toml"
333
+ if not poetry_lock_context_path.exists():
334
+ shutil.copy(layer.poetry_lock, pyproject_dst)
335
+ if not pyproject_context_path.exists():
336
+ shutil.copy(layer.pyproject, pyproject_dst)
337
+
338
+ delta = POETRY_LOCK_INSTALL_TEMPLATE.substitute(
339
+ PYPROJECT_PATH=pyproject_dst.relative_to(context_path),
340
+ POETRY_INSTALL_ARGS=extra_args,
341
+ SECRET_MOUNT=secret_mounts,
342
+ )
343
+ dockerfile += delta
344
+ return dockerfile
345
+
346
+
261
347
  class DockerIgnoreHandler:
262
348
  @staticmethod
263
349
  async def handle(layer: DockerIgnore, context_path: Path, _: str):
@@ -265,37 +351,9 @@ class DockerIgnoreHandler:
265
351
 
266
352
 
267
353
  class CopyConfigHandler:
268
- @staticmethod
269
- def list_dockerignore(root_path: Optional[Path], docker_ignore_file_path: Optional[Path]) -> List[str]:
270
- patterns: List[str] = []
271
- dockerignore_path: Optional[Path] = None
272
- if root_path:
273
- dockerignore_path = root_path / ".dockerignore"
274
- # DockerIgnore layer should be first priority
275
- if docker_ignore_file_path:
276
- dockerignore_path = docker_ignore_file_path
277
-
278
- # Return empty list if no .dockerignore file found
279
- if not dockerignore_path or not dockerignore_path.exists() or not dockerignore_path.is_file():
280
- logger.info(f".dockerignore file not found at path: {dockerignore_path}")
281
- return patterns
282
-
283
- try:
284
- with open(dockerignore_path, "r", encoding="utf-8") as f:
285
- for line in f:
286
- stripped_line = line.strip()
287
- # Skip empty lines, whitespace-only lines, and comments
288
- if not stripped_line or stripped_line.startswith("#"):
289
- continue
290
- patterns.append(stripped_line)
291
- except Exception as e:
292
- logger.error(f"Failed to read .dockerignore file at {dockerignore_path}: {e}")
293
- return []
294
- return patterns
295
-
296
354
  @staticmethod
297
355
  async def handle(
298
- layer: CopyConfig, context_path: Path, dockerfile: str, docker_ignore_file_path: Optional[Path]
356
+ layer: CopyConfig, context_path: Path, dockerfile: str, docker_ignore_patterns: list[str] = []
299
357
  ) -> str:
300
358
  # Copy the source config file or directory to the context path
301
359
  if layer.src.is_absolute() or ".." in str(layer.src):
@@ -311,11 +369,6 @@ class CopyConfigHandler:
311
369
  shutil.copy(abs_path, dst_path)
312
370
  elif layer.src.is_dir():
313
371
  # Copy the entire directory
314
- from flyte._initialize import _get_init_config
315
-
316
- init_config = _get_init_config()
317
- root_path = init_config.root_dir if init_config else None
318
- docker_ignore_patterns = CopyConfigHandler.list_dockerignore(root_path, docker_ignore_file_path)
319
372
  shutil.copytree(
320
373
  abs_path, dst_path, dirs_exist_ok=True, ignore=shutil.ignore_patterns(*docker_ignore_patterns)
321
374
  )
@@ -332,8 +385,9 @@ class CommandsHandler:
332
385
  @staticmethod
333
386
  async def handle(layer: Commands, _: Path, dockerfile: str) -> str:
334
387
  # Append raw commands to the dockerfile
388
+ secret_mounts = _get_secret_mounts_layer(layer.secret_mounts)
335
389
  for command in layer.commands:
336
- dockerfile += f"\nRUN {command}\n"
390
+ dockerfile += f"\nRUN {secret_mounts} {command}\n"
337
391
 
338
392
  return dockerfile
339
393
 
@@ -355,9 +409,8 @@ def _get_secret_commands(layers: typing.Tuple[Layer, ...]) -> typing.List[str]:
355
409
  secret = Secret(key=secret)
356
410
  secret_id = hash(secret)
357
411
  secret_env_key = "_".join([k.upper() for k in filter(None, (secret.group, secret.key))])
358
- secret_env = os.getenv(secret_env_key)
359
- if secret_env:
360
- return ["--secret", f"id={secret_id},env={secret_env}"]
412
+ if os.getenv(secret_env_key):
413
+ return ["--secret", f"id={secret_id},env={secret_env_key}"]
361
414
  secret_file_name = "_".join(list(filter(None, (secret.group, secret.key))))
362
415
  secret_file_path = f"/etc/secrets/{secret_file_name}"
363
416
  if not os.path.exists(secret_file_path):
@@ -365,7 +418,7 @@ def _get_secret_commands(layers: typing.Tuple[Layer, ...]) -> typing.List[str]:
365
418
  return ["--secret", f"id={secret_id},src={secret_file_path}"]
366
419
 
367
420
  for layer in layers:
368
- if isinstance(layer, (PipOption, AptPackages)):
421
+ if isinstance(layer, (PipOption, AptPackages, Commands)):
369
422
  if layer.secret_mounts:
370
423
  for secret_mount in layer.secret_mounts:
371
424
  commands.extend(_get_secret_command(secret_mount))
@@ -391,7 +444,7 @@ def _get_secret_mounts_layer(secrets: typing.Tuple[str | Secret, ...] | None) ->
391
444
 
392
445
 
393
446
  async def _process_layer(
394
- layer: Layer, context_path: Path, dockerfile: str, docker_ignore_file_path: Optional[Path]
447
+ layer: Layer, context_path: Path, dockerfile: str, docker_ignore_patterns: list[str] = []
395
448
  ) -> str:
396
449
  match layer:
397
450
  case PythonWheels():
@@ -424,11 +477,19 @@ async def _process_layer(
424
477
 
425
478
  case UVProject():
426
479
  # Handle UV project
427
- dockerfile = await UVProjectHandler.handle(layer, context_path, dockerfile)
480
+ dockerfile = await UVProjectHandler.handle(layer, context_path, dockerfile, docker_ignore_patterns)
481
+
482
+ case PoetryProject():
483
+ # Handle Poetry project
484
+ dockerfile = await PoetryProjectHandler.handel(layer, context_path, dockerfile, docker_ignore_patterns)
485
+
486
+ case PoetryProject():
487
+ # Handle Poetry project
488
+ dockerfile = await PoetryProjectHandler.handel(layer, context_path, dockerfile, docker_ignore_patterns)
428
489
 
429
490
  case CopyConfig():
430
491
  # Handle local files and folders
431
- dockerfile = await CopyConfigHandler.handle(layer, context_path, dockerfile, docker_ignore_file_path)
492
+ dockerfile = await CopyConfigHandler.handle(layer, context_path, dockerfile, docker_ignore_patterns)
432
493
 
433
494
  case Commands():
434
495
  # Handle commands
@@ -555,15 +616,6 @@ class DockerImageBuilder(ImageBuilder):
555
616
  else:
556
617
  logger.info("Buildx builder already exists.")
557
618
 
558
- def get_docker_ignore(self, image: Image) -> Optional[Path]:
559
- """Get the .dockerignore file path from the image layers."""
560
- # Look for DockerIgnore layer in the image layers
561
- for layer in image._layers:
562
- if isinstance(layer, DockerIgnore) and layer.path.strip():
563
- return Path(layer.path)
564
-
565
- return None
566
-
567
619
  async def _build_image(self, image: Image, *, push: bool = True, dry_run: bool = False) -> str:
568
620
  """
569
621
  if default image (only base image and locked), raise an error, don't have a dockerfile
@@ -572,6 +624,7 @@ class DockerImageBuilder(ImageBuilder):
572
624
  - start from the base image
573
625
  - use python to create a default venv and export variables
574
626
 
627
+
575
628
  Then for the layers
576
629
  - for each layer
577
630
  - find the appropriate layer handler
@@ -593,9 +646,11 @@ class DockerImageBuilder(ImageBuilder):
593
646
  PYTHON_VERSION=f"{image.python_version[0]}.{image.python_version[1]}",
594
647
  )
595
648
 
596
- docker_ignore_file_path = self.get_docker_ignore(image)
649
+ # Get .dockerignore file patterns first
650
+ docker_ignore_patterns = get_and_list_dockerignore(image)
651
+
597
652
  for layer in image._layers:
598
- dockerfile = await _process_layer(layer, tmp_path, dockerfile, docker_ignore_file_path)
653
+ dockerfile = await _process_layer(layer, tmp_path, dockerfile, docker_ignore_patterns)
599
654
 
600
655
  dockerfile += DOCKER_FILE_BASE_FOOTER.substitute(F_IMG_ID=image.uri)
601
656
 
@@ -135,11 +135,6 @@ class ImageBuildEngine:
135
135
 
136
136
  ImageBuilderType = typing.Literal["local", "remote"]
137
137
 
138
- _SEEN_IMAGES: typing.ClassVar[typing.Dict[str, str]] = {
139
- # Set default for the auto container. See Image._identifier_override for more info.
140
- "auto": Image.from_debian_base().uri,
141
- }
142
-
143
138
  @staticmethod
144
139
  @alru_cache
145
140
  async def image_exists(image: Image) -> Optional[str]:
@@ -235,7 +230,7 @@ class ImageBuildEngine:
235
230
 
236
231
 
237
232
  class ImageCache(BaseModel):
238
- image_lookup: Dict[str, Dict[str, str]]
233
+ image_lookup: Dict[str, str]
239
234
  serialized_form: str | None = None
240
235
 
241
236
  @property
@@ -273,11 +268,10 @@ class ImageCache(BaseModel):
273
268
  """
274
269
  tuples = []
275
270
  for k, v in self.image_lookup.items():
276
- for py_version, image_uri in v.items():
277
- tuples.append(
278
- [
279
- ("Name", f"{k} (py{py_version})"),
280
- ("image", image_uri),
281
- ]
282
- )
271
+ tuples.append(
272
+ [
273
+ ("Name", k),
274
+ ("image", v),
275
+ ]
276
+ )
283
277
  return tuples
@@ -16,6 +16,7 @@ import flyte.errors
16
16
  from flyte import Image, remote
17
17
  from flyte._code_bundle._utils import tar_strip_file_attributes
18
18
  from flyte._image import (
19
+ _BASE_REGISTRY,
19
20
  AptPackages,
20
21
  Architecture,
21
22
  Commands,
@@ -24,6 +25,7 @@ from flyte._image import (
24
25
  Env,
25
26
  PipOption,
26
27
  PipPackages,
28
+ PoetryProject,
27
29
  PythonWheels,
28
30
  Requirements,
29
31
  UVProject,
@@ -31,14 +33,14 @@ from flyte._image import (
31
33
  WorkDir,
32
34
  )
33
35
  from flyte._internal.imagebuild.image_builder import ImageBuilder, ImageChecker
34
- from flyte._internal.imagebuild.utils import copy_files_to_context
36
+ from flyte._internal.imagebuild.utils import copy_files_to_context, get_and_list_dockerignore
35
37
  from flyte._internal.runtime.task_serde import get_security_context
36
38
  from flyte._logging import logger
37
39
  from flyte._secret import Secret
38
40
  from flyte.remote import ActionOutputs, Run
39
41
 
40
42
  if TYPE_CHECKING:
41
- from flyte._protos.imagebuilder import definition_pb2 as image_definition_pb2
43
+ from flyteidl2.imagebuilder import definition_pb2 as image_definition_pb2
42
44
 
43
45
  IMAGE_TASK_NAME = "build-image"
44
46
  IMAGE_TASK_PROJECT = "system"
@@ -68,10 +70,11 @@ class RemoteImageChecker(ImageChecker):
68
70
  image_name = f"{repository.split('/')[-1]}:{tag}"
69
71
 
70
72
  try:
73
+ from flyteidl2.imagebuilder import definition_pb2 as image_definition__pb2
74
+ from flyteidl2.imagebuilder import payload_pb2 as image_payload__pb2
75
+ from flyteidl2.imagebuilder import service_pb2_grpc as image_service_pb2_grpc
76
+
71
77
  from flyte._initialize import _get_init_config
72
- from flyte._protos.imagebuilder import definition_pb2 as image_definition__pb2
73
- from flyte._protos.imagebuilder import payload_pb2 as image_payload__pb2
74
- from flyte._protos.imagebuilder import service_pb2_grpc as image_service_pb2_grpc
75
78
 
76
79
  cfg = _get_init_config()
77
80
  if cfg is None:
@@ -96,7 +99,7 @@ class RemoteImageBuilder(ImageBuilder):
96
99
  return [RemoteImageChecker]
97
100
 
98
101
  async def build_image(self, image: Image, dry_run: bool = False) -> str:
99
- from flyte._protos.workflow import run_definition_pb2
102
+ from flyteidl2.workflow import run_definition_pb2
100
103
 
101
104
  image_name = f"{image.name}:{image._final_tag}"
102
105
  spec, context = await _validate_configuration(image)
@@ -110,10 +113,18 @@ class RemoteImageBuilder(ImageBuilder):
110
113
  ).override.aio(secrets=_get_build_secrets_from_image(image))
111
114
 
112
115
  logger.warning("[bold blue]🐳 Submitting a new build...[/bold blue]")
116
+ if image.registry and image.registry != _BASE_REGISTRY:
117
+ target_image = f"{image.registry}/{image_name}"
118
+ else:
119
+ # Use the default system registry in the backend.
120
+ target_image = image_name
121
+
122
+ from flyte._initialize import get_init_config
123
+ cfg = get_init_config()
113
124
  run = cast(
114
125
  Run,
115
- await flyte.with_runcontext(project=IMAGE_TASK_PROJECT, domain=IMAGE_TASK_DOMAIN).run.aio(
116
- entity, spec=spec, context=context, target_image=image_name
126
+ await flyte.with_runcontext(project=cfg.project, domain=cfg.domain).run.aio(
127
+ entity, spec=spec, context=context, target_image=target_image
117
128
  ),
118
129
  )
119
130
  logger.warning(f"⏳ Waiting for build to finish at: [bold cyan link={run.url}]{run.url}[/bold cyan link]")
@@ -126,7 +137,7 @@ class RemoteImageBuilder(ImageBuilder):
126
137
  if run_details.action_details.raw_phase == run_definition_pb2.PHASE_SUCCEEDED:
127
138
  logger.warning(f"[bold green]✅ Build completed in {elapsed}![/bold green]")
128
139
  else:
129
- raise flyte.errors.ImageBuildError(f"❌ Build failed in {elapsed} at [cyan]{run.url}[/cyan]")
140
+ raise flyte.errors.ImageBuildError(f"❌ Build failed in {elapsed} at {run.url}")
130
141
 
131
142
  outputs = await run_details.outputs()
132
143
  return _get_fully_qualified_image_name(outputs)
@@ -180,12 +191,17 @@ async def _validate_configuration(image: Image) -> Tuple[str, Optional[str]]:
180
191
 
181
192
 
182
193
  def _get_layers_proto(image: Image, context_path: Path) -> "image_definition_pb2.ImageSpec":
183
- from flyte._protos.imagebuilder import definition_pb2 as image_definition_pb2
194
+ from flyteidl2.imagebuilder import definition_pb2 as image_definition_pb2
195
+
196
+ if image.dockerfile is not None:
197
+ raise flyte.errors.ImageBuildError(
198
+ "Custom Dockerfile is not supported with remote image builder.You can use local image builder instead."
199
+ )
184
200
 
185
201
  layers = []
186
202
  for layer in image._layers:
187
203
  secret_mounts = None
188
- pip_options = None
204
+ pip_options = image_definition_pb2.PipOptions()
189
205
 
190
206
  if isinstance(layer, PipOption):
191
207
  pip_options = image_definition_pb2.PipOptions(
@@ -251,12 +267,20 @@ def _get_layers_proto(image: Image, context_path: Path) -> "image_definition_pb2
251
267
  if "tool.uv.index" in line:
252
268
  raise ValueError("External sources are not supported in pyproject.toml")
253
269
 
254
- if layer.extra_index_urls and "--no-install-project" in layer.extra_index_urls:
270
+ if layer.project_install_mode == "dependencies_only":
255
271
  # Copy pyproject itself
256
272
  pyproject_dst = copy_files_to_context(layer.pyproject, context_path)
273
+ if pip_options.extra_args:
274
+ if "--no-install-project" not in pip_options.extra_args:
275
+ pip_options.extra_args += " --no-install-project"
276
+ else:
277
+ pip_options.extra_args = " --no-install-project"
278
+ if "--no-sources" not in pip_options.extra_args:
279
+ pip_options.extra_args += " --no-sources"
257
280
  else:
258
281
  # Copy the entire project
259
- pyproject_dst = copy_files_to_context(layer.pyproject.parent, context_path)
282
+ docker_ignore_patterns = get_and_list_dockerignore(image)
283
+ pyproject_dst = copy_files_to_context(layer.pyproject.parent, context_path, docker_ignore_patterns)
260
284
 
261
285
  uv_layer = image_definition_pb2.Layer(
262
286
  uv_project=image_definition_pb2.UVProject(
@@ -267,6 +291,29 @@ def _get_layers_proto(image: Image, context_path: Path) -> "image_definition_pb2
267
291
  )
268
292
  )
269
293
  layers.append(uv_layer)
294
+ elif isinstance(layer, PoetryProject):
295
+ for line in layer.pyproject.read_text().splitlines():
296
+ if "tool.poetry.source" in line:
297
+ raise ValueError("External sources are not supported in pyproject.toml")
298
+ extra_args = layer.extra_args or ""
299
+ if layer.project_install_mode == "dependencies_only":
300
+ # Copy pyproject itself
301
+ if "--no-root" not in extra_args:
302
+ extra_args += " --no-root"
303
+ pyproject_dst = copy_files_to_context(layer.pyproject, context_path)
304
+ else:
305
+ # Copy the entire project
306
+ pyproject_dst = copy_files_to_context(layer.pyproject.parent, context_path)
307
+
308
+ poetry_layer = image_definition_pb2.Layer(
309
+ poetry_project=image_definition_pb2.PoetryProject(
310
+ pyproject=str(pyproject_dst.relative_to(context_path)),
311
+ poetry_lock=str(copy_files_to_context(layer.poetry_lock, context_path).relative_to(context_path)),
312
+ extra_args=extra_args,
313
+ secret_mounts=secret_mounts,
314
+ )
315
+ )
316
+ layers.append(poetry_layer)
270
317
  elif isinstance(layer, Commands):
271
318
  commands_layer = image_definition_pb2.Layer(
272
319
  commands=image_definition_pb2.Commands(
@@ -325,4 +372,9 @@ def _get_build_secrets_from_image(image: Image) -> Optional[typing.List[Secret]]
325
372
  else:
326
373
  raise ValueError(f"Unsupported secret_mount type: {type(secret_mount)}")
327
374
 
375
+ image_registry_secret = image._image_registry_secret
376
+ if image_registry_secret:
377
+ secrets.append(
378
+ Secret(key=image_registry_secret.key, group=image_registry_secret.group, mount=DEFAULT_SECRET_DIR)
379
+ )
328
380
  return secrets
@@ -1,8 +1,12 @@
1
1
  import shutil
2
2
  from pathlib import Path
3
+ from typing import List, Optional
3
4
 
5
+ from flyte._image import DockerIgnore, Image
6
+ from flyte._logging import logger
4
7
 
5
- def copy_files_to_context(src: Path, context_path: Path) -> Path:
8
+
9
+ def copy_files_to_context(src: Path, context_path: Path, ignore_patterns: list[str] = []) -> Path:
6
10
  """
7
11
  This helper function ensures that absolute paths that users specify are converted correctly to a path in the
8
12
  context directory. Doing this prevents collisions while ensuring files are available in the context.
@@ -23,8 +27,52 @@ def copy_files_to_context(src: Path, context_path: Path) -> Path:
23
27
  dst_path = context_path / src
24
28
  dst_path.parent.mkdir(parents=True, exist_ok=True)
25
29
  if src.is_dir():
26
- # TODO: Add support dockerignore
27
- shutil.copytree(src, dst_path, dirs_exist_ok=True, ignore=shutil.ignore_patterns(".idea", ".venv"))
30
+ default_ignore_patterns = [".idea", ".venv"]
31
+ ignore_patterns = list(set(ignore_patterns + default_ignore_patterns))
32
+ shutil.copytree(src, dst_path, dirs_exist_ok=True, ignore=shutil.ignore_patterns(*ignore_patterns))
28
33
  else:
29
34
  shutil.copy(src, dst_path)
30
35
  return dst_path
36
+
37
+
38
+ def get_and_list_dockerignore(image: Image) -> List[str]:
39
+ """
40
+ Get and parse dockerignore patterns from .dockerignore file.
41
+
42
+ This function first looks for a DockerIgnore layer in the image's layers. If found, it uses
43
+ the path specified in that layer. If no DockerIgnore layer is found, it falls back to looking
44
+ for a .dockerignore file in the root_path directory.
45
+
46
+ :param image: The Image object
47
+ """
48
+ from flyte._initialize import _get_init_config
49
+
50
+ # Look for DockerIgnore layer in the image layers
51
+ dockerignore_path: Optional[Path] = None
52
+ patterns: List[str] = []
53
+
54
+ for layer in image._layers:
55
+ if isinstance(layer, DockerIgnore) and layer.path.strip():
56
+ dockerignore_path = Path(layer.path)
57
+ # If DockerIgnore layer not specified, set dockerignore_path under root_path
58
+ init_config = _get_init_config()
59
+ root_path = init_config.root_dir if init_config else None
60
+ if not dockerignore_path and root_path:
61
+ dockerignore_path = Path(root_path) / ".dockerignore"
62
+ # Return empty list if no .dockerignore file found
63
+ if not dockerignore_path or not dockerignore_path.exists() or not dockerignore_path.is_file():
64
+ logger.info(f".dockerignore file not found at path: {dockerignore_path}")
65
+ return patterns
66
+
67
+ try:
68
+ with open(dockerignore_path, "r", encoding="utf-8") as f:
69
+ for line in f:
70
+ stripped_line = line.strip()
71
+ # Skip empty lines, whitespace-only lines, and comments
72
+ if not stripped_line or stripped_line.startswith("#"):
73
+ continue
74
+ patterns.append(stripped_line)
75
+ except Exception as e:
76
+ logger.error(f"Failed to read .dockerignore file at {dockerignore_path}: {e}")
77
+ return []
78
+ return patterns