flyte 0.0.1b0__py3-none-any.whl → 2.0.0b46__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 (455) hide show
  1. flyte/__init__.py +83 -30
  2. flyte/_bin/connect.py +61 -0
  3. flyte/_bin/debug.py +38 -0
  4. flyte/_bin/runtime.py +87 -19
  5. flyte/_bin/serve.py +351 -0
  6. flyte/_build.py +3 -2
  7. flyte/_cache/cache.py +6 -5
  8. flyte/_cache/local_cache.py +216 -0
  9. flyte/_code_bundle/_ignore.py +31 -5
  10. flyte/_code_bundle/_packaging.py +42 -11
  11. flyte/_code_bundle/_utils.py +57 -34
  12. flyte/_code_bundle/bundle.py +130 -27
  13. flyte/_constants.py +1 -0
  14. flyte/_context.py +21 -5
  15. flyte/_custom_context.py +73 -0
  16. flyte/_debug/constants.py +37 -0
  17. flyte/_debug/utils.py +17 -0
  18. flyte/_debug/vscode.py +315 -0
  19. flyte/_deploy.py +396 -75
  20. flyte/_deployer.py +109 -0
  21. flyte/_environment.py +94 -11
  22. flyte/_excepthook.py +37 -0
  23. flyte/_group.py +2 -1
  24. flyte/_hash.py +1 -16
  25. flyte/_image.py +544 -234
  26. flyte/_initialize.py +443 -294
  27. flyte/_interface.py +40 -5
  28. flyte/_internal/controllers/__init__.py +22 -8
  29. flyte/_internal/controllers/_local_controller.py +159 -35
  30. flyte/_internal/controllers/_trace.py +18 -10
  31. flyte/_internal/controllers/remote/__init__.py +38 -9
  32. flyte/_internal/controllers/remote/_action.py +82 -12
  33. flyte/_internal/controllers/remote/_client.py +6 -2
  34. flyte/_internal/controllers/remote/_controller.py +290 -64
  35. flyte/_internal/controllers/remote/_core.py +155 -95
  36. flyte/_internal/controllers/remote/_informer.py +40 -20
  37. flyte/_internal/controllers/remote/_service_protocol.py +2 -2
  38. flyte/_internal/imagebuild/__init__.py +2 -10
  39. flyte/_internal/imagebuild/docker_builder.py +391 -84
  40. flyte/_internal/imagebuild/image_builder.py +111 -55
  41. flyte/_internal/imagebuild/remote_builder.py +409 -0
  42. flyte/_internal/imagebuild/utils.py +79 -0
  43. flyte/_internal/resolvers/_app_env_module.py +92 -0
  44. flyte/_internal/resolvers/_task_module.py +5 -38
  45. flyte/_internal/resolvers/app_env.py +26 -0
  46. flyte/_internal/resolvers/common.py +8 -1
  47. flyte/_internal/resolvers/default.py +2 -2
  48. flyte/_internal/runtime/convert.py +322 -33
  49. flyte/_internal/runtime/entrypoints.py +106 -18
  50. flyte/_internal/runtime/io.py +71 -23
  51. flyte/_internal/runtime/resources_serde.py +21 -7
  52. flyte/_internal/runtime/reuse.py +125 -0
  53. flyte/_internal/runtime/rusty.py +196 -0
  54. flyte/_internal/runtime/task_serde.py +239 -66
  55. flyte/_internal/runtime/taskrunner.py +48 -8
  56. flyte/_internal/runtime/trigger_serde.py +162 -0
  57. flyte/_internal/runtime/types_serde.py +7 -16
  58. flyte/_keyring/file.py +115 -0
  59. flyte/_link.py +30 -0
  60. flyte/_logging.py +241 -42
  61. flyte/_map.py +312 -0
  62. flyte/_metrics.py +59 -0
  63. flyte/_module.py +74 -0
  64. flyte/_pod.py +30 -0
  65. flyte/_resources.py +296 -33
  66. flyte/_retry.py +1 -7
  67. flyte/_reusable_environment.py +72 -7
  68. flyte/_run.py +461 -132
  69. flyte/_secret.py +47 -11
  70. flyte/_serve.py +333 -0
  71. flyte/_task.py +245 -56
  72. flyte/_task_environment.py +219 -97
  73. flyte/_task_plugins.py +47 -0
  74. flyte/_tools.py +8 -8
  75. flyte/_trace.py +15 -24
  76. flyte/_trigger.py +1027 -0
  77. flyte/_utils/__init__.py +12 -1
  78. flyte/_utils/asyn.py +3 -1
  79. flyte/_utils/async_cache.py +139 -0
  80. flyte/_utils/coro_management.py +5 -4
  81. flyte/_utils/description_parser.py +19 -0
  82. flyte/_utils/docker_credentials.py +173 -0
  83. flyte/_utils/helpers.py +45 -19
  84. flyte/_utils/module_loader.py +123 -0
  85. flyte/_utils/org_discovery.py +57 -0
  86. flyte/_utils/uv_script_parser.py +8 -1
  87. flyte/_version.py +16 -3
  88. flyte/app/__init__.py +27 -0
  89. flyte/app/_app_environment.py +362 -0
  90. flyte/app/_connector_environment.py +40 -0
  91. flyte/app/_deploy.py +130 -0
  92. flyte/app/_parameter.py +343 -0
  93. flyte/app/_runtime/__init__.py +3 -0
  94. flyte/app/_runtime/app_serde.py +383 -0
  95. flyte/app/_types.py +113 -0
  96. flyte/app/extras/__init__.py +9 -0
  97. flyte/app/extras/_auth_middleware.py +217 -0
  98. flyte/app/extras/_fastapi.py +93 -0
  99. flyte/app/extras/_model_loader/__init__.py +3 -0
  100. flyte/app/extras/_model_loader/config.py +7 -0
  101. flyte/app/extras/_model_loader/loader.py +288 -0
  102. flyte/cli/__init__.py +12 -0
  103. flyte/cli/_abort.py +28 -0
  104. flyte/cli/_build.py +114 -0
  105. flyte/cli/_common.py +493 -0
  106. flyte/cli/_create.py +371 -0
  107. flyte/cli/_delete.py +45 -0
  108. flyte/cli/_deploy.py +401 -0
  109. flyte/cli/_gen.py +316 -0
  110. flyte/cli/_get.py +446 -0
  111. flyte/cli/_option.py +33 -0
  112. {union/_cli → flyte/cli}/_params.py +152 -153
  113. flyte/cli/_plugins.py +209 -0
  114. flyte/cli/_prefetch.py +292 -0
  115. flyte/cli/_run.py +690 -0
  116. flyte/cli/_serve.py +338 -0
  117. flyte/cli/_update.py +86 -0
  118. flyte/cli/_user.py +20 -0
  119. flyte/cli/main.py +246 -0
  120. flyte/config/__init__.py +3 -0
  121. flyte/config/_config.py +248 -0
  122. flyte/config/_internal.py +73 -0
  123. flyte/config/_reader.py +225 -0
  124. flyte/connectors/__init__.py +11 -0
  125. flyte/connectors/_connector.py +330 -0
  126. flyte/connectors/_server.py +194 -0
  127. flyte/connectors/utils.py +159 -0
  128. flyte/errors.py +134 -2
  129. flyte/extend.py +24 -0
  130. flyte/extras/_container.py +69 -56
  131. flyte/git/__init__.py +3 -0
  132. flyte/git/_config.py +279 -0
  133. flyte/io/__init__.py +8 -1
  134. flyte/io/{structured_dataset → _dataframe}/__init__.py +32 -30
  135. flyte/io/{structured_dataset → _dataframe}/basic_dfs.py +75 -68
  136. flyte/io/{structured_dataset/structured_dataset.py → _dataframe/dataframe.py} +207 -242
  137. flyte/io/_dir.py +575 -113
  138. flyte/io/_file.py +587 -141
  139. flyte/io/_hashing_io.py +342 -0
  140. flyte/io/extend.py +7 -0
  141. flyte/models.py +635 -0
  142. flyte/prefetch/__init__.py +22 -0
  143. flyte/prefetch/_hf_model.py +563 -0
  144. flyte/remote/__init__.py +14 -3
  145. flyte/remote/_action.py +879 -0
  146. flyte/remote/_app.py +346 -0
  147. flyte/remote/_auth_metadata.py +42 -0
  148. flyte/remote/_client/_protocols.py +62 -4
  149. flyte/remote/_client/auth/_auth_utils.py +19 -0
  150. flyte/remote/_client/auth/_authenticators/base.py +8 -2
  151. flyte/remote/_client/auth/_authenticators/device_code.py +4 -5
  152. flyte/remote/_client/auth/_authenticators/factory.py +4 -0
  153. flyte/remote/_client/auth/_authenticators/passthrough.py +79 -0
  154. flyte/remote/_client/auth/_authenticators/pkce.py +17 -18
  155. flyte/remote/_client/auth/_channel.py +47 -18
  156. flyte/remote/_client/auth/_client_config.py +5 -3
  157. flyte/remote/_client/auth/_keyring.py +15 -2
  158. flyte/remote/_client/auth/_token_client.py +3 -3
  159. flyte/remote/_client/controlplane.py +206 -18
  160. flyte/remote/_common.py +66 -0
  161. flyte/remote/_data.py +107 -22
  162. flyte/remote/_logs.py +116 -33
  163. flyte/remote/_project.py +21 -19
  164. flyte/remote/_run.py +164 -631
  165. flyte/remote/_secret.py +72 -29
  166. flyte/remote/_task.py +387 -46
  167. flyte/remote/_trigger.py +368 -0
  168. flyte/remote/_user.py +43 -0
  169. flyte/report/_report.py +10 -6
  170. flyte/storage/__init__.py +13 -1
  171. flyte/storage/_config.py +237 -0
  172. flyte/storage/_parallel_reader.py +289 -0
  173. flyte/storage/_storage.py +268 -59
  174. flyte/syncify/__init__.py +56 -0
  175. flyte/syncify/_api.py +414 -0
  176. flyte/types/__init__.py +39 -0
  177. flyte/types/_interface.py +22 -7
  178. flyte/{io/pickle/transformer.py → types/_pickle.py} +37 -9
  179. flyte/types/_string_literals.py +8 -9
  180. flyte/types/_type_engine.py +230 -129
  181. flyte/types/_utils.py +1 -1
  182. flyte-2.0.0b46.data/scripts/debug.py +38 -0
  183. flyte-2.0.0b46.data/scripts/runtime.py +194 -0
  184. flyte-2.0.0b46.dist-info/METADATA +352 -0
  185. flyte-2.0.0b46.dist-info/RECORD +221 -0
  186. flyte-2.0.0b46.dist-info/entry_points.txt +8 -0
  187. flyte-2.0.0b46.dist-info/licenses/LICENSE +201 -0
  188. flyte/_api_commons.py +0 -3
  189. flyte/_cli/_common.py +0 -287
  190. flyte/_cli/_create.py +0 -42
  191. flyte/_cli/_delete.py +0 -23
  192. flyte/_cli/_deploy.py +0 -140
  193. flyte/_cli/_get.py +0 -235
  194. flyte/_cli/_run.py +0 -152
  195. flyte/_cli/main.py +0 -72
  196. flyte/_datastructures.py +0 -342
  197. flyte/_internal/controllers/pbhash.py +0 -39
  198. flyte/_protos/common/authorization_pb2.py +0 -66
  199. flyte/_protos/common/authorization_pb2.pyi +0 -108
  200. flyte/_protos/common/authorization_pb2_grpc.py +0 -4
  201. flyte/_protos/common/identifier_pb2.py +0 -71
  202. flyte/_protos/common/identifier_pb2.pyi +0 -82
  203. flyte/_protos/common/identifier_pb2_grpc.py +0 -4
  204. flyte/_protos/common/identity_pb2.py +0 -48
  205. flyte/_protos/common/identity_pb2.pyi +0 -72
  206. flyte/_protos/common/identity_pb2_grpc.py +0 -4
  207. flyte/_protos/common/list_pb2.py +0 -36
  208. flyte/_protos/common/list_pb2.pyi +0 -69
  209. flyte/_protos/common/list_pb2_grpc.py +0 -4
  210. flyte/_protos/common/policy_pb2.py +0 -37
  211. flyte/_protos/common/policy_pb2.pyi +0 -27
  212. flyte/_protos/common/policy_pb2_grpc.py +0 -4
  213. flyte/_protos/common/role_pb2.py +0 -37
  214. flyte/_protos/common/role_pb2.pyi +0 -53
  215. flyte/_protos/common/role_pb2_grpc.py +0 -4
  216. flyte/_protos/common/runtime_version_pb2.py +0 -28
  217. flyte/_protos/common/runtime_version_pb2.pyi +0 -24
  218. flyte/_protos/common/runtime_version_pb2_grpc.py +0 -4
  219. flyte/_protos/logs/dataplane/payload_pb2.py +0 -96
  220. flyte/_protos/logs/dataplane/payload_pb2.pyi +0 -168
  221. flyte/_protos/logs/dataplane/payload_pb2_grpc.py +0 -4
  222. flyte/_protos/secret/definition_pb2.py +0 -49
  223. flyte/_protos/secret/definition_pb2.pyi +0 -93
  224. flyte/_protos/secret/definition_pb2_grpc.py +0 -4
  225. flyte/_protos/secret/payload_pb2.py +0 -62
  226. flyte/_protos/secret/payload_pb2.pyi +0 -94
  227. flyte/_protos/secret/payload_pb2_grpc.py +0 -4
  228. flyte/_protos/secret/secret_pb2.py +0 -38
  229. flyte/_protos/secret/secret_pb2.pyi +0 -6
  230. flyte/_protos/secret/secret_pb2_grpc.py +0 -198
  231. flyte/_protos/secret/secret_pb2_grpc_grpc.py +0 -198
  232. flyte/_protos/validate/validate/validate_pb2.py +0 -76
  233. flyte/_protos/workflow/node_execution_service_pb2.py +0 -26
  234. flyte/_protos/workflow/node_execution_service_pb2.pyi +0 -4
  235. flyte/_protos/workflow/node_execution_service_pb2_grpc.py +0 -32
  236. flyte/_protos/workflow/queue_service_pb2.py +0 -106
  237. flyte/_protos/workflow/queue_service_pb2.pyi +0 -141
  238. flyte/_protos/workflow/queue_service_pb2_grpc.py +0 -172
  239. flyte/_protos/workflow/run_definition_pb2.py +0 -128
  240. flyte/_protos/workflow/run_definition_pb2.pyi +0 -310
  241. flyte/_protos/workflow/run_definition_pb2_grpc.py +0 -4
  242. flyte/_protos/workflow/run_logs_service_pb2.py +0 -41
  243. flyte/_protos/workflow/run_logs_service_pb2.pyi +0 -28
  244. flyte/_protos/workflow/run_logs_service_pb2_grpc.py +0 -69
  245. flyte/_protos/workflow/run_service_pb2.py +0 -133
  246. flyte/_protos/workflow/run_service_pb2.pyi +0 -175
  247. flyte/_protos/workflow/run_service_pb2_grpc.py +0 -412
  248. flyte/_protos/workflow/state_service_pb2.py +0 -58
  249. flyte/_protos/workflow/state_service_pb2.pyi +0 -71
  250. flyte/_protos/workflow/state_service_pb2_grpc.py +0 -138
  251. flyte/_protos/workflow/task_definition_pb2.py +0 -72
  252. flyte/_protos/workflow/task_definition_pb2.pyi +0 -65
  253. flyte/_protos/workflow/task_definition_pb2_grpc.py +0 -4
  254. flyte/_protos/workflow/task_service_pb2.py +0 -44
  255. flyte/_protos/workflow/task_service_pb2.pyi +0 -31
  256. flyte/_protos/workflow/task_service_pb2_grpc.py +0 -104
  257. flyte/io/_dataframe.py +0 -0
  258. flyte/io/pickle/__init__.py +0 -0
  259. flyte/remote/_console.py +0 -18
  260. flyte-0.0.1b0.dist-info/METADATA +0 -179
  261. flyte-0.0.1b0.dist-info/RECORD +0 -390
  262. flyte-0.0.1b0.dist-info/entry_points.txt +0 -3
  263. union/__init__.py +0 -54
  264. union/_api_commons.py +0 -3
  265. union/_bin/__init__.py +0 -0
  266. union/_bin/runtime.py +0 -113
  267. union/_build.py +0 -25
  268. union/_cache/__init__.py +0 -12
  269. union/_cache/cache.py +0 -141
  270. union/_cache/defaults.py +0 -9
  271. union/_cache/policy_function_body.py +0 -42
  272. union/_cli/__init__.py +0 -0
  273. union/_cli/_common.py +0 -263
  274. union/_cli/_create.py +0 -40
  275. union/_cli/_delete.py +0 -23
  276. union/_cli/_deploy.py +0 -120
  277. union/_cli/_get.py +0 -162
  278. union/_cli/_run.py +0 -150
  279. union/_cli/main.py +0 -72
  280. union/_code_bundle/__init__.py +0 -8
  281. union/_code_bundle/_ignore.py +0 -113
  282. union/_code_bundle/_packaging.py +0 -187
  283. union/_code_bundle/_utils.py +0 -342
  284. union/_code_bundle/bundle.py +0 -176
  285. union/_context.py +0 -146
  286. union/_datastructures.py +0 -295
  287. union/_deploy.py +0 -185
  288. union/_doc.py +0 -29
  289. union/_docstring.py +0 -26
  290. union/_environment.py +0 -43
  291. union/_group.py +0 -31
  292. union/_hash.py +0 -23
  293. union/_image.py +0 -760
  294. union/_initialize.py +0 -585
  295. union/_interface.py +0 -84
  296. union/_internal/__init__.py +0 -3
  297. union/_internal/controllers/__init__.py +0 -77
  298. union/_internal/controllers/_local_controller.py +0 -77
  299. union/_internal/controllers/pbhash.py +0 -39
  300. union/_internal/controllers/remote/__init__.py +0 -40
  301. union/_internal/controllers/remote/_action.py +0 -131
  302. union/_internal/controllers/remote/_client.py +0 -43
  303. union/_internal/controllers/remote/_controller.py +0 -169
  304. union/_internal/controllers/remote/_core.py +0 -341
  305. union/_internal/controllers/remote/_informer.py +0 -260
  306. union/_internal/controllers/remote/_service_protocol.py +0 -44
  307. union/_internal/imagebuild/__init__.py +0 -11
  308. union/_internal/imagebuild/docker_builder.py +0 -416
  309. union/_internal/imagebuild/image_builder.py +0 -243
  310. union/_internal/imagebuild/remote_builder.py +0 -0
  311. union/_internal/resolvers/__init__.py +0 -0
  312. union/_internal/resolvers/_task_module.py +0 -31
  313. union/_internal/resolvers/common.py +0 -24
  314. union/_internal/resolvers/default.py +0 -27
  315. union/_internal/runtime/__init__.py +0 -0
  316. union/_internal/runtime/convert.py +0 -163
  317. union/_internal/runtime/entrypoints.py +0 -121
  318. union/_internal/runtime/io.py +0 -136
  319. union/_internal/runtime/resources_serde.py +0 -134
  320. union/_internal/runtime/task_serde.py +0 -202
  321. union/_internal/runtime/taskrunner.py +0 -179
  322. union/_internal/runtime/types_serde.py +0 -53
  323. union/_logging.py +0 -124
  324. union/_protos/__init__.py +0 -0
  325. union/_protos/common/authorization_pb2.py +0 -66
  326. union/_protos/common/authorization_pb2.pyi +0 -106
  327. union/_protos/common/authorization_pb2_grpc.py +0 -4
  328. union/_protos/common/identifier_pb2.py +0 -71
  329. union/_protos/common/identifier_pb2.pyi +0 -82
  330. union/_protos/common/identifier_pb2_grpc.py +0 -4
  331. union/_protos/common/identity_pb2.py +0 -48
  332. union/_protos/common/identity_pb2.pyi +0 -72
  333. union/_protos/common/identity_pb2_grpc.py +0 -4
  334. union/_protos/common/list_pb2.py +0 -36
  335. union/_protos/common/list_pb2.pyi +0 -69
  336. union/_protos/common/list_pb2_grpc.py +0 -4
  337. union/_protos/common/policy_pb2.py +0 -37
  338. union/_protos/common/policy_pb2.pyi +0 -27
  339. union/_protos/common/policy_pb2_grpc.py +0 -4
  340. union/_protos/common/role_pb2.py +0 -37
  341. union/_protos/common/role_pb2.pyi +0 -51
  342. union/_protos/common/role_pb2_grpc.py +0 -4
  343. union/_protos/common/runtime_version_pb2.py +0 -28
  344. union/_protos/common/runtime_version_pb2.pyi +0 -24
  345. union/_protos/common/runtime_version_pb2_grpc.py +0 -4
  346. union/_protos/logs/dataplane/payload_pb2.py +0 -96
  347. union/_protos/logs/dataplane/payload_pb2.pyi +0 -168
  348. union/_protos/logs/dataplane/payload_pb2_grpc.py +0 -4
  349. union/_protos/secret/definition_pb2.py +0 -49
  350. union/_protos/secret/definition_pb2.pyi +0 -93
  351. union/_protos/secret/definition_pb2_grpc.py +0 -4
  352. union/_protos/secret/payload_pb2.py +0 -62
  353. union/_protos/secret/payload_pb2.pyi +0 -94
  354. union/_protos/secret/payload_pb2_grpc.py +0 -4
  355. union/_protos/secret/secret_pb2.py +0 -38
  356. union/_protos/secret/secret_pb2.pyi +0 -6
  357. union/_protos/secret/secret_pb2_grpc.py +0 -198
  358. union/_protos/validate/validate/validate_pb2.py +0 -76
  359. union/_protos/workflow/node_execution_service_pb2.py +0 -26
  360. union/_protos/workflow/node_execution_service_pb2.pyi +0 -4
  361. union/_protos/workflow/node_execution_service_pb2_grpc.py +0 -32
  362. union/_protos/workflow/queue_service_pb2.py +0 -75
  363. union/_protos/workflow/queue_service_pb2.pyi +0 -103
  364. union/_protos/workflow/queue_service_pb2_grpc.py +0 -172
  365. union/_protos/workflow/run_definition_pb2.py +0 -100
  366. union/_protos/workflow/run_definition_pb2.pyi +0 -256
  367. union/_protos/workflow/run_definition_pb2_grpc.py +0 -4
  368. union/_protos/workflow/run_logs_service_pb2.py +0 -41
  369. union/_protos/workflow/run_logs_service_pb2.pyi +0 -28
  370. union/_protos/workflow/run_logs_service_pb2_grpc.py +0 -69
  371. union/_protos/workflow/run_service_pb2.py +0 -133
  372. union/_protos/workflow/run_service_pb2.pyi +0 -173
  373. union/_protos/workflow/run_service_pb2_grpc.py +0 -412
  374. union/_protos/workflow/state_service_pb2.py +0 -58
  375. union/_protos/workflow/state_service_pb2.pyi +0 -69
  376. union/_protos/workflow/state_service_pb2_grpc.py +0 -138
  377. union/_protos/workflow/task_definition_pb2.py +0 -72
  378. union/_protos/workflow/task_definition_pb2.pyi +0 -65
  379. union/_protos/workflow/task_definition_pb2_grpc.py +0 -4
  380. union/_protos/workflow/task_service_pb2.py +0 -44
  381. union/_protos/workflow/task_service_pb2.pyi +0 -31
  382. union/_protos/workflow/task_service_pb2_grpc.py +0 -104
  383. union/_resources.py +0 -226
  384. union/_retry.py +0 -32
  385. union/_reusable_environment.py +0 -25
  386. union/_run.py +0 -374
  387. union/_secret.py +0 -61
  388. union/_task.py +0 -354
  389. union/_task_environment.py +0 -186
  390. union/_timeout.py +0 -47
  391. union/_tools.py +0 -27
  392. union/_utils/__init__.py +0 -11
  393. union/_utils/asyn.py +0 -119
  394. union/_utils/file_handling.py +0 -71
  395. union/_utils/helpers.py +0 -46
  396. union/_utils/lazy_module.py +0 -54
  397. union/_utils/uv_script_parser.py +0 -49
  398. union/_version.py +0 -21
  399. union/connectors/__init__.py +0 -0
  400. union/errors.py +0 -128
  401. union/extras/__init__.py +0 -5
  402. union/extras/_container.py +0 -263
  403. union/io/__init__.py +0 -11
  404. union/io/_dataframe.py +0 -0
  405. union/io/_dir.py +0 -425
  406. union/io/_file.py +0 -418
  407. union/io/pickle/__init__.py +0 -0
  408. union/io/pickle/transformer.py +0 -117
  409. union/io/structured_dataset/__init__.py +0 -122
  410. union/io/structured_dataset/basic_dfs.py +0 -219
  411. union/io/structured_dataset/structured_dataset.py +0 -1057
  412. union/py.typed +0 -0
  413. union/remote/__init__.py +0 -23
  414. union/remote/_client/__init__.py +0 -0
  415. union/remote/_client/_protocols.py +0 -129
  416. union/remote/_client/auth/__init__.py +0 -12
  417. union/remote/_client/auth/_authenticators/__init__.py +0 -0
  418. union/remote/_client/auth/_authenticators/base.py +0 -391
  419. union/remote/_client/auth/_authenticators/client_credentials.py +0 -73
  420. union/remote/_client/auth/_authenticators/device_code.py +0 -120
  421. union/remote/_client/auth/_authenticators/external_command.py +0 -77
  422. union/remote/_client/auth/_authenticators/factory.py +0 -200
  423. union/remote/_client/auth/_authenticators/pkce.py +0 -515
  424. union/remote/_client/auth/_channel.py +0 -184
  425. union/remote/_client/auth/_client_config.py +0 -83
  426. union/remote/_client/auth/_default_html.py +0 -32
  427. union/remote/_client/auth/_grpc_utils/__init__.py +0 -0
  428. union/remote/_client/auth/_grpc_utils/auth_interceptor.py +0 -204
  429. union/remote/_client/auth/_grpc_utils/default_metadata_interceptor.py +0 -144
  430. union/remote/_client/auth/_keyring.py +0 -154
  431. union/remote/_client/auth/_token_client.py +0 -258
  432. union/remote/_client/auth/errors.py +0 -16
  433. union/remote/_client/controlplane.py +0 -86
  434. union/remote/_data.py +0 -149
  435. union/remote/_logs.py +0 -74
  436. union/remote/_project.py +0 -86
  437. union/remote/_run.py +0 -820
  438. union/remote/_secret.py +0 -132
  439. union/remote/_task.py +0 -193
  440. union/report/__init__.py +0 -3
  441. union/report/_report.py +0 -178
  442. union/report/_template.html +0 -124
  443. union/storage/__init__.py +0 -24
  444. union/storage/_remote_fs.py +0 -34
  445. union/storage/_storage.py +0 -247
  446. union/storage/_utils.py +0 -5
  447. union/types/__init__.py +0 -11
  448. union/types/_renderer.py +0 -162
  449. union/types/_string_literals.py +0 -120
  450. union/types/_type_engine.py +0 -2131
  451. union/types/_utils.py +0 -80
  452. /flyte/{_cli → _debug}/__init__.py +0 -0
  453. /flyte/{_protos → _keyring}/__init__.py +0 -0
  454. {flyte-0.0.1b0.dist-info → flyte-2.0.0b46.dist-info}/WHEEL +0 -0
  455. {flyte-0.0.1b0.dist-info → flyte-2.0.0b46.dist-info}/top_level.txt +0 -0
