flyte 0.0.1b0__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.

Potentially problematic release.


This version of flyte might be problematic. Click here for more details.

Files changed (390) hide show
  1. flyte/__init__.py +62 -0
  2. flyte/_api_commons.py +3 -0
  3. flyte/_bin/__init__.py +0 -0
  4. flyte/_bin/runtime.py +126 -0
  5. flyte/_build.py +25 -0
  6. flyte/_cache/__init__.py +12 -0
  7. flyte/_cache/cache.py +146 -0
  8. flyte/_cache/defaults.py +9 -0
  9. flyte/_cache/policy_function_body.py +42 -0
  10. flyte/_cli/__init__.py +0 -0
  11. flyte/_cli/_common.py +287 -0
  12. flyte/_cli/_create.py +42 -0
  13. flyte/_cli/_delete.py +23 -0
  14. flyte/_cli/_deploy.py +140 -0
  15. flyte/_cli/_get.py +235 -0
  16. flyte/_cli/_run.py +152 -0
  17. flyte/_cli/main.py +72 -0
  18. flyte/_code_bundle/__init__.py +8 -0
  19. flyte/_code_bundle/_ignore.py +113 -0
  20. flyte/_code_bundle/_packaging.py +187 -0
  21. flyte/_code_bundle/_utils.py +339 -0
  22. flyte/_code_bundle/bundle.py +178 -0
  23. flyte/_context.py +146 -0
  24. flyte/_datastructures.py +342 -0
  25. flyte/_deploy.py +202 -0
  26. flyte/_doc.py +29 -0
  27. flyte/_docstring.py +32 -0
  28. flyte/_environment.py +43 -0
  29. flyte/_group.py +31 -0
  30. flyte/_hash.py +23 -0
  31. flyte/_image.py +760 -0
  32. flyte/_initialize.py +634 -0
  33. flyte/_interface.py +84 -0
  34. flyte/_internal/__init__.py +3 -0
  35. flyte/_internal/controllers/__init__.py +115 -0
  36. flyte/_internal/controllers/_local_controller.py +118 -0
  37. flyte/_internal/controllers/_trace.py +40 -0
  38. flyte/_internal/controllers/pbhash.py +39 -0
  39. flyte/_internal/controllers/remote/__init__.py +40 -0
  40. flyte/_internal/controllers/remote/_action.py +141 -0
  41. flyte/_internal/controllers/remote/_client.py +43 -0
  42. flyte/_internal/controllers/remote/_controller.py +361 -0
  43. flyte/_internal/controllers/remote/_core.py +402 -0
  44. flyte/_internal/controllers/remote/_informer.py +361 -0
  45. flyte/_internal/controllers/remote/_service_protocol.py +50 -0
  46. flyte/_internal/imagebuild/__init__.py +11 -0
  47. flyte/_internal/imagebuild/docker_builder.py +416 -0
  48. flyte/_internal/imagebuild/image_builder.py +241 -0
  49. flyte/_internal/imagebuild/remote_builder.py +0 -0
  50. flyte/_internal/resolvers/__init__.py +0 -0
  51. flyte/_internal/resolvers/_task_module.py +54 -0
  52. flyte/_internal/resolvers/common.py +31 -0
  53. flyte/_internal/resolvers/default.py +28 -0
  54. flyte/_internal/runtime/__init__.py +0 -0
  55. flyte/_internal/runtime/convert.py +199 -0
  56. flyte/_internal/runtime/entrypoints.py +135 -0
  57. flyte/_internal/runtime/io.py +136 -0
  58. flyte/_internal/runtime/resources_serde.py +138 -0
  59. flyte/_internal/runtime/task_serde.py +210 -0
  60. flyte/_internal/runtime/taskrunner.py +190 -0
  61. flyte/_internal/runtime/types_serde.py +54 -0
  62. flyte/_logging.py +124 -0
  63. flyte/_protos/__init__.py +0 -0
  64. flyte/_protos/common/authorization_pb2.py +66 -0
  65. flyte/_protos/common/authorization_pb2.pyi +108 -0
  66. flyte/_protos/common/authorization_pb2_grpc.py +4 -0
  67. flyte/_protos/common/identifier_pb2.py +71 -0
  68. flyte/_protos/common/identifier_pb2.pyi +82 -0
  69. flyte/_protos/common/identifier_pb2_grpc.py +4 -0
  70. flyte/_protos/common/identity_pb2.py +48 -0
  71. flyte/_protos/common/identity_pb2.pyi +72 -0
  72. flyte/_protos/common/identity_pb2_grpc.py +4 -0
  73. flyte/_protos/common/list_pb2.py +36 -0
  74. flyte/_protos/common/list_pb2.pyi +69 -0
  75. flyte/_protos/common/list_pb2_grpc.py +4 -0
  76. flyte/_protos/common/policy_pb2.py +37 -0
  77. flyte/_protos/common/policy_pb2.pyi +27 -0
  78. flyte/_protos/common/policy_pb2_grpc.py +4 -0
  79. flyte/_protos/common/role_pb2.py +37 -0
  80. flyte/_protos/common/role_pb2.pyi +53 -0
  81. flyte/_protos/common/role_pb2_grpc.py +4 -0
  82. flyte/_protos/common/runtime_version_pb2.py +28 -0
  83. flyte/_protos/common/runtime_version_pb2.pyi +24 -0
  84. flyte/_protos/common/runtime_version_pb2_grpc.py +4 -0
  85. flyte/_protos/logs/dataplane/payload_pb2.py +96 -0
  86. flyte/_protos/logs/dataplane/payload_pb2.pyi +168 -0
  87. flyte/_protos/logs/dataplane/payload_pb2_grpc.py +4 -0
  88. flyte/_protos/secret/definition_pb2.py +49 -0
  89. flyte/_protos/secret/definition_pb2.pyi +93 -0
  90. flyte/_protos/secret/definition_pb2_grpc.py +4 -0
  91. flyte/_protos/secret/payload_pb2.py +62 -0
  92. flyte/_protos/secret/payload_pb2.pyi +94 -0
  93. flyte/_protos/secret/payload_pb2_grpc.py +4 -0
  94. flyte/_protos/secret/secret_pb2.py +38 -0
  95. flyte/_protos/secret/secret_pb2.pyi +6 -0
  96. flyte/_protos/secret/secret_pb2_grpc.py +198 -0
  97. flyte/_protos/secret/secret_pb2_grpc_grpc.py +198 -0
  98. flyte/_protos/validate/validate/validate_pb2.py +76 -0
  99. flyte/_protos/workflow/node_execution_service_pb2.py +26 -0
  100. flyte/_protos/workflow/node_execution_service_pb2.pyi +4 -0
  101. flyte/_protos/workflow/node_execution_service_pb2_grpc.py +32 -0
  102. flyte/_protos/workflow/queue_service_pb2.py +106 -0
  103. flyte/_protos/workflow/queue_service_pb2.pyi +141 -0
  104. flyte/_protos/workflow/queue_service_pb2_grpc.py +172 -0
  105. flyte/_protos/workflow/run_definition_pb2.py +128 -0
  106. flyte/_protos/workflow/run_definition_pb2.pyi +310 -0
  107. flyte/_protos/workflow/run_definition_pb2_grpc.py +4 -0
  108. flyte/_protos/workflow/run_logs_service_pb2.py +41 -0
  109. flyte/_protos/workflow/run_logs_service_pb2.pyi +28 -0
  110. flyte/_protos/workflow/run_logs_service_pb2_grpc.py +69 -0
  111. flyte/_protos/workflow/run_service_pb2.py +133 -0
  112. flyte/_protos/workflow/run_service_pb2.pyi +175 -0
  113. flyte/_protos/workflow/run_service_pb2_grpc.py +412 -0
  114. flyte/_protos/workflow/state_service_pb2.py +58 -0
  115. flyte/_protos/workflow/state_service_pb2.pyi +71 -0
  116. flyte/_protos/workflow/state_service_pb2_grpc.py +138 -0
  117. flyte/_protos/workflow/task_definition_pb2.py +72 -0
  118. flyte/_protos/workflow/task_definition_pb2.pyi +65 -0
  119. flyte/_protos/workflow/task_definition_pb2_grpc.py +4 -0
  120. flyte/_protos/workflow/task_service_pb2.py +44 -0
  121. flyte/_protos/workflow/task_service_pb2.pyi +31 -0
  122. flyte/_protos/workflow/task_service_pb2_grpc.py +104 -0
  123. flyte/_resources.py +226 -0
  124. flyte/_retry.py +32 -0
  125. flyte/_reusable_environment.py +25 -0
  126. flyte/_run.py +411 -0
  127. flyte/_secret.py +61 -0
  128. flyte/_task.py +367 -0
  129. flyte/_task_environment.py +200 -0
  130. flyte/_timeout.py +47 -0
  131. flyte/_tools.py +27 -0
  132. flyte/_trace.py +128 -0
  133. flyte/_utils/__init__.py +20 -0
  134. flyte/_utils/asyn.py +119 -0
  135. flyte/_utils/coro_management.py +25 -0
  136. flyte/_utils/file_handling.py +72 -0
  137. flyte/_utils/helpers.py +108 -0
  138. flyte/_utils/lazy_module.py +54 -0
  139. flyte/_utils/uv_script_parser.py +49 -0
  140. flyte/_version.py +21 -0
  141. flyte/connectors/__init__.py +0 -0
  142. flyte/errors.py +143 -0
  143. flyte/extras/__init__.py +5 -0
  144. flyte/extras/_container.py +273 -0
  145. flyte/io/__init__.py +11 -0
  146. flyte/io/_dataframe.py +0 -0
  147. flyte/io/_dir.py +448 -0
  148. flyte/io/_file.py +468 -0
  149. flyte/io/pickle/__init__.py +0 -0
  150. flyte/io/pickle/transformer.py +117 -0
  151. flyte/io/structured_dataset/__init__.py +129 -0
  152. flyte/io/structured_dataset/basic_dfs.py +219 -0
  153. flyte/io/structured_dataset/structured_dataset.py +1061 -0
  154. flyte/py.typed +0 -0
  155. flyte/remote/__init__.py +25 -0
  156. flyte/remote/_client/__init__.py +0 -0
  157. flyte/remote/_client/_protocols.py +131 -0
  158. flyte/remote/_client/auth/__init__.py +12 -0
  159. flyte/remote/_client/auth/_authenticators/__init__.py +0 -0
  160. flyte/remote/_client/auth/_authenticators/base.py +397 -0
  161. flyte/remote/_client/auth/_authenticators/client_credentials.py +73 -0
  162. flyte/remote/_client/auth/_authenticators/device_code.py +118 -0
  163. flyte/remote/_client/auth/_authenticators/external_command.py +79 -0
  164. flyte/remote/_client/auth/_authenticators/factory.py +200 -0
  165. flyte/remote/_client/auth/_authenticators/pkce.py +516 -0
  166. flyte/remote/_client/auth/_channel.py +184 -0
  167. flyte/remote/_client/auth/_client_config.py +83 -0
  168. flyte/remote/_client/auth/_default_html.py +32 -0
  169. flyte/remote/_client/auth/_grpc_utils/__init__.py +0 -0
  170. flyte/remote/_client/auth/_grpc_utils/auth_interceptor.py +288 -0
  171. flyte/remote/_client/auth/_grpc_utils/default_metadata_interceptor.py +151 -0
  172. flyte/remote/_client/auth/_keyring.py +143 -0
  173. flyte/remote/_client/auth/_token_client.py +260 -0
  174. flyte/remote/_client/auth/errors.py +16 -0
  175. flyte/remote/_client/controlplane.py +95 -0
  176. flyte/remote/_console.py +18 -0
  177. flyte/remote/_data.py +155 -0
  178. flyte/remote/_logs.py +116 -0
  179. flyte/remote/_project.py +86 -0
  180. flyte/remote/_run.py +873 -0
  181. flyte/remote/_secret.py +132 -0
  182. flyte/remote/_task.py +227 -0
  183. flyte/report/__init__.py +3 -0
  184. flyte/report/_report.py +178 -0
  185. flyte/report/_template.html +124 -0
  186. flyte/storage/__init__.py +24 -0
  187. flyte/storage/_remote_fs.py +34 -0
  188. flyte/storage/_storage.py +251 -0
  189. flyte/storage/_utils.py +5 -0
  190. flyte/types/__init__.py +13 -0
  191. flyte/types/_interface.py +25 -0
  192. flyte/types/_renderer.py +162 -0
  193. flyte/types/_string_literals.py +120 -0
  194. flyte/types/_type_engine.py +2210 -0
  195. flyte/types/_utils.py +80 -0
  196. flyte-0.0.1b0.dist-info/METADATA +179 -0
  197. flyte-0.0.1b0.dist-info/RECORD +390 -0
  198. flyte-0.0.1b0.dist-info/WHEEL +5 -0
  199. flyte-0.0.1b0.dist-info/entry_points.txt +3 -0
  200. flyte-0.0.1b0.dist-info/top_level.txt +1 -0
  201. union/__init__.py +54 -0
  202. union/_api_commons.py +3 -0
  203. union/_bin/__init__.py +0 -0
  204. union/_bin/runtime.py +113 -0
  205. union/_build.py +25 -0
  206. union/_cache/__init__.py +12 -0
  207. union/_cache/cache.py +141 -0
  208. union/_cache/defaults.py +9 -0
  209. union/_cache/policy_function_body.py +42 -0
  210. union/_cli/__init__.py +0 -0
  211. union/_cli/_common.py +263 -0
  212. union/_cli/_create.py +40 -0
  213. union/_cli/_delete.py +23 -0
  214. union/_cli/_deploy.py +120 -0
  215. union/_cli/_get.py +162 -0
  216. union/_cli/_params.py +579 -0
  217. union/_cli/_run.py +150 -0
  218. union/_cli/main.py +72 -0
  219. union/_code_bundle/__init__.py +8 -0
  220. union/_code_bundle/_ignore.py +113 -0
  221. union/_code_bundle/_packaging.py +187 -0
  222. union/_code_bundle/_utils.py +342 -0
  223. union/_code_bundle/bundle.py +176 -0
  224. union/_context.py +146 -0
  225. union/_datastructures.py +295 -0
  226. union/_deploy.py +185 -0
  227. union/_doc.py +29 -0
  228. union/_docstring.py +26 -0
  229. union/_environment.py +43 -0
  230. union/_group.py +31 -0
  231. union/_hash.py +23 -0
  232. union/_image.py +760 -0
  233. union/_initialize.py +585 -0
  234. union/_interface.py +84 -0
  235. union/_internal/__init__.py +3 -0
  236. union/_internal/controllers/__init__.py +77 -0
  237. union/_internal/controllers/_local_controller.py +77 -0
  238. union/_internal/controllers/pbhash.py +39 -0
  239. union/_internal/controllers/remote/__init__.py +40 -0
  240. union/_internal/controllers/remote/_action.py +131 -0
  241. union/_internal/controllers/remote/_client.py +43 -0
  242. union/_internal/controllers/remote/_controller.py +169 -0
  243. union/_internal/controllers/remote/_core.py +341 -0
  244. union/_internal/controllers/remote/_informer.py +260 -0
  245. union/_internal/controllers/remote/_service_protocol.py +44 -0
  246. union/_internal/imagebuild/__init__.py +11 -0
  247. union/_internal/imagebuild/docker_builder.py +416 -0
  248. union/_internal/imagebuild/image_builder.py +243 -0
  249. union/_internal/imagebuild/remote_builder.py +0 -0
  250. union/_internal/resolvers/__init__.py +0 -0
  251. union/_internal/resolvers/_task_module.py +31 -0
  252. union/_internal/resolvers/common.py +24 -0
  253. union/_internal/resolvers/default.py +27 -0
  254. union/_internal/runtime/__init__.py +0 -0
  255. union/_internal/runtime/convert.py +163 -0
  256. union/_internal/runtime/entrypoints.py +121 -0
  257. union/_internal/runtime/io.py +136 -0
  258. union/_internal/runtime/resources_serde.py +134 -0
  259. union/_internal/runtime/task_serde.py +202 -0
  260. union/_internal/runtime/taskrunner.py +179 -0
  261. union/_internal/runtime/types_serde.py +53 -0
  262. union/_logging.py +124 -0
  263. union/_protos/__init__.py +0 -0
  264. union/_protos/common/authorization_pb2.py +66 -0
  265. union/_protos/common/authorization_pb2.pyi +106 -0
  266. union/_protos/common/authorization_pb2_grpc.py +4 -0
  267. union/_protos/common/identifier_pb2.py +71 -0
  268. union/_protos/common/identifier_pb2.pyi +82 -0
  269. union/_protos/common/identifier_pb2_grpc.py +4 -0
  270. union/_protos/common/identity_pb2.py +48 -0
  271. union/_protos/common/identity_pb2.pyi +72 -0
  272. union/_protos/common/identity_pb2_grpc.py +4 -0
  273. union/_protos/common/list_pb2.py +36 -0
  274. union/_protos/common/list_pb2.pyi +69 -0
  275. union/_protos/common/list_pb2_grpc.py +4 -0
  276. union/_protos/common/policy_pb2.py +37 -0
  277. union/_protos/common/policy_pb2.pyi +27 -0
  278. union/_protos/common/policy_pb2_grpc.py +4 -0
  279. union/_protos/common/role_pb2.py +37 -0
  280. union/_protos/common/role_pb2.pyi +51 -0
  281. union/_protos/common/role_pb2_grpc.py +4 -0
  282. union/_protos/common/runtime_version_pb2.py +28 -0
  283. union/_protos/common/runtime_version_pb2.pyi +24 -0
  284. union/_protos/common/runtime_version_pb2_grpc.py +4 -0
  285. union/_protos/logs/dataplane/payload_pb2.py +96 -0
  286. union/_protos/logs/dataplane/payload_pb2.pyi +168 -0
  287. union/_protos/logs/dataplane/payload_pb2_grpc.py +4 -0
  288. union/_protos/secret/definition_pb2.py +49 -0
  289. union/_protos/secret/definition_pb2.pyi +93 -0
  290. union/_protos/secret/definition_pb2_grpc.py +4 -0
  291. union/_protos/secret/payload_pb2.py +62 -0
  292. union/_protos/secret/payload_pb2.pyi +94 -0
  293. union/_protos/secret/payload_pb2_grpc.py +4 -0
  294. union/_protos/secret/secret_pb2.py +38 -0
  295. union/_protos/secret/secret_pb2.pyi +6 -0
  296. union/_protos/secret/secret_pb2_grpc.py +198 -0
  297. union/_protos/validate/validate/validate_pb2.py +76 -0
  298. union/_protos/workflow/node_execution_service_pb2.py +26 -0
  299. union/_protos/workflow/node_execution_service_pb2.pyi +4 -0
  300. union/_protos/workflow/node_execution_service_pb2_grpc.py +32 -0
  301. union/_protos/workflow/queue_service_pb2.py +75 -0
  302. union/_protos/workflow/queue_service_pb2.pyi +103 -0
  303. union/_protos/workflow/queue_service_pb2_grpc.py +172 -0
  304. union/_protos/workflow/run_definition_pb2.py +100 -0
  305. union/_protos/workflow/run_definition_pb2.pyi +256 -0
  306. union/_protos/workflow/run_definition_pb2_grpc.py +4 -0
  307. union/_protos/workflow/run_logs_service_pb2.py +41 -0
  308. union/_protos/workflow/run_logs_service_pb2.pyi +28 -0
  309. union/_protos/workflow/run_logs_service_pb2_grpc.py +69 -0
  310. union/_protos/workflow/run_service_pb2.py +133 -0
  311. union/_protos/workflow/run_service_pb2.pyi +173 -0
  312. union/_protos/workflow/run_service_pb2_grpc.py +412 -0
  313. union/_protos/workflow/state_service_pb2.py +58 -0
  314. union/_protos/workflow/state_service_pb2.pyi +69 -0
  315. union/_protos/workflow/state_service_pb2_grpc.py +138 -0
  316. union/_protos/workflow/task_definition_pb2.py +72 -0
  317. union/_protos/workflow/task_definition_pb2.pyi +65 -0
  318. union/_protos/workflow/task_definition_pb2_grpc.py +4 -0
  319. union/_protos/workflow/task_service_pb2.py +44 -0
  320. union/_protos/workflow/task_service_pb2.pyi +31 -0
  321. union/_protos/workflow/task_service_pb2_grpc.py +104 -0
  322. union/_resources.py +226 -0
  323. union/_retry.py +32 -0
  324. union/_reusable_environment.py +25 -0
  325. union/_run.py +374 -0
  326. union/_secret.py +61 -0
  327. union/_task.py +354 -0
  328. union/_task_environment.py +186 -0
  329. union/_timeout.py +47 -0
  330. union/_tools.py +27 -0
  331. union/_utils/__init__.py +11 -0
  332. union/_utils/asyn.py +119 -0
  333. union/_utils/file_handling.py +71 -0
  334. union/_utils/helpers.py +46 -0
  335. union/_utils/lazy_module.py +54 -0
  336. union/_utils/uv_script_parser.py +49 -0
  337. union/_version.py +21 -0
  338. union/connectors/__init__.py +0 -0
  339. union/errors.py +128 -0
  340. union/extras/__init__.py +5 -0
  341. union/extras/_container.py +263 -0
  342. union/io/__init__.py +11 -0
  343. union/io/_dataframe.py +0 -0
  344. union/io/_dir.py +425 -0
  345. union/io/_file.py +418 -0
  346. union/io/pickle/__init__.py +0 -0
  347. union/io/pickle/transformer.py +117 -0
  348. union/io/structured_dataset/__init__.py +122 -0
  349. union/io/structured_dataset/basic_dfs.py +219 -0
  350. union/io/structured_dataset/structured_dataset.py +1057 -0
  351. union/py.typed +0 -0
  352. union/remote/__init__.py +23 -0
  353. union/remote/_client/__init__.py +0 -0
  354. union/remote/_client/_protocols.py +129 -0
  355. union/remote/_client/auth/__init__.py +12 -0
  356. union/remote/_client/auth/_authenticators/__init__.py +0 -0
  357. union/remote/_client/auth/_authenticators/base.py +391 -0
  358. union/remote/_client/auth/_authenticators/client_credentials.py +73 -0
  359. union/remote/_client/auth/_authenticators/device_code.py +120 -0
  360. union/remote/_client/auth/_authenticators/external_command.py +77 -0
  361. union/remote/_client/auth/_authenticators/factory.py +200 -0
  362. union/remote/_client/auth/_authenticators/pkce.py +515 -0
  363. union/remote/_client/auth/_channel.py +184 -0
  364. union/remote/_client/auth/_client_config.py +83 -0
  365. union/remote/_client/auth/_default_html.py +32 -0
  366. union/remote/_client/auth/_grpc_utils/__init__.py +0 -0
  367. union/remote/_client/auth/_grpc_utils/auth_interceptor.py +204 -0
  368. union/remote/_client/auth/_grpc_utils/default_metadata_interceptor.py +144 -0
  369. union/remote/_client/auth/_keyring.py +154 -0
  370. union/remote/_client/auth/_token_client.py +258 -0
  371. union/remote/_client/auth/errors.py +16 -0
  372. union/remote/_client/controlplane.py +86 -0
  373. union/remote/_data.py +149 -0
  374. union/remote/_logs.py +74 -0
  375. union/remote/_project.py +86 -0
  376. union/remote/_run.py +820 -0
  377. union/remote/_secret.py +132 -0
  378. union/remote/_task.py +193 -0
  379. union/report/__init__.py +3 -0
  380. union/report/_report.py +178 -0
  381. union/report/_template.html +124 -0
  382. union/storage/__init__.py +24 -0
  383. union/storage/_remote_fs.py +34 -0
  384. union/storage/_storage.py +247 -0
  385. union/storage/_utils.py +5 -0
  386. union/types/__init__.py +11 -0
  387. union/types/_renderer.py +162 -0
  388. union/types/_string_literals.py +120 -0
  389. union/types/_type_engine.py +2131 -0
  390. union/types/_utils.py +80 -0
