flyte 0.0.1b3__py3-none-any.whl → 0.2.0a0__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 (319) hide show
  1. flyte/__init__.py +20 -4
  2. flyte/_bin/runtime.py +33 -7
  3. flyte/_build.py +3 -2
  4. flyte/_cache/cache.py +1 -2
  5. flyte/_code_bundle/_packaging.py +1 -1
  6. flyte/_code_bundle/_utils.py +0 -16
  7. flyte/_code_bundle/bundle.py +43 -12
  8. flyte/_context.py +8 -2
  9. flyte/_deploy.py +56 -15
  10. flyte/_environment.py +45 -4
  11. flyte/_excepthook.py +37 -0
  12. flyte/_group.py +2 -1
  13. flyte/_image.py +8 -4
  14. flyte/_initialize.py +112 -254
  15. flyte/_interface.py +3 -3
  16. flyte/_internal/controllers/__init__.py +19 -6
  17. flyte/_internal/controllers/_local_controller.py +83 -8
  18. flyte/_internal/controllers/_trace.py +2 -1
  19. flyte/_internal/controllers/remote/__init__.py +27 -7
  20. flyte/_internal/controllers/remote/_action.py +7 -2
  21. flyte/_internal/controllers/remote/_client.py +5 -1
  22. flyte/_internal/controllers/remote/_controller.py +159 -26
  23. flyte/_internal/controllers/remote/_core.py +13 -5
  24. flyte/_internal/controllers/remote/_informer.py +4 -4
  25. flyte/_internal/controllers/remote/_service_protocol.py +6 -6
  26. flyte/_internal/imagebuild/docker_builder.py +12 -1
  27. flyte/_internal/imagebuild/image_builder.py +16 -11
  28. flyte/_internal/runtime/convert.py +164 -21
  29. flyte/_internal/runtime/entrypoints.py +1 -1
  30. flyte/_internal/runtime/io.py +3 -3
  31. flyte/_internal/runtime/task_serde.py +140 -20
  32. flyte/_internal/runtime/taskrunner.py +4 -3
  33. flyte/_internal/runtime/types_serde.py +1 -1
  34. flyte/_logging.py +12 -1
  35. flyte/_map.py +215 -0
  36. flyte/_pod.py +19 -0
  37. flyte/_protos/common/list_pb2.py +3 -3
  38. flyte/_protos/common/list_pb2.pyi +2 -0
  39. flyte/_protos/logs/dataplane/payload_pb2.py +28 -24
  40. flyte/_protos/logs/dataplane/payload_pb2.pyi +11 -2
  41. flyte/_protos/workflow/common_pb2.py +27 -0
  42. flyte/_protos/workflow/common_pb2.pyi +14 -0
  43. flyte/_protos/workflow/environment_pb2.py +29 -0
  44. flyte/_protos/workflow/environment_pb2.pyi +12 -0
  45. flyte/_protos/workflow/queue_service_pb2.py +40 -41
  46. flyte/_protos/workflow/queue_service_pb2.pyi +35 -30
  47. flyte/_protos/workflow/queue_service_pb2_grpc.py +15 -15
  48. flyte/_protos/workflow/run_definition_pb2.py +61 -61
  49. flyte/_protos/workflow/run_definition_pb2.pyi +8 -4
  50. flyte/_protos/workflow/run_service_pb2.py +20 -24
  51. flyte/_protos/workflow/run_service_pb2.pyi +2 -6
  52. flyte/_protos/workflow/state_service_pb2.py +36 -28
  53. flyte/_protos/workflow/state_service_pb2.pyi +19 -15
  54. flyte/_protos/workflow/state_service_pb2_grpc.py +28 -28
  55. flyte/_protos/workflow/task_definition_pb2.py +29 -22
  56. flyte/_protos/workflow/task_definition_pb2.pyi +21 -5
  57. flyte/_protos/workflow/task_service_pb2.py +27 -11
  58. flyte/_protos/workflow/task_service_pb2.pyi +29 -1
  59. flyte/_protos/workflow/task_service_pb2_grpc.py +34 -0
  60. flyte/_run.py +166 -95
  61. flyte/_task.py +110 -28
  62. flyte/_task_environment.py +55 -72
  63. flyte/_trace.py +6 -14
  64. flyte/_utils/__init__.py +6 -0
  65. flyte/_utils/async_cache.py +139 -0
  66. flyte/_utils/coro_management.py +0 -2
  67. flyte/_utils/helpers.py +45 -19
  68. flyte/_utils/org_discovery.py +57 -0
  69. flyte/_version.py +2 -2
  70. flyte/cli/__init__.py +3 -0
  71. flyte/cli/_abort.py +28 -0
  72. flyte/{_cli → cli}/_common.py +73 -23
  73. flyte/cli/_create.py +145 -0
  74. flyte/{_cli → cli}/_delete.py +4 -4
  75. flyte/{_cli → cli}/_deploy.py +26 -14
  76. flyte/cli/_gen.py +163 -0
  77. flyte/{_cli → cli}/_get.py +98 -23
  78. {union/_cli → flyte/cli}/_params.py +106 -147
  79. flyte/{_cli → cli}/_run.py +99 -20
  80. flyte/cli/main.py +166 -0
  81. flyte/config/__init__.py +3 -0
  82. flyte/config/_config.py +216 -0
  83. flyte/config/_internal.py +64 -0
  84. flyte/config/_reader.py +207 -0
  85. flyte/errors.py +29 -0
  86. flyte/extras/_container.py +33 -43
  87. flyte/io/__init__.py +17 -1
  88. flyte/io/_dir.py +2 -2
  89. flyte/io/_file.py +3 -4
  90. flyte/io/{structured_dataset → _structured_dataset}/basic_dfs.py +1 -1
  91. flyte/io/{structured_dataset → _structured_dataset}/structured_dataset.py +1 -1
  92. flyte/{_datastructures.py → models.py} +56 -7
  93. flyte/remote/__init__.py +2 -1
  94. flyte/remote/_client/_protocols.py +2 -0
  95. flyte/remote/_client/auth/_auth_utils.py +14 -0
  96. flyte/remote/_client/auth/_channel.py +34 -3
  97. flyte/remote/_client/auth/_token_client.py +3 -3
  98. flyte/remote/_client/controlplane.py +13 -13
  99. flyte/remote/_console.py +1 -1
  100. flyte/remote/_data.py +10 -6
  101. flyte/remote/_logs.py +89 -29
  102. flyte/remote/_project.py +8 -9
  103. flyte/remote/_run.py +228 -131
  104. flyte/remote/_secret.py +12 -12
  105. flyte/remote/_task.py +179 -15
  106. flyte/report/_report.py +4 -4
  107. flyte/storage/__init__.py +5 -0
  108. flyte/storage/_config.py +233 -0
  109. flyte/storage/_storage.py +23 -3
  110. flyte/syncify/__init__.py +56 -0
  111. flyte/syncify/_api.py +371 -0
  112. flyte/types/__init__.py +23 -0
  113. flyte/types/_interface.py +22 -7
  114. flyte/{io/pickle/transformer.py → types/_pickle.py} +2 -1
  115. flyte/types/_type_engine.py +95 -18
  116. flyte-0.2.0a0.dist-info/METADATA +249 -0
  117. flyte-0.2.0a0.dist-info/RECORD +218 -0
  118. {flyte-0.0.1b3.dist-info → flyte-0.2.0a0.dist-info}/entry_points.txt +1 -1
  119. flyte/_api_commons.py +0 -3
  120. flyte/_cli/__init__.py +0 -0
  121. flyte/_cli/_create.py +0 -42
  122. flyte/_cli/main.py +0 -72
  123. flyte/_internal/controllers/pbhash.py +0 -39
  124. flyte/io/_dataframe.py +0 -0
  125. flyte/io/pickle/__init__.py +0 -0
  126. flyte-0.0.1b3.dist-info/METADATA +0 -179
  127. flyte-0.0.1b3.dist-info/RECORD +0 -390
  128. union/__init__.py +0 -54
  129. union/_api_commons.py +0 -3
  130. union/_bin/__init__.py +0 -0
  131. union/_bin/runtime.py +0 -113
  132. union/_build.py +0 -25
  133. union/_cache/__init__.py +0 -12
  134. union/_cache/cache.py +0 -141
  135. union/_cache/defaults.py +0 -9
  136. union/_cache/policy_function_body.py +0 -42
  137. union/_cli/__init__.py +0 -0
  138. union/_cli/_common.py +0 -263
  139. union/_cli/_create.py +0 -40
  140. union/_cli/_delete.py +0 -23
  141. union/_cli/_deploy.py +0 -120
  142. union/_cli/_get.py +0 -162
  143. union/_cli/_run.py +0 -150
  144. union/_cli/main.py +0 -72
  145. union/_code_bundle/__init__.py +0 -8
  146. union/_code_bundle/_ignore.py +0 -113
  147. union/_code_bundle/_packaging.py +0 -187
  148. union/_code_bundle/_utils.py +0 -342
  149. union/_code_bundle/bundle.py +0 -176
  150. union/_context.py +0 -146
  151. union/_datastructures.py +0 -295
  152. union/_deploy.py +0 -185
  153. union/_doc.py +0 -29
  154. union/_docstring.py +0 -26
  155. union/_environment.py +0 -43
  156. union/_group.py +0 -31
  157. union/_hash.py +0 -23
  158. union/_image.py +0 -760
  159. union/_initialize.py +0 -585
  160. union/_interface.py +0 -84
  161. union/_internal/__init__.py +0 -3
  162. union/_internal/controllers/__init__.py +0 -77
  163. union/_internal/controllers/_local_controller.py +0 -77
  164. union/_internal/controllers/pbhash.py +0 -39
  165. union/_internal/controllers/remote/__init__.py +0 -40
  166. union/_internal/controllers/remote/_action.py +0 -131
  167. union/_internal/controllers/remote/_client.py +0 -43
  168. union/_internal/controllers/remote/_controller.py +0 -169
  169. union/_internal/controllers/remote/_core.py +0 -341
  170. union/_internal/controllers/remote/_informer.py +0 -260
  171. union/_internal/controllers/remote/_service_protocol.py +0 -44
  172. union/_internal/imagebuild/__init__.py +0 -11
  173. union/_internal/imagebuild/docker_builder.py +0 -416
  174. union/_internal/imagebuild/image_builder.py +0 -243
  175. union/_internal/imagebuild/remote_builder.py +0 -0
  176. union/_internal/resolvers/__init__.py +0 -0
  177. union/_internal/resolvers/_task_module.py +0 -31
  178. union/_internal/resolvers/common.py +0 -24
  179. union/_internal/resolvers/default.py +0 -27
  180. union/_internal/runtime/__init__.py +0 -0
  181. union/_internal/runtime/convert.py +0 -163
  182. union/_internal/runtime/entrypoints.py +0 -121
  183. union/_internal/runtime/io.py +0 -136
  184. union/_internal/runtime/resources_serde.py +0 -134
  185. union/_internal/runtime/task_serde.py +0 -202
  186. union/_internal/runtime/taskrunner.py +0 -179
  187. union/_internal/runtime/types_serde.py +0 -53
  188. union/_logging.py +0 -124
  189. union/_protos/__init__.py +0 -0
  190. union/_protos/common/authorization_pb2.py +0 -66
  191. union/_protos/common/authorization_pb2.pyi +0 -106
  192. union/_protos/common/identifier_pb2.py +0 -71
  193. union/_protos/common/identifier_pb2.pyi +0 -82
  194. union/_protos/common/identity_pb2.py +0 -48
  195. union/_protos/common/identity_pb2.pyi +0 -72
  196. union/_protos/common/identity_pb2_grpc.py +0 -4
  197. union/_protos/common/list_pb2.py +0 -36
  198. union/_protos/common/list_pb2.pyi +0 -69
  199. union/_protos/common/list_pb2_grpc.py +0 -4
  200. union/_protos/common/policy_pb2.py +0 -37
  201. union/_protos/common/policy_pb2.pyi +0 -27
  202. union/_protos/common/policy_pb2_grpc.py +0 -4
  203. union/_protos/common/role_pb2.py +0 -37
  204. union/_protos/common/role_pb2.pyi +0 -51
  205. union/_protos/common/role_pb2_grpc.py +0 -4
  206. union/_protos/common/runtime_version_pb2.py +0 -28
  207. union/_protos/common/runtime_version_pb2.pyi +0 -24
  208. union/_protos/common/runtime_version_pb2_grpc.py +0 -4
  209. union/_protos/logs/dataplane/payload_pb2.py +0 -96
  210. union/_protos/logs/dataplane/payload_pb2.pyi +0 -168
  211. union/_protos/logs/dataplane/payload_pb2_grpc.py +0 -4
  212. union/_protos/secret/definition_pb2.py +0 -49
  213. union/_protos/secret/definition_pb2.pyi +0 -93
  214. union/_protos/secret/definition_pb2_grpc.py +0 -4
  215. union/_protos/secret/payload_pb2.py +0 -62
  216. union/_protos/secret/payload_pb2.pyi +0 -94
  217. union/_protos/secret/payload_pb2_grpc.py +0 -4
  218. union/_protos/secret/secret_pb2.py +0 -38
  219. union/_protos/secret/secret_pb2.pyi +0 -6
  220. union/_protos/secret/secret_pb2_grpc.py +0 -198
  221. union/_protos/validate/validate/validate_pb2.py +0 -76
  222. union/_protos/workflow/node_execution_service_pb2.py +0 -26
  223. union/_protos/workflow/node_execution_service_pb2.pyi +0 -4
  224. union/_protos/workflow/node_execution_service_pb2_grpc.py +0 -32
  225. union/_protos/workflow/queue_service_pb2.py +0 -75
  226. union/_protos/workflow/queue_service_pb2.pyi +0 -103
  227. union/_protos/workflow/queue_service_pb2_grpc.py +0 -172
  228. union/_protos/workflow/run_definition_pb2.py +0 -100
  229. union/_protos/workflow/run_definition_pb2.pyi +0 -256
  230. union/_protos/workflow/run_definition_pb2_grpc.py +0 -4
  231. union/_protos/workflow/run_logs_service_pb2.py +0 -41
  232. union/_protos/workflow/run_logs_service_pb2.pyi +0 -28
  233. union/_protos/workflow/run_logs_service_pb2_grpc.py +0 -69
  234. union/_protos/workflow/run_service_pb2.py +0 -133
  235. union/_protos/workflow/run_service_pb2.pyi +0 -173
  236. union/_protos/workflow/run_service_pb2_grpc.py +0 -412
  237. union/_protos/workflow/state_service_pb2.py +0 -58
  238. union/_protos/workflow/state_service_pb2.pyi +0 -69
  239. union/_protos/workflow/state_service_pb2_grpc.py +0 -138
  240. union/_protos/workflow/task_definition_pb2.py +0 -72
  241. union/_protos/workflow/task_definition_pb2.pyi +0 -65
  242. union/_protos/workflow/task_definition_pb2_grpc.py +0 -4
  243. union/_protos/workflow/task_service_pb2.py +0 -44
  244. union/_protos/workflow/task_service_pb2.pyi +0 -31
  245. union/_protos/workflow/task_service_pb2_grpc.py +0 -104
  246. union/_resources.py +0 -226
  247. union/_retry.py +0 -32
  248. union/_reusable_environment.py +0 -25
  249. union/_run.py +0 -374
  250. union/_secret.py +0 -61
  251. union/_task.py +0 -354
  252. union/_task_environment.py +0 -186
  253. union/_timeout.py +0 -47
  254. union/_tools.py +0 -27
  255. union/_utils/__init__.py +0 -11
  256. union/_utils/asyn.py +0 -119
  257. union/_utils/file_handling.py +0 -71
  258. union/_utils/helpers.py +0 -46
  259. union/_utils/lazy_module.py +0 -54
  260. union/_utils/uv_script_parser.py +0 -49
  261. union/_version.py +0 -21
  262. union/connectors/__init__.py +0 -0
  263. union/errors.py +0 -128
  264. union/extras/__init__.py +0 -5
  265. union/extras/_container.py +0 -263
  266. union/io/__init__.py +0 -11
  267. union/io/_dataframe.py +0 -0
  268. union/io/_dir.py +0 -425
  269. union/io/_file.py +0 -418
  270. union/io/pickle/__init__.py +0 -0
  271. union/io/pickle/transformer.py +0 -117
  272. union/io/structured_dataset/__init__.py +0 -122
  273. union/io/structured_dataset/basic_dfs.py +0 -219
  274. union/io/structured_dataset/structured_dataset.py +0 -1057
  275. union/py.typed +0 -0
  276. union/remote/__init__.py +0 -23
  277. union/remote/_client/__init__.py +0 -0
  278. union/remote/_client/_protocols.py +0 -129
  279. union/remote/_client/auth/__init__.py +0 -12
  280. union/remote/_client/auth/_authenticators/__init__.py +0 -0
  281. union/remote/_client/auth/_authenticators/base.py +0 -391
  282. union/remote/_client/auth/_authenticators/client_credentials.py +0 -73
  283. union/remote/_client/auth/_authenticators/device_code.py +0 -120
  284. union/remote/_client/auth/_authenticators/external_command.py +0 -77
  285. union/remote/_client/auth/_authenticators/factory.py +0 -200
  286. union/remote/_client/auth/_authenticators/pkce.py +0 -515
  287. union/remote/_client/auth/_channel.py +0 -184
  288. union/remote/_client/auth/_client_config.py +0 -83
  289. union/remote/_client/auth/_default_html.py +0 -32
  290. union/remote/_client/auth/_grpc_utils/__init__.py +0 -0
  291. union/remote/_client/auth/_grpc_utils/auth_interceptor.py +0 -204
  292. union/remote/_client/auth/_grpc_utils/default_metadata_interceptor.py +0 -144
  293. union/remote/_client/auth/_keyring.py +0 -154
  294. union/remote/_client/auth/_token_client.py +0 -258
  295. union/remote/_client/auth/errors.py +0 -16
  296. union/remote/_client/controlplane.py +0 -86
  297. union/remote/_data.py +0 -149
  298. union/remote/_logs.py +0 -74
  299. union/remote/_project.py +0 -86
  300. union/remote/_run.py +0 -820
  301. union/remote/_secret.py +0 -132
  302. union/remote/_task.py +0 -193
  303. union/report/__init__.py +0 -3
  304. union/report/_report.py +0 -178
  305. union/report/_template.html +0 -124
  306. union/storage/__init__.py +0 -24
  307. union/storage/_remote_fs.py +0 -34
  308. union/storage/_storage.py +0 -247
  309. union/storage/_utils.py +0 -5
  310. union/types/__init__.py +0 -11
  311. union/types/_renderer.py +0 -162
  312. union/types/_string_literals.py +0 -120
  313. union/types/_type_engine.py +0 -2131
  314. union/types/_utils.py +0 -80
  315. /union/_protos/common/authorization_pb2_grpc.py → /flyte/_protos/workflow/common_pb2_grpc.py +0 -0
  316. /union/_protos/common/identifier_pb2_grpc.py → /flyte/_protos/workflow/environment_pb2_grpc.py +0 -0
  317. /flyte/io/{structured_dataset → _structured_dataset}/__init__.py +0 -0
  318. {flyte-0.0.1b3.dist-info → flyte-0.2.0a0.dist-info}/WHEEL +0 -0
  319. {flyte-0.0.1b3.dist-info → flyte-0.2.0a0.dist-info}/top_level.txt +0 -0