flyte/_image.py CHANGED
@@ -1,24 +1,32 @@
1
1
  from __future__ import annotations
2
2
 
3
- import base64
4
3
  import hashlib
4
+ import os.path
5
5
  import sys
6
+ import typing
6
7
  from abc import abstractmethod
7
- from dataclasses import asdict, dataclass, field
8
+ from dataclasses import dataclass, field
8
9
  from functools import cached_property
9
10
  from pathlib import Path
10
- from typing import Callable, ClassVar, Dict, List, Literal, Optional, Tuple, TypeVar, Union
11
+ from typing import TYPE_CHECKING, ClassVar, Dict, List, Literal, Optional, Tuple, TypeVar, Union
11
12
 
12
13
  import rich.repr
14
+ from packaging.version import Version
15
+
16
+ if TYPE_CHECKING:
17
+ from flyte import Secret, SecretRequest
13
18
 
14
19
  # Supported Python versions
15
20
  PYTHON_3_10 = (3, 10)
16
21
  PYTHON_3_11 = (3, 11)
17
22
  PYTHON_3_12 = (3, 12)
18
23
  PYTHON_3_13 = (3, 13)
24
+ PYTHON_3_14 = (3, 14)
19
25
 
20
26
  # 0 is a file, 1 is a directory
21
27
  CopyConfigType = Literal[0, 1]