@@ -0,0 +1,416 @@
1
+ import asyncio
2
+ import shutil
3
+ import subprocess
4
+ import tempfile
5
+ from pathlib import Path
6
+ from string import Template
7
+ from typing import ClassVar, Protocol, cast
8
+
9
+ import aiofiles
10
+ import click
11
+
12
+ from flyte._image import (
13
+ AptPackages,
14
+ Commands,
15
+ CopyConfig,
16
+ Env,
17
+ Image,
18
+ Layer,
19
+ PipPackages,
20
+ Requirements,
21
+ UVProject,
22
+ WorkDir,
23
+ _DockerLines,
24
+ )
25
+ from flyte._logging import logger
26
+
27
+ _F_IMG_ID = "_F_IMG_ID"
28
+
29
+ UV_LOCK_INSTALL_TEMPLATE = Template("""\
30
+ WORKDIR /root
31
+ RUN --mount=type=cache,sharing=locked,mode=0777,target=/root/.cache/uv,id=uv \
32
+ --mount=type=bind,target=uv.lock,src=uv.lock \
33
+ --mount=type=bind,target=pyproject.toml,src=pyproject.toml \
34
+ uv sync $PIP_INSTALL_ARGS
35
+ WORKDIR /
36
+
37
+ # Update PATH and UV_PYTHON to point to the venv created by uv sync
38
+ ENV PATH="/root/.venv/bin:$$PATH" \
39
+ VIRTUALENV=/root/.venv \
40
+ UV_PYTHON=/root/.venv/bin/python
41
+ """)
42
+
43
+ UV_PACKAGE_INSTALL_COMMAND_TEMPLATE = Template("""\
44
+ RUN --mount=type=cache,sharing=locked,mode=0777,target=/root/.cache/uv,id=uv \
45
+ --mount=type=bind,target=requirements_uv.txt,src=requirements_uv.txt \
46
+ uv pip install --python $$UV_PYTHON $PIP_INSTALL_ARGS
47
+ """)
48
+
49
+ APT_INSTALL_COMMAND_TEMPLATE = Template("""\
50
+ RUN --mount=type=cache,sharing=locked,mode=0777,target=/var/cache/apt,id=apt \
51
+ apt-get update && apt-get install -y --no-install-recommends \
52
+ $APT_PACKAGES
53
+ """)
54
+
55
+ UV_PYTHON_INSTALL_COMMAND = Template("""\
56
+ RUN --mount=type=cache,sharing=locked,mode=0777,target=/root/.cache/uv,id=uv \
57
+ uv pip install $PIP_INSTALL_ARGS
58
+ """)
59
+
60
+ # uv pip install --python /root/env/bin/python
61
+ # new template
62
+ DOCKER_FILE_UV_BASE_TEMPLATE = Template("""\
63
+ #syntax=docker/dockerfile:1.5
64
+ FROM ghcr.io/astral-sh/uv:0.6.12 as uv
65
+ FROM $BASE_IMAGE
66
+
67
+ USER root
68
+
69
+ # Copy in uv so that later commands don't have to mount it in
70
+ COPY --from=uv /uv /usr/bin/uv
71
+
72
+ # Configure default envs
73
+ ENV UV_COMPILE_BYTECODE=1 \
74
+ UV_LINK_MODE=copy \
75
+ VIRTUALENV=/opt/venv \
76
+ UV_PYTHON=/opt/venv/bin/python \
77
+ PATH="/opt/venv/bin:$$PATH"
78
+
79
+ # Create a virtualenv with the user specified python version
80
+ RUN uv venv $$VIRTUALENV --python=$PYTHON_VERSION
81
+
82
+ # Adds nvidia just in case it exists
83
+ ENV PATH="$$PATH:/usr/local/nvidia/bin:/usr/local/cuda/bin" \
84
+ LD_LIBRARY_PATH="/usr/local/nvidia/lib64:$$LD_LIBRARY_PATH"
85
+ """)
86
+
87
+ # This gets added on to the end of the dockerfile
88
+ DOCKER_FILE_BASE_FOOTER = Template("""\
89
+ ENV _F_IMG_ID=$F_IMG_ID
90
+ WORKDIR /root
91
+ SHELL ["/bin/bash", "-c"]
92
+ """)
93
+
94
+
95
+ class Handler(Protocol):
96
+ @staticmethod
97
+ async def handle(layer: Layer, context_path: Path, dockerfile: str) -> str: ...
98
+
99
+
100
+ class PipAndRequirementsHandler:
101
+ @staticmethod
102
+ async def handle(layer: PipPackages, context_path: Path, dockerfile: str) -> str:
103
+ if isinstance(layer, Requirements):
104
+ async with aiofiles.open(layer.file) as f:
105
+ requirements = []
106
+ async for line in f:
107
+ requirement = await line
108
+ requirements.append(requirement.strip())
109
+ else:
110
+ requirements = list(layer.packages) if layer.packages else []
111
+ requirements_uv_path = context_path / "requirements_uv.txt"
112
+ async with aiofiles.open(requirements_uv_path, "w") as f:
113
+ reqs = "\n".join(requirements)
114
+ await f.write(reqs)
115
+
116
+ pip_install_args = []
117
+ if layer.index_url:
118
+ pip_install_args.append(f"--index-url {layer.index_url}")
119
+
120
+ if layer.extra_index_urls:
121
+ pip_install_args.extend([f"--extra-index-url {url}" for url in layer.extra_index_urls])
122
+
123
+ if layer.pre:
124
+ pip_install_args.append("--pre")
125
+
126
+ if layer.extra_args:
127
+ pip_install_args.append(layer.extra_args)
128
+
129
+ pip_install_args.extend(["--requirement", "requirements_uv.txt"])
130
+
131
+ delta = UV_PACKAGE_INSTALL_COMMAND_TEMPLATE.substitute(PIP_INSTALL_ARGS=" ".join(pip_install_args))
132
+ dockerfile += delta
133
+
134
+ return dockerfile
135
+
136
+
137
+ class _DockerLinesHandler:
138
+ @staticmethod
139
+ async def handle(layer: _DockerLines, context_path: Path, dockerfile: str) -> str:
140
+ # Add the lines to the dockerfile
141
+ for line in layer.lines:
142
+ dockerfile += f"\n{line}\n"
143
+
144
+ return dockerfile
145
+
146
+
147
+ class EnvHandler:
148
+ @staticmethod
149
+ async def handle(layer: Env, context_path: Path, dockerfile: str) -> str:
150
+ # Add the env vars to the dockerfile
151
+ for key, value in layer.env_vars:
152
+ dockerfile += f"\nENV {key}={value}\n"
153
+
154
+ return dockerfile
155
+
156
+
157
+ class AptPackagesHandler:
158
+ @staticmethod
159
+ async def handle(layer: AptPackages, context_path: Path, dockerfile: str) -> str:
160
+ packages = layer.packages
161
+ delta = APT_INSTALL_COMMAND_TEMPLATE.substitute(APT_PACKAGES=" ".join(packages))
162
+ dockerfile += delta
163
+
164
+ return dockerfile
165
+
166
+
167
+ class UVProjectHandler:
168
+ @staticmethod
169
+ async def handle(layer: UVProject, context_path: Path, dockerfile: str) -> str:
170
+ # copy the two files
171
+ shutil.copy(layer.pyproject, context_path)
172
+ shutil.copy(layer.uvlock, context_path)
173
+
174
+ # --locked: Assert that the `uv.lock` will remain unchanged
175
+ # --no-dev: Omit the development dependency group
176
+ # --no-install-project: Do not install the current project
177
+ additional_pip_install_args = ["--locked", "--no-dev", "--no-install-project"]
178
+ delta = UV_LOCK_INSTALL_TEMPLATE.substitute(PIP_INSTALL_ARGS=" ".join(additional_pip_install_args))
179
+ dockerfile += delta
180
+
181
+ return dockerfile
182
+
183
+
184
+ class CopyConfigHandler:
185
+ @staticmethod
186
+ async def handle(layer: CopyConfig, context_path: Path, dockerfile: str) -> str:
187
+ # Copy the source config file or directory to the context path
188
+ abs_path = layer.context_source.absolute()
189
+ dest_path = context_path / abs_path.name
190
+ image_dest_path = layer.image_dest + "/" + abs_path.name
191
+ if layer.context_source.is_file():
192
+ # Copy the file
193
+ shutil.copy(abs_path, dest_path)
194
+ elif layer.context_source.is_dir():
195
+ # Copy the entire directory
196
+ shutil.copytree(abs_path, dest_path)
197
+ else:
198
+ raise ValueError(f"Source path is neither file nor directory: {layer.context_source}")
199
+
200
+ # Add a copy command to the dockerfile
201
+ dockerfile += f"\nCOPY {abs_path.name} {image_dest_path}\n"
202
+
203
+ return dockerfile
204
+
205
+
206
+ class CommandsHandler:
207
+ @staticmethod
208
+ async def handle(layer: Commands, context_path: Path, dockerfile: str) -> str:
209
+ # Append raw commands to the dockerfile
210
+ for command in layer.commands:
211
+ dockerfile += f"\nRUN {command}\n"
212
+
213
+ return dockerfile
214
+
215
+
216
+ class WorkDirHandler:
217
+ @staticmethod
218
+ async def handle(layer: WorkDir, context_path: Path, dockerfile: str) -> str:
219
+ # cd to the workdir
220
+ dockerfile += f"\nWORKDIR {layer.workdir}\n"
221
+
222
+ return dockerfile
223
+
224
+
225
+ async def _process_layer(layer: Layer, context_path: Path, dockerfile: str) -> str:
226
+ match layer:
227
+ case Requirements() | PipPackages():
228
+ # Handle pip packages and requirements
229
+ dockerfile = await PipAndRequirementsHandler.handle(layer, context_path, dockerfile)
230
+
231
+ case AptPackages():
232
+ # Handle apt packages
233
+ dockerfile = await AptPackagesHandler.handle(layer, context_path, dockerfile)
234
+
235
+ case UVProject():
236
+ # Handle UV project
237
+ dockerfile = await UVProjectHandler.handle(layer, context_path, dockerfile)
238
+
239
+ case CopyConfig():
240
+ # Handle local files and folders
241
+ dockerfile = await CopyConfigHandler.handle(layer, context_path, dockerfile)
242
+
243
+ case Commands():
244
+ # Handle commands
245
+ dockerfile = await CommandsHandler.handle(layer, context_path, dockerfile)
246
+
247
+ case WorkDir():
248
+ # Handle workdir
249
+ dockerfile = await WorkDirHandler.handle(layer, context_path, dockerfile)
250
+
251
+ case Env():
252
+ # Handle environment variables
253
+ dockerfile = await EnvHandler.handle(layer, context_path, dockerfile)
254
+
255
+ case _DockerLines():
256
+ # Only for internal use
257
+ dockerfile = await _DockerLinesHandler.handle(layer, context_path, dockerfile)
258
+
259
+ case _:
260
+ raise NotImplementedError(f"Layer type {type(layer)} not supported")
261
+
262
+ return dockerfile
263
+
264
+
265
+ class DockerImageBuilder:
266
+ """Image builder using Docker and buildkit."""
267
+
268
+ builder_type: ClassVar = "docker"
269
+ _builder_name: ClassVar = "flytex"
270
+
271
+ async def build_image(self, image: Image, dry_run: bool = False) -> str:
272
+ if image.is_final:
273
+ if image._layers:
274
+ raise ValueError("Image is a default image and should already be built")
275
+
276
+ if image.dockerfile:
277
+ # If a dockerfile is provided, use it directly
278
+ return await self._build_from_dockerfile(image, push=True)
279
+
280
+ return await self._build_image(
281
+ image,
282
+ push=True,
283
+ dry_run=dry_run,
284
+ )
285
+
286
+ async def _build_from_dockerfile(self, image: Image, push: bool) -> str:
287
+ """
288
+ Build the image from a provided Dockerfile.
289
+ """
290
+ command = [
291
+ "docker",
292
+ "build",
293
+ "--tag",
294
+ f"{image.uri}",
295
+ "--platform",
296
+ ",".join(image.platform),
297
+ ".",
298
+ ]
299
+
300
+ if image.registry and push:
301
+ command.append("--push")
302
+
303
+ concat_command = " ".join(command)
304
+ logger.debug(f"Build command: {concat_command}")
305
+ click.secho(f"Run command: {concat_command} ", fg="blue")
306
+
307
+ await asyncio.to_thread(subprocess.run, command, cwd=str(cast(Path, image.dockerfile).cwd()), check=True)
308
+
309
+ return image.uri
310
+
311
+ @staticmethod
312
+ async def _ensure_buildx_builder():
313
+ """Ensure there is a docker buildx builder called flyte"""
314
+ # Check if buildx is available
315
+ try:
316
+ await asyncio.to_thread(
317
+ subprocess.run, ["docker", "buildx", "version"], check=True, stdout=subprocess.DEVNULL
318
+ )
319
+ except subprocess.CalledProcessError:
320
+ raise RuntimeError("Docker buildx is not available. Make sure BuildKit is installed and enabled.")
321
+
322
+ # List builders
323
+ result = await asyncio.to_thread(
324
+ subprocess.run, ["docker", "buildx", "ls"], capture_output=True, text=True, check=True
325
+ )
326
+ builders = result.stdout
327
+
328
+ # Check if there's any usable builder
329
+ if DockerImageBuilder._builder_name not in builders:
330
+ # No default builder found, create one
331
+ logger.info("No buildx builder found, creating one...")
332
+ await asyncio.to_thread(
333
+ subprocess.run,
334
+ [
335
+ "docker",
336
+ "buildx",
337
+ "create",
338
+ "--name",
339
+ DockerImageBuilder._builder_name,
340
+ "--platform",
341
+ "linux/amd64,linux/arm64",
342
+ ],
343
+ check=True,
344
+ )
345
+ else:
346
+ logger.info("Buildx builder already exists.")
347
+
348
+ async def _build_image(self, image: Image, *, push: bool = True, dry_run: bool = False) -> str:
349
+ """
350
+ if default image (only base image and locked), raise an error, don't have a dockerfile
351
+ if dockerfile, just build
352
+ in the main case, get the default Dockerfile template
353
+ - start from the base image
354
+ - use python to create a default venv and export variables
355
+
356
+ Then for the layers
357
+ - for each layer
358
+ - find the appropriate layer handler
359
+ - call layer handler with the context dir and the dockerfile
360
+ - handler can choose to do something (copy files from local) to the context and update the dockerfile
361
+ contents, returning the new string
362
+ """
363
+ # For testing, set `push=False` to just build the image locally and not push to
364
+ # registry.
365
+
366
+ await DockerImageBuilder._ensure_buildx_builder()
367
+
368
+ with tempfile.TemporaryDirectory() as tmp_dir:
369
+ logger.warning(f"Temporary directory: {tmp_dir}")
370
+ tmp_path = Path(tmp_dir)
371
+
372
+ dockerfile = DOCKER_FILE_UV_BASE_TEMPLATE.substitute(
373
+ BASE_IMAGE=image.base_image,
374
+ PYTHON_VERSION=f"{image.python_version[0]}.{image.python_version[1]}",
375
+ )
376
+
377
+ for layer in image._layers:
378
+ dockerfile = await _process_layer(layer, tmp_path, dockerfile)
379
+
380
+ dockerfile += DOCKER_FILE_BASE_FOOTER.substitute(F_IMG_ID=image.uri)
381
+
382
+ dockerfile_path = tmp_path / "Dockerfile"
383
+ async with aiofiles.open(dockerfile_path, mode="w") as f:
384
+ await f.write(dockerfile)
385
+
386
+ command = [
387
+ "docker",
388
+ "buildx",
389
+ "build",
390
+ "--builder",
391
+ DockerImageBuilder._builder_name,
392
+ "--tag",
393
+ f"{image.uri}",
394
+ "--platform",
395
+ ",".join(image.platform),
396
+ "--push" if push else "--load",
397
+ ]
398
+
399
+ if image.registry and push:
400
+ command.append("--push")
401
+ command.append(tmp_dir)
402
+
403
+ concat_command = " ".join(command)
404
+ logger.debug(f"Build command: {concat_command}")
405
+ if dry_run:
406
+ click.secho("Dry run for docker builder...")
407
+ click.secho(f"Context path: {tmp_path}")
408
+ click.secho(f"Dockerfile: {dockerfile}")
409
+ click.secho(f"Command: {concat_command}")
410
+ return image.uri
411
+ else:
412
+ click.secho(f"Run command: {concat_command} ", fg="blue")
413
+
414
+ await asyncio.to_thread(subprocess.run, command, check=True)
415
+
416
+ return image.uri
@@ -0,0 +1,241 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import json
5
+ import typing
6
+ from typing import ClassVar, Dict, Optional, Tuple
7
+
8
+ from async_lru import alru_cache
9
+ from pydantic import BaseModel
10
+ from typing_extensions import Protocol
11
+
12
+ from flyte._image import Architecture, Image
13
+ from flyte._logging import logger
14
+
15
+
16
+ class ImageBuilder(Protocol):
17
+ async def build_image(self, image: Image, dry_run: bool) -> str: ...
18
+
19
+
20
+ class ImageChecker(Protocol):
21
+ @classmethod
22
+ async def image_exists(
23
+ cls, repository: str, tag: str, arch: Tuple[Architecture, ...] = ("linux/amd64",)
24
+ ) -> bool: ...
25
+
26
+
27
+ class DockerAPIImageChecker(ImageChecker):
28
+ """
29
+ Unfortunately only works for docker hub as there's no way to get a public token for ghcr.io. See SO:
30
+ https://stackoverflow.com/questions/57316115/get-manifest-of-a-public-docker-image-hosted-on-docker-hub-using-the-docker-regi
31
+ The token used here seems to be short-lived (<1 second), so copy pasting doesn't even work.
32
+ """
33
+
34
+ @classmethod
35
+ async def image_exists(cls, repository: str, tag: str, arch: Tuple[Architecture, ...] = ("linux/amd64",)) -> bool:
36
+ import httpx
37
+
38
+ if "/" in repository:
39
+ if not repository.startswith("library/"):
40
+ raise ValueError("This checker only works with Docker Hub")
41
+ else:
42
+ repository = f"library/{repository}"
43
+
44
+ auth_url = "https://auth.docker.io/token"
45
+ service = "registry.docker.io"
46
+ scope = f"repository:{repository}:pull"
47
+
48
+ async with httpx.AsyncClient() as client:
49
+ # Get auth token
50
+ auth_response = await client.get(auth_url, params={"service": service, "scope": scope})
51
+ if auth_response.status_code != 200:
52
+ raise Exception(f"Failed to get auth token: {auth_response.status_code}")
53
+ token = auth_response.json()["token"]
54
+
55
+ manifest_url = f"https://registry-1.docker.io/v2/{repository}/manifests/{tag}"
56
+ headers = {
57
+ "Authorization": f"Bearer {token}",
58
+ "Accept": (
59
+ "application/vnd.docker.distribution.manifest.v2+json,"
60
+ "application/vnd.docker.distribution.manifest.list.v2+json"
61
+ ),
62
+ }
63
+ manifest_response = await client.get(manifest_url, headers=headers)
64
+
65
+ if manifest_response.status_code != 200:
66
+ raise Exception(f"Failed to get manifest: {manifest_response.status_code}")
67
+ manifest_list = manifest_response.json()["manifests"]
68
+ architectures = [f"{x['platform']['os']}/{x['platform']['architecture']}" for x in manifest_list]
69
+
70
+ if set(architectures) >= set(arch):
71
+ logger.debug(f"Image {repository}:{tag} found for architecture(s) {arch}, has {architectures}")
72
+ return True
73
+ else:
74
+ logger.debug(f"Image {repository}:{tag} not found for architecture(s) {arch}, only has {architectures}")
75
+ return False
76
+
77
+
78
+ class LocalDockerCommandImageChecker(ImageChecker):
79
+ command_name: ClassVar[str] = "docker"
80
+
81
+ @classmethod
82
+ async def image_exists(cls, repository: str, tag: str, arch: Tuple[Architecture, ...] = ("linux/amd64",)) -> bool:
83
+ # Check if the image exists locally by running the docker inspect command
84
+ process = await asyncio.create_subprocess_exec(
85
+ cls.command_name,
86
+ "manifest",
87
+ "inspect",
88
+ f"{repository}:{tag}",
89
+ stdout=asyncio.subprocess.PIPE,
90
+ stderr=asyncio.subprocess.PIPE,
91
+ )
92
+ stdout, stderr = await process.communicate()
93
+ if stderr and "manifest unknown" in stderr.decode():
94
+ logger.debug(f"Image {repository}:{tag} not found using the docker command.")
95
+ return False
96
+
97
+ if process.returncode != 0:
98
+ raise RuntimeError(f"Failed to run docker image inspect {repository}:{tag}")
99
+
100
+ inspect_data = json.loads(stdout.decode())
101
+ if "manifests" not in inspect_data:
102
+ raise RuntimeError(f"Invalid data returned from docker image inspect for {repository}:{tag}")
103
+ manifest_list = inspect_data["manifests"]
104
+ architectures = [f"{x['platform']['os']}/{x['platform']['architecture']}" for x in manifest_list]
105
+ if set(architectures) >= set(arch):
106
+ logger.debug(f"Image {repository}:{tag} found for architecture(s) {arch}, has {architectures}")
107
+ return True
108
+
109
+ # Otherwise write a message and return false to trigger build
110
+ logger.debug(f"Image {repository}:{tag} not found for architecture(s) {arch}, only has {architectures}")
111
+ return False
112
+
113
+
114
+ class LocalPodmanCommandImageChecker(LocalDockerCommandImageChecker):
115
+ command_name: ClassVar[str] = "podman"
116
+
117
+
118
+ class ImageBuildEngine:
119
+ """
120
+ ImageBuildEngine contains a list of builders that can be used to build an ImageSpec.
121
+ """
122
+
123
+ _REGISTRY: typing.ClassVar[typing.Dict[str, Tuple[ImageBuilder, int]]] = {}
124
+ _SEEN_IMAGES: typing.ClassVar[typing.Dict[str, str]] = {
125
+ # Set default for the auto container. See Image._identifier_override for more info.
126
+ "auto": Image.auto().uri,
127
+ }
128
+
129
+ @classmethod
130
+ def register(cls, builder_type: str, image_builder: ImageBuilder, priority: int = 5):
131
+ cls._REGISTRY[builder_type] = (image_builder, priority)
132
+
133
+ @classmethod
134
+ def get_registry(cls) -> Dict[str, Tuple[ImageBuilder, int]]:
135
+ return cls._REGISTRY
136
+
137
+ @staticmethod
138
+ @alru_cache
139
+ async def image_exists(image: Image) -> bool:
140
+ if image.base_image is not None and not image._layers:
141
+ logger.debug(f"Image {image} has a base image: {image.base_image} and no layers. Skip existence check.")
142
+ return True
143
+ assert image.registry is not None, f"Image registry is not set for {image}"
144
+ assert image.name is not None, f"Image name is not set for {image}"
145
+
146
+ repository = image.registry + "/" + image.name
147
+ tag = image._final_tag
148
+
149
+ if tag == "latest":
150
+ logger.debug(f"Image {image} has tag 'latest', skip existence check, always build")
151
+ return True
152
+
153
+ # Can get a public token for docker.io but ghcr requires a pat, so harder to get the manifest anonymously.
154
+ checkers = [LocalDockerCommandImageChecker, LocalPodmanCommandImageChecker, DockerAPIImageChecker]
155
+ for checker in checkers:
156
+ try:
157
+ exists = await checker.image_exists(repository, tag, tuple(image.platform))
158
+ logger.debug(f"Image {image} {exists=} in registry")
159
+ return exists
160
+ except Exception as e:
161
+ logger.debug(f"Error checking image existence with {checker.__name__}: {e}")
162
+ continue
163
+
164
+ # If all checkers fail, then assume the image exists. This is current flytekit behavior
165
+ logger.info(f"All checkers failed to check existence of {image.uri}, assuming it does exists")
166
+ return True
167
+
168
+ @classmethod
169
+ @alru_cache
170
+ async def build(
171
+ cls, image: Image, builder: Optional[str] = None, dry_run: bool = False, force: bool = False
172
+ ) -> str:
173
+ """
174
+ Build the image. Images to be tagged with latest will always be built. Otherwise, this engine will check the
175
+ registry to see if the manifest exists.
176
+
177
+ :param image:
178
+ :param builder:
179
+ :param dry_run: Tell the builder to not actually build. Different builders will have different behaviors.
180
+ :param force: Skip the existence check. Normally if the image already exists we won't build it.
181
+ :return:
182
+ """
183
+ # Always trigger a build if this is a dry run since builder shouldn't really do anything, or a force.
184
+ if force or dry_run or not await cls.image_exists(image):
185
+ logger.info(f"Image {image.uri} does not exist in registry or force/dry-run, building...")
186
+
187
+ # Validate the image before building
188
+ image.validate()
189
+
190
+ # If builder is not specified, use the first registered builder
191
+ img_builder = ImageBuildEngine._get_builder(builder)
192
+
193
+ result = await img_builder.build_image(image, dry_run=dry_run)
194
+ return result
195
+ else:
196
+ logger.info(f"Image {image.uri} already exists in registry. Skipping build.")
197
+ return image.uri
198
+
199
+ @classmethod
200
+ def _get_builder(cls, builder: Optional[str]) -> ImageBuilder:
201
+ if not builder:
202
+ from .docker_builder import DockerImageBuilder
203
+
204
+ return DockerImageBuilder()
205
+ if builder not in cls._REGISTRY:
206
+ raise AssertionError(f"Image builder {builder} is not registered.")
207
+ return cls._REGISTRY[builder][0]
208
+
209
+
210
+ class ImageCache(BaseModel):
211
+ image_lookup: Dict[str, str]
212
+ serialized_form: str | None = None
213
+
214
+ @property
215
+ def to_transport(self) -> str:
216
+ """
217
+ :return: returns the serialization context as a base64encoded, gzip compressed, json string
218
+ """
219
+ # This is so that downstream tasks continue to have the same image lookup abilities
220
+ import base64
221
+ import gzip
222
+ from io import BytesIO
223
+
224
+ if self.serialized_form:
225
+ return self.serialized_form
226
+ json_str = self.model_dump_json(exclude={"serialized_form"})
227
+ buf = BytesIO()
228
+ with gzip.GzipFile(mode="wb", fileobj=buf, mtime=0) as f:
229
+ f.write(json_str.encode("utf-8"))
230
+ return base64.b64encode(buf.getvalue()).decode("utf-8")
231
+
232
+ @classmethod
233
+ def from_transport(cls, s: str) -> ImageCache:
234
+ import base64
235
+ import gzip
236
+
237
+ compressed_val = base64.b64decode(s.encode("utf-8"))
238
+ json_str = gzip.decompress(compressed_val).decode("utf-8")
239
+ val = cls.model_validate_json(json_str)
240
+ val.serialized_form = s
241
+ return val
File without changes
File without changes