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
metaflow/lint.py CHANGED
@@ -52,7 +52,7 @@ def check_reserved_words(graph):
52
52
  msg = "Step name *%s* is a reserved word. Choose another name for the " "step."
53
53
  for node in graph:
54
54
  if node.name in RESERVED:
55
- raise LintWarn(msg % node.name)
55
+ raise LintWarn(msg % node.name, node.func_lineno, node.source_file)
56
56
 
57
57
 
58
58
  @linter.ensure_fundamentals
@@ -76,9 +76,9 @@ def check_that_end_is_end(graph):
76
76
  node = graph["end"]
77
77
 
78
78
  if node.has_tail_next or node.invalid_tail_next:
79
- raise LintWarn(msg0, node.tail_next_lineno)
79
+ raise LintWarn(msg0, node.tail_next_lineno, node.source_file)
80
80
  if node.num_args > 1:
81
- raise LintWarn(msg1, node.tail_next_lineno)
81
+ raise LintWarn(msg1, node.tail_next_lineno, node.source_file)
82
82
 
83
83
 
84
84
  @linter.ensure_fundamentals
@@ -90,7 +90,7 @@ def check_step_names(graph):
90
90
  )
91
91
  for node in graph:
92
92
  if re.search("[^a-z0-9_]", node.name) or node.name[0] == "_":
93
- raise LintWarn(msg.format(node), node.func_lineno)
93
+ raise LintWarn(msg.format(node), node.func_lineno, node.source_file)
94
94
 
95
95
 
96
96
  @linter.ensure_fundamentals
@@ -108,11 +108,11 @@ def check_num_args(graph):
108
108
  msg2 = "Step *{0.name}* is missing the 'self' argument."
109
109
  for node in graph:
110
110
  if node.num_args > 2:
111
- raise LintWarn(msg0.format(node), node.func_lineno)
111
+ raise LintWarn(msg0.format(node), node.func_lineno, node.source_file)
112
112
  elif node.num_args == 2 and node.type != "join":
113
- raise LintWarn(msg1.format(node), node.func_lineno)
113
+ raise LintWarn(msg1.format(node), node.func_lineno, node.source_file)
114
114
  elif node.num_args == 0:
115
- raise LintWarn(msg2.format(node), node.func_lineno)
115
+ raise LintWarn(msg2.format(node), node.func_lineno, node.source_file)
116
116
 
117
117
 
118
118
  @linter.ensure_static_graph
@@ -125,7 +125,7 @@ def check_static_transitions(graph):
125
125
  )
126
126
  for node in graph:
127
127
  if node.type != "end" and not node.has_tail_next:
128
- raise LintWarn(msg.format(node), node.func_lineno)
128
+ raise LintWarn(msg.format(node), node.func_lineno, node.source_file)
129
129
 
130
130
 
131
131
  @linter.ensure_static_graph
@@ -134,11 +134,17 @@ def check_valid_transitions(graph):
134
134
  msg = (
135
135
  "Step *{0.name}* specifies an invalid self.next() transition. "
136
136
  "Make sure the self.next() expression matches with one of the "
137
- "supported transition types."
137
+ "supported transition types:\n"
138
+ " • Linear: self.next(self.step_name)\n"
139
+ " • Fan-out: self.next(self.step1, self.step2, ...)\n"
140
+ " • Foreach: self.next(self.step, foreach='variable')\n"
141
+ " • Switch: self.next({{\"key\": self.step, ...}}, condition='variable')\n\n"
142
+ "For switch statements, keys must be string literals, numbers or config expressions "
143
+ "(self.config.key_name), not variables."
138
144
  )
139
145
  for node in graph:
140
146
  if node.type != "end" and node.has_tail_next and node.invalid_tail_next:
141
- raise LintWarn(msg.format(node), node.tail_next_lineno)
147
+ raise LintWarn(msg.format(node), node.tail_next_lineno, node.source_file)
142
148
 
143
149
 
144
150
  @linter.ensure_static_graph
@@ -151,7 +157,11 @@ def check_unknown_transitions(graph):
151
157
  for node in graph:
152
158
  unknown = [n for n in node.out_funcs if n not in graph]