28
+ SOURCE_ROOT = Path(__file__).parent.parent.parent
29
+ DIST_FOLDER = SOURCE_ROOT / "dist"
22
30
 
23
31
  T = TypeVar("T")
24
32
 
@@ -44,8 +52,6 @@ class Layer:
44
52
  layered images programmatically.
45
53
  """
46
54
 
47
- _compute_identifier: Callable[[Layer], str] = field(default=lambda x: x.__str__(), init=True)
48
-
49
55
  @abstractmethod
50
56
  def update_hash(self, hasher: hashlib._Hash):
51
57
  """
@@ -53,7 +59,6 @@ class Layer:
53
59
 
54
60
  :param hasher: The hash object to update with the layer's data.
55
61
  """
56
- ...
57
62
 
58
63
  def validate(self):
59
64
  """
@@ -64,24 +69,33 @@ class Layer:
64
69
 
65
70
  @rich.repr.auto
66
71
  @dataclass(kw_only=True, frozen=True, repr=True)
67
- class PipPackages(Layer):
68
- packages: Optional[Tuple[str, ...]] = None
72
+ class PipOption:
69
73
  index_url: Optional[str] = None
70
74
  extra_index_urls: Optional[Tuple[str] | Tuple[str, ...] | List[str]] = None
71
75
  pre: bool = False
