ob-metaflow 2.11.13.1__py2.py3-none-any.whl → 2.19.7.1rc0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (289) hide show
  1. metaflow/R.py +10 -7
  2. metaflow/__init__.py +40 -25
  3. metaflow/_vendor/imghdr/__init__.py +186 -0
  4. metaflow/_vendor/importlib_metadata/__init__.py +1063 -0
  5. metaflow/_vendor/importlib_metadata/_adapters.py +68 -0
  6. metaflow/_vendor/importlib_metadata/_collections.py +30 -0
  7. metaflow/_vendor/importlib_metadata/_compat.py +71 -0
  8. metaflow/_vendor/importlib_metadata/_functools.py +104 -0
  9. metaflow/_vendor/importlib_metadata/_itertools.py +73 -0
  10. metaflow/_vendor/importlib_metadata/_meta.py +48 -0
  11. metaflow/_vendor/importlib_metadata/_text.py +99 -0
  12. metaflow/_vendor/importlib_metadata/py.typed +0 -0
  13. metaflow/_vendor/typeguard/__init__.py +48 -0
  14. metaflow/_vendor/typeguard/_checkers.py +1070 -0
  15. metaflow/_vendor/typeguard/_config.py +108 -0
  16. metaflow/_vendor/typeguard/_decorators.py +233 -0
  17. metaflow/_vendor/typeguard/_exceptions.py +42 -0
  18. metaflow/_vendor/typeguard/_functions.py +308 -0
  19. metaflow/_vendor/typeguard/_importhook.py +213 -0
  20. metaflow/_vendor/typeguard/_memo.py +48 -0
  21. metaflow/_vendor/typeguard/_pytest_plugin.py +127 -0
  22. metaflow/_vendor/typeguard/_suppression.py +86 -0
  23. metaflow/_vendor/typeguard/_transformer.py +1229 -0
  24. metaflow/_vendor/typeguard/_union_transformer.py +55 -0
  25. metaflow/_vendor/typeguard/_utils.py +173 -0
  26. metaflow/_vendor/typeguard/py.typed +0 -0
  27. metaflow/_vendor/typing_extensions.py +3641 -0
  28. metaflow/_vendor/v3_7/importlib_metadata/__init__.py +1063 -0
  29. metaflow/_vendor/v3_7/importlib_metadata/_adapters.py +68 -0
  30. metaflow/_vendor/v3_7/importlib_metadata/_collections.py +30 -0
  31. metaflow/_vendor/v3_7/importlib_metadata/_compat.py +71 -0
  32. metaflow/_vendor/v3_7/importlib_metadata/_functools.py +104 -0
  33. metaflow/_vendor/v3_7/importlib_metadata/_itertools.py +73 -0
  34. metaflow/_vendor/v3_7/importlib_metadata/_meta.py +48 -0
  35. metaflow/_vendor/v3_7/importlib_metadata/_text.py +99 -0
  36. metaflow/_vendor/v3_7/importlib_metadata/py.typed +0 -0
  37. metaflow/_vendor/v3_7/typeguard/__init__.py +48 -0
  38. metaflow/_vendor/v3_7/typeguard/_checkers.py +906 -0
  39. metaflow/_vendor/v3_7/typeguard/_config.py +108 -0
  40. metaflow/_vendor/v3_7/typeguard/_decorators.py +237 -0
  41. metaflow/_vendor/v3_7/typeguard/_exceptions.py +42 -0
  42. metaflow/_vendor/v3_7/typeguard/_functions.py +310 -0
  43. metaflow/_vendor/v3_7/typeguard/_importhook.py +213 -0
  44. metaflow/_vendor/v3_7/typeguard/_memo.py +48 -0
  45. metaflow/_vendor/v3_7/typeguard/_pytest_plugin.py +100 -0
  46. metaflow/_vendor/v3_7/typeguard/_suppression.py +88 -0
  47. metaflow/_vendor/v3_7/typeguard/_transformer.py +1207 -0
  48. metaflow/_vendor/v3_7/typeguard/_union_transformer.py +54 -0
  49. metaflow/_vendor/v3_7/typeguard/_utils.py +169 -0
  50. metaflow/_vendor/v3_7/typeguard/py.typed +0 -0
  51. metaflow/_vendor/v3_7/typing_extensions.py +3072 -0
  52. metaflow/_vendor/yaml/__init__.py +427 -0
  53. metaflow/_vendor/yaml/composer.py +139 -0
  54. metaflow/_vendor/yaml/constructor.py +748 -0
  55. metaflow/_vendor/yaml/cyaml.py +101 -0
  56. metaflow/_vendor/yaml/dumper.py +62 -0
  57. metaflow/_vendor/yaml/emitter.py +1137 -0
  58. metaflow/_vendor/yaml/error.py +75 -0
  59. metaflow/_vendor/yaml/events.py +86 -0
  60. metaflow/_vendor/yaml/loader.py +63 -0
  61. metaflow/_vendor/yaml/nodes.py +49 -0
  62. metaflow/_vendor/yaml/parser.py +589 -0
  63. metaflow/_vendor/yaml/reader.py +185 -0
  64. metaflow/_vendor/yaml/representer.py +389 -0
  65. metaflow/_vendor/yaml/resolver.py +227 -0
  66. metaflow/_vendor/yaml/scanner.py +1435 -0
  67. metaflow/_vendor/yaml/serializer.py +111 -0
  68. metaflow/_vendor/yaml/tokens.py +104 -0
  69. metaflow/cards.py +5 -0
  70. metaflow/cli.py +331 -785
  71. metaflow/cli_args.py +17 -0
  72. metaflow/cli_components/__init__.py +0 -0
  73. metaflow/cli_components/dump_cmd.py +96 -0
  74. metaflow/cli_components/init_cmd.py +52 -0
  75. metaflow/cli_components/run_cmds.py +546 -0
  76. metaflow/cli_components/step_cmd.py +334 -0
  77. metaflow/cli_components/utils.py +140 -0
  78. metaflow/client/__init__.py +1 -0
  79. metaflow/client/core.py +467 -73
  80. metaflow/client/filecache.py +75 -35
  81. metaflow/clone_util.py +7 -1
  82. metaflow/cmd/code/__init__.py +231 -0
  83. metaflow/cmd/develop/stub_generator.py +756 -288
  84. metaflow/cmd/develop/stubs.py +12 -28
  85. metaflow/cmd/main_cli.py +6 -4
  86. metaflow/cmd/make_wrapper.py +78 -0
  87. metaflow/datastore/__init__.py +1 -0
  88. metaflow/datastore/content_addressed_store.py +41 -10
  89. metaflow/datastore/datastore_set.py +11 -2
  90. metaflow/datastore/flow_datastore.py +156 -10
  91. metaflow/datastore/spin_datastore.py +91 -0
  92. metaflow/datastore/task_datastore.py +154 -39
  93. metaflow/debug.py +5 -0
  94. metaflow/decorators.py +404 -78
  95. metaflow/exception.py +8 -2
  96. metaflow/extension_support/__init__.py +527 -376
  97. metaflow/extension_support/_empty_file.py +2 -2
  98. metaflow/extension_support/plugins.py +49 -31
  99. metaflow/flowspec.py +482 -33
  100. metaflow/graph.py +210 -42
  101. metaflow/includefile.py +84 -40
  102. metaflow/lint.py +141 -22
  103. metaflow/meta_files.py +13 -0
  104. metaflow/{metadata → metadata_provider}/heartbeat.py +24 -8
  105. metaflow/{metadata → metadata_provider}/metadata.py +86 -1
  106. metaflow/metaflow_config.py +175 -28
  107. metaflow/metaflow_config_funcs.py +51 -3
  108. metaflow/metaflow_current.py +4 -10
  109. metaflow/metaflow_environment.py +139 -53
  110. metaflow/metaflow_git.py +115 -0
  111. metaflow/metaflow_profile.py +18 -0
  112. metaflow/metaflow_version.py +150 -66
  113. metaflow/mflog/__init__.py +4 -3
  114. metaflow/mflog/save_logs.py +2 -2
  115. metaflow/multicore_utils.py +31 -14
  116. metaflow/package/__init__.py +673 -0
  117. metaflow/packaging_sys/__init__.py +880 -0
  118. metaflow/packaging_sys/backend.py +128 -0
  119. metaflow/packaging_sys/distribution_support.py +153 -0
  120. metaflow/packaging_sys/tar_backend.py +99 -0
  121. metaflow/packaging_sys/utils.py +54 -0
  122. metaflow/packaging_sys/v1.py +527 -0
  123. metaflow/parameters.py +149 -28
  124. metaflow/plugins/__init__.py +74 -5
  125. metaflow/plugins/airflow/airflow.py +40 -25
  126. metaflow/plugins/airflow/airflow_cli.py +22 -5
  127. metaflow/plugins/airflow/airflow_decorator.py +1 -1
  128. metaflow/plugins/airflow/airflow_utils.py +5 -3
  129. metaflow/plugins/airflow/sensors/base_sensor.py +4 -4
  130. metaflow/plugins/airflow/sensors/external_task_sensor.py +2 -2
  131. metaflow/plugins/airflow/sensors/s3_sensor.py +2 -2
  132. metaflow/plugins/argo/argo_client.py +78 -33
  133. metaflow/plugins/argo/argo_events.py +6 -6
  134. metaflow/plugins/argo/argo_workflows.py +2410 -527
  135. metaflow/plugins/argo/argo_workflows_cli.py +571 -121
  136. metaflow/plugins/argo/argo_workflows_decorator.py +43 -12
  137. metaflow/plugins/argo/argo_workflows_deployer.py +106 -0
  138. metaflow/plugins/argo/argo_workflows_deployer_objects.py +453 -0
  139. metaflow/plugins/argo/capture_error.py +73 -0
  140. metaflow/plugins/argo/conditional_input_paths.py +35 -0
  141. metaflow/plugins/argo/exit_hooks.py +209 -0
  142. metaflow/plugins/argo/jobset_input_paths.py +15 -0
  143. metaflow/plugins/argo/param_val.py +19 -0
  144. metaflow/plugins/aws/aws_client.py +10 -3
  145. metaflow/plugins/aws/aws_utils.py +55 -2
  146. metaflow/plugins/aws/batch/batch.py +72 -5
  147. metaflow/plugins/aws/batch/batch_cli.py +33 -10
  148. metaflow/plugins/aws/batch/batch_client.py +4 -3
  149. metaflow/plugins/aws/batch/batch_decorator.py +102 -35
  150. metaflow/plugins/aws/secrets_manager/aws_secrets_manager_secrets_provider.py +13 -10
  151. metaflow/plugins/aws/step_functions/dynamo_db_client.py +0 -3
  152. metaflow/plugins/aws/step_functions/production_token.py +1 -1
  153. metaflow/plugins/aws/step_functions/step_functions.py +65 -8
  154. metaflow/plugins/aws/step_functions/step_functions_cli.py +101 -7
  155. metaflow/plugins/aws/step_functions/step_functions_decorator.py +1 -2
  156. metaflow/plugins/aws/step_functions/step_functions_deployer.py +97 -0
  157. metaflow/plugins/aws/step_functions/step_functions_deployer_objects.py +264 -0
  158. metaflow/plugins/azure/azure_exceptions.py +1 -1
  159. metaflow/plugins/azure/azure_secret_manager_secrets_provider.py +240 -0
  160. metaflow/plugins/azure/azure_tail.py +1 -1
  161. metaflow/plugins/azure/includefile_support.py +2 -0
  162. metaflow/plugins/cards/card_cli.py +66 -30
  163. metaflow/plugins/cards/card_creator.py +25 -1
  164. metaflow/plugins/cards/card_datastore.py +21 -49
  165. metaflow/plugins/cards/card_decorator.py +132 -8
  166. metaflow/plugins/cards/card_modules/basic.py +112 -17
  167. metaflow/plugins/cards/card_modules/bundle.css +1 -1
  168. metaflow/plugins/cards/card_modules/card.py +16 -1
  169. metaflow/plugins/cards/card_modules/chevron/renderer.py +1 -1
  170. metaflow/plugins/cards/card_modules/components.py +665 -28
  171. metaflow/plugins/cards/card_modules/convert_to_native_type.py +36 -7
  172. metaflow/plugins/cards/card_modules/json_viewer.py +232 -0
  173. metaflow/plugins/cards/card_modules/main.css +1 -0
  174. metaflow/plugins/cards/card_modules/main.js +68 -49
  175. metaflow/plugins/cards/card_modules/renderer_tools.py +1 -0
  176. metaflow/plugins/cards/card_modules/test_cards.py +26 -12
  177. metaflow/plugins/cards/card_server.py +39 -14
  178. metaflow/plugins/cards/component_serializer.py +2 -9
  179. metaflow/plugins/cards/metadata.py +22 -0
  180. metaflow/plugins/catch_decorator.py +9 -0
  181. metaflow/plugins/datastores/azure_storage.py +10 -1
  182. metaflow/plugins/datastores/gs_storage.py +6 -2
  183. metaflow/plugins/datastores/local_storage.py +12 -6
  184. metaflow/plugins/datastores/spin_storage.py +12 -0
  185. metaflow/plugins/datatools/local.py +2 -0
  186. metaflow/plugins/datatools/s3/s3.py +126 -75
  187. metaflow/plugins/datatools/s3/s3op.py +254 -121
  188. metaflow/plugins/env_escape/__init__.py +3 -3
  189. metaflow/plugins/env_escape/client_modules.py +102 -72
  190. metaflow/plugins/env_escape/server.py +7 -0
  191. metaflow/plugins/env_escape/stub.py +24 -5
  192. metaflow/plugins/events_decorator.py +343 -185
  193. metaflow/plugins/exit_hook/__init__.py +0 -0
  194. metaflow/plugins/exit_hook/exit_hook_decorator.py +46 -0
  195. metaflow/plugins/exit_hook/exit_hook_script.py +52 -0
  196. metaflow/plugins/gcp/__init__.py +1 -1
  197. metaflow/plugins/gcp/gcp_secret_manager_secrets_provider.py +11 -6
  198. metaflow/plugins/gcp/gs_tail.py +10 -6
  199. metaflow/plugins/gcp/includefile_support.py +3 -0
  200. metaflow/plugins/kubernetes/kube_utils.py +108 -0
  201. metaflow/plugins/kubernetes/kubernetes.py +411 -130
  202. metaflow/plugins/kubernetes/kubernetes_cli.py +168 -36
  203. metaflow/plugins/kubernetes/kubernetes_client.py +104 -2
  204. metaflow/plugins/kubernetes/kubernetes_decorator.py +246 -88
  205. metaflow/plugins/kubernetes/kubernetes_job.py +253 -581
  206. metaflow/plugins/kubernetes/kubernetes_jobsets.py +1071 -0
  207. metaflow/plugins/kubernetes/spot_metadata_cli.py +69 -0
  208. metaflow/plugins/kubernetes/spot_monitor_sidecar.py +109 -0
  209. metaflow/plugins/logs_cli.py +359 -0
  210. metaflow/plugins/{metadata → metadata_providers}/local.py +144 -84
  211. metaflow/plugins/{metadata → metadata_providers}/service.py +103 -26
  212. metaflow/plugins/metadata_providers/spin.py +16 -0
  213. metaflow/plugins/package_cli.py +36 -24
  214. metaflow/plugins/parallel_decorator.py +128 -11
  215. metaflow/plugins/parsers.py +16 -0
  216. metaflow/plugins/project_decorator.py +51 -5
  217. metaflow/plugins/pypi/bootstrap.py +357 -105
  218. metaflow/plugins/pypi/conda_decorator.py +82 -81
  219. metaflow/plugins/pypi/conda_environment.py +187 -52
  220. metaflow/plugins/pypi/micromamba.py +157 -47
  221. metaflow/plugins/pypi/parsers.py +268 -0
  222. metaflow/plugins/pypi/pip.py +88 -13
  223. metaflow/plugins/pypi/pypi_decorator.py +37 -1
  224. metaflow/plugins/pypi/utils.py +48 -2
  225. metaflow/plugins/resources_decorator.py +2 -2
  226. metaflow/plugins/secrets/__init__.py +3 -0
  227. metaflow/plugins/secrets/secrets_decorator.py +26 -181
  228. metaflow/plugins/secrets/secrets_func.py +49 -0
  229. metaflow/plugins/secrets/secrets_spec.py +101 -0
  230. metaflow/plugins/secrets/utils.py +74 -0
  231. metaflow/plugins/tag_cli.py +4 -7
  232. metaflow/plugins/test_unbounded_foreach_decorator.py +41 -6
  233. metaflow/plugins/timeout_decorator.py +3 -3
  234. metaflow/plugins/uv/__init__.py +0 -0
  235. metaflow/plugins/uv/bootstrap.py +128 -0
  236. metaflow/plugins/uv/uv_environment.py +72 -0
  237. metaflow/procpoll.py +1 -1
  238. metaflow/pylint_wrapper.py +5 -1
  239. metaflow/runner/__init__.py +0 -0
  240. metaflow/runner/click_api.py +717 -0
  241. metaflow/runner/deployer.py +470 -0
  242. metaflow/runner/deployer_impl.py +201 -0
  243. metaflow/runner/metaflow_runner.py +714 -0
  244. metaflow/runner/nbdeploy.py +132 -0
  245. metaflow/runner/nbrun.py +225 -0
  246. metaflow/runner/subprocess_manager.py +650 -0
  247. metaflow/runner/utils.py +335 -0
  248. metaflow/runtime.py +1078 -260
  249. metaflow/sidecar/sidecar_worker.py +1 -1
  250. metaflow/system/__init__.py +5 -0
  251. metaflow/system/system_logger.py +85 -0
  252. metaflow/system/system_monitor.py +108 -0
  253. metaflow/system/system_utils.py +19 -0
  254. metaflow/task.py +521 -225
  255. metaflow/tracing/__init__.py +7 -7
  256. metaflow/tracing/span_exporter.py +31 -38
  257. metaflow/tracing/tracing_modules.py +38 -43
  258. metaflow/tuple_util.py +27 -0
  259. metaflow/user_configs/__init__.py +0 -0
  260. metaflow/user_configs/config_options.py +563 -0
  261. metaflow/user_configs/config_parameters.py +598 -0
  262. metaflow/user_decorators/__init__.py +0 -0
  263. metaflow/user_decorators/common.py +144 -0
  264. metaflow/user_decorators/mutable_flow.py +512 -0
  265. metaflow/user_decorators/mutable_step.py +424 -0
  266. metaflow/user_decorators/user_flow_decorator.py +264 -0
  267. metaflow/user_decorators/user_step_decorator.py +749 -0
  268. metaflow/util.py +243 -27
  269. metaflow/vendor.py +23 -7
  270. metaflow/version.py +1 -1
  271. ob_metaflow-2.19.7.1rc0.data/data/share/metaflow/devtools/Makefile +355 -0
  272. ob_metaflow-2.19.7.1rc0.data/data/share/metaflow/devtools/Tiltfile +726 -0
  273. ob_metaflow-2.19.7.1rc0.data/data/share/metaflow/devtools/pick_services.sh +105 -0
  274. ob_metaflow-2.19.7.1rc0.dist-info/METADATA +87 -0
  275. ob_metaflow-2.19.7.1rc0.dist-info/RECORD +445 -0
  276. {ob_metaflow-2.11.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/WHEEL +1 -1
  277. {ob_metaflow-2.11.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/entry_points.txt +1 -0
  278. metaflow/_vendor/v3_5/__init__.py +0 -1
  279. metaflow/_vendor/v3_5/importlib_metadata/__init__.py +0 -644
  280. metaflow/_vendor/v3_5/importlib_metadata/_compat.py +0 -152
  281. metaflow/package.py +0 -188
  282. ob_metaflow-2.11.13.1.dist-info/METADATA +0 -85
  283. ob_metaflow-2.11.13.1.dist-info/RECORD +0 -308
  284. /metaflow/_vendor/{v3_5/zipp.py → zipp.py} +0 -0
  285. /metaflow/{metadata → metadata_provider}/__init__.py +0 -0
  286. /metaflow/{metadata → metadata_provider}/util.py +0 -0
  287. /metaflow/plugins/{metadata → metadata_providers}/__init__.py +0 -0
  288. {ob_metaflow-2.11.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info/licenses}/LICENSE +0 -0
  289. {ob_metaflow-2.11.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,335 @@
1
+ import os
2
+ import ast
3
+ import time
4
+ import asyncio
5
+ import tempfile
6
+ import select
7
+ import fcntl
8
+ from contextlib import contextmanager
9
+ from subprocess import CalledProcessError
10
+ from typing import Any, Dict, TYPE_CHECKING, ContextManager, Tuple
11
+
12
+ if TYPE_CHECKING:
13
+ import tempfile
14
+ import metaflow.runner.subprocess_manager
15
+ import metaflow.runner.click_api
16
+
17
+
18
+ def get_current_cell(ipython):
19
+ if ipython:
20
+ return ipython.history_manager.input_hist_raw[-1]
21
+ return None
22
+
23
+
24
+ def format_flowfile(cell):
25
+ """
26
+ Formats the given cell content to create a valid Python script that can be
27
+ executed as a Metaflow flow.
28
+ """
29
+ flowspec = [
30
+ x
31
+ for x in ast.parse(cell).body
32
+ if isinstance(x, ast.ClassDef) and any(b.id == "FlowSpec" for b in x.bases)
33
+ ]
34
+
35
+ if not flowspec:
36
+ raise ModuleNotFoundError(
37
+ "The cell doesn't contain any class that inherits from 'FlowSpec'"
38
+ )
39
+
40
+ lines = cell.splitlines()[: flowspec[0].end_lineno]
41
+ lines += ["if __name__ == '__main__':", f" {flowspec[0].name}()"]
42
+ return "\n".join(lines)
43
+
44
+
45
+ def check_process_exited(
46
+ command_obj: "metaflow.runner.subprocess_manager.CommandManager",
47
+ ) -> bool:
48
+ if isinstance(command_obj.process, asyncio.subprocess.Process):
49
+ return command_obj.process.returncode is not None
50
+ else:
51
+ return command_obj.process.poll() is not None
52
+
53
+
54
+ @contextmanager
55
+ def temporary_fifo() -> ContextManager[Tuple[str, int]]:
56
+ """
57
+ Create and open the read side of a temporary FIFO in a non-blocking mode.
58
+
59
+ Returns
60
+ -------
61
+ str
62
+ Path to the temporary FIFO.
63
+ int
64
+ File descriptor of the temporary FIFO.
65
+ """
66
+ with tempfile.TemporaryDirectory() as temp_dir:
67
+ path = os.path.join(temp_dir, "fifo")
68
+ os.mkfifo(path)
69
+ # Blocks until the write side is opened unless in non-blocking mode
70
+ fd = os.open(path, os.O_RDONLY | os.O_NONBLOCK)
71
+ try:
72
+ yield path, fd
73
+ finally:
74
+ os.close(fd)
75
+
76
+
77
+ def read_from_fifo_when_ready(
78
+ fifo_fd: int,
79
+ command_obj: "metaflow.runner.subprocess_manager.CommandManager",
80
+ encoding: str = "utf-8",
81
+ timeout: int = 3600,
82
+ ) -> str:
83
+ """
84
+ Read the content from the FIFO file descriptor when it is ready.
85
+
86
+ Parameters
87
+ ----------
88
+ fifo_fd : int
89
+ File descriptor of the FIFO.
90
+ command_obj : CommandManager
91
+ Command manager object that handles the write side of the FIFO.
92
+ encoding : str, optional
93
+ Encoding to use while reading the file, by default "utf-8".
94
+ timeout : int, optional
95
+ Timeout for reading the file in seconds, by default 3600.
96
+
97
+ Returns
98
+ -------
99
+ str
100
+ Content read from the FIFO.
101
+
102
+ Raises
103
+ ------
104
+ TimeoutError
105
+ If no event occurs on the FIFO within the timeout.
106
+ CalledProcessError
107
+ If the process managed by `command_obj` has exited without writing any
108
+ content to the FIFO.
109
+ """
110
+ content = bytearray()
111
+ poll = select.poll()
112
+ poll.register(fifo_fd, select.POLLIN)
113
+ while True:
114
+ if check_process_exited(command_obj) and command_obj.process.returncode != 0:
115
+ raise CalledProcessError(
116
+ command_obj.process.returncode, command_obj.command
117
+ )
118
+
119
+ if timeout < 0:
120
+ raise TimeoutError("Timeout while waiting for the file content")
121
+
122
+ poll_begin = time.time()
123
+ # We poll for a very short time to be also able to check if the file was closed
124
+ # If the file is closed, we assume that we only have one writer so if we have
125
+ # data, we break out. This is to work around issues in macos
126
+ events = poll.poll(min(10, timeout * 1000))
127
+ timeout -= time.time() - poll_begin
128
+
129
+ try:
130
+ data = os.read(fifo_fd, 8192)
131
+ if data:
132
+ content += data
133
+ # We got data! Now switch to blocking mode for guaranteed complete reads.
134
+ # In blocking mode, read() won't return 0 until writer closes AND all
135
+ # kernel buffers are drained - this is POSIX guaranteed.
136
+ flags = fcntl.fcntl(fifo_fd, fcntl.F_GETFL)
137
+ fcntl.fcntl(fifo_fd, fcntl.F_SETFL, flags & ~os.O_NONBLOCK)
138
+
139
+ # Now do blocking reads until true EOF
140
+ while True:
141
+ chunk = os.read(fifo_fd, 8192)
142
+ if not chunk:
143
+ # True EOF - all data drained
144
+ break
145
+ content += chunk
146
+ # All data read, exit main loop
147
+ break
148
+ else:
149
+ if len(events):
150
+ # We read an EOF -- consider the file done
151
+ break
152
+ else:
153
+ # We had no events (just a timeout) and the read didn't return
154
+ # an exception so the file is still open; we continue waiting for data
155
+ pass
156
+ except BlockingIOError:
157
+ # File not ready yet, continue waiting
158
+ pass
159
+
160
+ if not content and check_process_exited(command_obj):
161
+ raise CalledProcessError(command_obj.process.returncode, command_obj.command)
162
+
163
+ return content.decode(encoding)
164
+
165
+
166
+ async def async_read_from_fifo_when_ready(
167
+ fifo_fd: int,
168
+ command_obj: "metaflow.runner.subprocess_manager.CommandManager",
169
+ encoding: str = "utf-8",
170
+ timeout: int = 3600,
171
+ ) -> str:
172
+ """
173
+ Read the content from the FIFO file descriptor when it is ready.
174
+
175
+ Parameters
176
+ ----------
177
+ fifo_fd : int
178
+ File descriptor of the FIFO.
179
+ command_obj : CommandManager
180
+ Command manager object that handles the write side of the FIFO.
181
+ encoding : str, optional
182
+ Encoding to use while reading the file, by default "utf-8".
183
+ timeout : int, optional
184
+ Timeout for reading the file in seconds, by default 3600.
185
+
186
+ Returns
187
+ -------
188
+ str
189
+ Content read from the FIFO.
190
+
191
+ Raises
192
+ ------
193
+ TimeoutError
194
+ If no event occurs on the FIFO within the timeout.
195
+ CalledProcessError
196
+ If the process managed by `command_obj` has exited without writing any
197
+ content to the FIFO.
198
+ """
199
+ return await asyncio.to_thread(
200
+ read_from_fifo_when_ready, fifo_fd, command_obj, encoding, timeout
201
+ )
202
+
203
+
204
+ def make_process_error_message(
205
+ command_obj: "metaflow.runner.subprocess_manager.CommandManager",
206
+ ):
207
+ stdout_log = open(command_obj.log_files["stdout"], encoding="utf-8").read()
208
+ stderr_log = open(command_obj.log_files["stderr"], encoding="utf-8").read()
209
+ command = " ".join(command_obj.command)
210
+ error_message = "Error executing: '%s':\n" % command
211
+ if stdout_log.strip():
212
+ error_message += "\nStdout:\n%s\n" % stdout_log
213
+ if stderr_log.strip():
214
+ error_message += "\nStderr:\n%s\n" % stderr_log
215
+ return error_message
216
+
217
+
218
+ def handle_timeout(
219
+ attribute_file_fd: int,
220
+ command_obj: "metaflow.runner.subprocess_manager.CommandManager",
221
+ file_read_timeout: int,
222
+ ):
223
+ """
224
+ Handle the timeout for a running subprocess command that reads a file
225
+ and raises an error with appropriate logs if a TimeoutError occurs.
226
+
227
+ Parameters
228
+ ----------
229
+ attribute_file_fd : int
230
+ File descriptor belonging to the FIFO containing the attribute data.
231
+ command_obj : CommandManager
232
+ Command manager object that encapsulates the running command details.
233
+ file_read_timeout : int
234
+ Timeout for reading the file, in seconds
235
+
236
+ Returns
237
+ -------
238
+ str
239
+ Content read from the temporary file.
240
+
241
+ Raises
242
+ ------
243
+ RuntimeError
244
+ If a TimeoutError occurs, it raises a RuntimeError with the command's
245
+ stdout and stderr logs.
246
+ """
247
+ try:
248
+ return read_from_fifo_when_ready(
249
+ attribute_file_fd, command_obj=command_obj, timeout=file_read_timeout
250
+ )
251
+ except (CalledProcessError, TimeoutError) as e:
252
+ raise RuntimeError(make_process_error_message(command_obj)) from e
253
+
254
+
255
+ async def async_handle_timeout(
256
+ attribute_file_fd: "int",
257
+ command_obj: "metaflow.runner.subprocess_manager.CommandManager",
258
+ file_read_timeout: int,
259
+ ):
260
+ """
261
+ Handle the timeout for a running subprocess command that reads a file
262
+ and raises an error with appropriate logs if a TimeoutError occurs.
263
+
264
+ Parameters
265
+ ----------
266
+ attribute_file_fd : int
267
+ File descriptor belonging to the FIFO containing the attribute data.
268
+ command_obj : CommandManager
269
+ Command manager object that encapsulates the running command details.
270
+ file_read_timeout : int
271
+ Timeout for reading the file, in seconds
272
+
273
+ Returns
274
+ -------
275
+ str
276
+ Content read from the temporary file.
277
+
278
+ Raises
279
+ ------
280
+ RuntimeError
281
+ If a TimeoutError occurs, it raises a RuntimeError with the command's
282
+ stdout and stderr logs.
283
+ """
284
+ try:
285
+ return await async_read_from_fifo_when_ready(
286
+ attribute_file_fd, command_obj=command_obj, timeout=file_read_timeout
287
+ )
288
+ except (CalledProcessError, TimeoutError) as e:
289
+ raise RuntimeError(make_process_error_message(command_obj)) from e
290
+
291
+
292
+ def get_lower_level_group(
293
+ api: "metaflow.runner.click_api.MetaflowAPI",
294
+ top_level_kwargs: Dict[str, Any],
295
+ sub_command: str,
296
+ sub_command_kwargs: Dict[str, Any],
297
+ ) -> "metaflow.runner.click_api.MetaflowAPI":
298
+ """
299
+ Retrieve a lower-level group from the API based on the type and provided arguments.
300
+
301
+ Parameters
302
+ ----------
303
+ api : MetaflowAPI
304
+ Metaflow API instance.
305
+ top_level_kwargs : Dict[str, Any]
306
+ Top-level keyword arguments to pass to the API.
307
+ sub_command : str
308
+ Sub-command of API to get the API for
309
+ sub_command_kwargs : Dict[str, Any]
310
+ Sub-command arguments
311
+
312
+ Returns
313
+ -------
314
+ MetaflowAPI
315
+ The lower-level group object retrieved from the API.
316
+
317
+ Raises
318
+ ------
319
+ ValueError
320
+ If the `_type` is None.
321
+ """
322
+ sub_command_obj = getattr(api(**top_level_kwargs), sub_command)
323
+
324
+ if sub_command_obj is None:
325
+ raise ValueError(f"Sub-command '{sub_command}' not found in API '{api.name}'")
326
+
327
+ return sub_command_obj(**sub_command_kwargs)
328
+
329
+
330
+ @contextmanager
331
+ def with_dir(new_dir):
332
+ current_dir = os.getcwd()
333
+ os.chdir(new_dir)
334
+ yield new_dir
335
+ os.chdir(current_dir)