153
159
  if unknown:
154
- raise LintWarn(msg.format(node, step=unknown[0]), node.tail_next_lineno)
160
+ raise LintWarn(
161
+ msg.format(node, step=unknown[0]),
162
+ node.tail_next_lineno,
163
+ node.source_file,
164
+ )
155
165
 
156
166
 
157
167
  @linter.ensure_acyclicity
@@ -165,9 +175,13 @@ def check_for_acyclicity(graph):
165
175
 
166
176
  def check_path(node, seen):
167
177
  for n in node.out_funcs:
178
+ if node.type == "split-switch" and n == node.name:
179
+ continue
168
180
  if n in seen:
169
181
  path = "->".join(seen + [n])
170
- raise LintWarn(msg.format(path), node.tail_next_lineno)
182
+ raise LintWarn(
183
+ msg.format(path), node.tail_next_lineno, node.source_file
184
+ )
171
185
  else:
172
186
  check_path(graph[n], seen + [n])
173
187
 
@@ -195,7 +209,7 @@ def check_for_orphans(graph):
195
209
  orphans = nodeset - seen
196
210
  if orphans:
197
211
  orphan = graph[list(orphans)[0]]
198
- raise LintWarn(msg.format(orphan), orphan.func_lineno)
212
+ raise LintWarn(msg.format(orphan), orphan.func_lineno, orphan.source_file)
199
213
 
200
214
 
201
215
  @linter.ensure_static_graph
@@ -226,26 +240,48 @@ def check_split_join_balance(graph):
226
240
  new_stack = split_stack
227
241
  elif node.type in ("split", "foreach"):
228
242
  new_stack = split_stack + [("split", node.out_funcs)]
243
+ elif node.type == "split-switch":
244
+ # For a switch, continue traversal down each path with the same stack
245
+ for n in node.out_funcs:
246
+ if node.type == "split-switch" and n == node.name:
247
+ continue
248
+ traverse(graph[n], split_stack)
249
+ return
229
250
  elif node.type == "end":
251
+ new_stack = split_stack
230
252
  if split_stack:
231
253
  _, split_roots = split_stack.pop()
232
254
  roots = ", ".join(split_roots)
233
- raise LintWarn(msg0.format(roots=roots))
255
+ raise LintWarn(
256
+ msg0.format(roots=roots), node.func_lineno, node.source_file
257
+ )
234
258
  elif node.type == "join":
259
+ new_stack = split_stack
235
260
  if split_stack:
236
261
  _, split_roots = split_stack[-1]
237
262
  new_stack = split_stack[:-1]
238
- if len(node.in_funcs) != len(split_roots):
239
- paths = ", ".join(node.in_funcs)
263
+
264
+ # Resolve each incoming function to its root branch from the split.
265
+ resolved_branches = set(
266
+ graph[n].split_branches[-1] for n in node.in_funcs
267
+ )
268
+
269
+ # compares the set of resolved branches against the expected branches
270
+ # from the split.
271
+ if len(resolved_branches) != len(
272
+ split_roots
273
+ ) or resolved_branches ^ set(split_roots):
274
+ paths = ", ".join(resolved_branches)
240
275
  roots = ", ".join(split_roots)
241
276
  raise LintWarn(
242
277
  msg1.format(
243
278
  node, paths=paths, num_roots=len(split_roots), roots=roots
244
279
  ),
245
280
  node.func_lineno,
281
+ node.source_file,
246
282
  )
247
283
  else:
248
- raise LintWarn(msg2.format(node), node.func_lineno)
284
+ raise LintWarn(msg2.format(node), node.func_lineno, node.source_file)
249
285
 
250
286
  # check that incoming steps come from the same lineage
251
287
  # (no cross joins)
@@ -256,14 +292,56 @@ def check_split_join_balance(graph):
256
292
  return tuple(graph[n].split_parents)
257
293
 
258
294
  if not all_equal(map(parents, node.in_funcs)):
259
- raise LintWarn(msg3.format(node), node.func_lineno)
295
+ raise LintWarn(msg3.format(node), node.func_lineno, node.source_file)
296
+ else:
297
+ new_stack = split_stack
260
298
 
