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,263 @@
1
+ import os
2
+ import pathlib
3
+ from typing import Any, Dict, List, Literal, Optional, Tuple, Type, Union
4
+
5
+ from flyteidl.core import tasks_pb2
6
+
7
+ from union import Image, storage
8
+ from union._datastructures import NativeInterface, SerializationContext
9
+ from union._logging import logger
10
+ from union._task import TaskTemplate
11
+
12
+ _PRIMARY_CONTAINER_NAME_FIELD = "primary_container_name"
13
+
14
+
15
+ def _extract_command_key(cmd: str, **kwargs) -> Any:
16
+ """
17
+ Extract the key from the command using regex.
18
+ """
19
+ import re
20
+
21
+ input_regex = r"^\{\{\s*\.inputs\.(.*?)\s*\}\}$"
22
+ match = re.match(input_regex, cmd)
23
+ if match:
24
+ return match.group(1)
25
+ return None
26
+
27
+
28
+ def _extract_path_command_key(cmd: str, input_data_dir: Optional[str]) -> Optional[str]:
29
+ """
30
+ Extract the key from the path-like command using regex.
31
+ """
32
+ import re
33
+
34
+ input_data_dir = input_data_dir or ""
35
+ input_regex = rf"{re.escape(input_data_dir)}/(.+)$"
36
+ match = re.match(input_regex, cmd)
37
+ if match:
38
+ return match.group(1)
39
+ return None
40
+
41
+
42
+ class ContainerTask(TaskTemplate):
43
+ """
44
+ This is an intermediate class that represents Flyte Tasks that run a container at execution time. This is the vast
45
+ majority of tasks - the typical ``@task`` decorated tasks; for instance, all run a container. An example of
46
+ something that doesn't run a container would be something like the Athena SQL task.
47
+
48
+ :param name: Name of the task
49
+ :param image: The container image to use for the task. This can be a string or an Image object.
50
+ :param command: The command to run in the container. This can be a list of strings or a single string.
51
+ :param inputs: The inputs to the task. This is a dictionary of input names to types.
52
+ :param arguments: The arguments to pass to the command. This is a list of strings.
53
+ :param outputs: The outputs of the task. This is a dictionary of output names to types.
54
+ :param input_data_dir: The directory where the input data is stored. This is a string or a Path object.
55
+ :param output_data_dir: The directory where the output data is stored. This is a string or a Path object.
56
+ :param metadata_format: The format of the output file. This can be "JSON", "YAML", or "PROTO".
57
+ :param local_logs: If True, logs will be printed to the console in the local execution.
58
+ """
59
+
60
+ MetadataFormat = Literal["JSON", "YAML", "PROTO"]
61
+
62
+ def __init__(
63
+ self,
64
+ name: str,
65
+ image: Union[str, Image],
66
+ command: List[str],
67
+ inputs: Optional[Dict[str, Type]] = None,
68
+ arguments: Optional[List[str]] = None,
69
+ outputs: Optional[Dict[str, Type]] = None,
70
+ input_data_dir: str | pathlib.Path = "/var/inputs",
71
+ output_data_dir: str | pathlib.Path = "/var/outputs",
72
+ metadata_format: MetadataFormat = "JSON",
73
+ local_logs: bool = False,
74
+ **kwargs,
75
+ ):
76
+ super().__init__(
77
+ task_type="raw-container",
78
+ name=name,
79
+ image=image,
80
+ interface=NativeInterface({k: (v, None) for k, v in inputs.items()}, outputs),
81
+ **kwargs,
82
+ )
83
+ self._image = image
84
+ self._cmd = command
85
+ self._args = arguments
86
+ self._input_data_dir = input_data_dir
87
+ self._output_data_dir = output_data_dir
88
+ self._metadata_format = metadata_format
89
+ self._inputs = inputs
90
+ self._outputs = outputs
91
+ self.local_logs = local_logs
92
+
93
+ def _render_command_and_volume_binding(self, cmd: str, **kwargs) -> Tuple[str, Dict[str, Dict[str, str]]]:
94
+ """
95
+ We support template-style references to inputs, e.g., "{{.inputs.infile}}".
96
+
97
+ For FlyteFile and FlyteDirectory commands, e.g., "/var/inputs/inputs", we extract the key from strings that
98
+ begin with the specified `input_data_dir`.
99
+ """
100
+ # from flytekit.types.directory import FlyteDirectory
101
+ # from flytekit.types.file import FlyteFile
102
+
103
+ volume_binding = {}
104
+ path_k = _extract_path_command_key(cmd, self._input_data_dir)
105
+ k = path_k if path_k else _extract_command_key(cmd)
106
+
107
+ if k:
108
+ input_val = kwargs.get(k)
109
+ # TODO: Add support file and directory transformer first
110
+ # if type(input_val) in [FlyteFile, FlyteDirectory]:
111
+ # if not path_k:
112
+ # raise AssertionError(
113
+ # "FlyteFile and FlyteDirectory commands should not use the template syntax like this:
114
+ # {{.inputs.infile}}\n"
115
+ # "Please use a path-like syntax, such as: /var/inputs/infile.\n"
116
+ # "This requirement is due to how Flyte Propeller processes template syntax inputs."
117
+ # )
118
+ # local_flyte_file_or_dir_path = str(input_val)
119
+ # remote_flyte_file_or_dir_path = os.path.join(self._input_data_dir, k) # type: ignore
120
+ # volume_binding[local_flyte_file_or_dir_path] = {
121
+ # "bind": remote_flyte_file_or_dir_path,
122
+ # "mode": "rw",
123
+ # }
124
+ # command = remote_flyte_file_or_dir_path
125
+ command = str(input_val)
126
+ else:
127
+ command = cmd
128
+
129
+ return command, volume_binding
130
+
131
+ def _prepare_command_and_volumes(
132
+ self, cmd_and_args: List[str], **kwargs
133
+ ) -> Tuple[List[str], Dict[str, Dict[str, str]]]:
134
+ """
135
+ Prepares the command and volume bindings for the container based on input arguments and command templates.
136
+
137
+ Parameters:
138
+ - cmd_and_args (List[str]): The command and arguments to prepare.
139
+ - **kwargs: Keyword arguments representing task inputs.
140
+
141
+ Returns:
142
+ - Tuple[List[str], Dict[str, Dict[str, str]]]: A tuple containing the prepared commands and volume bindings.
143
+ """
144
+
145
+ commands = []
146
+ volume_bindings = {}
147
+
148
+ for cmd in cmd_and_args:
149
+ command, volume_binding = self._render_command_and_volume_binding(cmd, **kwargs)
150
+ commands.append(command)
151
+ volume_bindings.update(volume_binding)
152
+
153
+ return commands, volume_bindings
154
+
155
+ def _pull_image_if_not_exists(self, client, image: str):
156
+ try:
157
+ if not client.images.list(filters={"reference": image}):
158
+ logger.info(f"Pulling image: {image} for container task: {self.name}")
159
+ client.images.pull(image)
160
+ except Exception as e:
161
+ logger.error(f"Failed to pull image {image}: {e!s}")
162
+ raise
163
+
164
+ def _string_to_timedelta(self, s: str):
165
+ import datetime
166
+ import re
167
+
168
+ regex = r"(?:(\d+) days?, )?(?:(\d+):)?(\d+):(\d+)(?:\.(\d+))?"
169
+ parts = re.match(regex, s)
170
+ if not parts:
171
+ raise ValueError("Invalid timedelta string format")
172
+
173
+ days = int(parts.group(1)) if parts.group(1) else 0
174
+ hours = int(parts.group(2)) if parts.group(2) else 0
175
+ minutes = int(parts.group(3)) if parts.group(3) else 0
176
+ seconds = int(parts.group(4)) if parts.group(4) else 0
177
+ microseconds = int(parts.group(5)) if parts.group(5) else 0
178
+
179
+ return datetime.timedelta(
180
+ days=days,
181
+ hours=hours,
182
+ minutes=minutes,
183
+ seconds=seconds,
184
+ microseconds=microseconds,
185
+ )
186
+
187
+ def _convert_output_val_to_correct_type(self, output_val: Any, output_type: Type) -> Any:
188
+ import datetime
189
+
190
+ if issubclass(output_type, bool):
191
+ return output_val.lower() != "false"
192
+ elif issubclass(output_type, datetime.datetime):
193
+ return datetime.datetime.fromisoformat(output_val)
194
+ elif issubclass(output_type, datetime.timedelta):
195
+ return self._string_to_timedelta(output_val)
196
+ else:
197
+ return output_type(output_val)
198
+
199
+ def _get_output_dict(self, output_directory: str) -> Dict[str, Any]:
200
+ output_dict = {}
201
+ if self._outputs:
202
+ for k, output_type in self._outputs.items():
203
+ output_path = os.path.join(output_directory, k)
204
+ with open(output_path, "r") as f:
205
+ output_val = f.read()
206
+ output_dict[k] = self._convert_output_val_to_correct_type(output_val, output_type)
207
+ return output_dict
208
+
209
+ def execute(self, **kwargs) -> Any:
210
+ try:
211
+ import docker
212
+ except ImportError:
213
+ raise ImportError("Docker is not installed. Please install Docker by running `pip install docker`.")
214
+
215
+ # Normalize the input and output directories
216
+ self._input_data_dir = os.path.normpath(self._input_data_dir) if self._input_data_dir else ""
217
+ self._output_data_dir = os.path.normpath(self._output_data_dir) if self._output_data_dir else ""
218
+
219
+ output_directory = storage.get_random_local_directory()
220
+ cmd_and_args = (self._cmd or []) + (self._args or [])
221
+ commands, volume_bindings = self._prepare_command_and_volumes(cmd_and_args, **kwargs)
222
+ volume_bindings[output_directory] = {"bind": self._output_data_dir, "mode": "rw"}
223
+
224
+ client = docker.from_env()
225
+ self._pull_image_if_not_exists(client, self._image)
226
+
227
+ container = client.containers.run(
228
+ self._image, command=commands, remove=True, volumes=volume_bindings, detach=True
229
+ )
230
+
231
+ # Wait for the container to finish the task
232
+ # TODO: Add a 'timeout' parameter to control the max wait time for the container to finish the task.
233
+
234
+ if self.local_logs:
235
+ for log in container.logs(stream=True):
236
+ print(f"[Local Container] {log.strip()}")
237
+
238
+ container.wait()
239
+
240
+ output_dict = self._get_output_dict(output_directory)
241
+ return output_dict
242
+
243
+ def data_loading_config(self, sctx: SerializationContext) -> tasks_pb2.DataLoadingConfig:
244
+ literal_to_protobuf = {
245
+ "JSON": tasks_pb2.DataLoadingConfig.JSON,
246
+ "YAML": tasks_pb2.DataLoadingConfig.YAML,
247
+ "PROTO": tasks_pb2.DataLoadingConfig.PROTO,
248
+ }
249
+
250
+ return tasks_pb2.DataLoadingConfig(
251
+ input_path=self._input_data_dir,
252
+ output_path=self._output_data_dir,
253
+ enabled=True,
254
+ format=literal_to_protobuf.get(self._metadata_format, "JSON"),
255
+ )
256
+
257
+ def container_args(self, sctx: SerializationContext) -> List[str]:
258
+ return self._cmd + (self._args if self._args else [])
259
+
260
+ def config(self, sctx: SerializationContext) -> Optional[Dict[str, str]]:
261
+ if self.pod_template is None:
262
+ return {}
263
+ return {_PRIMARY_CONTAINER_NAME_FIELD: self.primary_container_name}
union/io/__init__.py ADDED
@@ -0,0 +1,11 @@
1
+ """
2
+ ## IO data types
3
+
4
+ This package contains additional data types beyond the primitive data types in python to abstract data flow
5
+ of large datasets in Union.
6
+ """
7
+
8
+ __all__ = ["Dir", "File"]
9
+
10
+ from ._dir import Dir
11
+ from ._file import File
union/io/_dataframe.py ADDED
File without changes
union/io/_dir.py ADDED
@@ -0,0 +1,425 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from pathlib import Path
5
+ from typing import AsyncIterator, Generic, Iterator, List, Optional, Type, TypeVar, Union
6
+
7
+ from flyteidl.core import literals_pb2, types_pb2
8
+ from fsspec.asyn import AsyncFileSystem
9
+ from pydantic import BaseModel, model_validator
10
+
11
+ import union.storage as storage
12
+ from union.io._file import File
13
+ from union.types import TypeEngine, TypeTransformer, TypeTransformerFailedError
14
+
15
+ # Type variable for the directory format
16
+ T = TypeVar("T")
17
+
18
+
19
+ class Dir(BaseModel, Generic[T]):
20
+ """
21
+ A generic directory class representing a directory with files of a specified format.
22
+ Provides both async and sync interfaces for directory operations.
23
+ Users are responsible for handling all I/O - the type transformer for Dir does not do any automatic uploading
24
+ or downloading of files.
25
+
26
+ The generic type T represents the format of the files in the directory.
27
+
28
+ Example:
29
+ ```python
30
+ # Async usage
31
+ from pandas import DataFrame
32
+ data_dir = Dir[DataFrame](path="s3://my-bucket/data/")
33
+
34
+ # Walk through files
35
+ async for file in data_dir.walk():
36
+ async with file.open() as f:
37
+ content = await f.read()
38
+
39
+ # Sync alternative
40
+ for file in data_dir.walk_sync():
41
+ with file.open_sync() as f:
42
+ content = f.read()
43
+ ```
44
+ """
45
+
46
+ # Represents either a local or remote path.
47
+ path: str
48
+ name: Optional[str] = None
49
+ format: str = ""
50
+
51
+ class Config:
52
+ arbitrary_types_allowed = True
53
+
54
+ @model_validator(mode="before")
55
+ @classmethod
56
+ def pre_init(cls, data):
57
+ if data.get("name") is None:
58
+ data["name"] = Path(data["path"]).name
59
+ return data
60
+
61
+ async def walk(self, recursive: bool = True, max_depth: Optional[int] = None) -> AsyncIterator[File[T]]:
62
+ """
63
+ Asynchronously walk through the directory and yield File objects.
64
+
65
+ Args:
66
+ recursive: If True, recursively walk subdirectories
67
+ max_depth: Maximum depth for recursive walking
68
+
69
+ Yields:
70
+ File objects for each file found in the directory
71
+
72
+ Example:
73
+ ```python
74
+ async for file in directory.walk():
75
+ local_path = await file.download()
76
+ # Process the file
77
+ ```
78
+ """
79
+ fs = storage.get_underlying_filesystem(path=self.path)
80
+ if recursive is False:
81
+ max_depth = 2
82
+
83
+ # Note if the path is actually just a file, no walking is done.
84
+ if isinstance(fs, AsyncFileSystem):
85
+ async for parent, _, files in fs._walk(self.path, maxdepth=max_depth):
86
+ for file in files:
87
+ full_file = fs.unstrip_protocol(parent + fs.sep + file)
88
+ yield File[T](path=full_file)
89
+ else:
90
+ for parent, _, files in fs.walk(self.path, maxdepth=max_depth):
91
+ for file in files:
92
+ if "file" in fs.protocol:
93
+ full_file = os.path.join(parent, file)
94
+ else:
95
+ full_file = fs.unstrip_protocol(parent + fs.sep + file)
96
+ yield File[T](path=full_file)
97
+
98
+ def walk_sync(
99
+ self, recursive: bool = True, file_pattern: str = "*", max_depth: Optional[int] = None
100
+ ) -> Iterator[File[T]]:
101
+ """
102
+ Synchronously walk through the directory and yield File objects.
103
+
104
+ Args:
105
+ recursive: If True, recursively walk subdirectories
106
+ file_pattern: Glob pattern to filter files
107
+ max_depth: Maximum depth for recursive walking
108
+
109
+ Yields:
110
+ File objects for each file found in the directory
111
+
112
+ Example:
113
+ ```python
114
+ for file in directory.walk_sync():
115
+ local_path = file.download_sync()
116
+ # Process the file
117
+ ```
118
+ """
119
+ fs = storage.get_underlying_filesystem(path=self.path)
120
+ for parent, _, files in fs.walk(self.path, maxdepth=max_depth):
121
+ for file in files:
122
+ if "file" in fs.protocol:
123
+ full_file = os.path.join(parent, file)
124
+ else:
125
+ full_file = fs.unstrip_protocol(parent + fs.sep + file)
126
+ yield File[T](path=full_file)
127
+
128
+ async def list_files(self) -> List[File[T]]:
129
+ """
130
+ Asynchronously get a list of all files in the directory (non-recursive).
131
+
132
+ Returns:
133
+ A list of File objects
134
+
135
+ Example:
136
+ ```python
137
+ files = await directory.list_files()
138
+ for file in files:
139
+ # Process the file
140
+ ```
141
+ """
142
+ # todo: this should probably also just defer to fsspec.find()
143
+ files = []
144
+ async for file in self.walk(recursive=False):
145
+ files.append(file)
146
+ return files
147
+
148
+ def list_files_sync(self) -> List[File[T]]:
149
+ """
150
+ Synchronously get a list of all files in the directory (non-recursive).
151
+
152
+ Returns:
153
+ A list of File objects
154
+
155
+ Example:
156
+ ```python
157
+ files = directory.list_files_sync()
158
+ for file in files:
159
+ # Process the file
160
+ ```
161
+ """
162
+ return list(self.walk_sync(recursive=False))
163
+
164
+ async def download(self, local_path: Optional[Union[str, Path]] = None) -> str:
165
+ """
166
+ Asynchronously download the entire directory to a local path.
167
+
168
+ Args:
169
+ local_path: The local path to download the directory to. If None, a temporary
170
+ directory will be used.
171
+
172
+ Returns:
173
+ The path to the downloaded directory
174
+
175
+ Example:
176
+ ```python
177
+ local_dir = await directory.download('/tmp/my_data/')
178
+ ```
179
+ """
180
+ local_dest = str(local_path) if local_path else str(storage.get_random_local_path())
181
+ if not storage.is_remote(self.path):
182
+ if not local_path or local_path == self.path:
183
+ # Skip copying
184
+ return self.path
185
+ else:
186
+ # Shell out to a thread to copy
187
+ import asyncio
188
+ import shutil
189
+
190
+ async def copy_tree():
191
+ loop = asyncio.get_event_loop()
192
+ await loop.run_in_executor(None, lambda: shutil.copytree(self.path, local_dest, dirs_exist_ok=True))
193
+
194
+ await copy_tree()
195
+ return await storage.get(self.path, local_dest, recursive=True)
196
+
197
+ def download_sync(self, local_path: Optional[Union[str, Path]] = None) -> str:
198
+ """
199
+ Synchronously download the entire directory to a local path.
200
+
201
+ Args:
202
+ local_path: The local path to download the directory to. If None, a temporary
203
+ directory will be used.
204
+
205
+ Returns:
206
+ The path to the downloaded directory
207
+
208
+ Example:
209
+ ```python
210
+ local_dir = directory.download_sync('/tmp/my_data/')
211
+ ```
212
+ """
213
+ local_dest = str(local_path) if local_path else str(storage.get_random_local_path())
214
+ if not storage.is_remote(self.path):
215
+ if not local_path or local_path == self.path:
216
+ # Skip copying
217
+ return self.path
218
+ else:
219
+ # Shell out to a thread to copy
220
+ import shutil
221
+
222
+ shutil.copytree(self.path, local_dest, dirs_exist_ok=True)
223
+
224
+ # Figure this out when we figure out the final synchronicity story
225
+ raise NotImplementedError("Sync download is not implemented for remote paths")
226
+
227
+ @classmethod
228
+ async def from_local(cls, local_path: Union[str, Path], remote_path: Optional[str] = None) -> Dir[T]:
229
+ """
230
+ Asynchronously create a new Dir by uploading a local directory to the configured remote store.
231
+
232
+ Args:
233
+ local_path: Path to the local directory
234
+ remote_path: Optional path to store the directory remotely. If None, a path will be generated.
235
+
236
+ Returns:
237
+ A new Dir instance pointing to the uploaded directory
238
+
239
+ Example:
240
+ ```python
241
+ remote_dir = await Dir[DataFrame].from_local('/tmp/data_dir/', 's3://bucket/data/')
242
+ ```
243
+ """
244
+ local_path_str = str(local_path)
245
+ dirname = os.path.basename(os.path.normpath(local_path_str))
246
+
247
+ output_path = await storage.put(from_path=local_path_str, to_path=remote_path, recursive=True)
248
+ return cls(path=output_path, name=dirname)
249
+
250
+ @classmethod
251
+ def from_local_sync(cls, local_path: Union[str, Path], remote_path: Optional[str] = None) -> Dir[T]:
252
+ """
253
+ Synchronously create a new Dir by uploading a local directory to the configured remote store.
254
+
255
+ Args:
256
+ local_path: Path to the local directory
257
+ remote_path: Optional path to store the directory remotely. If None, a path will be generated.
258
+
259
+ Returns:
260
+ A new Dir instance pointing to the uploaded directory
261
+
262
+ Example:
263
+ ```python
264
+ remote_dir = Dir[DataFrame].from_local_sync('/tmp/data_dir/', 's3://bucket/data/')
265
+ ```
266
+ """
267
+ # Implement this after we figure out the final synchronicity story
268
+ raise NotImplementedError("Sync upload is not implemented for remote paths")
269
+
270
+ async def exists(self) -> bool:
271
+ """
272
+ Asynchronously check if the directory exists.
273
+
274
+ Returns:
275
+ True if the directory exists, False otherwise
276
+
277
+ Example:
278
+ ```python
279
+ if await directory.exists():
280
+ # Process the directory
281
+ ```
282
+ """
283
+ fs = storage.get_underlying_filesystem(path=self.path)
284
+ if isinstance(fs, AsyncFileSystem):
285
+ return await fs._exists(self.path)
286
+ else:
287
+ return fs.exists(self.path)
288
+
289
+ def exists_sync(self) -> bool:
290
+ """
291
+ Synchronously check if the directory exists.
292
+
293
+ Returns:
294
+ True if the directory exists, False otherwise
295
+
296
+ Example:
297
+ ```python
298
+ if directory.exists_sync():
299
+ # Process the directory
300
+ ```
301
+ """
302
+ fs = storage.get_underlying_filesystem(path=self.path)
303
+ return fs.exists(self.path)
304
+
305
+ async def get_file(self, file_name: str) -> Optional[File[T]]:
306
+ """
307
+ Asynchronously get a specific file from the directory.
308
+
309
+ Args:
310
+ file_name: The name of the file to get
311
+
312
+ Returns:
313
+ A File instance if the file exists, None otherwise
314
+
315
+ Example:
316
+ ```python
317
+ file = await directory.get_file("data.csv")
318
+ if file:
319
+ # Process the file
320
+ ```
321
+ """
322
+ fs = storage.get_underlying_filesystem(path=self.path)
323
+ file_path = fs.sep.join([self.path, file_name])
324
+ file = File[T](path=file_path)
325
+
326
+ if fs.exists(file_path):
327
+ return file
328
+ return None
329
+
330
+ def get_file_sync(self, file_name: str) -> Optional[File[T]]:
331
+ """
332
+ Synchronously get a specific file from the directory.
333
+
334
+ Args:
335
+ file_name: The name of the file to get
336
+
337
+ Returns:
338
+ A File instance if the file exists, None otherwise
339
+
340
+ Example:
341
+ ```python
342
+ file = directory.get_file_sync("data.csv")
343
+ if file:
344
+ # Process the file
345
+ ```
346
+ """
347
+ file_path = os.path.join(self.path, file_name)
348
+ file = File[T](path=file_path)
349
+
350
+ if file.exists_sync():
351
+ return file
352
+ return None
353
+
354
+
355
+ class DirTransformer(TypeTransformer[Dir]):
356
+ """
357
+ Transformer for Dir objects. This type transformer does not handle any i/o. That is now the responsibility of the
358
+ user.
359
+ """
360
+
361
+ def __init__(self):
362
+ super().__init__(name="Dir", t=Dir)
363
+
364
+ def get_literal_type(self, t: Type[Dir]) -> types_pb2.LiteralType:
365
+ """Get the Flyte literal type for a File type."""
366
+ return types_pb2.LiteralType(
367
+ blob=types_pb2.BlobType(
368
+ # todo: set format from generic
369
+ format="", # Format is determined by the generic type T
370
+ dimensionality=types_pb2.BlobType.BlobDimensionality.MULTIPART,
371
+ )
372
+ )
373
+
374
+ async def to_literal(
375
+ self,
376
+ python_val: Dir,
377
+ python_type: Type[Dir],
378
+ expected: types_pb2.LiteralType,
379
+ ) -> literals_pb2.Literal:
380
+ """Convert a Dir object to a Flyte literal."""
381
+ if not isinstance(python_val, Dir):
382
+ raise TypeTransformerFailedError(f"Expected Dir object, received {type(python_val)}")
383
+
384
+ return literals_pb2.Literal(
385
+ scalar=literals_pb2.Scalar(
386
+ blob=literals_pb2.Blob(
387
+ metadata=literals_pb2.BlobMetadata(
388
+ type=types_pb2.BlobType(
389
+ format=python_val.format, dimensionality=types_pb2.BlobType.BlobDimensionality.MULTIPART
390
+ )
391
+ ),
392
+ uri=python_val.path,
393
+ )
394
+ )
395
+ )
396
+
397
+ async def to_python_value(
398
+ self,
399
+ lv: literals_pb2.Literal,
400
+ expected_python_type: Type[Dir],
401
+ ) -> Dir:
402
+ """Convert a Flyte literal to a File object."""
403
+ if not lv.scalar.HasField("blob"):
404
+ raise TypeTransformerFailedError(f"Expected blob literal, received {lv}")
405
+ if not lv.scalar.blob.metadata.type.dimensionality == types_pb2.BlobType.BlobDimensionality.MULTIPART:
406
+ raise TypeTransformerFailedError(
407
+ f"Expected multipart, received {lv.scalar.blob.metadata.type.dimensionality}"
408
+ )
409
+
410
+ uri = lv.scalar.blob.uri
411
+ filename = Path(uri).name
412
+ f = Dir(path=uri, name=filename, format=lv.scalar.blob.metadata.type.format)
413
+ return f
414
+
415
+ def guess_python_type(self, literal_type: types_pb2.LiteralType) -> Type[Dir]:
416
+ """Guess the Python type from a Flyte literal type."""
417
+ if (
418
+ literal_type.HasField("blob")
419
+ and literal_type.blob.dimensionality == types_pb2.BlobType.BlobDimensionality.MULTIPART
420
+ ):
421
+ return Dir
422
+ raise ValueError(f"Cannot guess python type from {literal_type}")
423
+
424
+
425
+ TypeEngine.register(DirTransformer())