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.
- metaflow/R.py +10 -7
- metaflow/__init__.py +40 -25
- metaflow/_vendor/imghdr/__init__.py +186 -0
- metaflow/_vendor/importlib_metadata/__init__.py +1063 -0
- metaflow/_vendor/importlib_metadata/_adapters.py +68 -0
- metaflow/_vendor/importlib_metadata/_collections.py +30 -0
- metaflow/_vendor/importlib_metadata/_compat.py +71 -0
- metaflow/_vendor/importlib_metadata/_functools.py +104 -0
- metaflow/_vendor/importlib_metadata/_itertools.py +73 -0
- metaflow/_vendor/importlib_metadata/_meta.py +48 -0
- metaflow/_vendor/importlib_metadata/_text.py +99 -0
- metaflow/_vendor/importlib_metadata/py.typed +0 -0
- metaflow/_vendor/typeguard/__init__.py +48 -0
- metaflow/_vendor/typeguard/_checkers.py +1070 -0
- metaflow/_vendor/typeguard/_config.py +108 -0
- metaflow/_vendor/typeguard/_decorators.py +233 -0
- metaflow/_vendor/typeguard/_exceptions.py +42 -0
- metaflow/_vendor/typeguard/_functions.py +308 -0
- metaflow/_vendor/typeguard/_importhook.py +213 -0
- metaflow/_vendor/typeguard/_memo.py +48 -0
- metaflow/_vendor/typeguard/_pytest_plugin.py +127 -0
- metaflow/_vendor/typeguard/_suppression.py +86 -0
- metaflow/_vendor/typeguard/_transformer.py +1229 -0
- metaflow/_vendor/typeguard/_union_transformer.py +55 -0
- metaflow/_vendor/typeguard/_utils.py +173 -0
- metaflow/_vendor/typeguard/py.typed +0 -0
- metaflow/_vendor/typing_extensions.py +3641 -0
- metaflow/_vendor/v3_7/importlib_metadata/__init__.py +1063 -0
- metaflow/_vendor/v3_7/importlib_metadata/_adapters.py +68 -0
- metaflow/_vendor/v3_7/importlib_metadata/_collections.py +30 -0
- metaflow/_vendor/v3_7/importlib_metadata/_compat.py +71 -0
- metaflow/_vendor/v3_7/importlib_metadata/_functools.py +104 -0
- metaflow/_vendor/v3_7/importlib_metadata/_itertools.py +73 -0
- metaflow/_vendor/v3_7/importlib_metadata/_meta.py +48 -0
- metaflow/_vendor/v3_7/importlib_metadata/_text.py +99 -0
- metaflow/_vendor/v3_7/importlib_metadata/py.typed +0 -0
- metaflow/_vendor/v3_7/typeguard/__init__.py +48 -0
- metaflow/_vendor/v3_7/typeguard/_checkers.py +906 -0
- metaflow/_vendor/v3_7/typeguard/_config.py +108 -0
- metaflow/_vendor/v3_7/typeguard/_decorators.py +237 -0
- metaflow/_vendor/v3_7/typeguard/_exceptions.py +42 -0
- metaflow/_vendor/v3_7/typeguard/_functions.py +310 -0
- metaflow/_vendor/v3_7/typeguard/_importhook.py +213 -0
- metaflow/_vendor/v3_7/typeguard/_memo.py +48 -0
- metaflow/_vendor/v3_7/typeguard/_pytest_plugin.py +100 -0
- metaflow/_vendor/v3_7/typeguard/_suppression.py +88 -0
- metaflow/_vendor/v3_7/typeguard/_transformer.py +1207 -0
- metaflow/_vendor/v3_7/typeguard/_union_transformer.py +54 -0
- metaflow/_vendor/v3_7/typeguard/_utils.py +169 -0
- metaflow/_vendor/v3_7/typeguard/py.typed +0 -0
- metaflow/_vendor/v3_7/typing_extensions.py +3072 -0
- metaflow/_vendor/yaml/__init__.py +427 -0
- metaflow/_vendor/yaml/composer.py +139 -0
- metaflow/_vendor/yaml/constructor.py +748 -0
- metaflow/_vendor/yaml/cyaml.py +101 -0
- metaflow/_vendor/yaml/dumper.py +62 -0
- metaflow/_vendor/yaml/emitter.py +1137 -0
- metaflow/_vendor/yaml/error.py +75 -0
- metaflow/_vendor/yaml/events.py +86 -0
- metaflow/_vendor/yaml/loader.py +63 -0
- metaflow/_vendor/yaml/nodes.py +49 -0
- metaflow/_vendor/yaml/parser.py +589 -0
- metaflow/_vendor/yaml/reader.py +185 -0
- metaflow/_vendor/yaml/representer.py +389 -0
- metaflow/_vendor/yaml/resolver.py +227 -0
- metaflow/_vendor/yaml/scanner.py +1435 -0
- metaflow/_vendor/yaml/serializer.py +111 -0
- metaflow/_vendor/yaml/tokens.py +104 -0
- metaflow/cards.py +5 -0
- metaflow/cli.py +331 -785
- metaflow/cli_args.py +17 -0
- metaflow/cli_components/__init__.py +0 -0
- metaflow/cli_components/dump_cmd.py +96 -0
- metaflow/cli_components/init_cmd.py +52 -0
- metaflow/cli_components/run_cmds.py +546 -0
- metaflow/cli_components/step_cmd.py +334 -0
- metaflow/cli_components/utils.py +140 -0
- metaflow/client/__init__.py +1 -0
- metaflow/client/core.py +467 -73
- metaflow/client/filecache.py +75 -35
- metaflow/clone_util.py +7 -1
- metaflow/cmd/code/__init__.py +231 -0
- metaflow/cmd/develop/stub_generator.py +756 -288
- metaflow/cmd/develop/stubs.py +12 -28
- metaflow/cmd/main_cli.py +6 -4
- metaflow/cmd/make_wrapper.py +78 -0
- metaflow/datastore/__init__.py +1 -0
- metaflow/datastore/content_addressed_store.py +41 -10
- metaflow/datastore/datastore_set.py +11 -2
- metaflow/datastore/flow_datastore.py +156 -10
- metaflow/datastore/spin_datastore.py +91 -0
- metaflow/datastore/task_datastore.py +154 -39
- metaflow/debug.py +5 -0
- metaflow/decorators.py +404 -78
- metaflow/exception.py +8 -2
- metaflow/extension_support/__init__.py +527 -376
- metaflow/extension_support/_empty_file.py +2 -2
- metaflow/extension_support/plugins.py +49 -31
- metaflow/flowspec.py +482 -33
- metaflow/graph.py +210 -42
- metaflow/includefile.py +84 -40
- metaflow/lint.py +141 -22
- metaflow/meta_files.py +13 -0
- metaflow/{metadata → metadata_provider}/heartbeat.py +24 -8
- metaflow/{metadata → metadata_provider}/metadata.py +86 -1
- metaflow/metaflow_config.py +175 -28
- metaflow/metaflow_config_funcs.py +51 -3
- metaflow/metaflow_current.py +4 -10
- metaflow/metaflow_environment.py +139 -53
- metaflow/metaflow_git.py +115 -0
- metaflow/metaflow_profile.py +18 -0
- metaflow/metaflow_version.py +150 -66
- metaflow/mflog/__init__.py +4 -3
- metaflow/mflog/save_logs.py +2 -2
- metaflow/multicore_utils.py +31 -14
- metaflow/package/__init__.py +673 -0
- metaflow/packaging_sys/__init__.py +880 -0
- metaflow/packaging_sys/backend.py +128 -0
- metaflow/packaging_sys/distribution_support.py +153 -0
- metaflow/packaging_sys/tar_backend.py +99 -0
- metaflow/packaging_sys/utils.py +54 -0
- metaflow/packaging_sys/v1.py +527 -0
- metaflow/parameters.py +149 -28
- metaflow/plugins/__init__.py +74 -5
- metaflow/plugins/airflow/airflow.py +40 -25
- metaflow/plugins/airflow/airflow_cli.py +22 -5
- metaflow/plugins/airflow/airflow_decorator.py +1 -1
- metaflow/plugins/airflow/airflow_utils.py +5 -3
- metaflow/plugins/airflow/sensors/base_sensor.py +4 -4
- metaflow/plugins/airflow/sensors/external_task_sensor.py +2 -2
- metaflow/plugins/airflow/sensors/s3_sensor.py +2 -2
- metaflow/plugins/argo/argo_client.py +78 -33
- metaflow/plugins/argo/argo_events.py +6 -6
- metaflow/plugins/argo/argo_workflows.py +2410 -527
- metaflow/plugins/argo/argo_workflows_cli.py +571 -121
- metaflow/plugins/argo/argo_workflows_decorator.py +43 -12
- metaflow/plugins/argo/argo_workflows_deployer.py +106 -0
- metaflow/plugins/argo/argo_workflows_deployer_objects.py +453 -0
- metaflow/plugins/argo/capture_error.py +73 -0
- metaflow/plugins/argo/conditional_input_paths.py +35 -0
- metaflow/plugins/argo/exit_hooks.py +209 -0
- metaflow/plugins/argo/jobset_input_paths.py +15 -0
- metaflow/plugins/argo/param_val.py +19 -0
- metaflow/plugins/aws/aws_client.py +10 -3
- metaflow/plugins/aws/aws_utils.py +55 -2
- metaflow/plugins/aws/batch/batch.py +72 -5
- metaflow/plugins/aws/batch/batch_cli.py +33 -10
- metaflow/plugins/aws/batch/batch_client.py +4 -3
- metaflow/plugins/aws/batch/batch_decorator.py +102 -35
- metaflow/plugins/aws/secrets_manager/aws_secrets_manager_secrets_provider.py +13 -10
- metaflow/plugins/aws/step_functions/dynamo_db_client.py +0 -3
- metaflow/plugins/aws/step_functions/production_token.py +1 -1
- metaflow/plugins/aws/step_functions/step_functions.py +65 -8
- metaflow/plugins/aws/step_functions/step_functions_cli.py +101 -7
- metaflow/plugins/aws/step_functions/step_functions_decorator.py +1 -2
- metaflow/plugins/aws/step_functions/step_functions_deployer.py +97 -0
- metaflow/plugins/aws/step_functions/step_functions_deployer_objects.py +264 -0
- metaflow/plugins/azure/azure_exceptions.py +1 -1
- metaflow/plugins/azure/azure_secret_manager_secrets_provider.py +240 -0
- metaflow/plugins/azure/azure_tail.py +1 -1
- metaflow/plugins/azure/includefile_support.py +2 -0
- metaflow/plugins/cards/card_cli.py +66 -30
- metaflow/plugins/cards/card_creator.py +25 -1
- metaflow/plugins/cards/card_datastore.py +21 -49
- metaflow/plugins/cards/card_decorator.py +132 -8
- metaflow/plugins/cards/card_modules/basic.py +112 -17
- metaflow/plugins/cards/card_modules/bundle.css +1 -1
- metaflow/plugins/cards/card_modules/card.py +16 -1
- metaflow/plugins/cards/card_modules/chevron/renderer.py +1 -1
- metaflow/plugins/cards/card_modules/components.py +665 -28
- metaflow/plugins/cards/card_modules/convert_to_native_type.py +36 -7
- metaflow/plugins/cards/card_modules/json_viewer.py +232 -0
- metaflow/plugins/cards/card_modules/main.css +1 -0
- metaflow/plugins/cards/card_modules/main.js +68 -49
- metaflow/plugins/cards/card_modules/renderer_tools.py +1 -0
- metaflow/plugins/cards/card_modules/test_cards.py +26 -12
- metaflow/plugins/cards/card_server.py +39 -14
- metaflow/plugins/cards/component_serializer.py +2 -9
- metaflow/plugins/cards/metadata.py +22 -0
- metaflow/plugins/catch_decorator.py +9 -0
- metaflow/plugins/datastores/azure_storage.py +10 -1
- metaflow/plugins/datastores/gs_storage.py +6 -2
- metaflow/plugins/datastores/local_storage.py +12 -6
- metaflow/plugins/datastores/spin_storage.py +12 -0
- metaflow/plugins/datatools/local.py +2 -0
- metaflow/plugins/datatools/s3/s3.py +126 -75
- metaflow/plugins/datatools/s3/s3op.py +254 -121
- metaflow/plugins/env_escape/__init__.py +3 -3
- metaflow/plugins/env_escape/client_modules.py +102 -72
- metaflow/plugins/env_escape/server.py +7 -0
- metaflow/plugins/env_escape/stub.py +24 -5
- metaflow/plugins/events_decorator.py +343 -185
- metaflow/plugins/exit_hook/__init__.py +0 -0
- metaflow/plugins/exit_hook/exit_hook_decorator.py +46 -0
- metaflow/plugins/exit_hook/exit_hook_script.py +52 -0
- metaflow/plugins/gcp/__init__.py +1 -1
- metaflow/plugins/gcp/gcp_secret_manager_secrets_provider.py +11 -6
- metaflow/plugins/gcp/gs_tail.py +10 -6
- metaflow/plugins/gcp/includefile_support.py +3 -0
- metaflow/plugins/kubernetes/kube_utils.py +108 -0
- metaflow/plugins/kubernetes/kubernetes.py +411 -130
- metaflow/plugins/kubernetes/kubernetes_cli.py +168 -36
- metaflow/plugins/kubernetes/kubernetes_client.py +104 -2
- metaflow/plugins/kubernetes/kubernetes_decorator.py +246 -88
- metaflow/plugins/kubernetes/kubernetes_job.py +253 -581
- metaflow/plugins/kubernetes/kubernetes_jobsets.py +1071 -0
- metaflow/plugins/kubernetes/spot_metadata_cli.py +69 -0
- metaflow/plugins/kubernetes/spot_monitor_sidecar.py +109 -0
- metaflow/plugins/logs_cli.py +359 -0
- metaflow/plugins/{metadata → metadata_providers}/local.py +144 -84
- metaflow/plugins/{metadata → metadata_providers}/service.py +103 -26
- metaflow/plugins/metadata_providers/spin.py +16 -0
- metaflow/plugins/package_cli.py +36 -24
- metaflow/plugins/parallel_decorator.py +128 -11
- metaflow/plugins/parsers.py +16 -0
- metaflow/plugins/project_decorator.py +51 -5
- metaflow/plugins/pypi/bootstrap.py +357 -105
- metaflow/plugins/pypi/conda_decorator.py +82 -81
- metaflow/plugins/pypi/conda_environment.py +187 -52
- metaflow/plugins/pypi/micromamba.py +157 -47
- metaflow/plugins/pypi/parsers.py +268 -0
- metaflow/plugins/pypi/pip.py +88 -13
- metaflow/plugins/pypi/pypi_decorator.py +37 -1
- metaflow/plugins/pypi/utils.py +48 -2
- metaflow/plugins/resources_decorator.py +2 -2
- metaflow/plugins/secrets/__init__.py +3 -0
- metaflow/plugins/secrets/secrets_decorator.py +26 -181
- metaflow/plugins/secrets/secrets_func.py +49 -0
- metaflow/plugins/secrets/secrets_spec.py +101 -0
- metaflow/plugins/secrets/utils.py +74 -0
- metaflow/plugins/tag_cli.py +4 -7
- metaflow/plugins/test_unbounded_foreach_decorator.py +41 -6
- metaflow/plugins/timeout_decorator.py +3 -3
- metaflow/plugins/uv/__init__.py +0 -0
- metaflow/plugins/uv/bootstrap.py +128 -0
- metaflow/plugins/uv/uv_environment.py +72 -0
- metaflow/procpoll.py +1 -1
- metaflow/pylint_wrapper.py +5 -1
- metaflow/runner/__init__.py +0 -0
- metaflow/runner/click_api.py +717 -0
- metaflow/runner/deployer.py +470 -0
- metaflow/runner/deployer_impl.py +201 -0
- metaflow/runner/metaflow_runner.py +714 -0
- metaflow/runner/nbdeploy.py +132 -0
- metaflow/runner/nbrun.py +225 -0
- metaflow/runner/subprocess_manager.py +650 -0
- metaflow/runner/utils.py +335 -0
- metaflow/runtime.py +1078 -260
- metaflow/sidecar/sidecar_worker.py +1 -1
- metaflow/system/__init__.py +5 -0
- metaflow/system/system_logger.py +85 -0
- metaflow/system/system_monitor.py +108 -0
- metaflow/system/system_utils.py +19 -0
- metaflow/task.py +521 -225
- metaflow/tracing/__init__.py +7 -7
- metaflow/tracing/span_exporter.py +31 -38
- metaflow/tracing/tracing_modules.py +38 -43
- metaflow/tuple_util.py +27 -0
- metaflow/user_configs/__init__.py +0 -0
- metaflow/user_configs/config_options.py +563 -0
- metaflow/user_configs/config_parameters.py +598 -0
- metaflow/user_decorators/__init__.py +0 -0
- metaflow/user_decorators/common.py +144 -0
- metaflow/user_decorators/mutable_flow.py +512 -0
- metaflow/user_decorators/mutable_step.py +424 -0
- metaflow/user_decorators/user_flow_decorator.py +264 -0
- metaflow/user_decorators/user_step_decorator.py +749 -0
- metaflow/util.py +243 -27
- metaflow/vendor.py +23 -7
- metaflow/version.py +1 -1
- ob_metaflow-2.19.7.1rc0.data/data/share/metaflow/devtools/Makefile +355 -0
- ob_metaflow-2.19.7.1rc0.data/data/share/metaflow/devtools/Tiltfile +726 -0
- ob_metaflow-2.19.7.1rc0.data/data/share/metaflow/devtools/pick_services.sh +105 -0
- ob_metaflow-2.19.7.1rc0.dist-info/METADATA +87 -0
- ob_metaflow-2.19.7.1rc0.dist-info/RECORD +445 -0
- {ob_metaflow-2.11.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/WHEEL +1 -1
- {ob_metaflow-2.11.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/entry_points.txt +1 -0
- metaflow/_vendor/v3_5/__init__.py +0 -1
- metaflow/_vendor/v3_5/importlib_metadata/__init__.py +0 -644
- metaflow/_vendor/v3_5/importlib_metadata/_compat.py +0 -152
- metaflow/package.py +0 -188
- ob_metaflow-2.11.13.1.dist-info/METADATA +0 -85
- ob_metaflow-2.11.13.1.dist-info/RECORD +0 -308
- /metaflow/_vendor/{v3_5/zipp.py → zipp.py} +0 -0
- /metaflow/{metadata → metadata_provider}/__init__.py +0 -0
- /metaflow/{metadata → metadata_provider}/util.py +0 -0
- /metaflow/plugins/{metadata → metadata_providers}/__init__.py +0 -0
- {ob_metaflow-2.11.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info/licenses}/LICENSE +0 -0
- {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,
|
|
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
|