261
299
  for n in node.out_funcs:
300
+ if node.type == "split-switch" and n == node.name:
301
+ continue
262
302
  traverse(graph[n], new_stack)
263
303
 
264
304
  traverse(graph["start"], [])
265
305
 
266
306
 
307
+ @linter.ensure_static_graph
308
+ @linter.check
309
+ def check_switch_splits(graph):
310
+ """Check conditional split constraints"""
311
+ msg0 = (
312
+ "Step *{0.name}* is a switch split but defines {num} transitions. "
313
+ "Switch splits must define at least 2 transitions."
314
+ )
315
+ msg1 = "Step *{0.name}* is a switch split but has no condition variable."
316
+ msg2 = "Step *{0.name}* is a switch split but has no switch cases defined."
317
+
318
+ for node in graph:
319
+ if node.type == "split-switch":
320
+ # Check at least 2 outputs
321
+ if len(node.out_funcs) < 2:
322
+ raise LintWarn(
323
+ msg0.format(node, num=len(node.out_funcs)),
324
+ node.func_lineno,
325
+ node.source_file,
326
+ )
327
+
328
+ # Check condition exists
329
+ if not node.condition:
330
+ raise LintWarn(
331
+ msg1.format(node),
332
+ node.func_lineno,
333
+ node.source_file,
334
+ )
335
+
336
+ # Check switch cases exist
337
+ if not node.switch_cases:
338
+ raise LintWarn(
339
+ msg2.format(node),
340
+ node.func_lineno,
341
+ node.source_file,
342
+ )
343
+
344
+
267
345
  @linter.ensure_static_graph
268
346
  @linter.check
269
347
  def check_empty_foreaches(graph):
@@ -276,7 +354,9 @@ def check_empty_foreaches(graph):
276
354
  if node.type == "foreach":
277
355
  joins = [n for n in node.out_funcs if graph[n].type == "join"]
278
356
  if joins:
279
- raise LintWarn(msg.format(node, join=joins[0]))
357
+ raise LintWarn(
358
+ msg.format(node, join=joins[0]), node.func_lineno, node.source_file
359
+ )
280
360
 
281
361
 
282
362
  @linter.ensure_static_graph
@@ -290,7 +370,22 @@ def check_parallel_step_after_next(graph):
290
370
  if node.parallel_foreach and not all(
291
371
  graph[out_node].parallel_step for out_node in node.out_funcs
292
372
  ):
293
- raise LintWarn(msg.format(node))
373
+ raise LintWarn(msg.format(node), node.func_lineno, node.source_file)
374
+
375
+
376
+ @linter.ensure_static_graph
377
+ @linter.check
378
+ def check_join_followed_by_parallel_step(graph):
379
+ msg = (
380
+ "An @parallel step should be followed by a join step. Step *{0}* is called "
381
+ "after an @parallel step but is not a join step. Please add an extra `inputs` "
382
+ "argument to the step."
383
+ )
384
+ for node in graph:
385
+ if node.parallel_step and not graph[node.out_funcs[0]].type == "join":
386
+ raise LintWarn(
387
+ msg.format(node.out_funcs[0]), node.func_lineno, node.source_file
388
+ )
294
389
 
295
390
 
296
391
  @linter.ensure_static_graph
@@ -305,7 +400,9 @@ def check_parallel_foreach_calls_parallel_step(graph):
305
400
  for node2 in graph:
306
401
  if node2.out_funcs and node.name in node2.out_funcs:
307
402
  if not node2.parallel_foreach:
308
- raise LintWarn(msg.format(node, node2))
403
+ raise LintWarn(
404
+ msg.format(node, node2), node.func_lineno, node.source_file
405
+ )
309
406
 
310
407
 
311
408
  @linter.ensure_non_nested_foreach
@@ -318,4 +415,26 @@ def check_nested_foreach(graph):
318
415
  for node in graph:
319
416
  if node.type == "foreach":
320
417
  if any(graph[p].type == "foreach" for p in node.split_parents):
