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
@@ -1,5 +1,7 @@
1
+ import gzip
1
2
  import os
2
3
  import shutil
4
+ import tarfile
3
5
  import tempfile
4
6
  import typing
5
7
  from datetime import datetime, timezone
@@ -7,19 +9,23 @@ from pathlib import Path
7
9
  from typing import TYPE_CHECKING, Optional, Tuple, cast
8
10
  from uuid import uuid4
9
11
 
10
- import click
12
+ import aiofiles
11
13
 
12
14
  import flyte
13
15
  import flyte.errors
14
16
  from flyte import Image, remote
17
+ from flyte._code_bundle._utils import tar_strip_file_attributes
15
18
  from flyte._image import (
19
+ _BASE_REGISTRY,
16
20
  AptPackages,
17
21
  Architecture,
18
22
  Commands,
19
23
  CopyConfig,
20
24
  DockerIgnore,
21
25
  Env,
26
+ PipOption,
22
27
  PipPackages,
28
+ PoetryProject,
23
29
  PythonWheels,
24
30
  Requirements,
25
31
  UVProject,
@@ -27,15 +33,16 @@ from flyte._image import (
27
33
  WorkDir,
28
34
  )
29
35
  from flyte._internal.imagebuild.image_builder import ImageBuilder, ImageChecker
30
- 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
37
+ from flyte._internal.runtime.task_serde import get_security_context
31
38
  from flyte._logging import logger
39
+ from flyte._secret import Secret
32
40
  from flyte.remote import ActionOutputs, Run
33
41
 
34
42
  if TYPE_CHECKING:
35
- from flyte._protos.imagebuilder import definition_pb2 as image_definition_pb2
43
+ from flyteidl2.imagebuilder import definition_pb2 as image_definition_pb2
36
44
 
37
45
  IMAGE_TASK_NAME = "build-image"
38
- OPTIMIZE_TASK_NAME = "optimize_task"
39
46
  IMAGE_TASK_PROJECT = "system"
40
47
  IMAGE_TASK_DOMAIN = "production"
41
48
 
@@ -63,10 +70,11 @@ class RemoteImageChecker(ImageChecker):
63
70
  image_name = f"{repository.split('/')[-1]}:{tag}"
64
71
 
65
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
+
66
77
  from flyte._initialize import _get_init_config
67
- from flyte._protos.imagebuilder import definition_pb2 as image_definition__pb2
68
- from flyte._protos.imagebuilder import payload_pb2 as image_payload__pb2
69
- from flyte._protos.imagebuilder import service_pb2_grpc as image_service_pb2_grpc
70
78
 
71
79
  cfg = _get_init_config()
72
80
  if cfg is None:
@@ -78,10 +86,10 @@ class RemoteImageChecker(ImageChecker):
78
86
  raise ValueError("remote client should not be None")
79
87
  cls._images_client = image_service_pb2_grpc.ImageServiceStub(cfg.client._channel)
80
88
  resp = await cls._images_client.GetImage(req)
81
- logger.warning(click.style(f"Image {resp.image.fqin} found. Skip building.", fg="blue"))
89
+ logger.warning(f"[blue]Image {resp.image.fqin} found. Skip building.[/blue]")
82
90
  return resp.image.fqin
83
91
  except Exception:
84
- logger.warning(click.style(f"Image {image_name} was not found or has expired.", fg="blue"))
92
+ logger.warning(f"[blue]Image {image_name} was not found or has expired.[/blue]", extra={"highlight": False})
85
93
  return None
86
94
 
87
95
 
@@ -91,49 +99,45 @@ class RemoteImageBuilder(ImageBuilder):
91
99
  return [RemoteImageChecker]
92
100
 
93
101
  async def build_image(self, image: Image, dry_run: bool = False) -> str:
94
- from flyte._protos.workflow import run_definition_pb2
102
+ from flyteidl2.workflow import run_definition_pb2
95
103
 
96
104
  image_name = f"{image.name}:{image._final_tag}"
97
105
  spec, context = await _validate_configuration(image)
98
106
 
99
107
  start = datetime.now(timezone.utc)
100
- entity = remote.Task.get(
108
+ entity = await remote.Task.get(
101
109
  name=IMAGE_TASK_NAME,
102
110
  project=IMAGE_TASK_PROJECT,
103
111
  domain=IMAGE_TASK_DOMAIN,
104
112
  auto_version="latest",
105
- )
113
+ ).override.aio(secrets=_get_build_secrets_from_image(image))
114
+
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()
106
124
  run = cast(
107
125
  Run,
108
- await flyte.with_runcontext(project=IMAGE_TASK_PROJECT, domain=IMAGE_TASK_DOMAIN).run.aio(
109
- 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
110
128
  ),
111
129
  )
112
- logger.warning(click.style("🐳 Submitting a new build...", fg="blue", bold=True))
130
+ logger.warning(f" Waiting for build to finish at: [bold cyan link={run.url}]{run.url}[/bold cyan link]")
113
131
 
114
- logger.warning(click.style("⏳ Waiting for build to finish at: " + click.style(run.url, fg="cyan"), bold=True))
115
132
  await run.wait.aio(quiet=True)
116
133
  run_details = await run.details.aio()
117
134
 
118
135
  elapsed = str(datetime.now(timezone.utc) - start).split(".")[0]
119
136
 
120
137
  if run_details.action_details.raw_phase == run_definition_pb2.PHASE_SUCCEEDED:
121
- logger.warning(click.style(f"✅ Build completed in {elapsed}!", bold=True, fg="green"))
122
- try:
123
- entity = remote.Task.get(
124
- name=OPTIMIZE_TASK_NAME,
125
- project=IMAGE_TASK_PROJECT,
126
- domain=IMAGE_TASK_DOMAIN,
127
- auto_version="latest",
128
- )
129
- await flyte.with_runcontext(project=IMAGE_TASK_PROJECT, domain=IMAGE_TASK_DOMAIN).run.aio(
130
- entity, spec=spec, context=context, target_image=image_name
131
- )
132
- except Exception as e:
133
- # Ignore the error if optimize is not enabled in the backend.
134
- logger.warning(f"Failed to run optimize task with error: {e}")
138
+ logger.warning(f"[bold green]✅ Build completed in {elapsed}![/bold green]")
135
139
  else:
136
- raise flyte.errors.ImageBuildError(f"❌ Build failed in {elapsed} at {click.style(run.url, fg='cyan')}")
140
+ raise flyte.errors.ImageBuildError(f"❌ Build failed in {elapsed} at {run.url}")
137
141
 
138
142
  outputs = await run_details.outputs()
139
143
  return _get_fully_qualified_image_name(outputs)
@@ -157,17 +161,29 @@ async def _validate_configuration(image: Image) -> Tuple[str, Optional[str]]:
157
161
 
158
162
  if any(context_path.iterdir()):
159
163
  # If there are files in the context directory, upload it
160
- archive = Path(shutil.make_archive(str(tmp_path / "context"), "xztar", context_path))
161
- st = archive.stat()
162
- if st.st_size > 5 * 1024 * 1024:
163
- logger.warning(
164
- click.style(
165
- f"Context size is {st.st_size / (1024 * 1024):.2f} MB, which is larger than 5 MB. "
166
- "Upload and build speed will be impacted.",
167
- fg="yellow",
164
+ tar_path = tmp_path / "context.tar"
165
+ with tarfile.open(tar_path, "w", dereference=False) as tar:
166
+ files: typing.List[str] = os.listdir(context_path)
167
+ for ws_file in files:
168
+ tar.add(
169
+ os.path.join(context_path, ws_file),
170
+ recursive=True,
171
+ arcname=ws_file,
172
+ filter=tar_strip_file_attributes,
168
173
  )
174
+ context_dst = Path(f"{tar_path!s}.gz")
175
+ with gzip.GzipFile(filename=context_dst, mode="wb", mtime=0) as gzipped:
176
+ async with aiofiles.open(tar_path, "rb") as tar_file:
177
+ content = await tar_file.read()
178
+ gzipped.write(content)
179
+
180
+ context_size = tar_path.stat().st_size
181
+ if context_size > 5 * 1024 * 1024:
182
+ logger.warning(
183
+ f"[yellow]Context size is {context_size / (1024 * 1024):.2f} MB, which is larger than 5 MB. "
184
+ "Upload and build speed will be impacted.[/yellow]",
169
185
  )
170
- _, context_url = await remote.upload_file.aio(archive)
186
+ _, context_url = await remote.upload_file.aio(context_dst)
171
187
  else:
172
188
  context_url = ""
173
189
 
@@ -175,13 +191,36 @@ async def _validate_configuration(image: Image) -> Tuple[str, Optional[str]]:
175
191
 
176
192
 
177
193
  def _get_layers_proto(image: Image, context_path: Path) -> "image_definition_pb2.ImageSpec":
178
- 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
+ )
179
200
 
180
201
  layers = []
181
202
  for layer in image._layers:
203
+ secret_mounts = None
204
+ pip_options = image_definition_pb2.PipOptions()
205
+
206
+ if isinstance(layer, PipOption):
207
+ pip_options = image_definition_pb2.PipOptions(
208
+ index_url=layer.index_url,
209
+ extra_index_urls=layer.extra_index_urls,
210
+ pre=layer.pre,
211
+ extra_args=layer.extra_args,
212
+ )
213
+
214
+ if hasattr(layer, "secret_mounts"):
215
+ sc = get_security_context(layer.secret_mounts)
216
+ secret_mounts = sc.secrets if sc else None
217
+
182
218
  if isinstance(layer, AptPackages):
183
219
  apt_layer = image_definition_pb2.Layer(
184
- apt_packages=image_definition_pb2.AptPackages(packages=layer.packages)
220
+ apt_packages=image_definition_pb2.AptPackages(
221
+ packages=layer.packages,
222
+ secret_mounts=secret_mounts,
223
+ ),
185
224
  )
186
225
  layers.append(apt_layer)
187
226
  elif isinstance(layer, PythonWheels):
@@ -189,12 +228,8 @@ def _get_layers_proto(image: Image, context_path: Path) -> "image_definition_pb2
189
228
  wheel_layer = image_definition_pb2.Layer(
190
229
  python_wheels=image_definition_pb2.PythonWheels(
191
230
  dir=str(dst_path.relative_to(context_path)),
192
- options=image_definition_pb2.PipOptions(
193
- index_url=layer.index_url,
194
- extra_index_urls=layer.extra_index_urls,
195
- pre=layer.pre,
196
- extra_args=layer.extra_args,
197
- ),
231
+ options=pip_options,
232
+ secret_mounts=secret_mounts,
198
233
  )
199
234
  )
200
235
  layers.append(wheel_layer)
@@ -204,12 +239,8 @@ def _get_layers_proto(image: Image, context_path: Path) -> "image_definition_pb2
204
239
  requirements_layer = image_definition_pb2.Layer(
205
240
  requirements=image_definition_pb2.Requirements(
206
241
  file=str(dst_path.relative_to(context_path)),
207
- options=image_definition_pb2.PipOptions(
208
- index_url=layer.index_url,
209
- extra_index_urls=layer.extra_index_urls,
210
- pre=layer.pre,
211
- extra_args=layer.extra_args,
212
- ),
242
+ options=pip_options,
243
+ secret_mounts=secret_mounts,
213
244
  )
214
245
  )
215
246
  layers.append(requirements_layer)
@@ -226,12 +257,8 @@ def _get_layers_proto(image: Image, context_path: Path) -> "image_definition_pb2
226
257
  pip_layer = image_definition_pb2.Layer(
227
258
  pip_packages=image_definition_pb2.PipPackages(
228
259
  packages=packages,
229
- options=image_definition_pb2.PipOptions(
230
- index_url=layer.index_url,
231
- extra_index_urls=layer.extra_index_urls,
232
- pre=layer.pre,
233
- extra_args=layer.extra_args,
234
- ),
260
+ options=pip_options,
261
+ secret_mounts=secret_mounts,
235
262
  )
236
263
  )
237
264
  layers.append(pip_layer)
@@ -239,18 +266,60 @@ def _get_layers_proto(image: Image, context_path: Path) -> "image_definition_pb2
239
266
  for line in layer.pyproject.read_text().splitlines():
240
267
  if "tool.uv.index" in line:
241
268
  raise ValueError("External sources are not supported in pyproject.toml")
242
- shutil.copy2(layer.pyproject, context_path / layer.pyproject.name)
269
+
270
+ if layer.project_install_mode == "dependencies_only":
271
+ # Copy pyproject itself
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"
280
+ else:
281
+ # Copy the entire project
282
+ docker_ignore_patterns = get_and_list_dockerignore(image)
283
+ pyproject_dst = copy_files_to_context(layer.pyproject.parent, context_path, docker_ignore_patterns)
243
284
 
244
285
  uv_layer = image_definition_pb2.Layer(
245
286
  uv_project=image_definition_pb2.UVProject(
246
- pyproject=str(layer.pyproject.name),
247
- uvlock=str(layer.uvlock.name),
287
+ pyproject=str(pyproject_dst.relative_to(context_path)),
288
+ uvlock=str(copy_files_to_context(layer.uvlock, context_path).relative_to(context_path)),
289
+ options=pip_options,
290
+ secret_mounts=secret_mounts,
248
291
  )
249
292
  )
250
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)
251
317
  elif isinstance(layer, Commands):
252
318
  commands_layer = image_definition_pb2.Layer(
253
- commands=image_definition_pb2.Commands(cmd=list(layer.commands))
319
+ commands=image_definition_pb2.Commands(
320
+ cmd=list(layer.commands),
321
+ secret_mounts=secret_mounts,
322
+ )
254
323
  )
255
324
  layers.append(commands_layer)
256
325
  elif isinstance(layer, DockerIgnore):
@@ -287,3 +356,25 @@ def _get_layers_proto(image: Image, context_path: Path) -> "image_definition_pb2
287
356
 
288
357
  def _get_fully_qualified_image_name(outputs: ActionOutputs) -> str:
289
358
  return outputs.pb2.literals[0].value.scalar.primitive.string_value
359
+
360
+
361
+ def _get_build_secrets_from_image(image: Image) -> Optional[typing.List[Secret]]:
362
+ secrets = []
363
+ DEFAULT_SECRET_DIR = Path("/etc/flyte/secrets")
364
+ for layer in image._layers:
365
+ if isinstance(layer, (PipOption, Commands, AptPackages)) and layer.secret_mounts is not None:
366
+ for secret_mount in layer.secret_mounts:
367
+ # Mount all the image secrets to a default directory that will be passed to the BuildKit server.
368
+ if isinstance(secret_mount, Secret):
369
+ secrets.append(Secret(key=secret_mount.key, group=secret_mount.group, mount=DEFAULT_SECRET_DIR))
370
+ elif isinstance(secret_mount, str):
371
+ secrets.append(Secret(key=secret_mount, mount=DEFAULT_SECRET_DIR))
372
+ else:
373
+ raise ValueError(f"Unsupported secret_mount type: {type(secret_mount)}")
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
+ )
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,7 +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
- shutil.copytree(src, dst_path, dirs_exist_ok=True)
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))
27
33
  else:
