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
@@ -1,5 +1,6 @@
1
1
  from metaflow.client import Task
2
- from metaflow import JSONType, namespace
2
+ from metaflow.parameters import JSONTypeClass
3
+ from metaflow import namespace
3
4
  from metaflow.util import resolve_identity
4
5
  from metaflow.exception import (
5
6
  CommandException,
@@ -29,7 +30,7 @@ from .exception import (
29
30
  )
30
31
  import traceback
31
32
  from collections import namedtuple
32
-
33
+ from .metadata import _save_metadata
33
34
  from .card_resolver import resolve_paths_from_task, resumed_info
34
35
 
35
36
  id_func = id
@@ -388,6 +389,22 @@ def card_read_options_and_arguments(func):
388
389
  return wrapper
389
390
 
390
391
 
392
+ def _extract_reload_token(data, task, mf_card):
393
+ if "render_seq" not in data:
394
+ return "never"
395
+
396
+ if data["render_seq"] == "final":
397
+ # final data update should always trigger a card reload to show
398
+ # the final card, hence a different token for the final update
399
+ return "final"
400
+ elif mf_card.RELOAD_POLICY == mf_card.RELOAD_POLICY_ALWAYS:
401
+ return "render-seq-%s" % data["render_seq"]
402
+ elif mf_card.RELOAD_POLICY == mf_card.RELOAD_POLICY_NEVER:
403
+ return "never"
404
+ elif mf_card.RELOAD_POLICY == mf_card.RELOAD_POLICY_ONCHANGE:
405
+ return mf_card.reload_content_token(task, data)
406
+
407
+
391
408
  def update_card(mf_card, mode, task, data, timeout_value=None):
392
409
  """
393
410
  This method will be responsible for creating a card/data-update based on the `mode`.
@@ -432,31 +449,21 @@ def update_card(mf_card, mode, task, data, timeout_value=None):
432
449
  - `timeout_stack_trace` : stack trace of the function if it timed out.
433
450
  """
434
451
 
435
- def _reload_token():
436
- if "render_seq" not in data:
437
- return "never"
438
-
439
- if data["render_seq"] == "final":
440
- # final data update should always trigger a card reload to show
441
- # the final card, hence a different token for the final update
442
- return "final"
443
- elif mf_card.RELOAD_POLICY == mf_card.RELOAD_POLICY_ALWAYS:
444
- return "render-seq-%s" % data["render_seq"]
445
- elif mf_card.RELOAD_POLICY == mf_card.RELOAD_POLICY_NEVER:
446
- return "never"
447
- elif mf_card.RELOAD_POLICY == mf_card.RELOAD_POLICY_ONCHANGE:
448
- return mf_card.reload_content_token(task, data)
449
-
450
452
  def _add_token_html(html):
451
453
  if html is None:
452
454
  return None
453
- return html.replace(mf_card.RELOAD_POLICY_TOKEN, _reload_token())
455
+ return html.replace(
456
+ mf_card.RELOAD_POLICY_TOKEN,
457
+ _extract_reload_token(data=data, task=task, mf_card=mf_card),
458
+ )
454
459
 
455
460
  def _add_token_json(json_msg):
456
461
  if json_msg is None:
457
462
  return None
458
463
  return {
459
- "reload_token": _reload_token(),
464
+ "reload_token": _extract_reload_token(
465
+ data=data, task=task, mf_card=mf_card
466
+ ),
460
467
  "data": json_msg,
461
468
  "created_on": time.time(),
462
469
  }
@@ -545,7 +552,7 @@ def update_card(mf_card, mode, task, data, timeout_value=None):
545
552
  "--options",
546
553
  default=None,
547
554
  show_default=True,
548
- type=JSONType,
555
+ type=JSONTypeClass(),
549
556
  help="arguments of the card being created.",
550
557
  )
551
558
  @click.option(
@@ -606,6 +613,14 @@ def update_card(mf_card, mode, task, data, timeout_value=None):
606
613
  hidden=True,
607
614
  help="Delete data-file and component-file after reading. (internal)",
608
615
  )
616
+ @click.option(
617
+ "--save-metadata",
618
+ default=None,
619
+ show_default=True,
620
+ type=JSONTypeClass(),
621
+ hidden=True,
622
+ help="JSON string containing metadata to be saved. (internal)",
623
+ )
609
624
  @click.pass_context
610
625
  def create(
611
626
  ctx,
@@ -620,6 +635,7 @@ def create(
620
635
  card_uuid=None,
621
636
  delete_input_files=None,
622
637
  id=None,
638
+ save_metadata=None,
623
639
  ):
624
640
  card_id = id
625
641
  rendered_info = None # Variable holding all the information which will be rendered
@@ -684,10 +700,15 @@ def create(
684
700
  try:
685
701
  if options is not None:
686
702
  mf_card = filtered_card(
687
- options=options, components=component_arr, graph=graph_dict
703
+ options=options,
704
+ components=component_arr,
705
+ graph=graph_dict,
706
+ flow=ctx.obj.flow,
688
707
  )
689
708
  else:
690
- mf_card = filtered_card(components=component_arr, graph=graph_dict)
709
+ mf_card = filtered_card(
710
+ components=component_arr, graph=graph_dict, flow=ctx.obj.flow
711
+ )
691
712
  except TypeError as e:
692
713
  if render_error_card:
693
714
  mf_card = None
@@ -740,8 +761,16 @@ def create(
740
761
  # 3. `refresh` is implemented but it raises an exception. (We do nothing. Don't store anything.)
741
762
  # 4. `refresh` is implemented but it times out. (We do nothing. Don't store anything.)
742
763
 
764
+ def _render_error_card(stack_trace):
765
+ _card = error_card()
766
+ token = _extract_reload_token(data, task, _card)
767
+ return _card.render(
768
+ task,
769
+ stack_trace=stack_trace,
770
+ ).replace(_card.RELOAD_POLICY_TOKEN, token)
771
+
743
772
  if error_stack_trace is not None and mode != "refresh":
744
- rendered_content = error_card().render(task, stack_trace=error_stack_trace)
773
+ rendered_content = _render_error_card(error_stack_trace)
745
774
  elif (
746
775
  rendered_info.is_implemented
747
776
  and rendered_info.timed_out
@@ -753,18 +782,15 @@ def create(
753
782
  "To increase the timeout duration for card rendering, please set the `timeout` parameter in the @card decorator. "
754
783
  "\nStack Trace : \n%s"
755
784
  ) % (timeout, rendered_info.timeout_stack_trace)
756
- rendered_content = error_card().render(
757
- task,
758
- stack_trace=timeout_stack_trace,
759
- )
785
+ rendered_content = _render_error_card(timeout_stack_trace)
760
786
  elif (
761
787
  rendered_info.is_implemented
762
788
  and rendered_info.data is None
763
789
  and render_error_card
764
790
  and mode != "refresh"
765
791
  ):
766
- rendered_content = error_card().render(
767
- task, stack_trace="No information rendered from card of type %s" % type
792
+ rendered_content = _render_error_card(
793
+ "No information rendered from card of type %s" % type
768
794
  )
769
795
  elif (
770
796
  not rendered_info.is_implemented
@@ -775,7 +801,7 @@ def create(
775
801
  "Card of type %s is a runtime time card with no `render_runtime` implemented. "
776
802
  "Please implement `render_runtime` method to allow rendering this card at runtime."
777
803
  ) % type
778
- rendered_content = error_card().render(task, stack_trace=message)
804
+ rendered_content = _render_error_card(message)
779
805
 
780
806
  # todo : should we save native type for error card or error type ?
781
807
  if type is not None and re.match(CARD_ID_PATTERN, type) is not None:
@@ -807,6 +833,16 @@ def create(
807
833
  % (card_info.type, card_info.hash[:NUM_SHORT_HASH_CHARS]),
808
834
  fg="green",
809
835
  )
836
+ if save_metadata:
837
+ _save_metadata(
838
+ ctx.obj.metadata,
839
+ task.parent.parent.id,
840
+ task.parent.id,
841
+ task.id,
842
+ task.current_attempt,
843
+ card_uuid,
844
+ save_metadata,
845
+ )
810
846
 
811
847
 
812
848
  @card.command()
@@ -5,6 +5,8 @@ import json
5
5
  import sys
6
6
  import os
7
7
  from metaflow import current
8
+ from typing import Callable, Tuple, Dict
9
+
8
10
 
9
11
  ASYNC_TIMEOUT = 30
10
12
 
@@ -44,8 +46,18 @@ class CardProcessManager:
44
46
 
45
47
 
46
48
  class CardCreator:
47
- def __init__(self, top_level_options):
49
+ def __init__(
50
+ self,
51
+ top_level_options,
52
+ should_save_metadata_lambda: Callable[[str], Tuple[bool, Dict]],
53
+ ):
54
+ # should_save_metadata_lambda is a lambda that provides a flag to indicate if
55
+ # card metadata should be written to the metadata store.
56
+ # It gets called only once when the card is created inside the subprocess.
57
+ # The intent is that this is a stateful lambda that will ensure that we only end
58
+ # up writing to the metadata store once.
48
59
  self._top_level_options = top_level_options
60
+ self._should_save_metadata = should_save_metadata_lambda
49
61
 
50
62
  def create(
51
63
  self,
@@ -62,6 +74,8 @@ class CardCreator:
62
74
  # Setting `final` will affect the Reload token set during the card refresh
63
75
  # data creation along with synchronous execution of subprocess.
64
76
  # Setting `sync` will only cause synchronous execution of subprocess.
77
+ save_metadata = False
78
+ metadata_dict = {}
65
79
  if mode != "render" and not runtime_card:
66
80
  # silently ignore runtime updates for cards that don't support them
67
81
  return
@@ -71,6 +85,8 @@ class CardCreator:
71
85
  component_strings = []
72
86
  else:
73
87
  component_strings = current.card._serialize_components(card_uuid)
88
+ # Since the mode is a render, we can check if we need to write to the metadata store.
89
+ save_metadata, metadata_dict = self._should_save_metadata(card_uuid)
74
90
  data = current.card._get_latest_data(card_uuid, final=final, mode=mode)
75
91
  runspec = "/".join([current.run_id, current.step_name, current.task_id])
76
92
  self._run_cards_subprocess(
@@ -85,6 +101,8 @@ class CardCreator:
85
101
  data,
86
102
  final=final,
87
103
  sync=sync,
104
+ save_metadata=save_metadata,
105
+ metadata_dict=metadata_dict,
88
106
  )
89
107
 
90
108
  def _run_cards_subprocess(
@@ -100,6 +118,8 @@ class CardCreator:
100
118
  data=None,
101
119
  final=False,
102
120
  sync=False,
121
+ save_metadata=False,
122
+ metadata_dict=None,
103
123
  ):
104
124
  components_file = data_file = None
105
125
  wait = final or sync
@@ -122,6 +142,7 @@ class CardCreator:
122
142
  executable,
123
143
  sys.argv[0],
124
144
  ]
145
+
125
146
  cmd += self._top_level_options + [
126
147
  "card",
127
148
  "create",
@@ -155,6 +176,9 @@ class CardCreator:
155
176
  if data_file is not None:
156
177
  cmd += ["--data-file", data_file.name]
157
178
 
179
+ if save_metadata:
180
+ cmd += ["--save-metadata", json.dumps(metadata_dict)]
181
+
158
182
  response, fail = self._run_command(
159
183
  cmd,
160
184
  card_uuid,
@@ -1,7 +1,3 @@
1
- """
2
-
3
- """
4
-
5
1
  from collections import namedtuple
6
2
  from io import BytesIO
7
3
  import os
@@ -13,10 +9,10 @@ from metaflow.metaflow_config import (
13
9
  CARD_S3ROOT,
14
10
  CARD_LOCALROOT,
15
11
  DATASTORE_LOCAL_DIR,
12
+ DATASTORE_SPIN_LOCAL_DIR,
16
13
  CARD_SUFFIX,
17
14
  CARD_AZUREROOT,
18
15
  CARD_GSROOT,
19
- SKIP_CARD_DUALWRITE,
20
16
  )
21
17
  import metaflow.metaflow_config as metaflow_config
22
18
 
@@ -63,25 +59,28 @@ class CardDatastore(object):
63
59
  return CARD_AZUREROOT
64
60
  elif storage_type == "gs":
65
61
  return CARD_GSROOT
66
- elif storage_type == "local":
62
+ elif storage_type == "local" or storage_type == "spin":
67
63
  # Borrowing some of the logic from LocalStorage.get_storage_root
68
64
  result = CARD_LOCALROOT
65
+ local_dir = (
66
+ DATASTORE_SPIN_LOCAL_DIR
67
+ if storage_type == "spin"
68
+ else DATASTORE_LOCAL_DIR
69
+ )
69
70
  if result is None:
70
71
  current_path = os.getcwd()
71
- check_dir = os.path.join(current_path, DATASTORE_LOCAL_DIR, CARD_SUFFIX)
72
+ check_dir = os.path.join(current_path, local_dir)
72
73
  check_dir = os.path.realpath(check_dir)
73
74
  orig_path = check_dir
74
75
  while not os.path.isdir(check_dir):
75
76
  new_path = os.path.dirname(current_path)
76
77
  if new_path == current_path:
77
- break # We are no longer making upward progress
78
+ # No longer making upward progress so we
79
+ # return the top level path
80
+ return os.path.join(orig_path, CARD_SUFFIX)
78
81
  current_path = new_path
79
- check_dir = os.path.join(
80
- current_path, DATASTORE_LOCAL_DIR, CARD_SUFFIX
81
- )
82
- result = orig_path
83
-
84
- return result
82
+ check_dir = os.path.join(current_path, local_dir)
83
+ return os.path.join(check_dir, CARD_SUFFIX)
85
84
  else:
86
85
  # Let's make it obvious we need to update this block for each new datastore backend...
87
86
  raise NotImplementedError(
@@ -231,23 +230,6 @@ class CardDatastore(object):
231
230
 
232
231
  def save_card(self, uuid, card_type, card_html, card_id=None, overwrite=True):
233
232
  card_file_name = card_type
234
- # TEMPORARY_WORKAROUND: FIXME (LATER) : Fix the duplication of below block in a few months.
235
- # Check file blame to understand the age of this temporary workaround.
236
-
237
- # This function will end up saving cards at two locations.
238
- # Thereby doubling the number of cards. (Which is a temporary fix)
239
- # Why do this ? :
240
- # When cards were introduced there was an assumption made about task-ids being unique.
241
- # This assumption was incorrect.
242
- # Only the pathspec needs to be unique but there is no such guarantees about task-ids.
243
- # When task-ids are non-unique, card read would result in finding incorrect cards.
244
- # This happens because cards were stored based on task-ids.
245
- # If we immediately switch from storing based on task-ids to a step-name abstraction folder,
246
- # then card reading will crash for many users.
247
- # It would especially happen for users who are accessing cards created by a newer
248
- # MF client from an older version of MF client.
249
- # It will also easily end up breaking the metaflow-ui (which maybe using a client from an older version).
250
- # Hence, we are writing cards to both paths so that we can introduce breaking changes later in the future.
251
233
  card_path_with_steps = self.get_card_location(
252
234
  self._get_card_write_path(),
253
235
  card_file_name,
@@ -255,24 +237,10 @@ class CardDatastore(object):
255
237
  card_id=card_id,
256
238
  suffix=CardNameSuffix.CARD,
257
239
  )
258
- if SKIP_CARD_DUALWRITE:
259
- self._backend.save_bytes(
260
- [(card_path_with_steps, BytesIO(bytes(card_html, "utf-8")))],
261
- overwrite=overwrite,
262
- )
263
- else:
264
- card_path_without_steps = self.get_card_location(
265
- self._get_card_read_path(with_steps=False),
266
- card_file_name,
267
- uuid,
268
- card_id=card_id,
269
- suffix=CardNameSuffix.CARD,
270
- )
271
- for cp in [card_path_with_steps, card_path_without_steps]:
272
- self._backend.save_bytes(
273
- [(cp, BytesIO(bytes(card_html, "utf-8")))], overwrite=overwrite
274
- )
275
-
240
+ self._backend.save_bytes(
241
+ [(card_path_with_steps, BytesIO(bytes(card_html, "utf-8")))],
242
+ overwrite=overwrite,
243
+ )
276
244
  return self.info_from_path(card_path_with_steps, suffix=CardNameSuffix.CARD)
277
245
 
278
246
  def _list_card_paths(self, card_type=None, card_hash=None, card_id=None):
@@ -283,6 +251,10 @@ class CardDatastore(object):
283
251
  )
284
252
 
285
253
  if len(card_paths_with_steps) == 0:
254
+ # The listing logic is reading the cards with steps and without steps
255
+ # because earlier versions of clients (ones that wrote cards before June 2022),
256
+ # would have written cards without steps. So as a fallback we will try to check for the
257
+ # cards without steps.
286
258
  card_paths_without_steps = self._backend.list_content(
287
259
  [self._get_card_read_path(with_steps=False)]
288
260
  )
@@ -1,13 +1,18 @@
1
+ import json
2
+ import os
3
+ import re
4
+ import tempfile
5
+ from typing import Tuple, Dict
6
+
1
7
  from metaflow.decorators import StepDecorator
8
+ from metaflow.metadata_provider import MetaDatum
2
9
  from metaflow.metaflow_current import current
10
+ from metaflow.user_configs.config_options import ConfigInput
11
+ from metaflow.user_configs.config_parameters import dump_config_values
3
12
  from metaflow.util import to_unicode
13
+
4
14
  from .component_serializer import CardComponentCollector, get_card_class
5
15
  from .card_creator import CardCreator
6
-
7
-
8
- # from metaflow import get_metadata
9
- import re
10
-
11
16
  from .exception import CARD_ID_PATTERN, TYPE_CHECK_REGEX
12
17
 
13
18
  ASYNC_TIMEOUT = 30
@@ -19,6 +24,24 @@ def warning_message(message, logger=None, ts=False):
19
24
  logger(msg, timestamp=ts, bad=True)
20
25
 
21
26
 
27
+ class MetadataStateManager(object):
28
+ def __init__(self, info_func):
29
+ self._info_func = info_func
30
+ self._metadata_registered = {}
31
+
32
+ def register_metadata(self, card_uuid) -> Tuple[bool, Dict]:
33
+ info = self._info_func()
34
+ # Check that metadata was not written yet. We only want to write once.
35
+ if (
36
+ info is None
37
+ or info.get(card_uuid) is None
38
+ or self._metadata_registered.get(card_uuid)
39
+ ):
40
+ return False, {}
41
+ self._metadata_registered[card_uuid] = True
42
+ return True, info.get(card_uuid)
43
+
44
+
22
45
  class CardDecorator(StepDecorator):
23
46
  """
24
47
  Creates a human-readable report, a Metaflow Card, after this step completes.
@@ -52,11 +75,14 @@ class CardDecorator(StepDecorator):
52
75
  The or one of the cards attached to this step.
53
76
  """
54
77
 
78
+ _GLOBAL_CARD_INFO = {}
79
+
55
80
  name = "card"
56
81
  defaults = {
57
82
  "type": "default",
58
83
  "options": {},
59
84
  "scope": "task",
85
+ "rank": None, # Can be one of "high", "medium", "low". Can help derive ordering on the UI.
60
86
  "timeout": 45,
61
87
  "id": None,
62
88
  "save_errors": True,
@@ -73,6 +99,12 @@ class CardDecorator(StepDecorator):
73
99
 
74
100
  card_creator = None
75
101
 
102
+ _config_values = None
103
+
104
+ _config_file_name = None
105
+
106
+ task_finished_decos = 0
107
+
76
108
  def __init__(self, *args, **kwargs):
77
109
  super(CardDecorator, self).__init__(*args, **kwargs)
78
110
  self._task_datastore = None
@@ -82,6 +114,7 @@ class CardDecorator(StepDecorator):
82
114
  self._is_editable = False
83
115
  self._card_uuid = None
84
116
  self._user_set_card_id = None
117
+ self._metadata_registered = False
85
118
 
86
119
  @classmethod
87
120
  def _set_card_creator(cls, card_creator):
@@ -103,20 +136,61 @@ class CardDecorator(StepDecorator):
103
136
  def _increment_step_counter(cls):
104
137
  cls.step_counter += 1
105
138
 
139
+ @classmethod
140
+ def _increment_completed_counter(cls):
141
+ cls.task_finished_decos += 1
142
+
143
+ @classmethod
144
+ def _set_config_values(cls, config_values):
145
+ cls._config_values = config_values
146
+
147
+ @classmethod
148
+ def _set_config_file_name(cls, flow):
149
+ # Only create a config file from the very first card decorator.
150
+ if cls._config_values and not cls._config_file_name:
151
+ with tempfile.NamedTemporaryFile(
152
+ mode="w", encoding="utf-8", delete=False
153
+ ) as config_file:
154
+ config_value = dump_config_values(flow)
155
+ json.dump(config_value, config_file)
156
+ cls._config_file_name = config_file.name
157
+
158
+ @classmethod
159
+ def _register_card_info(cls, **kwargs):
160
+ if not kwargs.get("card_uuid"):
161
+ raise ValueError("card_uuid is required")
162
+ cls._GLOBAL_CARD_INFO[kwargs["card_uuid"]] = kwargs
163
+
164
+ @classmethod
165
+ def all_cards_info(cls):
166
+ return cls._GLOBAL_CARD_INFO.copy()
167
+
106
168
  def step_init(
107
169
  self, flow, graph, step_name, decorators, environment, flow_datastore, logger
108
170
  ):
109
171
  self._flow_datastore = flow_datastore
110
172
  self._environment = environment
111
173
  self._logger = logger
174
+
112
175
  self.card_options = None
113
176
 
177
+ # We check for configuration options. We do this here before they are
178
+ # converted to properties.
179
+ self._set_config_values(
180
+ [
181
+ (config.name, ConfigInput.make_key_name(config.name))
182
+ for _, config in flow._get_parameters()
183
+ if config.IS_CONFIG_PARAMETER
184
+ ]
185
+ )
186
+
114
187
  self.card_options = self.attributes["options"]
115
188
 
116
189
  evt_name = "step-init"
117
190
  # `'%s-%s'%(evt_name,step_name)` ensures that we capture this once per @card per @step.
118
191
  # Since there can be many steps checking if event is registered for `evt_name` will only make it check it once for all steps.
119
192
  # Hence, we have `_is_event_registered('%s-%s'%(evt_name,step_name))`
193
+ self._is_runtime_card = False
120
194
  evt = "%s-%s" % (evt_name, step_name)
121
195
  if not self._is_event_registered(evt):
122
196
  # We set the total count of decorators so that we can use it for
@@ -145,6 +219,19 @@ class CardDecorator(StepDecorator):
145
219
  self._task_datastore = task_datastore
146
220
  self._metadata = metadata
147
221
 
222
+ # If we have configs, we need to dump them to a file so we can re-use them
223
+ # when calling the card creation subprocess.
224
+ # Since a step can contain multiple card decorators, and all the card creation processes
225
+ # will reference the same config file (because of how the CardCreator is created (only single class instance)),
226
+ # we need to ensure that a single config file is being referenced for all card create commands.
227
+ # This config file will be removed when the last card decorator has finished creating its card.
228
+ self._set_config_file_name(flow)
229
+ # The MetadataStateManager is used to track the state of the metadata registration.
230
+ # It is there to ensure that we only register metadata for the card once. This is so that we
231
+ # avoid any un-necessary metadata writes because the create command can be called multiple times during the
232
+ # card creation process.
233
+ self._metadata_state_manager = MetadataStateManager(self.all_cards_info)
234
+
148
235
  card_type = self.attributes["type"]
149
236
  card_class = get_card_class(card_type)
150
237
 
@@ -178,7 +265,12 @@ class CardDecorator(StepDecorator):
178
265
  # we need to ensure that `current.card` has `CardComponentCollector` instantiated only once.
179
266
  if not self._is_event_registered("pre-step"):
180
267
  self._register_event("pre-step")
181
- self._set_card_creator(CardCreator(self._create_top_level_args()))
268
+ self._set_card_creator(
269
+ CardCreator(
270
+ self._create_top_level_args(flow),
271
+ self._metadata_state_manager.register_metadata,
272
+ )
273
+ )
182
274
 
183
275
  current._update_env(
184
276
  {"card": CardComponentCollector(self._logger, self.card_creator)}
@@ -201,6 +293,18 @@ class CardDecorator(StepDecorator):
201
293
  )
202
294
  self._card_uuid = card_metadata["uuid"]
203
295
 
296
+ self._register_card_info(
297
+ card_uuid=self._card_uuid,
298
+ rank=self.attributes["rank"],
299
+ type=self.attributes["type"],
300
+ options=self.card_options,
301
+ is_editable=self._is_editable,
302
+ is_runtime_card=self._is_runtime_card,
303
+ refresh_interval=self.attributes["refresh_interval"],
304
+ customize=customize,
305
+ id=self._user_set_card_id,
306
+ )
307
+
204
308
  # This means that we are calling `task_pre_step` on the last card decorator.
205
309
  # We can now `finalize` method in the CardComponentCollector object.
206
310
  # This will set up the `current.card` object for usage inside `@step` code.
@@ -222,6 +326,8 @@ class CardDecorator(StepDecorator):
222
326
  self.card_creator.create(mode="render", final=True, **create_options)
223
327
  self.card_creator.create(mode="refresh", final=True, **create_options)
224
328
 
329
+ self._cleanup(step_name)
330
+
225
331
  @staticmethod
226
332
  def _options(mapping):
227
333
  for k, v in mapping.items():
@@ -231,9 +337,13 @@ class CardDecorator(StepDecorator):
231
337
  for value in v:
232
338
  yield "--%s" % k
233
339
  if not isinstance(value, bool):
234
- yield to_unicode(value)
340
+ if isinstance(value, tuple):
341
+ for val in value:
342
+ yield to_unicode(val)
343
+ else:
344
+ yield to_unicode(value)
235
345
 
236
- def _create_top_level_args(self):
346
+ def _create_top_level_args(self, flow):
237
347
  top_level_options = {
238
348
  "quiet": True,
239
349
  "metadata": self._metadata.TYPE,
@@ -246,4 +356,18 @@ class CardDecorator(StepDecorator):
246
356
  # We don't provide --with as all execution is taking place in
247
357
  # the context of the main process
248
358
  }
359
+ if self._config_values:
360
+ top_level_options["config-value"] = self._config_values
361
+ top_level_options["local-config-file"] = self._config_file_name
362
+
249
363
  return list(self._options(top_level_options))
364
+
365
+ def _cleanup(self, step_name):
366
+ self._increment_completed_counter()
367
+ if self.task_finished_decos == self.total_decos_on_step[step_name]:
368
+ # Unlink the config file if it exists
369
+ if self._config_file_name:
370
+ try:
371
+ os.unlink(self._config_file_name)
372
+ except Exception as e:
373
+ pass