321
- raise LintWarn(msg.format(node))
418
+ raise LintWarn(msg.format(node), node.func_lineno, node.source_file)
419
+
420
+
421
+ @linter.ensure_static_graph
422
+ @linter.check
423
+ def check_ambiguous_joins(graph):
424
+ for node in graph:
425
+ if node.type == "join":
426
+ problematic_parents = [
427
+ p_name
428
+ for p_name in node.in_funcs
429
+ if graph[p_name].type == "split-switch"
430
+ ]
431
+ if problematic_parents:
432
+ msg = (
433
+ "A conditional path cannot lead directly to a join step.\n"
434
+ "In your conditional step(s) {parents}, one or more of the possible paths transition directly to the join step {join_name}.\n"
435
+ "As a workaround, please introduce an intermediate, unconditional step on that specific path before joining."
436
+ ).format(
437
+ parents=", ".join("*%s*" % p for p in problematic_parents),
438
+ join_name="*%s*" % node.name,
439
+ )
440
+ raise LintWarn(msg, node.func_lineno, node.source_file)
metaflow/meta_files.py ADDED
@@ -0,0 +1,13 @@
1
+ _UNINITIALIZED = object()
2
+ _info_file_content = _UNINITIALIZED
3
+
4
+
5
+ def read_info_file():
6
+ # Prevent circular import
7
+ from .packaging_sys import MetaflowCodeContent
8
+
9
+ global _info_file_content
10
+
11
+ if id(_info_file_content) == id(_UNINITIALIZED):
12
+ _info_file_content = MetaflowCodeContent.get_info()
13
+ return _info_file_content
@@ -1,11 +1,12 @@
1
+ import json
1
2
  import time
3
+ from threading import Thread
4
+
2
5
  import requests
3
- import json
4
6
 
5
- from threading import Thread
6
- from metaflow.sidecar import MessageTypes, Message
7
- from metaflow.metaflow_config import SERVICE_HEADERS
8
7
  from metaflow.exception import MetaflowException
8
+ from metaflow.metaflow_config import SERVICE_HEADERS
9
+ from metaflow.sidecar import Message, MessageTypes
9
10
 
10
11
  HB_URL_KEY = "hb_url"
11
12
 
@@ -51,14 +52,29 @@ class MetadataHeartBeat(object):
51
52
  time.sleep(frequency_secs)
52
53
  retry_counter = 0
53
54
  except HeartBeatException as e:
55
+ print(e)
54
56
  retry_counter = retry_counter + 1
55
- time.sleep(4**retry_counter)
57
+ time.sleep(1.5**retry_counter)
56
58
 
57
59
  def _heartbeat(self):
58
60
  if self.hb_url is not None:
59
- response = requests.post(
60
- url=self.hb_url, data="{}", headers=self.headers.copy()
61
- )
61
+ try:
62
+ response = requests.post(
63
+ url=self.hb_url, data="{}", headers=self.headers.copy()
64
+ )
65
+ except requests.exceptions.ConnectionError as e:
66
+ raise HeartBeatException(
67
+ "HeartBeat request (%s) failed" " (ConnectionError)" % (self.hb_url)
68
+ )
69
+ except requests.exceptions.Timeout as e:
70
+ raise HeartBeatException(
71
+ "HeartBeat request (%s) failed" " (Timeout)" % (self.hb_url)
72
+ )
73
+ except requests.exceptions.RequestException as e:
74
+ raise HeartBeatException(
75
+ "HeartBeat request (%s) failed"
76
+ " (RequestException) %s" % (self.hb_url, str(e))
77
+ )
62
78
  # Unfortunately, response.json() returns a string that we need
63
79
  # to cast to json; however when the request encounters an error
64
80
  # the return type is a json blob :/
@@ -5,6 +5,7 @@ import time
5
5
  from collections import namedtuple
6
6
  from itertools import chain
7
7
 
8
+ from typing import List
8
9
  from metaflow.exception import MetaflowInternalError, MetaflowTaggingError
9
10
  from metaflow.tagging_util import validate_tag
10
11
  from metaflow.util import get_username, resolve_identity_as_tuple, is_stringish
@@ -76,6 +77,12 @@ class ObjectOrder:
76
77
 
77
78
  @with_metaclass(MetadataProviderMeta)
78
79
  class MetadataProvider(object):