28
34
  shutil.copy(src, dst_path)
29
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
@@ -1,13 +1,11 @@
1
- import inspect
2
- import os
3
1
  import pathlib
4
- import sys
5
2
  from typing import Tuple
6
3
 
4
+ from flyte._module import extract_obj_module
7
5
  from flyte._task import AsyncFunctionTaskTemplate, TaskTemplate
8
6
 
9
7
 
10
- def extract_task_module(task: TaskTemplate, /, source_dir: pathlib.Path | None = None) -> Tuple[str, str]:
8
+ def extract_task_module(task: TaskTemplate, /, source_dir: pathlib.Path) -> Tuple[str, str]:
11
9
  """
12
10
  Extract the task module from the task template.
13
11
 
@@ -15,40 +13,9 @@ def extract_task_module(task: TaskTemplate, /, source_dir: pathlib.Path | None =
15
13
  :param source_dir: The source directory to use for relative paths.
16
14
  :return: A tuple containing the entity name, module
17
15
  """
18
- entity_name = task.name
19
16
  if isinstance(task, AsyncFunctionTaskTemplate):
20
- entity_module = inspect.getmodule(task.func)
21
- if entity_module is None:
22
- raise ValueError(f"Task {entity_name} has no module.")
23
-
24
- fp = entity_module.__file__
25
- if fp is None:
26
- raise ValueError(f"Task {entity_name} has no module.")
27
-
28
- file_path = pathlib.Path(fp)
29
- # Get the relative path to the current directory
30
- # Will raise ValueError if the file is not in the source directory
31
- relative_path = file_path.relative_to(str(source_dir))
32
-
33
- if relative_path == pathlib.Path("."):
34
- entity_module_name = entity_module.__name__
35
- else:
36
- # Replace file separators with dots and remove the '.py' extension
37
- dotted_path = os.path.splitext(str(relative_path))[0].replace(os.sep, ".")
38
- entity_module_name = dotted_path
39
-
40
17
  entity_name = task.func.__name__