@@ -5,24 +5,19 @@ from typing import Any, Dict, List, Literal, Optional, Tuple, Type, Union
5
5
  from flyteidl.core import tasks_pb2
6
6
 
7
7
  from flyte import Image, storage
8
- from flyte._datastructures import NativeInterface, SerializationContext
9
8
  from flyte._logging import logger
10
9
  from flyte._task import TaskTemplate
10
+ from flyte.models import NativeInterface, SerializationContext
11
11
 
12
- _PRIMARY_CONTAINER_NAME_FIELD = "primary_container_name"
13
12
 
14
-
15
- def _extract_command_key(cmd: str, **kwargs) -> Any:
13
+ def _extract_command_key(cmd: str, **kwargs) -> List[Any] | None:
16
14
  """
17
15
  Extract the key from the command using regex.
18
16
  """
19
17
  import re
20
18
 
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
19
+ input_regex = r"\{\{\.inputs\.([a-zA-Z0-9_]+)\}\}"
20
+ return re.findall(input_regex, cmd)
26
21
 
27
22
 
28
23
  def _extract_path_command_key(cmd: str, input_data_dir: Optional[str]) -> Optional[str]:
@@ -70,7 +65,7 @@ class ContainerTask(TaskTemplate):
70
65
  input_data_dir: str | pathlib.Path = "/var/inputs",