72
76
  extra_args: Optional[str] = None
77
+ secret_mounts: Optional[Tuple[str | Secret, ...]] = None
78
+
79
+ def get_pip_install_args(self) -> List[str]:
80
+ pip_install_args = []
81
+ if self.index_url:
82
+ pip_install_args.append(f"--index-url {self.index_url}")
83
+
84
+ if self.extra_index_urls:
85
+ pip_install_args.extend([f"--extra-index-url {url}" for url in self.extra_index_urls])
73
86
 
74
- # todo: to be implemented
75
- # secret_mounts: Optional[List[Tuple[str, str]]] = None
87
+ if self.pre:
88
+ pip_install_args.append("--pre")
89
+
90
+ if self.extra_args:
91
+ pip_install_args.append(self.extra_args)
92
+ return pip_install_args
76
93
 
77
94
  def update_hash(self, hasher: hashlib._Hash):
78
95
  """
79
- Update the hash with the pip packages
96
+ Update the hash with the PipOption
80
97
  """
81
98
  hash_input = ""
82
- if self.packages:
83
- for package in self.packages:
84
- hash_input += package
85
99
  if self.index_url:
86
100
  hash_input += self.index_url
87
101
  if self.extra_index_urls:
@@ -91,10 +105,53 @@ class PipPackages(Layer):
91
105
  hash_input += str(self.pre)
92
106
  if self.extra_args:
93
107
  hash_input += self.extra_args
108
+ if self.secret_mounts:
109
+ for secret_mount in self.secret_mounts:
110
+ hash_input += str(secret_mount)
94
111
 
95
112
  hasher.update(hash_input.encode("utf-8"))
96
113
 
97
114
 
115
+ @rich.repr.auto
116
+ @dataclass(kw_only=True, frozen=True, repr=True)
117
+ class PipPackages(PipOption, Layer):
118
+ packages: Optional[Tuple[str, ...]] = None
119
+
120
+ def update_hash(self, hasher: hashlib._Hash):
121
+ """
122
+ Update the hash with the pip packages
123
+ """
124
+ super().update_hash(hasher)
125
+ hash_input = ""
126
+ if self.packages:
127
+ for package in self.packages:
128
+ hash_input += package
129
+
130
+ hasher.update(hash_input.encode("utf-8"))
131
+
132
+
133
+ @rich.repr.auto
134
+ @dataclass(kw_only=True, frozen=True, repr=True)
135
+ class PythonWheels(PipOption, Layer):
136
+ wheel_dir: Path
137
+ wheel_dir_name: str = field(init=False)
138
+ package_name: str
139
+
140
+ def __post_init__(self):
141
+ object.__setattr__(self, "wheel_dir_name", self.wheel_dir.name)
142
+
143
+ def update_hash(self, hasher: hashlib._Hash):
144
+ super().update_hash(hasher)
145
+ from ._utils import filehash_update
146
+
147
+ # Iterate through all the wheel files in the directory and update the hash
148
+ for wheel_file in self.wheel_dir.glob("*.whl"):
149
+ if not wheel_file.is_file():
150
+ # Skip if it's not a file (e.g., directory or symlink)
151
+ continue
152
+ filehash_update(wheel_file, hasher)
153
+
154
+
98
155
  @rich.repr.auto
99
156
  @dataclass(kw_only=True, frozen=True, repr=True)
100
157
  class Requirements(PipPackages):
@@ -107,22 +164,134 @@ class Requirements(PipPackages):
107
164
  filehash_update(self.file, hasher)
108
165
 
109
166
 
167
+ @rich.repr.auto
168
+ @dataclass(frozen=True, repr=True)
169
+ class UVProject(PipOption, Layer):
170
+ pyproject: Path
171
+ uvlock: Path
172
+ project_install_mode: typing.Literal["dependencies_only", "install_project"] = "dependencies_only"
173
+
174
+ def validate(self):
175
+ if not self.pyproject.exists():
176
+ raise FileNotFoundError(f"pyproject.toml file {self.pyproject.resolve()} does not exist")
177
+ if not self.pyproject.is_file():
178
+ raise ValueError(f"Pyproject file {self.pyproject.resolve()} is not a file")
179
+ if not self.uvlock.exists():
180
+ raise ValueError(f"UVLock file {self.uvlock.resolve()} does not exist")
181
+ super().validate()
182
+
183
+ def update_hash(self, hasher: hashlib._Hash):
184
+ from ._utils import filehash_update, update_hasher_for_source
185
+
186
+ super().update_hash(hasher)
187
+ if self.project_install_mode == "dependencies_only":
188
+ filehash_update(self.uvlock, hasher)
189
+ filehash_update(self.pyproject, hasher)
190
+ else:
191
+ update_hasher_for_source(self.pyproject.parent, hasher)
192
+
193
+
194
+ @rich.repr.auto
195
+ @dataclass(frozen=True, repr=True)
196
+ class PoetryProject(Layer):
197
+ """
198
+ Poetry does not use pip options, so the PoetryProject class do not inherits PipOption class
199
+ """
200
+
201
+ pyproject: Path
202
+ poetry_lock: Path
203
+ extra_args: Optional[str] = None
204
+ project_install_mode: typing.Literal["dependencies_only", "install_project"] = "dependencies_only"
205
+ secret_mounts: Optional[Tuple[str | Secret, ...]] = None
206
+
207
+ def validate(self):
208
+ if not self.pyproject.exists():
209
+ raise FileNotFoundError(f"pyproject.toml file {self.pyproject} does not exist")
210
+ if not self.pyproject.is_file():
211
+ raise ValueError(f"Pyproject file {self.pyproject} is not a file")
212
+ if not self.poetry_lock.exists():
213
+ raise ValueError(f"poetry.lock file {self.poetry_lock} does not exist")
214
+ super().validate()
215
+
216
+ def update_hash(self, hasher: hashlib._Hash):
217
+ from ._utils import filehash_update, update_hasher_for_source
218
+
219
+ hash_input = ""
220
+ if self.extra_args:
221
+ hash_input += self.extra_args
222
+ if self.secret_mounts:
223
+ for secret_mount in self.secret_mounts:
224
+ hash_input += str(secret_mount)
225
+ hasher.update(hash_input.encode("utf-8"))
226
+
227
+ if self.project_install_mode == "dependencies_only":
228
+ filehash_update(self.poetry_lock, hasher)
229
+ filehash_update(self.pyproject, hasher)
230
+ else:
231
+ update_hasher_for_source(self.pyproject.parent, hasher)
232
+
233
+
234
+ @rich.repr.auto
235
+ @dataclass(frozen=True, repr=True)
236
+ class UVScript(PipOption, Layer):
237
+ script: Path
238
+ script_name: str = field(init=False)
239
+
240
+ def __post_init__(self):
241
+ object.__setattr__(self, "script_name", self.script.name)
242
+
243
+ def validate(self):
244
+ if not self.script.exists():
245
+ raise FileNotFoundError(f"UV script {self.script} does not exist")
246
+ if not self.script.is_file():
247
+ raise ValueError(f"UV script {self.script} is not a file")
248
+ if not self.script.suffix == ".py":
249
+ raise ValueError(f"UV script {self.script} must have a .py extension")
250
+ super().validate()
251
+
252
+ def update_hash(self, hasher: hashlib._Hash):
253
+ from ._utils import parse_uv_script_file
254
+
255
+ header = parse_uv_script_file(self.script)
256
+ h_tuple = _ensure_tuple(header)
257
+ if h_tuple:
258
+ hasher.update(h_tuple.__str__().encode("utf-8"))
259
+ super().update_hash(hasher)
260
+ if header.pyprojects:
261
+ for pyproject in header.pyprojects:
262
+ UVProject(
263
+ Path(pyproject) / "pyproject.toml", Path(pyproject) / "uv.lock", "install_project"
264
+ ).update_hash(hasher)
265
+
266
+
110
267
  @rich.repr.auto
111
268
  @dataclass(frozen=True, repr=True)
112
269
  class AptPackages(Layer):
113
270
  packages: Tuple[str, ...]
271
+ secret_mounts: Optional[Tuple[str | Secret, ...]] = None
114
272
 
115
273
  def update_hash(self, hasher: hashlib._Hash):
116
- hasher.update("".join(self.packages).encode("utf-8"))
274
+ hash_input = "".join(self.packages)
275
+
276
+ if self.secret_mounts:
277
+ for secret_mount in self.secret_mounts:
278
+ hash_input += str(secret_mount)
279
+ hasher.update(hash_input.encode("utf-8"))
117
280
 
118
281
 
119
282
  @rich.repr.auto
120
283
  @dataclass(frozen=True, repr=True)
121
284
  class Commands(Layer):
122
285
  commands: Tuple[str, ...]
286
+ secret_mounts: Optional[Tuple[str | Secret, ...]] = None
123
287
 
124
288
  def update_hash(self, hasher: hashlib._Hash):
125
- hasher.update("".join(self.commands).encode("utf-8"))
289
+ hash_input = "".join(self.commands)
290
+
291
+ if self.secret_mounts:
292
+ for secret_mount in self.secret_mounts:
293
+ hash_input += str(secret_mount)
294
+ hasher.update(hash_input.encode("utf-8"))
126
295
 
127
296
 
128
297
  @rich.repr.auto
@@ -136,37 +305,38 @@ class WorkDir(Layer):
136
305
 
137
306
  @rich.repr.auto
138
307
  @dataclass(frozen=True, repr=True)
139
- class CopyConfig(Layer):
140
- path_type: CopyConfigType
141
- context_source: Path
142
- image_dest: str = "."
143
-
144
- def validate(self):
145
- if not self.context_source.exists():
146
- raise ValueError(f"Source folder {self.context_source.absolute()} does not exist")
147
- if not self.context_source.is_dir() and self.path_type == 1:
148
- raise ValueError(f"Source folder {self.context_source.absolute()} is not a directory")
149
- if not self.context_source.is_file() and self.path_type == 0:
150
- raise ValueError(f"Source file {self.context_source.absolute()} is not a file")
308
+ class DockerIgnore(Layer):
309
+ path: str
151
310
 