80
+ TYPE = None
81
+
82
+ @classmethod
83
+ def metadata_str(cls):
84
+ return "%s@%s" % (cls.TYPE, cls.INFO)
85
+
79
86
  @classmethod
80
87
  def compute_info(cls, val):
81
88
  """
@@ -623,6 +630,25 @@ class MetadataProvider(object):
623
630
  sys_info["r_version"] = env["r_version_code"]
624
631
  return sys_info
625
632
 
633
+ def _get_git_info_as_dict(self):
634
+ git_info = {}
635
+ # NOTE: For flows executing remotely, we want to read from the INFO file of the code package that contains
636
+ # information on the original environment that deployed the flow.
637
+ # Otherwise git related info will be missing, as the repository is not part of the codepackage.
638
+ from metaflow.packaging_sys import MetaflowCodeContent
639
+
640
+ env = MetaflowCodeContent.get_info() or self._environment.get_environment_info()
641
+ for key in [
642
+ "repo_url",
643
+ "branch_name",
644
+ "commit_sha",
645
+ "has_uncommitted_changes",
646
+ ]:
647
+ if key in env and env[key]:
648
+ git_info[key] = env[key]
649
+
650
+ return git_info
651
+
626
652
  def _get_system_tags(self):
627
653
  """Convert system info dictionary into a list of system tags"""
628
654
  return [
@@ -653,19 +679,78 @@ class MetadataProvider(object):
653
679
  if code_sha:
654
680
  code_url = os.environ.get("METAFLOW_CODE_URL")
655
681
  code_ds = os.environ.get("METAFLOW_CODE_DS")
682
+ code_metadata = os.environ.get("METAFLOW_CODE_METADATA")
656
683
  metadata.append(
657
684
  MetaDatum(
658
685
  field="code-package",
659
686
  value=json.dumps(
660
- {"ds_type": code_ds, "sha": code_sha, "location": code_url}
687
+ {
688
+ "ds_type": code_ds,
689
+ "sha": code_sha,
690
+ "location": code_url,
691
+ "metadata": code_metadata,
692
+ }
661
693
  ),
662
694
  type="code-package",
663
695
  tags=["attempt_id:{0}".format(attempt)],
664
696
  )
665
697
  )
698
+ # Add script name as metadata
699
+ script_name = self._environment.get_environment_info()["script"]
700
+ metadata.append(
701
+ MetaDatum(
702
+ field="script-name",
703
+ value=script_name,
704
+ type="script-name",
705
+ tags=["attempt_id:{0}".format(attempt)],
706
+ )
707
+ )
708
+ # And add git metadata
709
+ git_info = self._get_git_info_as_dict()
710
+ if git_info:
711
+ metadata.append(
712
+ MetaDatum(
713
+ field="git-info",
714
+ value=json.dumps(git_info),
715
+ type="git-info",
716
+ tags=["attempt_id:{0}".format(attempt)],
717
+ )
718
+ )
666
719
  if metadata:
667
720
  self.register_metadata(run_id, step_name, task_id, metadata)
668
721
 
722
+ @classmethod
723
+ def filter_tasks_by_metadata(
724
+ cls,
725
+ flow_name: str,
726
+ run_id: str,
727
+ step_name: str,
728
+ field_name: str,
729
+ pattern: str,
730
+ ) -> List[str]:
731
+ """
732
+ Filter tasks by metadata field and pattern, returning task pathspecs that match criteria.
733
+
734
+ Parameters
735
+ ----------
736
+ flow_name : str
737
+ Flow name, that the run belongs to.
738
+ run_id: str
739
+ Run id, together with flow_id, that identifies the specific Run whose tasks to query
740
+ step_name: str
741
+ Step name to query tasks from
742
+ field_name: str
743
+ Metadata field name to query
744
+ pattern: str
745
+ Pattern to match in metadata field value
746
+
747
+ Returns
748
+ -------
749
+ List[str]
750
+ List of task pathspecs that satisfy the query
751
+ """
752
+ raise NotImplementedError()
753
+
669
754
  @staticmethod
670
755
  def _apply_filter(elts, filters):
671
756
  if filters is None: