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,4 +1,4 @@
1
- from typing import Any, List, Optional, Union
1
+ from typing import Any, List, Optional, Union, Callable
2
2
  from .basic import (
3
3
  LogComponent,
4
4
  ErrorComponent,
@@ -7,25 +7,15 @@ from .basic import (
7
7
  ImageComponent,
8
8
  SectionComponent,
9
9
  MarkdownComponent,
10
+ PythonCodeComponent,
10
11
  )
11
- from .card import MetaflowCardComponent
12
+ from .card import MetaflowCardComponent, with_default_component_id
12
13
  from .convert_to_native_type import TaskToDict, _full_classname
13
14
  from .renderer_tools import render_safely
15
+ from .json_viewer import JSONViewer as _JSONViewer, YAMLViewer as _YAMLViewer
14
16
  import uuid
15
-
16
-
17
- def create_component_id(component):
18
- uuid_bit = "".join(uuid.uuid4().hex.split("-"))[:6]
19
- return type(component).__name__.lower() + "_" + uuid_bit
20
-
21
-
22
- def with_default_component_id(func):
23
- def ret_func(self, *args, **kwargs):
24
- if self.component_id is None:
25
- self.component_id = create_component_id(self)
26
- return func(self, *args, **kwargs)
27
-
28
- return ret_func
17
+ import inspect
18
+ import textwrap
29
19
 
30
20
 
31
21
  def _warning_with_component(component, msg):
@@ -667,19 +657,38 @@ class Markdown(UserComponent):
667
657
  )
668
658
  ```
669
659
 
660
+ Multi-line strings with indentation are automatically dedented:
661
+ ```
662
+ current.card.append(
663
+ Markdown(f'''
664
+ # Header
665
+ - Item 1
666
+ - Item 2
667
+ ''')
668
+ )
669
+ ```
670
+
670
671
  Parameters
671
672
  ----------
672
673
  text : str
673
- Text formatted in Markdown.
674
+ Text formatted in Markdown. Leading whitespace common to all lines
675
+ is automatically removed to support indented multi-line strings.
674
676
  """
675
677
 
676
678
  REALTIME_UPDATABLE = True
677
679
 
680
+ @staticmethod
681
+ def _dedent_text(text):
682
+ """Remove common leading whitespace from all lines."""
683
+ if text is None:
684
+ return None
685
+ return textwrap.dedent(text)
686
+
678
687
  def update(self, text=None):
679
- self._text = text
688
+ self._text = self._dedent_text(text)
680
689
 
681
690
  def __init__(self, text=None):
682
- self._text = text
691
+ self._text = self._dedent_text(text)
683
692
 
684
693
  @with_default_component_id
685
694
  @render_safely
@@ -712,15 +721,15 @@ class ProgressBar(UserComponent):
712
721
 
713
722
  Parameters
714
723
  ----------
715
- max : int
724
+ max : int, default 100
716
725
  The maximum value of the progress bar.
717
- label : str, optional
726
+ label : str, optional, default None
718
727
  Optional label for the progress bar.
719
- value : int, optional
728
+ value : int, default 0
720
729
  Optional initial value of the progress bar.
721
- unit : str, optional
730
+ unit : str, optional, default None
722
731
  Optional unit for the progress bar.
723
- metadata : str, optional
732
+ metadata : str, optional, default None
724
733
  Optional additional information to show on the progress bar.
725
734
  """
726
735
 
@@ -731,10 +740,10 @@ class ProgressBar(UserComponent):
731
740
  def __init__(
732
741
  self,
733
742
  max: int = 100,
734
- label: str = None,
743
+ label: Optional[str] = None,
735
744
  value: int = 0,
736
- unit: str = None,
737
- metadata: str = None,
745
+ unit: Optional[str] = None,
746
+ metadata: Optional[str] = None,
738
747
  ):
739
748
  self._label = label
740
749
  self._max = max
@@ -742,7 +751,7 @@ class ProgressBar(UserComponent):
742
751
  self._unit = unit
743
752
  self._metadata = metadata
744
753
 
745
- def update(self, new_value: int, metadata: str = None):
754
+ def update(self, new_value: int, metadata: Optional[str] = None):
746
755
  self._value = new_value
747
756
  if metadata is not None:
748
757
  self._metadata = metadata
@@ -765,6 +774,234 @@ class ProgressBar(UserComponent):
765
774
  return data
766
775
 
767
776
 
777
+ class ValueBox(UserComponent):
778
+ """
779
+ A Value Box component for displaying key metrics with styling and change indicators.
780
+
781
+ Inspired by Shiny's value box component, this displays a primary value with optional
782
+ title, subtitle, theme, and change indicators.
783
+
784
+ Example:
785
+ ```
786
+ # Basic value box
787
+ value_box = ValueBox(
788
+ title="Revenue",
789
+ value="$1.2M",
790
+ subtitle="Monthly Revenue",
791
+ change_indicator="Up 15% from last month"
792
+ )
793
+ current.card.append(value_box)
794
+
795
+ # Themed value box
796
+ value_box = ValueBox(
797
+ title="Total Savings",
798
+ value=50000,
799
+ theme="success",
800
+ change_indicator="Up 30% from last month"
801
+ )
802
+ current.card.append(value_box)
803
+
804
+ # Updatable value box for real-time metrics
805
+ metrics_box = ValueBox(
806
+ title="Processing Progress",
807
+ value=0,
808
+ subtitle="Items processed"
809
+ )
810
+ current.card.append(metrics_box)
811
+
812
+ for i in range(1000):
813
+ metrics_box.update(value=i, change_indicator=f"Rate: {i*2}/sec")
814
+ ```
815
+
816
+ Parameters
817
+ ----------
818
+ title : str, optional
819
+ The title/label for the value box (usually displayed above the value).
820
+ Must be 200 characters or less.
821
+ value : Union[str, int, float]
822
+ The main value to display prominently. Required parameter.
823
+ subtitle : str, optional
824
+ Additional descriptive text displayed below the title.
825
+ Must be 300 characters or less.
826
+ theme : str, optional
827
+ CSS class name for styling the value box. Supported themes: 'default', 'success',
828
+ 'warning', 'danger', 'bg-gradient-indigo-purple'. Custom themes must be valid CSS class names.
829
+ change_indicator : str, optional
830
+ Text indicating change or additional context (e.g., "Up 30% VS PREVIOUS 30 DAYS").
831
+ Must be 200 characters or less.
832
+ """
833
+
834
+ type = "valueBox"
835
+
836
+ REALTIME_UPDATABLE = True
837
+
838
+ # Valid built-in themes
839
+ VALID_THEMES = {
840
+ "default",
841
+ "success",
842
+ "warning",
843
+ "danger",
844
+ "bg-gradient-indigo-purple",
845
+ }
846
+
847
+ def __init__(
848
+ self,
849
+ title: Optional[str] = None,
850
+ value: Union[str, int, float] = "",
851
+ subtitle: Optional[str] = None,
852
+ theme: Optional[str] = None,
853
+ change_indicator: Optional[str] = None,
854
+ ):
855
+ # Validate inputs
856
+ self._validate_title(title)
857
+ self._validate_value(value)
858
+ self._validate_subtitle(subtitle)
859
+ self._validate_theme(theme)
860
+ self._validate_change_indicator(change_indicator)
861
+
862
+ self._title = title
863
+ self._value = value
864
+ self._subtitle = subtitle
865
+ self._theme = theme
866
+ self._change_indicator = change_indicator
867
+
868
+ def update(
869
+ self,
870
+ title: Optional[str] = None,
871
+ value: Optional[Union[str, int, float]] = None,
872
+ subtitle: Optional[str] = None,
873
+ theme: Optional[str] = None,
874
+ change_indicator: Optional[str] = None,
875
+ ):
876
+ """
877
+ Update the value box with new data.
878
+
879
+ Parameters
880
+ ----------
881
+ title : str, optional
882
+ New title for the value box.
883
+ value : Union[str, int, float], optional
884
+ New value to display.
885
+ subtitle : str, optional
886
+ New subtitle text.
887
+ theme : str, optional
888
+ New theme/styling class.
889
+ change_indicator : str, optional
890
+ New change indicator text.
891
+ """
892
+ if title is not None:
893
+ self._validate_title(title)
894
+ self._title = title
895
+ if value is not None:
896
+ self._validate_value(value)
897
+ self._value = value
898
+ if subtitle is not None:
899
+ self._validate_subtitle(subtitle)
900
+ self._subtitle = subtitle
901
+ if theme is not None:
902
+ self._validate_theme(theme)
903
+ self._theme = theme
904
+ if change_indicator is not None:
905
+ self._validate_change_indicator(change_indicator)
906
+ self._change_indicator = change_indicator
907
+
908
+ def _validate_title(self, title: Optional[str]) -> None:
909
+ """Validate title parameter."""
910
+ if title is not None:
911
+ if not isinstance(title, str):
912
+ raise TypeError(f"Title must be a string, got {type(title).__name__}")
913
+ if len(title) > 200:
914
+ raise ValueError(
915
+ f"Title must be 200 characters or less, got {len(title)} characters"
916
+ )
917
+ if not title.strip():
918
+ raise ValueError("Title cannot be empty or whitespace only")
919
+
920
+ def _validate_value(self, value: Union[str, int, float]) -> None:
921
+ """Validate value parameter."""
922
+ if value is None:
923
+ raise ValueError("Value is required and cannot be None")
924
+ if not isinstance(value, (str, int, float)):
925
+ raise TypeError(
926
+ f"Value must be str, int, or float, got {type(value).__name__}"
927
+ )
928
+ if isinstance(value, str):
929
+ if len(value) > 100:
930
+ raise ValueError(
931
+ f"String value must be 100 characters or less, got {len(value)} characters"
932
+ )
933
+ if not value.strip():
934
+ raise ValueError("String value cannot be empty or whitespace only")
935
+ if isinstance(value, (int, float)):
936
+ if not (-1e15 <= value <= 1e15):
937
+ raise ValueError(
938
+ f"Numeric value must be between -1e15 and 1e15, got {value}"
939
+ )
940
+
941
+ def _validate_subtitle(self, subtitle: Optional[str]) -> None:
942
+ """Validate subtitle parameter."""
943
+ if subtitle is not None:
944
+ if not isinstance(subtitle, str):
945
+ raise TypeError(
946
+ f"Subtitle must be a string, got {type(subtitle).__name__}"
947
+ )
948
+ if len(subtitle) > 300:
949
+ raise ValueError(
950
+ f"Subtitle must be 300 characters or less, got {len(subtitle)} characters"
951
+ )
952
+ if not subtitle.strip():
953
+ raise ValueError("Subtitle cannot be empty or whitespace only")
954
+
955
+ def _validate_theme(self, theme: Optional[str]) -> None:
956
+ """Validate theme parameter."""
957
+ if theme is not None:
958
+ if not isinstance(theme, str):
959
+ raise TypeError(f"Theme must be a string, got {type(theme).__name__}")
960
+ if not theme.strip():
961
+ raise ValueError("Theme cannot be empty or whitespace only")
962
+ # Allow custom themes but warn if not in valid set
963
+ if theme not in self.VALID_THEMES:
964
+ import re
965
+
966
+ # Basic CSS class name validation
967
+ if not re.match(r"^[a-zA-Z][a-zA-Z0-9_-]*$", theme):
968
+ raise ValueError(
969
+ f"Theme must be a valid CSS class name, got '{theme}'"
970
+ )
971
+
972
+ def _validate_change_indicator(self, change_indicator: Optional[str]) -> None:
973
+ """Validate change_indicator parameter."""
974
+ if change_indicator is not None:
975
+ if not isinstance(change_indicator, str):
976
+ raise TypeError(
977
+ f"Change indicator must be a string, got {type(change_indicator).__name__}"
978
+ )
979
+ if len(change_indicator) > 200:
980
+ raise ValueError(
981
+ f"Change indicator must be 200 characters or less, got {len(change_indicator)} characters"
982
+ )
983
+ if not change_indicator.strip():
984
+ raise ValueError("Change indicator cannot be empty or whitespace only")
985
+
986
+ @with_default_component_id
987
+ @render_safely
988
+ def render(self):
989
+ data = {
990
+ "type": self.type,
991
+ "id": self.component_id,
992
+ "value": self._value,
993
+ }
994
+ if self._title is not None:
995
+ data["title"] = self._title
996
+ if self._subtitle is not None:
997
+ data["subtitle"] = self._subtitle
998
+ if self._theme is not None:
999
+ data["theme"] = self._theme
1000
+ if self._change_indicator is not None:
1001
+ data["change_indicator"] = self._change_indicator
1002
+ return data
1003
+
1004
+
768
1005
  class VegaChart(UserComponent):
769
1006
  type = "vegaChart"
770
1007
 
@@ -823,3 +1060,403 @@ class VegaChart(UserComponent):
823
1060
  if self._chart_inside_table and "autosize" not in self._spec:
824
1061
  data["spec"]["autosize"] = "fit-x"
825
1062
  return data
1063
+
1064
+
1065
+ class PythonCode(UserComponent):
1066
+ """
1067
+ A component to display Python code with syntax highlighting.
1068
+
1069
+ Example:
1070
+ ```python
1071
+ @card
1072
+ @step
1073
+ def my_step(self):
1074
+ # Using code_func
1075
+ def my_function():
1076
+ x = 1
1077
+ y = 2
1078
+ return x + y
1079
+ current.card.append(
1080
+ PythonCode(my_function)
1081
+ )
1082
+
1083
+ # Using code_string
1084
+ code = '''
1085
+ def another_function():
1086
+ return "Hello World"
1087
+ '''
1088
+ current.card.append(
1089
+ PythonCode(code_string=code)
1090
+ )
1091
+ ```
1092
+
1093
+ Parameters
1094
+ ----------
1095
+ code_func : Callable[..., Any], optional, default None
1096
+ The function whose source code should be displayed.
1097
+ code_string : str, optional, default None
1098
+ A string containing Python code to display.
1099
+ Either code_func or code_string must be provided.
1100
+ """
1101
+
1102
+ def __init__(
1103
+ self,
1104
+ code_func: Optional[Callable[..., Any]] = None,
1105
+ code_string: Optional[str] = None,
1106
+ ):
1107
+ if code_func is not None:
1108
+ self._code_string = inspect.getsource(code_func)
1109
+ else:
1110
+ self._code_string = code_string
1111
+
1112
+ @with_default_component_id
1113
+ @render_safely
1114
+ def render(self):
1115
+ if self._code_string is None:
1116
+ return ErrorComponent(
1117
+ "`PythonCode` component requires a `code_func` or `code_string` argument. ",
1118
+ "None provided for both",
1119
+ ).render()
1120
+ _code_component = PythonCodeComponent(self._code_string)
1121
+ _code_component.component_id = self.component_id
1122
+ return _code_component.render()
1123
+
1124
+
1125
+ class EventsTimeline(UserComponent):
1126
+ """
1127
+ An events timeline component for displaying structured log messages in real-time.
1128
+
1129
+ This component displays events in a timeline format with the latest events at the top.
1130
+ Each event can contain structured data including other UserComponents for rich display.
1131
+
1132
+ Example: Basic usage
1133
+ ```python
1134
+ @card
1135
+ @step
1136
+ def my_step(self):
1137
+ from metaflow.cards import EventsTimeline
1138
+ from metaflow import current
1139
+
1140
+ # Create an events component
1141
+ events = EventsTimeline(title="Processing Events")
1142
+ current.card.append(events)
1143
+
1144
+ # Add events during processing
1145
+ for i in range(10):
1146
+ events.update(
1147
+ event_data={
1148
+ "timestamp": datetime.now().isoformat(),
1149
+ "event_type": "processing",
1150
+ "item_id": i,
1151
+ "status": "completed",
1152
+ "duration_ms": random.randint(100, 500)
1153
+ }
1154
+ )
1155
+ time.sleep(1)
1156
+ ```
1157
+
1158
+ Example: With styling and rich components
1159
+ ```python
1160
+ from metaflow.cards import EventsTimeline, Markdown, PythonCode
1161
+
1162
+ events = EventsTimeline(title="Agent Actions")
1163
+ current.card.append(events)
1164
+
1165
+ # Event with styling
1166
+ events.update(
1167
+ event_data={
1168
+ "action": "tool_call",
1169
+ "function": "get_weather",
1170
+ "result": "Success"
1171
+ },
1172
+ style_theme="success"
1173
+ )
1174
+
1175
+ # Event with rich components
1176
+ events.update(
1177
+ event_data={
1178
+ "action": "code_execution",
1179
+ "status": "completed"
1180
+ },
1181
+ payloads={
1182
+ "code": PythonCode(code_string="print('Hello World')"),
1183
+ "notes": Markdown("**Important**: This ran successfully")
1184
+ },
1185
+ style_theme="info"
1186
+ )
1187
+ ```
1188
+
1189
+ Parameters
1190
+ ----------
1191
+ title : str, optional
1192
+ Title for the events timeline.
1193
+ max_events : int, default 100
1194
+ Maximum number of events to display. Older events are removed from display
1195
+ but total count is still tracked. Stats and relative time display are always enabled.
1196
+ """
1197
+
1198
+ type = "eventsTimeline"
1199
+
1200
+ REALTIME_UPDATABLE = True
1201
+
1202
+ # Valid style themes
1203
+ VALID_THEMES = {
1204
+ "default",
1205
+ "success",
1206
+ "warning",
1207
+ "error",
1208
+ "info",
1209
+ "primary",
1210
+ "secondary",
1211
+ "tool_call",
1212
+ "ai_response",
1213
+ }
1214
+
1215
+ def __init__(
1216
+ self,
1217
+ title: Optional[str] = None,
1218
+ max_events: int = 100,
1219
+ ):
1220
+ self._title = title
1221
+ self._max_events = max_events
1222
+ self._events = []
1223
+
1224
+ # Metadata tracking
1225
+ self._total_events_count = 0
1226
+ self._first_event_time = None
1227
+ self._last_update_time = None
1228
+ self._finished = False
1229
+
1230
+ def update(
1231
+ self,
1232
+ event_data: dict,
1233
+ style_theme: Optional[str] = None,
1234
+ priority: Optional[str] = None,
1235
+ payloads: Optional[dict] = None,
1236
+ finished: Optional[bool] = None,
1237
+ ):
1238
+ """
1239
+ Add a new event to the timeline.
1240
+
1241
+ Parameters
1242
+ ----------
1243
+ event_data : dict
1244
+ Basic event metadata (strings, numbers, simple values only).
1245
+ This appears in the main event display area.
1246
+ style_theme : str, optional
1247
+ Visual theme for this event. Valid values: 'default', 'success', 'warning',
1248
+ 'error', 'info', 'primary', 'secondary', 'tool_call', 'ai_response'.
1249
+ priority : str, optional
1250
+ Priority level for the event ('low', 'normal', 'high', 'critical').
1251
+ Affects visual prominence.
1252
+ payloads : dict, optional
1253
+ Rich payload components that will be displayed in collapsible sections.
1254
+ Values must be UserComponent instances: ValueBox, Image, Markdown,
1255
+ Artifact, JSONViewer, YAMLViewer. VegaChart is not supported inside EventsTimeline.
1256
+ finished : bool, optional
1257
+ Mark the timeline as finished. When True, the status indicator will show
1258
+ "Finished" in the header.
1259
+ """
1260
+ import time
1261
+
1262
+ # Validate style_theme
1263
+ if style_theme is not None and style_theme not in self.VALID_THEMES:
1264
+ import re
1265
+
1266
+ if not re.match(r"^[a-zA-Z][a-zA-Z0-9_-]*$", style_theme):
1267
+ raise ValueError(
1268
+ f"Invalid style_theme '{style_theme}'. Must be a valid CSS class name."
1269
+ )
1270
+
1271
+ # Validate payloads contain only allowed UserComponents
1272
+ if payloads is not None:
1273
+ allowed_components = (
1274
+ ValueBox,
1275
+ Image,
1276
+ Markdown,
1277
+ Artifact,
1278
+ PythonCode,
1279
+ _JSONViewer,
1280
+ _YAMLViewer,
1281
+ )
1282
+ for key, payload in payloads.items():
1283
+ if not isinstance(payload, allowed_components):
1284
+ raise TypeError(
1285
+ f"Payload '{key}' must be one of: ValueBox, Image, Markdown, "
1286
+ f"Artifact, JSONViewer, YAMLViewer. Got {type(payload).__name__}"
1287
+ )
1288
+
1289
+ # Add timestamp if not provided
1290
+ if "timestamp" not in event_data:
1291
+ event_data["timestamp"] = time.time()
1292
+
1293
+ # Create event object with metadata and payloads
1294
+ event = {
1295
+ "metadata": event_data,
1296
+ "payloads": payloads or {},
1297
+ "event_id": f"event_{self._total_events_count}",
1298
+ "received_at": time.time(),
1299
+ }
1300
+
1301
+ # Add styling metadata if provided
1302
+ if style_theme is not None:
1303
+ event["style_theme"] = style_theme
1304
+ if priority is not None:
1305
+ event["priority"] = priority
1306
+
1307
+ # Update metadata
1308
+ self._total_events_count += 1
1309
+ self._last_update_time = time.time()
1310
+ if self._first_event_time is None:
1311
+ self._first_event_time = time.time()
1312
+
1313
+ # Update finished status if provided
1314
+ if finished is not None:
1315
+ self._finished = finished
1316
+
1317
+ # Add the event to the beginning of the list (latest first)
1318
+ self._events.insert(0, event)
1319
+
1320
+ # Trim displayed events if we exceed max_events
1321
+ if len(self._events) > self._max_events:
1322
+ self._events = self._events[: self._max_events]
1323
+
1324
+ def get_stats(self) -> dict:
1325
+ """
1326
+ Get timeline statistics.
1327
+
1328
+ Returns
1329
+ -------
1330
+ dict
1331
+ Statistics including total events, display count, timing info, etc.
1332
+ """
1333
+ import time
1334
+
1335
+ current_time = time.time()
1336
+
1337
+ stats = {
1338
+ "total_events": self._total_events_count,
1339
+ "displayed_events": len(self._events),
1340
+ "last_update": self._last_update_time,
1341
+ "first_event": self._first_event_time,
1342
+ }
1343
+
1344
+ # seconds_since_last_update removed; UI derives recency from last event timestamp
1345
+
1346
+ # Add finished status
1347
+ stats["finished"] = self._finished
1348
+
1349
+ if self._first_event_time and self._total_events_count > 1:
1350
+ runtime = self._last_update_time - self._first_event_time
1351
+ if runtime > 0:
1352
+ stats["events_per_minute"] = round(
1353
+ (self._total_events_count / runtime) * 60, 1
1354
+ )
1355
+ stats["total_runtime_seconds"] = round(runtime, 1)
1356
+
1357
+ return stats
1358
+
1359
+ def _render_subcomponents(self):
1360
+ """
1361
+ Render any UserComponents within event payloads.
1362
+ """
1363
+ rendered_events = []
1364
+
1365
+ for event in self._events:
1366
+ rendered_event = dict(event) # Copy event metadata
1367
+
1368
+ # Event metadata should only contain simple values (no components)
1369
+ rendered_event["metadata"] = event["metadata"]
1370
+
1371
+ # Render payload components
1372
+ rendered_payloads = {}
1373
+ for key, payload in event["payloads"].items():
1374
+ if isinstance(payload, MetaflowCardComponent):
1375
+ # Render the component
1376
+ rendered_payloads[key] = payload.render()
1377
+ else:
1378
+ # This shouldn't happen due to validation, but handle gracefully
1379
+ rendered_payloads[key] = str(payload)
1380
+
1381
+ rendered_event["payloads"] = rendered_payloads
1382
+ rendered_events.append(rendered_event)
1383
+
1384
+ return rendered_events
1385
+
1386
+ @with_default_component_id
1387
+ @render_safely
1388
+ def render(self):
1389
+ data = {
1390
+ "type": self.type,
1391
+ "id": self.component_id,
1392
+ "events": self._render_subcomponents(),
1393
+ "config": {
1394
+ "show_stats": True,
1395
+ "show_relative_time": True,
1396
+ "max_events": self._max_events,
1397
+ },
1398
+ }
1399
+
1400
+ if self._title is not None:
1401
+ data["title"] = self._title
1402
+
1403
+ # Always include stats
1404
+ data["stats"] = self.get_stats()
1405
+
1406
+ return data
1407
+
1408
+
1409
+ # Rich viewer components
1410
+ class JSONViewer(_JSONViewer, UserComponent):
1411
+ """
1412
+ A component for displaying JSON data with syntax highlighting and collapsible sections.
1413
+
1414
+ This component provides a rich view of JSON data with proper formatting, syntax highlighting,
1415
+ and the ability to collapse/expand sections for better readability.
1416
+
1417
+ Example:
1418
+ ```python
1419
+ from metaflow.cards import JSONViewer, EventsTimeline
1420
+ from metaflow import current
1421
+
1422
+ # Use in events timeline
1423
+ events = EventsTimeline(title="API Calls")
1424
+ events.update({
1425
+ "action": "api_request",
1426
+ "endpoint": "/users",
1427
+ "payload": JSONViewer({"user_id": 123, "fields": ["name", "email"]})
1428
+ })
1429
+
1430
+ # Use standalone
1431
+ data = {"config": {"debug": True, "timeout": 30}}
1432
+ current.card.append(JSONViewer(data, collapsible=True))
1433
+ ```
1434
+ """
1435
+
1436
+ pass
1437
+
1438
+
1439
+ class YAMLViewer(_YAMLViewer, UserComponent):
1440
+ """
1441
+ A component for displaying YAML data with syntax highlighting and collapsible sections.
1442
+
1443
+ This component provides a rich view of YAML data with proper formatting and syntax highlighting.
1444
+
1445
+ Example:
1446
+ ```python
1447
+ from metaflow.cards import YAMLViewer, EventsTimeline
1448
+ from metaflow import current
1449
+
1450
+ # Use in events timeline
1451
+ events = EventsTimeline(title="Configuration Changes")
1452
+ events.update({
1453
+ "action": "config_update",
1454
+ "config": YAMLViewer({
1455
+ "database": {"host": "localhost", "port": 5432},
1456
+ "features": ["auth", "logging"]
1457
+ })
1458
+ })
1459
+ ```
1460
+ """
1461
+
1462
+ pass