152
311
  def update_hash(self, hasher: hashlib._Hash):
153
- from ._utils import update_hasher_for_source
154
-
155
- update_hasher_for_source(self.context_source, hasher)
156
- if self.image_dest:
157
- hasher.update(self.image_dest.encode("utf-8"))
312
+ hasher.update(self.path.encode("utf-8"))
158
313
 
159
314
 
160
315
  @rich.repr.auto
161
316
  @dataclass(frozen=True, repr=True)
162
- class UVProject(Layer):
163
- pyproject: Path
164
- uvlock: Path
317
+ class CopyConfig(Layer):
318
+ path_type: CopyConfigType
319
+ src: Path
320
+ dst: str
321
+
322
+ def __post_init__(self):
323
+ if self.path_type not in (0, 1):
324
+ raise ValueError(f"Invalid path_type {self.path_type}, must be 0 (file) or 1 (directory)")
325
+
326
+ def validate(self):
327
+ if not self.src.exists():
328
+ raise ValueError(f"Source folder {self.src.absolute()} does not exist")
329
+ if not self.src.is_dir() and self.path_type == 1:
330
+ raise ValueError(f"Source folder {self.src.absolute()} is not a directory")
331
+ if not self.src.is_file() and self.path_type == 0:
332
+ raise ValueError(f"Source file {self.src.absolute()} is not a file")
165
333
 
166
334
  def update_hash(self, hasher: hashlib._Hash):
167
- from ._utils import filehash_update
335
+ from ._utils import update_hasher_for_source
168
336
 
169
- filehash_update(self.uvlock, hasher)
337
+ update_hasher_for_source(self.src, hasher)
338
+ if self.dst:
339
+ hasher.update(self.dst.encode("utf-8"))
170
340
 
171
341
 
172
342
  @rich.repr.auto
@@ -204,8 +374,9 @@ class Env(Layer):
204
374
 
205
375
  Architecture = Literal["linux/amd64", "linux/arm64"]
206
376
 
207
- _BASE_REGISTRY = "ghcr.io/unionai-oss"
377
+ _BASE_REGISTRY = "ghcr.io/flyteorg"
208
378
  _DEFAULT_IMAGE_NAME = "flyte"
379
+ _DEFAULT_IMAGE_REF_NAME = "default"
209
380
 
210
381
 
211
382
  def _detect_python_version() -> Tuple[int, int]:
@@ -238,74 +409,82 @@ class Image:
238
409
  registry: Optional[str] = field(default=None)
239
410
  name: Optional[str] = field(default=None)
240
411
  platform: Tuple[Architecture, ...] = field(default=("linux/amd64",))
241
- tag: Optional[str] = field(default=None)
242
412
  python_version: Tuple[int, int] = field(default_factory=_detect_python_version)
243
-
244
- # For .auto() images. Don't compute an actual identifier.
245
- _identifier_override: Optional[str] = field(default=None, init=False)
246
- # This is set on default images. These images are built from the base Dockerfile in this library and shouldn't be
247
- # modified with additional layers.
248
- is_final: bool = field(default=False)
413
+ # Refer to the image_refs (name:image-uri) set in CLI or config
414
+ _ref_name: Optional[str] = field(default=None)
249
415
 
250
416
  # Layers to be added to the image. In init, because frozen, but users shouldn't access, so underscore.
251
417
  _layers: Tuple[Layer, ...] = field(default_factory=tuple)
252
418
 
419
+ # Only settable internally.
420
+ _tag: Optional[str] = field(default=None, init=False)
421
+
253
422
  _DEFAULT_IMAGE_PREFIXES: ClassVar = {
254
423
  PYTHON_3_10: "py3.10-",
255
424
  PYTHON_3_11: "py3.11-",
256
425
  PYTHON_3_12: "py3.12-",
257
426
  PYTHON_3_13: "py3.13-",
427
+ PYTHON_3_14: "py3.14-",
258
428
  }
259
429
 
260
- @cached_property
261
- def identifier(self) -> str:
262
- """
263
- This identifier is a hash of the layers and properties of the image. It is used to look up previously built
264
- images. Why is this useful? For example, if a user has Image.from_uv_base().with_source_file("a/local/file"),
265
- it's not necessarily the case that that file exists within the image (further commands may have removed/changed
266
- it), and certainly not the case that the path to the file, inside the image (which is used as part of the layer
267
- hash computation), is the same. That is, inside the image when a task runs, as we come across the same Image
268
- declaration, we need a way of identifying the image and its uri, without hashing all the layers again. This
269
- is what this identifier is for. See the ImageCache object for additional information.
270
-
271
- :return: A unique identifier of the Image
272
- """
273
- if self._identifier_override:
274
- return self._identifier_override
275
-
276
- # Only get the non-None values in the ImageSpec to ensure the hash is consistent
277
- # across different SDK versions.
278
- # Can potentially add a second hashing function to the Layer protocol, but relying on just asdict/str
279
- # representation for now.
280
- image_dict = asdict(self, dict_factory=lambda x: {k: v for (k, v) in x if v is not None and k != "_layers"})
281
- layers_str_repr = "".join([layer._compute_identifier(layer) for layer in self._layers])
282
- image_dict["layers"] = layers_str_repr
283
- spec_bytes = image_dict.__str__().encode("utf-8")
284
- return base64.urlsafe_b64encode(hashlib.md5(spec_bytes).digest()).decode("ascii").rstrip("=")
430
+ # class-level token not included in __init__
431
+ _token: ClassVar[object] = object()
432
+
433
+ # Underscore cuz we may rename in the future, don't expose for now,
434
+ _image_registry_secret: Optional[Secret] = None
435
+
436
+ # check for the guard that we put in place
437
+ def __post_init__(self):
438
+ if object.__getattribute__(self, "__dict__").pop("_guard", None) is not Image._token:
439
+ raise TypeError(
440
+ "Direct instantiation of Image not allowed, please use one of the various from_...() methods instead"
441
+ )
442
+
443
+ # Private constructor for internal use only
444
+ @classmethod
445
+ def _new(cls, **kwargs) -> Image:
446
+ # call the normal __init__, injecting a private keyword that users won't know
447
+ obj = cls.__new__(cls) # allocate
448
+ # set guard to prevent direct construction
449
+ object.__setattr__(obj, "_guard", cls._token)
450
+ cls.__init__(obj, **kwargs) # run dataclass generated __init__
451
+ return obj
285
452
 
286
453
  def validate(self):
287
454
  for layer in self._layers:
288
455
  layer.validate()
289
456
 
290
457
  @classmethod
291
- def _get_default_image_for(cls, python_version: Tuple[int, int], flyte_version: Optional[str] = None) -> Image:
458
+ def _get_default_image_for(
459
+ cls,
460
+ python_version: Tuple[int, int],
461
+ flyte_version: Optional[str] = None,
462
+ install_flyte: bool = True,
463
+ platform: Optional[Tuple[Architecture, ...]] = None,
464
+ ) -> Image:
292
465
  # Would love a way to move this outside of this class (but still needs to be accessible via Image.auto())
293
466
  # this default image definition may need to be updated once there is a released pypi version