18
+ entity_module_name = extract_obj_module(task.func, source_dir)
19
+ return entity_name, entity_module_name
41
20
  else:
42
- raise NotImplementedError(f"Task module {entity_name} not implemented.")
43
-
44
- if entity_module_name == "__main__":
45
- """
46
- This case is for the case in which the task is run from the main module.
47
- """
48
- fp = sys.modules["__main__"].__file__
49
- if fp is None:
50
- raise ValueError(f"Task {entity_name} has no module.")
51
- main_path = pathlib.Path(fp)
52
- entity_module_name = main_path.stem
53
-
54
- return entity_name, entity_module_name
21
+ raise NotImplementedError(f"Task module {task.name} not implemented.")
@@ -1,6 +1,6 @@
1
1
  import importlib
2
2
  from pathlib import Path
3
- from typing import List, Optional
3
+ from typing import List
4
4
 
5
5
  from flyte._internal.resolvers._task_module import extract_task_module
6
6
  from flyte._internal.resolvers.common import Resolver
@@ -23,6 +23,6 @@ class DefaultTaskResolver(Resolver):
23
23
  task_def = getattr(task_module, task_name)
24
24
  return task_def
25
25
 
26
- def loader_args(self, task: TaskTemplate, root_dir: Optional[Path] = None) -> List[str]: # type:ignore
26
+ def loader_args(self, task: TaskTemplate, root_dir: Path) -> List[str]: # type:ignore
27
27
  t, m = extract_task_module(task, root_dir)
28
28
  return ["mod", m, "instance", t]