71
66
  output_data_dir: str | pathlib.Path = "/var/outputs",
72
67
  metadata_format: MetadataFormat = "JSON",
73
- local_logs: bool = False,
68
+ local_logs: bool = True,
74
69
  **kwargs,
75
70
  ):
76
71
  super().__init__(
@@ -106,34 +101,33 @@ class ContainerTask(TaskTemplate):
106
101
  For FlyteFile and FlyteDirectory commands, e.g., "/var/inputs/inputs", we extract the key from strings that
107
102
  begin with the specified `input_data_dir`.
108
103
  """
109
- # from flytekit.types.directory import FlyteDirectory
110
- # from flytekit.types.file import FlyteFile
104
+ from flyte.io import Dir, File
111
105
 
112
106
  volume_binding: Dict[str, Dict[str, str]] = {}
113
107
  path_k = _extract_path_command_key(cmd, str(self._input_data_dir))
114
- k = path_k if path_k else _extract_command_key(cmd)
115
-
116
- if k:
117
- input_val = kwargs.get(k)
118
- # TODO: Add support file and directory transformer first
119
- # if type(input_val) in [FlyteFile, FlyteDirectory]:
120
- # if not path_k:
121
- # raise AssertionError(
122
- # "FlyteFile and FlyteDirectory commands should not use the template syntax like this:
123
- # {{.inputs.infile}}\n"
124
- # "Please use a path-like syntax, such as: /var/inputs/infile.\n"
125
- # "This requirement is due to how Flyte Propeller processes template syntax inputs."
126
- # )
127
- # local_flyte_file_or_dir_path = str(input_val)
128
- # remote_flyte_file_or_dir_path = os.path.join(self._input_data_dir, k) # type: ignore
129
- # volume_binding[local_flyte_file_or_dir_path] = {
130
- # "bind": remote_flyte_file_or_dir_path,
131
- # "mode": "rw",
132
- # }
133
- # command = remote_flyte_file_or_dir_path
134
- command = str(input_val)
135
- else:
136
- command = cmd
108
+ keys = path_k if path_k else _extract_command_key(cmd)
109
+
110
+ if keys:
111
+ for k in keys:
112
+ input_val = kwargs.get(k)
113
+ # TODO: Add support file and directory transformer first
114
+ if type(input_val) in [File, Dir]:
115
+ if not path_k:
116
+ raise AssertionError(
117
+ "File and Directory commands should not use the template syntax "
118
+ "like this: {{.inputs.infile}}\n"
119
+ "Please use a path-like syntax, such as: /var/inputs/infile.\n"
120
+ "This requirement is due to how Flyte Propeller processes template syntax inputs."
121
+ )
122
+ local_flyte_file_or_dir_path = str(input_val)
123
+ remote_flyte_file_or_dir_path = os.path.join(self._input_data_dir, k) # type: ignore
124
+ volume_binding[local_flyte_file_or_dir_path] = {
125
+ "bind": remote_flyte_file_or_dir_path,
126
+ "mode": "rw",
127
+ }
128
+ command = remote_flyte_file_or_dir_path
129
+ else:
130
+ command = cmd
137
131
 
138
132
  return command, volume_binding
139
133
 
@@ -215,7 +209,7 @@ class ContainerTask(TaskTemplate):
215
209
  output_dict[k] = self._convert_output_val_to_correct_type(output_val, output_type)
216
210
  return output_dict
217
211
 
218
- def execute(self, **kwargs) -> Any:
212
+ async def execute(self, **kwargs) -> Any:
219
213
  try:
220
214
  import docker
221
215
  except ImportError:
@@ -235,6 +229,7 @@ class ContainerTask(TaskTemplate):
235
229
  raise AssertionError(f"Only Image objects are supported, not strings. Got {self._image} instead.")
236
230
  uri = self._image.uri
237
231
  self._pull_image_if_not_exists(client, uri)
232
+ print(f"Command: {commands!r}")
238
233
 
239
234
  container = client.containers.run(uri, command=commands, remove=True, volumes=volume_bindings, detach=True)
240
235
 
@@ -258,16 +253,11 @@ class ContainerTask(TaskTemplate):
258
253
  }
259
254
 
260
255
  return tasks_pb2.DataLoadingConfig(
261
- input_path=self._input_data_dir,
262
- output_path=self._output_data_dir,
256
+ input_path=str(self._input_data_dir),
257
+ output_path=str(self._output_data_dir),
263
258
  enabled=True,
264
259
  format=literal_to_protobuf.get(self._metadata_format, "JSON"),
265
260
  )
266
261
 
267
262
  def container_args(self, sctx: SerializationContext) -> List[str]:
268
263
  return self._cmd + (self._args if self._args else [])
269
-
270
- def config(self, sctx: SerializationContext) -> Dict[str, str]:
271
- if self.pod_template is None:
272
- return {}
273
- return {_PRIMARY_CONTAINER_NAME_FIELD: self.primary_container_name}
flyte/io/__init__.py CHANGED
@@ -3,9 +3,25 @@
3
3
 
4
4
  This package contains additional data types beyond the primitive data types in python to abstract data flow
5
5
  of large datasets in Union.
6
+
6
7
  """
7
8
 
8
- __all__ = ["Dir", "File"]
9
+ __all__ = [
10
+ "Dir",
11
+ "File",
12
+ "StructuredDataset",
13
+ "StructuredDatasetDecoder",
14
+ "StructuredDatasetEncoder",
15
+ "StructuredDatasetTransformerEngine",
16
+ "lazy_import_structured_dataset_handler",
17
+ ]
9
18
 
10
19
  from ._dir import Dir
11
20
  from ._file import File
21
+ from ._structured_dataset import (
22
+ StructuredDataset,
23
+ StructuredDatasetDecoder,
24
+ StructuredDatasetEncoder,
25
+ StructuredDatasetTransformerEngine,
26
+ lazy_import_structured_dataset_handler,
27
+ )
flyte/io/_dir.py CHANGED
@@ -244,7 +244,7 @@ class Dir(BaseModel, Generic[T], SerializableType):
244
244
 
245
245
  shutil.copytree(self.path, local_dest, dirs_exist_ok=True)
246
246
 
247
- # Figure this out when we figure out the final synchronicity story
247
+ # Figure this out when we figure out the final sync story
248
248
  raise NotImplementedError("Sync download is not implemented for remote paths")
249
249
 
250
250
  @classmethod
@@ -287,7 +287,7 @@ class Dir(BaseModel, Generic[T], SerializableType):
287
287
  remote_dir = Dir[DataFrame].from_local_sync('/tmp/data_dir/', 's3://bucket/data/')
288
288
  ```
289
289
  """
290
- # Implement this after we figure out the final synchronicity story
290
+ # Implement this after we figure out the final sync story
291
291
  raise NotImplementedError("Sync upload is not implemented for remote paths")
292
292
 
293
293
  async def exists(self) -> bool:
flyte/io/_file.py CHANGED
@@ -22,7 +22,6 @@ from fsspec.asyn import AsyncFileSystem
22
22
  from fsspec.utils import get_protocol
23
23
  from mashumaro.types import SerializableType
24
24
  from pydantic import BaseModel, model_validator
25
- from synchronicity import Synchronizer
26
25
 
27
26
  import flyte.storage as storage
28
27
  from flyte._context import internal_ctx
@@ -33,8 +32,6 @@ from flyte.types import TypeEngine, TypeTransformer, TypeTransformerFailedError
33
32
  # Type variable for the file format
34
33
  T = TypeVar("T")
35
34
 
36
- synced = Synchronizer()
37
-
38
35
 
39
36
  class File(BaseModel, Generic[T], SerializableType):
40
37
  """
@@ -235,6 +232,8 @@ class File(BaseModel, Generic[T], SerializableType):
235
232
  # This code is broadly similar to what storage.get_stream does, but without actually reading from the stream
236
233
  file_handle = None
237
234
  try:
235
+ if "b" not in mode:
236
+ raise ValueError("Mode must include 'b' for binary access, when using remote files.")
238
237
  if isinstance(fs, AsyncFileSystem):
239
238
  file_handle = await fs.open_async(self.path, mode)
240
239
  yield file_handle
@@ -314,7 +313,7 @@ class File(BaseModel, Generic[T], SerializableType):
314
313
  with fs.open(self.path, **open_kwargs) as f:
315
314
  yield f
316
315
 
317
- # @synced.wrap - enabling this did not work - synchronicity/pydantic issue
316
+ # TODO sync needs to be implemented
318
317
  async def download(self, local_path: Optional[Union[str, Path]] = None) -> str:
319
318
  """
320
319
  Asynchronously download the file to a local path.
@@ -9,7 +9,7 @@ from fsspec.core import split_protocol, strip_protocol
9
9
  import flyte.storage as storage
10
10
  from flyte._logging import logger
11
11
  from flyte._utils import lazy_module
12
- from flyte.io.structured_dataset.structured_dataset import (
12
+ from flyte.io._structured_dataset.structured_dataset import (
13
13
  CSV,
14
14
  PARQUET,
15
15
  StructuredDataset,
@@ -168,7 +168,7 @@ class StructuredDataset(SerializableType, DataClassJSONMixin):
168
168
  return self._literal_sd
169
169
 
170
170
  def open(self, dataframe_type: Type[DF]):
171
- from flyte.io.structured_dataset import lazy_import_structured_dataset_handler
171
+ from flyte.io._structured_dataset import lazy_import_structured_dataset_handler
172
172
 
173
173
  """
174
174
  Load the handler if needed. For the use case like:
@@ -5,7 +5,9 @@ import os
5
5
  import pathlib
6
6
  import tempfile
7
7
  from dataclasses import dataclass, field, replace
8
- from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Tuple, Type
8
+ from typing import TYPE_CHECKING, Any, Callable, ClassVar, Dict, Literal, Optional, Tuple, Type
9
+
10
+ import rich.repr
9
11
 
10
12
  from flyte._docstring import Docstring
11
13
  from flyte._interface import extract_return_annotation
@@ -13,6 +15,8 @@ from flyte._logging import logger
13
15
  from flyte._utils.helpers import base36_encode
14
16
 
15
17
  if TYPE_CHECKING:
18
+ from flyteidl.core import literals_pb2
19
+
16
20
  from flyte._internal.imagebuild.image_builder import ImageCache
17
21
  from flyte.report import Report
18
22
 
@@ -27,6 +31,7 @@ def generate_random_name() -> str:
27
31
  return str(uuid4()) # Placeholder for actual random name generation logic
28
32
 
29
33
 
34
+ @rich.repr.auto
30
35
  @dataclass(frozen=True, kw_only=True)
31
36
  class ActionID:
32
37
  """
@@ -56,17 +61,19 @@ class ActionID:
56
61
  name = generate_random_name()
57
62
  return replace(self, name=name)
58
63
 
59
- def new_sub_action_from(self, task_name: str, input_hash: str, group: str | None) -> ActionID:
64
+ def new_sub_action_from(self, task_call_seq: int, task_hash: str, input_hash: str, group: str | None) -> ActionID:
60
65
  """Make a deterministic name"""
61
66
  import hashlib
62
67
 
63
- components = f"{self.run_name}-{self.name}-{input_hash}-{task_name}" + (f"-{group}" if group else "")
68
+ components = f"{self.name}-{input_hash}-{task_hash}-{task_call_seq}" + (f"-{group}" if group else "")
69
+ logger.debug(f"----- Generating sub-action ID from components: {components}")
64
70
  # has the components into something deterministic
65
71
  bytes_digest = hashlib.md5(components.encode()).digest()
66
72
  new_name = base36_encode(bytes_digest)
67
73
  return self.new_sub_action(new_name)
68
74
 
69
75
 
76
+ @rich.repr.auto
70
77
  @dataclass(frozen=True, kw_only=True)
71
78
  class RawDataPath:
72
79
  """
@@ -90,6 +97,7 @@ class RawDataPath:
90
97
  # Create a temporary directory for data storage
91
98
  p = tempfile.mkdtemp()
92
99
  logger.debug(f"Creating temporary directory for data storage: {p}")
100
+ pathlib.Path(p).mkdir(parents=True, exist_ok=True)
93
101
  return RawDataPath(path=p)
94
102
  case str():
95
103
  return RawDataPath(path=local_folder)
@@ -132,11 +140,13 @@ class RawDataPath:
132
140
  return remote_path
133
141
 
134
142
 
143
+ @rich.repr.auto
135
144
  @dataclass(frozen=True)
136
145
  class GroupData:
137
146
  name: str
138
147
 
139
148
 
149
+ @rich.repr.auto
140
150
  @dataclass(frozen=True, kw_only=True)
141
151
  class TaskContext:
142
152
  """
@@ -159,6 +169,7 @@ class TaskContext:
159
169
  code_bundle: CodeBundle | None = None
160
170
  compiled_image_cache: ImageCache | None = None
161
171
  data: Dict[str, Any] = field(default_factory=dict)
172
+ mode: Literal["local", "remote", "hybrid"] = "remote"
162
173
 
163
174
  def replace(self, **kwargs) -> TaskContext:
164
175
  if "data" in kwargs:
@@ -176,6 +187,7 @@ class TaskContext:
176
187
  return self.data.get(key)
177
188
 
178
189
 
190
+ @rich.repr.auto
179
191
  @dataclass(frozen=True, kw_only=True)
180
192
  class CodeBundle:
181
193
  """
@@ -210,6 +222,7 @@ class CodeBundle:
210
222
  return replace(self, downloaded_path=path)
211
223
 
212
224
 
225
+ @rich.repr.auto
213
226
  @dataclass(frozen=True)
214
227
  class Checkpoints:
215
228
  """
@@ -220,6 +233,13 @@ class Checkpoints:
220
233
  checkpoint_path: str | None
221
234
 
222
235
 
236
+ class _has_default:
237
+ """
238
+ A marker class to indicate that a specific input has a default value or not.
239
+ This is used to determine if the input is required or not.
240
+ """
241
+
242
+
223
243
  @dataclass(frozen=True)
224
244
  class NativeInterface:
225
245
  """
@@ -229,7 +249,14 @@ class NativeInterface:
229
249
 
230
250
  inputs: Dict[str, Tuple[Type, Any]]
231
251
  outputs: Dict[str, Type]
232
- docstring: Optional[Docstring] = field(default=None)
252
+ docstring: Optional[Docstring] = None
253
+
254
+ # This field is used to indicate that the task has a default value for the input, but already in the
255
+ # remote form.
256
+ _remote_defaults: Optional[Dict[str, literals_pb2.Literal]] = field(default=None, repr=False)
257
+
258
+ has_default: ClassVar[Type[_has_default]] = _has_default # This can be used to indicate if a specific input
259
+ # has a default value or not, in the case when the default value is not known. An example would be remote tasks.
233
260
 
234
261
  def has_outputs(self) -> bool:
235
262
  """
@@ -237,12 +264,31 @@ class NativeInterface:
237
264
  """
238
265
  return self.outputs is not None and len(self.outputs) > 0
239
266
 
267
+ def num_required_inputs(self) -> int:
268
+ """
269
+ Get the number of required inputs for the task. This is used to determine how many inputs are required for the
270
+ task execution.
271
+ """
272
+ return sum(1 for t in self.inputs.values() if t[1] is inspect.Parameter.empty)
273
+
240
274
  @classmethod
241
- def from_types(cls, inputs: Dict[str, Type], outputs: Dict[str, Type]) -> NativeInterface:
275
+ def from_types(
276
+ cls,
277
+ inputs: Dict[str, Tuple[Type, Type[_has_default] | Type[inspect._empty]]],
278
+ outputs: Dict[str, Type],
279
+ default_inputs: Optional[Dict[str, literals_pb2.Literal]] = None,
280
+ ) -> NativeInterface:
242
281
  """
243
282
  Create a new NativeInterface from the given types. This is used to create a native interface for the task.
283
+ :param inputs: A dictionary of input names and their types and a value indicating if they have a default value.
284
+ :param outputs: A dictionary of output names and their types.
285
+ :param default_inputs: Optional dictionary of default inputs for remote tasks.
286
+ :return: A NativeInterface object with the given inputs and outputs.
244
287
  """
245
- return cls(inputs={k: (v, inspect.Parameter.empty) for k, v in inputs.items()}, outputs=outputs)
288
+ for k, v in inputs.items():
289
+ if v[1] is cls.has_default and (default_inputs is None or k not in default_inputs):
290
+ raise ValueError(f"Input {k} has a default value but no default input provided for remote task.")
291
+ return cls(inputs=inputs, outputs=outputs, _remote_defaults=default_inputs)
246
292
 
247
293
  @classmethod
248
294
  def from_callable(cls, func: Callable) -> NativeInterface:
@@ -290,7 +336,10 @@ class NativeInterface:
290
336
  tp = tpe[0] if isinstance(tpe[0], str) else tpe[0].__name__
291
337
  i += f"{key}: {tp}"
292
338
  if tpe[1] is not inspect.Parameter.empty:
293
- i += f" = {tpe[1]}"
339
+ if tpe[1] is self.has_default:
340
+ i += " = ..."
341
+ else:
342
+ i += f" = {tpe[1]}"
294
343
  i += ")"
295
344
  if self.outputs:
296
345
  initial = True
flyte/remote/__init__.py CHANGED
@@ -11,6 +11,7 @@ __all__ = [
11
11
  "Run",
12
12
  "RunDetails",
13
13
  "Secret",
14
+ "SecretTypes",
14
15
  "Task",
15
16
  "create_channel",
16
17
  "upload_dir",
@@ -21,5 +22,5 @@ from ._client.auth import create_channel
21
22
  from ._data import upload_dir, upload_file
22
23
  from ._project import Project
23
24
  from ._run import Action, ActionDetails, ActionInputs, ActionOutputs, Run, RunDetails
24
- from ._secret import Secret
25
+ from ._secret import Secret, SecretTypes
25
26
  from ._task import Task
@@ -58,6 +58,8 @@ class TaskService(Protocol):
58
58
  self, request: task_service_pb2.GetTaskDetailsRequest
59
59
  ) -> task_service_pb2.GetTaskDetailsResponse: ...
60
60
 
61
+ async def ListTasks(self, request: task_service_pb2.ListTasksRequest) -> task_service_pb2.ListTasksResponse: ...
62
+
61
63
 
62
64
  class RunService(Protocol):
63
65
  async def CreateRun(self, request: run_service_pb2.CreateRunRequest) -> run_service_pb2.CreateRunResponse: ...
@@ -0,0 +1,14 @@
1
+ from __future__ import annotations
2
+
3
+ import base64
4
+ from typing import Literal
5
+
6
+
7
+ def decode_api_key(encoded_str: str) -> tuple[str, str, str, str | Literal["None"]]:
8
+ """Decode encoded base64 string into app credentials. endpoint, client_id, client_secret, org"""
9
+ endpoint, client_id, client_secret, org = base64.b64decode(encoded_str.encode("utf-8")).decode("utf-8").split(":")
10
+ # For consistency, let's make sure org is always a non-empty string
11
+ if not org:
12
+ org = "None"
13
+
14
+ return endpoint, client_id, client_secret, org
@@ -1,3 +1,4 @@
1
+ import os
1
2
  import ssl
2
3
  import typing
3
4
 
@@ -15,6 +16,11 @@ from ._authenticators.factory import (
15
16
  get_async_proxy_authenticator,
16
17
  )
17
18
 
19
+ # Set environment variables for gRPC, this reduces log spew and avoids unnecessary warnings
20
+ if "GRPC_VERBOSITY" not in os.environ:
21
+ os.environ["GRPC_VERBOSITY"] = "ERROR"
22
+ os.environ["GRPC_CPP_MIN_LOG_LEVEL"] = "ERROR"
23
+
18
24
  # Initialize gRPC AIO early enough so it can be used in the main thread
19
25
  init_grpc_aio()
20
26
 
@@ -44,7 +50,9 @@ def bootstrap_ssl_from_server(endpoint: str) -> grpc.ChannelCredentials:
44
50
 
45
51
 
46
52
  async def create_channel(
47
- endpoint: str,
53
+ endpoint: str | None,
54
+ api_key: str | None = None,
55
+ /,
48
56
  insecure: typing.Optional[bool] = None,
49
57
  insecure_skip_verify: typing.Optional[bool] = False,
50
58
  ca_cert_file_path: typing.Optional[str] = None,
@@ -66,6 +74,7 @@ async def create_channel(
66
74
  and create authentication interceptors that perform async operations.
67
75
 
68
76
  :param endpoint: The endpoint URL for the gRPC channel
77
+ :param api_key: API key for authentication; if provided, it will be used to detect the endpoint and credentials.
69
78
  :param insecure: Whether to use an insecure channel (no SSL)
70
79
  :param insecure_skip_verify: Whether to skip SSL certificate verification
71
80
  :param ca_cert_file_path: Path to CA certificate file for SSL verification
@@ -104,6 +113,18 @@ async def create_channel(
104
113
  - refresh_access_token_params: Parameters to add when refreshing access token
105
114
  :return: grpc.aio.Channel with authentication interceptors configured
106
115
  """
116
+ assert endpoint or api_key, "Either endpoint or api_key must be specified"
117
+
118
+ if api_key:
119
+ from flyte.remote._client.auth._auth_utils import decode_api_key
120
+
121
+ endpoint, client_id, client_secret, org = decode_api_key(api_key)
122
+ kwargs["auth_type"] = "ClientSecret"
123
+ kwargs["client_id"] = client_id
124
+ kwargs["client_secret"] = client_secret
125
+ kwargs["client_credentials_secret"] = client_secret
126
+
127
+ assert endpoint, "Endpoint must be specified by this point"
107
128
 
108
129
  if not ssl_credentials:
109
130
  if insecure_skip_verify:
@@ -119,7 +140,12 @@ async def create_channel(
119
140
 
120
141
  # Create an unauthenticated channel first to use to get the server metadata
121
142
  if insecure:
122
- unauthenticated_channel = grpc.aio.insecure_channel(endpoint, **kwargs)
143
+ insecure_kwargs = {}
144
+ if kw_opts := kwargs.get("options"):
145
+ insecure_kwargs["options"] = kw_opts
146
+ if compression:
147
+ insecure_kwargs["compression"] = compression
148
+ unauthenticated_channel = grpc.aio.insecure_channel(endpoint, **insecure_kwargs)
123
149
  else:
124
150
  unauthenticated_channel = grpc.aio.secure_channel(
125
151
  target=endpoint,
@@ -173,7 +199,12 @@ async def create_channel(
173
199
  interceptors.extend(auth_interceptors)
174
200
 
175
201
  if insecure:
176
- return grpc.aio.insecure_channel(endpoint, interceptors=interceptors, **kwargs)
202
+ insecure_kwargs = {}
203
+ if kw_opts := kwargs.get("options"):
204
+ insecure_kwargs["options"] = kw_opts
205
+ if compression:
206
+ insecure_kwargs["compression"] = compression
207
+ return grpc.aio.insecure_channel(endpoint, interceptors=interceptors, **insecure_kwargs)
177
208
 
178
209
  return grpc.aio.secure_channel(
179
210
  target=endpoint,
@@ -94,7 +94,7 @@ async def get_token(
94
94
  http_proxy_url: typing.Optional[str] = None,
95
95
  verify: typing.Optional[typing.Union[bool, str]] = None,
96
96
  refresh_token: typing.Optional[str] = None,
97
- ) -> typing.Tuple[str, str, int]:
97
+ ) -> typing.Tuple[str, str | None, int]:
98
98
  """
99
99
  Retrieves an access token from the specified token endpoint.
100
100
 
@@ -165,7 +165,7 @@ async def get_token(
165
165
  if "refresh_token" in j:
166
166
  new_refresh_token = j["refresh_token"]
167
167
  else:
168
- raise AuthenticationError("Token not yet available, try again in some time")
168
+ logger.info("No refresh token received, this is expected for client credentials flow")
169
169
 
170
170
  return j["access_token"], new_refresh_token, j["expires_in"]
171
171
 
@@ -213,7 +213,7 @@ async def poll_token_endpoint(
213
213
  scopes: typing.Optional[typing.List[str]] = None,
214
214
  http_proxy_url: typing.Optional[str] = None,
215
215
  verify: typing.Optional[typing.Union[bool, str]] = None,
216
- ) -> typing.Tuple[str, str, int]:
216
+ ) -> typing.Tuple[str, str | None, int]:
217
217
  """
218
218
  Polls the token endpoint until authentication is complete or times out.
219
219
 
@@ -39,21 +39,21 @@ class ClientSet:
39
39
 
40
40
  @classmethod
41
41
  async def for_endpoint(cls, endpoint: str, *, insecure: bool = False, **kwargs) -> ClientSet:
42
- if insecure:
43
- del kwargs["api_key"]
44
- del kwargs["auth_type"]
45
- del kwargs["headless"]
46
- del kwargs["command"]
47
- del kwargs["client_id"]
48
- del kwargs["client_credentials_secret"]
49
- del kwargs["client_config"]
50
- del kwargs["rpc_retries"]
51
- del kwargs["http_proxy_url"]
52
- return cls(await create_channel(endpoint, insecure=insecure, **kwargs), endpoint, insecure=insecure, **kwargs)
42
+ return cls(
43
+ await create_channel(endpoint, None, insecure=insecure, **kwargs), endpoint, insecure=insecure, **kwargs
44
+ )
53
45
 
54
46
  @classmethod
55
- async def for_api_key(cls, api_key: str, **kwargs) -> ClientSet:
56
- raise NotImplementedError
47
+ async def for_api_key(cls, api_key: str, *, insecure: bool = False, **kwargs) -> ClientSet:
48
+ from flyte.remote._client.auth._auth_utils import decode_api_key
49
+
50
+ # Parsing the API key is done in create_channel, but cleaner to redo it here rather than getting create_channel
51
+ # to return the endpoint
52
+ endpoint, _, _, _ = decode_api_key(api_key)
53
+
54
+ return cls(
55
+ await create_channel(None, api_key, insecure=insecure, **kwargs), endpoint, insecure=insecure, **kwargs
56
+ )
57
57
 
58
58
  @classmethod
59
59
  async def for_serverless(cls) -> ClientSet:
flyte/remote/_console.py CHANGED
@@ -7,7 +7,7 @@ def _get_http_domain(endpoint: str, insecure: bool) -> str:
7
7
  if parsed.scheme == "dns":
8
8
  domain = parsed.path.lstrip("/")
9
9
  else:
10
- domain = parsed.netloc
10
+ domain = parsed.netloc or parsed.path
11
11
  # TODO: make console url configurable
12
12
  if domain.split(":")[0] == "localhost":
13
13
  domain = "localhost:8080"
flyte/remote/_data.py CHANGED
@@ -15,8 +15,9 @@ import httpx
15
15
  from flyteidl.service import dataproxy_pb2
16
16
  from google.protobuf import duration_pb2
17
17
 
18
- from flyte._initialize import CommonInit, get_client, get_common_config, requires_client
19
- from flyte.errors import RuntimeSystemError
18
+ from flyte._initialize import CommonInit, ensure_client, get_client, get_common_config
19
+ from flyte._logging import make_hyperlink
20
+ from flyte.errors import InitializationError, RuntimeSystemError
20
21
 
21
22
  _UPLOAD_EXPIRES_IN = timedelta(seconds=60)
22
23
 
@@ -83,11 +84,13 @@ async def _upload_single_file(
83
84
  raise RuntimeSystemError(
84
85
  "PermissionDenied", f"Failed to get signed url for {fp}, please check your permissions."
85
86
  )
87
+ elif e.code() == grpc.StatusCode.UNAVAILABLE:
88
+ raise InitializationError("EndpointUnavailable", "user", "Service is unavailable.")
86
89
  else:
87
90
  raise RuntimeSystemError(e.code().value, f"Failed to get signed url for {fp}.")
88
91
  except Exception as e:
89
92
  raise RuntimeSystemError(type(e).__name__, f"Failed to get signed url for {fp}.") from e
90
- logger.debug(f"Uploading to signed url {resp.signed_url} for {fp}")
93
+ logger.debug(f'Uploading to {make_hyperlink("signed url", resp.signed_url)} for {fp}')
91
94
  extra_headers = get_extra_headers_for_protocol(resp.native_url)
92
95
  extra_headers.update(resp.headers)
93
96
  encoded_md5 = b64encode(md5_bytes)
@@ -100,7 +103,8 @@ async def _upload_single_file(
100
103
  if put_resp.status_code != 200:
101
104
  raise RuntimeSystemError(
102
105
  "UploadFailed",
103
- f"Failed to upload {fp} to {resp.signed_url}, status code: {put_resp.status_code}",
106
+ f"Failed to upload {fp} to {resp.signed_url}, status code: {put_resp.status_code}, "
107
+ f"response: {put_resp.text}",
104
108
  )
105
109
  # TODO in old code we did this
106
110
  # if self._config.platform.insecure_skip_verify is True
@@ -109,7 +113,6 @@ async def _upload_single_file(
109
113
  return str_digest, resp.native_url
110
114
 
111
115
 
112
- @requires_client
113
116
  async def upload_file(fp: Path, verify: bool = True) -> Tuple[str, str]:
114
117
  """
115
118
  Uploads a file to a remote location and returns the remote URI.
@@ -119,13 +122,13 @@ async def upload_file(fp: Path, verify: bool = True) -> Tuple[str, str]:
119
122
  :return: A tuple containing the MD5 digest and the remote URI.
120
123
  """
121
124
  # This is a placeholder implementation. Replace with actual upload logic.
125
+ ensure_client()
122
126
  cfg = get_common_config()
123
127
  if not fp.is_file():
124
128
  raise ValueError(f"{fp} is not a single file, upload arg must be a single file.")
125
129
  return await _upload_single_file(cfg, fp, verify=verify)
126
130
 
127
131
 
128
- @requires_client
129
132
  async def upload_dir(dir_path: Path, verify: bool = True) -> str:
130
133
  """
131
134
  Uploads a directory to a remote location and returns the remote URI.
@@ -135,6 +138,7 @@ async def upload_dir(dir_path: Path, verify: bool = True) -> str:
135
138
  :return: The remote URI of the uploaded directory.
136
139
  """
137
140
  # This is a placeholder implementation. Replace with actual upload logic.
141
+ ensure_client()
138
142
  cfg = get_common_config()
139
143
  if not dir_path.is_dir():
140
144
  raise ValueError(f"{dir_path} is not a directory, upload arg must be a directory.")