294
- preset_tag = None
295
- if flyte_version:
296
- preset_tag = flyte_version if flyte_version.startswith("v") else f"v{flyte_version}"
297
- preset_tag = f"py{python_version[0]}.{python_version[1]}-{preset_tag}"
298
- image = Image(
467
+ from flyte._version import __version__
468
+
469
+ dev_mode = (__version__ and "dev" in __version__) and not flyte_version and install_flyte
470
+ if install_flyte is False:
471
+ preset_tag = f"py{python_version[0]}.{python_version[1]}"
472
+ else:
473
+ if flyte_version is None:
474
+ flyte_version = __version__.replace("+", "-")
475
+ suffix = flyte_version if flyte_version.startswith("v") else f"v{flyte_version}"
476
+ preset_tag = f"py{python_version[0]}.{python_version[1]}-{suffix}"
477
+ image = Image._new(
299
478
  base_image=f"python:{python_version[0]}.{python_version[1]}-slim-bookworm",
300
479
  registry=_BASE_REGISTRY,
301
480
  name=_DEFAULT_IMAGE_NAME,
302
- tag=preset_tag,
303
- platform=("linux/amd64", "linux/arm64"),
481
+ python_version=python_version,
482
+ platform=("linux/amd64", "linux/arm64") if platform is None else platform,
304
483
  )
305
484
  labels_and_user = _DockerLines(
306
485
  (
307
- "LABEL org.opencontainers.image.authors='Union.AI <sales@union.ai>'",
308
- "LABEL org.opencontainers.image.source=https://github.com/unionai/unionv2",
486
+ "LABEL org.opencontainers.image.authors='Union.AI <info@union.ai>'",
487
+ "LABEL org.opencontainers.image.source=https://github.com/flyteorg/flyte",
309
488
  "RUN useradd --create-home --shell /bin/bash flytekit &&"
310
489
  " chown -R flytekit /root && chown -R flytekit /home",
311
490
  "WORKDIR /root",
@@ -320,105 +499,80 @@ class Image:
320
499
  "UV_LINK_MODE": "copy",
321
500
  }
322
501
  )
323
- image = image.with_apt_packages(["build-essential", "ca-certificates"])
502
+ image = image.with_apt_packages("build-essential", "ca-certificates")
324
503
 
325
- base_packages = ["kubernetes", "msgpack", "mashumaro"]
326
-
327
- # Add in flyte library
328
- if flyte_version:
329
- base_packages.append(f"flyte=={flyte_version}")
330
- image = image.with_pip_packages(base_packages)
331
- else:
332
- from flyte._version import __version__
333
-
334
- if cls._is_editable_install() or (__version__ and "dev" in __version__):
335
- image = image.with_pip_packages(base_packages)
336
- image = image.with_local_v2()
504
+ if install_flyte:
505
+ if dev_mode:
506
+ if os.path.exists(DIST_FOLDER):
507
+ image = image.with_local_v2()
337
508
  else:
338
- base_packages.append(f"flyte=={__version__}")
339
- image = image.with_pip_packages(base_packages)
509
+ flyte_version = typing.cast(str, flyte_version)
510
+ if Version(flyte_version).is_devrelease or Version(flyte_version).is_prerelease:
511
+ image = image.with_pip_packages(f"flyte=={flyte_version}", pre=True)
512
+ else:
513
+ image = image.with_pip_packages(f"flyte=={flyte_version}")
514
+ if not dev_mode:
515
+ object.__setattr__(image, "_tag", preset_tag)
340
516
 
341
517
  return image
342
518
 
343
- @staticmethod
344
- def _is_editable_install():
345
- """Internal hacky function to see if the current install is editable or not."""
346
- curr = Path(__file__)
347
- pyproject = curr.parent.parent.parent / "pyproject.toml"
348
- return pyproject.exists()
349
-
350
- @classmethod
351
- def from_uv_debian(
352
- cls,
353
- registry: str,
354
- name: str,
355
- tag: Optional[str] = None,
356
- python_version: Optional[Tuple[int, int]] = None,
357
- arch: Union[Architecture, Tuple[Architecture, ...]] = "linux/amd64",
358
- ) -> Image:
359
- """
360
- This creates a new debian-based base image.
361
- If using the Union or docker builders, image will have uv available and a virtualenv created at /opt/venv.
362
-
363
- :param registry: Registry to use for the image
364
- :param name: Name of the image
365
- :param tag: Tag to use for the image
366
- :param python_version: Python version to use for the image
367
- :param arch: Architecture to use for the image, default is linux/amd64
368
- :return: Image
369
- """
370
- base_image = "debian:bookworm-slim"
371
- plat = arch if isinstance(arch, tuple) else (arch,)
372
- if python_version is None:
373
- python_version = _detect_python_version()
374
- img = cls(
375
- base_image=base_image, name=name, registry=registry, tag=tag, platform=plat, python_version=python_version
376
- )
377
- return img
378
-
379
519
  @classmethod
380
- def auto(
520
+ def from_debian_base(
381
521
  cls,
382
522
  python_version: Optional[Tuple[int, int]] = None,
383
523
  flyte_version: Optional[str] = None,
524
+ install_flyte: bool = True,
384
525
  registry: Optional[str] = None,
526
+ registry_secret: Optional[str | Secret] = None,
385
527
  name: Optional[str] = None,
528
+ platform: Optional[Tuple[Architecture, ...]] = None,
386
529
  ) -> Image:
387
530
  """
388
531
  Use this method to start using the default base image, built from this library's base Dockerfile
389
532
  Default images are multi-arch amd/arm64
390
533
 
391
534
  :param python_version: If not specified, will use the current Python version
392
- :param flyte_version: Union version to use
535
+ :param flyte_version: Flyte version to use
536
+ :param install_flyte: If True, will install the flyte library in the image
393
537
  :param registry: Registry to use for the image
538
+ :param registry_secret: Secret to use to pull/push the private image.
394
539
  :param name: Name of the image if you want to override the default name
540
+ :param platform: Platform to use for the image, default is linux/amd64, use tuple for multiple values
541
+ Example: ("linux/amd64", "linux/arm64")
395
542
 
396
543
  :return: Image
397
544
  """
398
545
  if python_version is None:
399
546
  python_version = _detect_python_version()
400
547
 
401
- base_image = cls._get_default_image_for(python_version=python_version, flyte_version=flyte_version)
402
- if name is not None and registry is None:
403
- raise ValueError("Both name and registry must be specified to override the default image name.")
548
+ base_image = cls._get_default_image_for(
549
+ python_version=python_version,
550
+ flyte_version=flyte_version,
551
+ install_flyte=install_flyte,
552
+ platform=platform,
553
+ )
404
554
 
405
- if registry and name:
406
- return base_image.clone(registry=registry, name=name)
555
+ if registry or name:
556
+ return base_image.clone(registry=registry, name=name, registry_secret=registry_secret)
407
557
 
408
- # Set this to auto for all auto images because the meaning of "auto" can change (based on logic inside
409
- # _get_default_image_for, acts differently in a running task container) so let's make sure it stays auto.
410
- object.__setattr__(base_image, "_identifier_override", "auto")
411
558
  return base_image
412
559
 
413
560
  @classmethod
414
- def from_prebuilt(cls, image_uri: str) -> Image:
561
+ def from_base(cls, image_uri: str) -> Image:
415
562
  """
416
563
  Use this method to start with a pre-built base image. This image must already exist in the registry of course.
417
564
 
418
565
  :param image_uri: The full URI of the image, in the format <registry>/<name>:<tag>
419
566
  :return:
420
567
  """
421
- img = cls(base_image=image_uri)
568
+ img = cls._new(base_image=image_uri)
569
+ return img
570
+
571
+ @classmethod
572
+ def from_ref_name(cls, name: str = _DEFAULT_IMAGE_REF_NAME) -> Image:
573
+ # NOTE: set image name as _ref_name to enable adding additional layers.
574
+ # See: https://github.com/flyteorg/flyte-sdk/blob/14de802701aab7b8615ffb99c650a36305ef01f7/src/flyte/_image.py#L642
575
+ img = cls._new(name=name, _ref_name=name)
422
576
  return img
423
577
 
424
578
  @classmethod
@@ -428,8 +582,14 @@ class Image:
428
582
  *,
429
583
  name: str,
430
584
  registry: str | None = None,
585
+ registry_secret: Optional[str | Secret] = None,
431
586
  python_version: Optional[Tuple[int, int]] = None,
432
- arch: Union[Architecture, Tuple[Architecture, ...]] = "linux/amd64",
587
+ index_url: Optional[str] = None,
588
+ extra_index_urls: Union[str, List[str], Tuple[str, ...], None] = None,
589
+ pre: bool = False,
590
+ extra_args: Optional[str] = None,
591
+ platform: Optional[Tuple[Architecture, ...]] = None,
592
+ secret_mounts: Optional[SecretRequest] = None,
433
593
  ) -> Image:
434
594
  """
435
595
  Use this method to create a new image with the specified uv script.
@@ -447,78 +607,127 @@ class Image:
447
607
  ```
448
608
 
449
609
  For more information on the uv script format, see the documentation:
450
- <href="https://docs.astral.sh/uv/guides/scripts/#declaring-script-dependencies">
451
- UV: Declaring script dependencies</href>
610
+ [UV: Declaring script dependencies](https://docs.astral.sh/uv/guides/scripts/#declaring-script-dependencies)
452
611
 
453
612
  :param name: name of the image
454
613
  :param registry: registry to use for the image
614
+ :param registry_secret: Secret to use to pull/push the private image.
615
+ :param python_version: Python version to use for the image, if not specified, will use the current Python
616
+ version
455
617
  :param script: path to the uv script
456
- :param arch: architecture to use for the image, default is linux/amd64, use tuple for multiple values
618
+ :param platform: architecture to use for the image, default is linux/amd64, use tuple for multiple values
619
+ :param python_version: Python version for the image, if not specified, will use the current Python version
620
+ :param index_url: index url to use for pip install, default is None
621
+ :param extra_index_urls: extra index urls to use for pip install, default is True
622
+ :param pre: whether to allow pre-release versions, default is False
623
+ :param extra_args: extra arguments to pass to pip install, default is None
624
+ :param secret_mounts: Secret mounts to use for the image, default is None.
457
625
 
458
626
  :return: Image
627
+
628
+ Args:
629
+ secret_mounts:
459
630
  """
460
- from ._utils import parse_uv_script_file
631
+ ll = UVScript(
632
+ script=Path(script),
633
+ index_url=index_url,
634
+ extra_index_urls=_ensure_tuple(extra_index_urls) if extra_index_urls else None,
635
+ pre=pre,
636
+ extra_args=extra_args,
637
+ secret_mounts=_ensure_tuple(secret_mounts) if secret_mounts else None,
638
+ )
461
639
 
462
- if isinstance(script, str):
463
- script = Path(script)
464
- if not script.exists():
465
- raise FileNotFoundError(f"UV script {script} does not exist")
466
- if not script.is_file():
467
- raise ValueError(f"UV script {script} is not a file")
468
- if not script.suffix == ".py":
469
- raise ValueError(f"UV script {script} must have a .py extension")
470
- header = parse_uv_script_file(script)
471
- if registry is None:
472
- raise ValueError("registry must be specified")
473
- img = cls.from_uv_debian(registry=registry, name=name, arch=arch, python_version=python_version)
474
- if header.dependencies:
475
- return img.with_pip_packages(header.dependencies)
476
- # todo: override the _identifier_override to be the script name or a hash of the script contents
477
- # This is needed because inside the image, the identifier will be computed to be something different.
478
- return img
640
+ img = cls.from_debian_base(
641
+ registry=registry,
642
+ registry_secret=registry_secret,
643
+ install_flyte=False,
644
+ name=name,
645
+ python_version=python_version,
646
+ platform=platform,
647
+ )
648
+
649
+ return img.clone(addl_layer=ll)
479
650
 
480
651
  def clone(
481
- self, registry: Optional[str] = None, name: Optional[str] = None, addl_layer: Optional[Layer] = None
652
+ self,
653
+ registry: Optional[str] = None,
654
+ registry_secret: Optional[str | Secret] = None,
655
+ name: Optional[str] = None,
656
+ base_image: Optional[str] = None,
657
+ python_version: Optional[Tuple[int, int]] = None,
658
+ addl_layer: Optional[Layer] = None,
482
659
  ) -> Image:
483
660
  """
484
661
  Use this method to clone the current image and change the registry and name
485
662
 
486
663
  :param registry: Registry to use for the image
664
+ :param registry_secret: Secret to use to pull/push the private image.
487
665
  :param name: Name of the image
488
-
666
+ :param python_version: Python version for the image, if not specified, will use the current Python version
667
+ :param addl_layer: Additional layer to add to the image. This will be added to the end of the layers.
489
668
  :return:
490
669
  """
670
+ from flyte import Secret
671
+
672
+ if addl_layer and self.dockerfile:
673
+ # We don't know how to inspect dockerfiles to know what kind it is (OS, python version, uv vs poetry, etc)
674
+ # so there's no guarantee any of the layering logic will work.
675
+ raise ValueError(
676
+ "Flyte current cannot add additional layers to a Dockerfile-based Image."
677
+ " Please amend the dockerfile directly."
678
+ )
491
679
  registry = registry if registry else self.registry
492
680
  name = name if name else self.name
681
+ registry_secret = registry_secret if registry_secret else self._image_registry_secret
682
+ base_image = base_image if base_image else self.base_image
683
+ if addl_layer and (not name):
684
+ raise ValueError(
685
+ f"Cannot add additional layer {addl_layer} to an image without name. Please first clone()."
686
+ )
493
687
  new_layers = (*self._layers, addl_layer) if addl_layer else self._layers
494
- img = Image(
495
- base_image=self.base_image,
688
+ img = Image._new(
689
+ base_image=base_image,
496
690
  dockerfile=self.dockerfile,
497
691
  registry=registry,
498
692
  name=name,
499
- tag=self.tag,
500
693
  platform=self.platform,
501
- python_version=self.python_version,
502
- is_final=self.is_final,
694
+ python_version=python_version or self.python_version,
503
695
  _layers=new_layers,
696
+ _image_registry_secret=Secret(key=registry_secret) if isinstance(registry_secret, str) else registry_secret,
697
+ _ref_name=self._ref_name,
504
698
  )
505
699
 
506
700
  return img
507
701
 
508
702
  @classmethod
509
- def from_dockerfile(cls, file: Path, registry: str, name: str, tag: Optional[str] = None) -> Image:
703
+ def from_dockerfile(
704
+ cls, file: Path, registry: str, name: str, platform: Union[Architecture, Tuple[Architecture, ...], None] = None
705
+ ) -> Image:
510
706
  """
511
- Use this method to create a new image with the specified dockerfile
707
+ Use this method to create a new image with the specified dockerfile. Note you cannot use additional layers
708
+ after this, as the system doesn't attempt to parse/understand the Dockerfile, and what kind of setup it has
709
+ (python version, uv vs poetry, etc), so please put all logic into the dockerfile itself.
710
+
711
+ Also since Python sees paths as from the calling directory, please use Path objects with absolute paths. The
712
+ context for the builder will be the directory where the dockerfile is located.
512
713
 
513
714
  :param file: path to the dockerfile
514
715
  :param name: name of the image
515
716
  :param registry: registry to use for the image
516
- :param tag: tag to use for the image
717
+ :param platform: architecture to use for the image, default is linux/amd64, use tuple for multiple values
718
+ Example: ("linux/amd64", "linux/arm64")
517
719
 
518
720
  :return:
519
721
  """
520
- tag = tag or "latest"
521
- img = cls(dockerfile=file, registry=registry, name=name, tag=tag)
722
+ platform = _ensure_tuple(platform) if platform else None
723
+ kwargs = {
724
+ "dockerfile": file,
725
+ "registry": registry,
726
+ "name": name,
727
+ }
728
+ if platform:
729
+ kwargs["platform"] = platform
730
+ img = cls._new(**kwargs)
522
731
 
523
732
  return img
524
733
 
@@ -531,6 +740,8 @@ class Image:
531
740
  from ._utils import filehash_update
532
741
 
533
742
  hasher = hashlib.md5()
743
+ if self.base_image:
744
+ hasher.update(self.base_image.encode("utf-8"))
534
745
  if self.dockerfile:
535
746
  # Note the location of the dockerfile shouldn't matter, only the contents
536
747
  filehash_update(self.dockerfile, hasher)
@@ -541,7 +752,7 @@ class Image:
541
752
 
542
753
  @property
543
754
  def _final_tag(self) -> str:
544
- t = self.tag if self.tag else self._get_hash_digest()
755
+ t = self._tag if self._tag else self._get_hash_digest()
545
756
  return t or "latest"
546
757
 
547
758
  @cached_property
@@ -552,6 +763,9 @@ class Image:
552
763
  if self.registry and self.name:
553
764
  tag = self._final_tag
554
765
  return f"{self.registry}/{self.name}:{tag}"
766
+ elif self._ref_name and len(self._layers) == 0:
767
+ assert self.base_image is not None, f"Base image is not set for image ref name {self._ref_name}"
768
+ return self.base_image
555
769
  elif self.name:
556
770
  return f"{self.name}:{self._final_tag}"
557
771
  elif self.base_image:
@@ -570,30 +784,36 @@ class Image:
570
784
  new_image = self.clone(addl_layer=WorkDir(workdir=workdir))
571
785
  return new_image
572
786
 
573
- def with_requirements(self, file: Path) -> Image:
787
+ def with_requirements(
788
+ self,
789
+ file: str | Path,
790
+ secret_mounts: Optional[SecretRequest] = None,
791
+ ) -> Image:
574
792
  """
575
793
  Use this method to create a new image with the specified requirements file layered on top of the current image
576
794
  Cannot be used in conjunction with conda
577
795
 
578
796
  :param file: path to the requirements file, must be a .txt file
797
+ :param secret_mounts: list of secret to mount for the build process.
579
798
  :return:
580
799
  """
581
- if not file.exists():
582
- raise FileNotFoundError(f"Requirements file {file} does not exist")
583
- if not file.is_file():
584
- raise ValueError(f"Requirements file {file} is not a file")
800
+ if isinstance(file, str):
801
+ file = Path(file)
585
802
  if file.suffix != ".txt":
586
803
  raise ValueError(f"Requirements file {file} must have a .txt extension")
587
- new_image = self.clone(addl_layer=Requirements(file=file))
804
+ new_image = self.clone(
805
+ addl_layer=Requirements(file=file, secret_mounts=_ensure_tuple(secret_mounts) if secret_mounts else None)
806
+ )
588
807
  return new_image
589
808
 
590
809
  def with_pip_packages(
591
810
  self,
592
- packages: Union[str, List[str], Tuple[str, ...]],
811
+ *packages: str,
593
812
  index_url: Optional[str] = None,
594
813
  extra_index_urls: Union[str, List[str], Tuple[str, ...], None] = None,
595
814
  pre: bool = False,
596
815
  extra_args: Optional[str] = None,
816
+ secret_mounts: Optional[SecretRequest] = None,
597
817
  ) -> Image:
598
818
  """
599
819
  Use this method to create a new image with the specified pip packages layered on top of the current image
@@ -601,9 +821,24 @@ class Image:
601
821
 
602
822
  Example:
603
823
  ```python
604
- @flyte.task(image=(flyte.Image
605
- .ubuntu_python()
606
- .with_pip_packages(["requests", "numpy"])))
824
+ @flyte.task(image=(flyte.Image.from_debian_base().with_pip_packages("requests", "numpy")))
825
+ def my_task(x: int) -> int:
826
+ import numpy as np
827
+ return np.sum([x, 1])
828
+ ```
829
+
830
+ To mount secrets during the build process to download private packages, you can use the `secret_mounts`.
831
+ In the below example, "GITHUB_PAT" will be mounted as env var "GITHUB_PAT",
832
+ and "apt-secret" will be mounted at /etc/apt/apt-secret.
833
+ Example:
834
+ ```python
835
+ private_package = "git+https://$GITHUB_PAT@github.com/flyteorg/flytex.git@2e20a2acebfc3877d84af643fdd768edea41d533"
836
+ @flyte.task(
837
+ image=(
838
+ flyte.Image.from_debian_base()
839
+ .with_pip_packages("private_package", secret_mounts=[Secret(key="GITHUB_PAT")])
840
+ .with_apt_packages("git", secret_mounts=[Secret(key="apt-secret", mount="/etc/apt/apt-secret")])
841
+ )
607
842
  def my_task(x: int) -> int:
608
843
  import numpy as np
609
844
  return np.sum([x, 1])
@@ -614,12 +849,10 @@ class Image:
614
849
  :param extra_index_urls: extra index urls to use for pip install, default is None
615
850
  :param pre: whether to allow pre-release versions, default is False
616
851
  :param extra_args: extra arguments to pass to pip install, default is None
617
- # :param secret_mounts: todo
618
- :param extra_args: extra arguments to pass to pip install, default is None
852
+ :param secret_mounts: list of secret to mount for the build process.
619
853
  :return: Image
620
854
  """
621
-
622
- new_packages: Optional[Tuple] = _ensure_tuple(packages) if packages else None
855
+ new_packages: Optional[Tuple] = packages or None
623
856
  new_extra_index_urls: Optional[Tuple] = _ensure_tuple(extra_index_urls) if extra_index_urls else None
624
857
 
625
858
  ll = PipPackages(
@@ -628,6 +861,7 @@ class Image:
628
861
  extra_index_urls=new_extra_index_urls,
629
862
  pre=pre,
630
863
  extra_args=extra_args,
864
+ secret_mounts=_ensure_tuple(secret_mounts) if secret_mounts else None,
631
865
  )
632
866
  new_image = self.clone(addl_layer=ll)
633
867
  return new_image
@@ -643,73 +877,161 @@ class Image:
643
877
  new_image = self.clone(addl_layer=Env.from_dict(env_vars))
644
878
  return new_image
645
879
 
646
- def with_source_folder(self, context_source: Path, image_dest: Optional[str] = None) -> Image:
880
+ def with_source_folder(self, src: Path, dst: str = ".", copy_contents_only: bool = False) -> Image:
647
881
  """
648
882
  Use this method to create a new image with the specified local directory layered on top of the current image.
649
883
  If dest is not specified, it will be copied to the working directory of the image
650
884
 
651
- :param context_source: root folder of the source code from the build context to be copied
652
- :param image_dest: destination folder in the image
885
+ :param src: root folder of the source code from the build context to be copied
886
+ :param dst: destination folder in the image
887
+ :param copy_contents_only: If True, will copy the contents of the source folder to the destination folder,
888
+ instead of the folder itself. Default is False.
653
889
  :return: Image
654
890
  """
655
- image_dest = image_dest if image_dest else "."
656
- new_image = self.clone(addl_layer=CopyConfig(path_type=1, context_source=context_source, image_dest=image_dest))
891
+ if not copy_contents_only:
892
+ dst = str("./" + src.name) if dst == "." else dst
893
+ new_image = self.clone(addl_layer=CopyConfig(path_type=1, src=src, dst=dst))
657
894
  return new_image
658
895
 
659
- def with_source_file(self, context_source: Path, image_dest: Optional[str] = None) -> Image:
896
+ def with_source_file(self, src: Path, dst: str = ".") -> Image:
660
897
  """
661
898
  Use this method to create a new image with the specified local file layered on top of the current image.
662
899
  If dest is not specified, it will be copied to the working directory of the image
663
900
 
664
- :param context_source: root folder of the source code from the build context to be copied
665
- :param image_dest: destination folder in the image
901
+ :param src: root folder of the source code from the build context to be copied
902
+ :param dst: destination folder in the image
666
903
  :return: Image
667
904
  """
668
- image_dest = image_dest if image_dest else "."
669
- new_image = self.clone(addl_layer=CopyConfig(path_type=0, context_source=context_source, image_dest=image_dest))
905
+ new_image = self.clone(addl_layer=CopyConfig(path_type=0, src=src, dst=dst))
670
906
  return new_image
671
907
 
672
- def with_uv_project(self, pyproject_file: Path) -> Image:
908
+ def with_dockerignore(self, path: Path) -> Image:
909
+ new_image = self.clone(addl_layer=DockerIgnore(path=str(path)))
910
+ return new_image
911
+
912
+ def with_uv_project(
913
+ self,
914
+ pyproject_file: str | Path,
915
+ uvlock: Path | None = None,
916
+ index_url: Optional[str] = None,
917
+ extra_index_urls: Union[List[str], Tuple[str, ...], None] = None,
918
+ pre: bool = False,
919
+ extra_args: Optional[str] = None,
920
+ secret_mounts: Optional[SecretRequest] = None,
921
+ project_install_mode: typing.Literal["dependencies_only", "install_project"] = "dependencies_only",
922
+ ) -> Image:
673
923
  """
674
924
  Use this method to create a new image with the specified uv.lock file layered on top of the current image
675
925
  Must have a corresponding pyproject.toml file in the same directory
676
926
  Cannot be used in conjunction with conda
677
- In the Union builders, using this will change the virtual env to /root/.venv
927
+
928
+ By default, this method copies the pyproject.toml and uv.lock files into the image.
929
+
930
+ If `project_install_mode` is "install_project", it will also copy directory
931
+ where the pyproject.toml file is located into the image.
678
932
 
679
933
  :param pyproject_file: path to the pyproject.toml file, needs to have a corresponding uv.lock file
680
- :return:
934
+ :param uvlock: path to the uv.lock file, if not specified, will use the default uv.lock file in the same
935
+ directory as the pyproject.toml file. (pyproject.parent / uv.lock)
936
+ :param index_url: index url to use for pip install, default is None
937
+ :param extra_index_urls: extra index urls to use for pip install, default is None
938
+ :param pre: whether to allow pre-release versions, default is False
939
+ :param extra_args: extra arguments to pass to pip install, default is None
940
+ :param secret_mounts: list of secret mounts to use for the build process.
941
+ :param project_install_mode: whether to install the project as a package or
942
+ only dependencies, default is "dependencies_only"
943
+ :return: Image
681
944
  """
682
- if not pyproject_file.exists():
683
- raise FileNotFoundError(f"UVLock file {pyproject_file} does not exist")
684
- if not pyproject_file.is_file():
685
- raise ValueError(f"UVLock file {pyproject_file} is not a file")
686
- lock = pyproject_file.parent / "uv.lock"
687
- if not lock.exists():
688
- raise ValueError(f"UVLock file {lock} does not exist")
689
- new_image = self.clone(addl_layer=UVProject(pyproject=pyproject_file, uvlock=lock))
945
+ if isinstance(pyproject_file, str):
946
+ pyproject_file = Path(pyproject_file)
947
+ new_image = self.clone(
948
+ addl_layer=UVProject(
949
+ pyproject=pyproject_file,
950
+ uvlock=uvlock or (pyproject_file.parent / "uv.lock"),
951
+ index_url=index_url,
952
+ extra_index_urls=extra_index_urls,
953
+ pre=pre,
954
+ extra_args=extra_args,
955
+ secret_mounts=_ensure_tuple(secret_mounts) if secret_mounts else None,
956
+ project_install_mode=project_install_mode,
957
+ )
958
+ )
690
959
  return new_image
691
960
 
692
- def with_apt_packages(self, packages: Union[str, List[str], Tuple[str, ...]]) -> Image:
961
+ def with_poetry_project(
962
+ self,
963
+ pyproject_file: str | Path,
964
+ poetry_lock: Path | None = None,
965
+ extra_args: Optional[str] = None,
966
+ secret_mounts: Optional[SecretRequest] = None,
967
+ project_install_mode: typing.Literal["dependencies_only", "install_project"] = "dependencies_only",
968
+ ):
969
+ """
970
+ Use this method to create a new image with the specified pyproject.toml layered on top of the current image.
971
+ Must have a corresponding pyproject.toml file in the same directory.
972
+ Cannot be used in conjunction with conda.
973
+
974
+ By default, this method copies the entire project into the image,
975
+ including files such as pyproject.toml, poetry.lock, and the src/ directory.
976
+
977
+ If you prefer not to install the current project, you can pass through `extra_args`
978
+ `--no-root`. In this case, the image builder will only copy pyproject.toml and poetry.lock
979
+ into the image.
980
+
981
+ :param pyproject_file: Path to the pyproject.toml file. A poetry.lock file must exist in the same directory
982
+ unless `poetry_lock` is explicitly provided.
983
+ :param poetry_lock: Path to the poetry.lock file. If not specified, the default is the file named
984
+ 'poetry.lock' in the same directory as `pyproject_file` (pyproject.parent / "poetry.lock").
985
+ :param extra_args: Extra arguments to pass through to the package installer/resolver, default is None.
986
+ :param secret_mounts: Secrets to make available during dependency resolution/build (e.g., private indexes).
987
+ :param project_install_mode: whether to install the project as a package or
988
+ only dependencies, default is "dependencies_only"
989
+ :return: Image
990
+ """
991
+ if isinstance(pyproject_file, str):
992
+ pyproject_file = Path(pyproject_file)
993
+ new_image = self.clone(
994
+ addl_layer=PoetryProject(
995
+ pyproject=pyproject_file,
996
+ poetry_lock=poetry_lock or (pyproject_file.parent / "poetry.lock"),
997
+ extra_args=extra_args,
998
+ secret_mounts=_ensure_tuple(secret_mounts) if secret_mounts else None,
999
+ project_install_mode=project_install_mode,
1000
+ )
1001
+ )
1002
+ return new_image
1003
+
1004
+ def with_apt_packages(self, *packages: str, secret_mounts: Optional[SecretRequest] = None) -> Image:
693
1005
  """
694
1006
  Use this method to create a new image with the specified apt packages layered on top of the current image
695
1007
 
696
1008
  :param packages: list of apt packages to install
1009
+ :param secret_mounts: list of secret mounts to use for the build process.
697
1010
  :return: Image
698
1011
  """
699
- pkgs = _ensure_tuple(packages)
700
- new_image = self.clone(addl_layer=AptPackages(packages=pkgs))
1012
+ new_image = self.clone(
1013
+ addl_layer=AptPackages(
1014
+ packages=packages,
1015
+ secret_mounts=_ensure_tuple(secret_mounts) if secret_mounts else None,
1016
+ )
1017
+ )
701
1018
  return new_image
702
1019
 
703
- def with_commands(self, commands: List[str]) -> Image:
1020
+ def with_commands(self, commands: List[str], secret_mounts: Optional[SecretRequest] = None) -> Image:
704
1021
  """
705
1022
  Use this method to create a new image with the specified commands layered on top of the current image
706
1023
  Be sure not to use RUN in your command.
707
1024
 
708
1025
  :param commands: list of commands to run
1026
+ :param secret_mounts: list of secret mounts to use for the build process.
709
1027
  :return: Image
710
1028
  """
711
1029
  new_commands: Tuple = _ensure_tuple(commands)
712
- new_image = self.clone(addl_layer=Commands(commands=new_commands))
1030
+ new_image = self.clone(
1031
+ addl_layer=Commands(
1032
+ commands=new_commands, secret_mounts=_ensure_tuple(secret_mounts) if secret_mounts else None
1033
+ )
1034
+ )
713
1035
  return new_image
714
1036
 
715
1037
  def with_local_v2(self) -> Image:
@@ -719,23 +1041,11 @@ class Image:
719
1041
 
720
1042
  :return: Image
721
1043
  """
722
- dist_folder = Path(__file__).parent.parent.parent / "dist"
723
- # Manually declare the CopyConfig instead of using with_source_folder so we can set the hashing
1044
+ # Manually declare the PythonWheel so we can set the hashing
724
1045
  # used to compute the identifier. Can remove if we ever decide to expose the lambda in with_ commands
725
- with_dist = self.clone(
726
- addl_layer=CopyConfig(
727
- path_type=1, context_source=dist_folder, image_dest=".", _compute_identifier=lambda x: "/dist"
728
- )
729
- )
1046
+ with_dist = self.clone(addl_layer=PythonWheels(wheel_dir=DIST_FOLDER, package_name="flyte", pre=True))
730
1047
 
731
- return with_dist.with_commands(
732
- [
733
- "--mount=type=cache,sharing=locked,mode=0777,target=/root/.cache/uv,id=uv"
734
- " --mount=from=uv,source=/uv,target=/usr/bin/uv"
735
- " --mount=source=dist,target=/dist,type=bind"
736
- " uv pip install --python $VIRTUALENV $(ls /dist/*whl)"
737
- ]
738
- )
1048
+ return with_dist
739
1049
 
740
1050
  def __img_str__(self) -> str:
741
1051
  """