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
metaflow/client/core.py
CHANGED
|
@@ -5,6 +5,7 @@ import os
|
|
|
5
5
|
import tarfile
|
|
6
6
|
from collections import namedtuple
|
|
7
7
|
from datetime import datetime
|
|
8
|
+
from tempfile import TemporaryDirectory
|
|
8
9
|
from io import BytesIO
|
|
9
10
|
from itertools import chain
|
|
10
11
|
from typing import (
|
|
@@ -16,6 +17,7 @@ from typing import (
|
|
|
16
17
|
List,
|
|
17
18
|
NamedTuple,
|
|
18
19
|
Optional,
|
|
20
|
+
TYPE_CHECKING,
|
|
19
21
|
Tuple,
|
|
20
22
|
)
|
|
21
23
|
|
|
@@ -30,13 +32,17 @@ from metaflow.exception import (
|
|
|
30
32
|
from metaflow.includefile import IncludedFile
|
|
31
33
|
from metaflow.metaflow_config import DEFAULT_METADATA, MAX_ATTEMPTS
|
|
32
34
|
from metaflow.metaflow_environment import MetaflowEnvironment
|
|
35
|
+
from metaflow.package import MetaflowPackage
|
|
36
|
+
from metaflow.packaging_sys import ContentType
|
|
33
37
|
from metaflow.plugins import ENVIRONMENTS, METADATA_PROVIDERS
|
|
34
38
|
from metaflow.unbounded_foreach import CONTROL_TASK_TAG
|
|
35
39
|
from metaflow.util import cached_property, is_stringish, resolve_identity, to_unicode
|
|
36
40
|
|
|
37
|
-
from .. import INFO_FILE
|
|
38
41
|
from .filecache import FileCache
|
|
39
42
|
|
|
43
|
+
if TYPE_CHECKING:
|
|
44
|
+
from metaflow.metadata_provider import MetadataProvider
|
|
45
|
+
|
|
40
46
|
try:
|
|
41
47
|
# python2
|
|
42
48
|
import cPickle as pickle
|
|
@@ -82,28 +88,16 @@ def metadata(ms: str) -> str:
|
|
|
82
88
|
get_metadata()).
|
|
83
89
|
"""
|
|
84
90
|
global current_metadata
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
else:
|
|
96
|
-
metadata_type = "local"
|
|
97
|
-
res = [m for m in METADATA_PROVIDERS if m.TYPE == metadata_type]
|
|
98
|
-
if not res:
|
|
99
|
-
print(
|
|
100
|
-
"Cannot find a '%s' metadata provider -- "
|
|
101
|
-
"try specifying one explicitly using <type>@<info>",
|
|
102
|
-
metadata_type,
|
|
103
|
-
)
|
|
104
|
-
return get_metadata()
|
|
105
|
-
current_metadata = res[0]
|
|
106
|
-
current_metadata.INFO = ms
|
|
91
|
+
provider, info = _metadata(ms)
|
|
92
|
+
if provider is None:
|
|
93
|
+
print(
|
|
94
|
+
"Cannot find a metadata provider -- "
|
|
95
|
+
"try specifying one explicitly using <type>@<info>",
|
|
96
|
+
)
|
|
97
|
+
return get_metadata()
|
|
98
|
+
current_metadata = provider
|
|
99
|
+
if info:
|
|
100
|
+
current_metadata.INFO = info
|
|
107
101
|
return get_metadata()
|
|
108
102
|
|
|
109
103
|
|
|
@@ -127,7 +121,7 @@ def get_metadata() -> str:
|
|
|
127
121
|
"""
|
|
128
122
|
if current_metadata is False:
|
|
129
123
|
default_metadata()
|
|
130
|
-
return
|
|
124
|
+
return current_metadata.metadata_str()
|
|
131
125
|
|
|
132
126
|
|
|
133
127
|
def default_metadata() -> str:
|
|
@@ -151,7 +145,7 @@ def default_metadata() -> str:
|
|
|
151
145
|
if default:
|
|
152
146
|
current_metadata = default[0]
|
|
153
147
|
else:
|
|
154
|
-
from metaflow.plugins.
|
|
148
|
+
from metaflow.plugins.metadata_providers import LocalMetadataProvider
|
|
155
149
|
|
|
156
150
|
current_metadata = LocalMetadataProvider
|
|
157
151
|
return get_metadata()
|
|
@@ -213,6 +207,20 @@ def default_namespace() -> str:
|
|
|
213
207
|
return get_namespace()
|
|
214
208
|
|
|
215
209
|
|
|
210
|
+
def inspect_spin(datastore_root: str = "."):
|
|
211
|
+
"""
|
|
212
|
+
Set metadata provider to spin metadata so that users can inspect spin
|
|
213
|
+
steps, tasks, and artifacts.
|
|
214
|
+
|
|
215
|
+
Parameters
|
|
216
|
+
----------
|
|
217
|
+
datastore_root : str, default "."
|
|
218
|
+
The root path to the spin datastore.
|
|
219
|
+
"""
|
|
220
|
+
metadata_str = f"spin@{datastore_root}"
|
|
221
|
+
metadata(metadata_str)
|
|
222
|
+
|
|
223
|
+
|
|
216
224
|
MetaflowArtifacts = NamedTuple
|
|
217
225
|
|
|
218
226
|
|
|
@@ -268,13 +276,28 @@ class MetaflowObject(object):
|
|
|
268
276
|
_object: Optional["MetaflowObject"] = None,
|
|
269
277
|
_parent: Optional["MetaflowObject"] = None,
|
|
270
278
|
_namespace_check: bool = True,
|
|
279
|
+
_metaflow: Optional["Metaflow"] = None,
|
|
280
|
+
_current_namespace: Optional[str] = None,
|
|
281
|
+
_current_metadata: Optional[str] = None,
|
|
271
282
|
):
|
|
272
|
-
|
|
283
|
+
# the default namespace is activated lazily at the first
|
|
284
|
+
# get_namespace(). The other option of activating
|
|
285
|
+
# the namespace at the import time is problematic, since there
|
|
286
|
+
# may be other modules that alter environment variables etc.
|
|
287
|
+
# which may affect the namespace setting.
|
|
288
|
+
self._metaflow = Metaflow(_current_metadata) or _metaflow
|
|
273
289
|
self._parent = _parent
|
|
274
290
|
self._path_components = None
|
|
275
291
|
self._attempt = attempt
|
|
292
|
+
self._current_namespace = _current_namespace or get_namespace()
|
|
276
293
|
self._namespace_check = _namespace_check
|
|
277
294
|
|
|
295
|
+
# If the current namespace is False, we disable checking for namespace for this
|
|
296
|
+
# and all children objects. Not setting namespace_check to False has the consequence
|
|
297
|
+
# of preventing access to children objects after the namespace changes
|
|
298
|
+
if self._current_namespace is None:
|
|
299
|
+
self._namespace_check = False
|
|
300
|
+
|
|
278
301
|
if self._attempt is not None:
|
|
279
302
|
if self._NAME not in ["task", "artifact"]:
|
|
280
303
|
raise MetaflowNotFound(
|
|
@@ -295,7 +318,7 @@ class MetaflowObject(object):
|
|
|
295
318
|
# distinguish between "attempt will happen" and "no such
|
|
296
319
|
# attempt exists".
|
|
297
320
|
|
|
298
|
-
if pathspec:
|
|
321
|
+
if pathspec and _object is None:
|
|
299
322
|
ids = pathspec.split("/")
|
|
300
323
|
|
|
301
324
|
if self._NAME == "flow" and len(ids) != 1:
|
|
@@ -339,8 +362,8 @@ class MetaflowObject(object):
|
|
|
339
362
|
self._user_tags = frozenset(self._object.get("tags") or [])
|
|
340
363
|
self._system_tags = frozenset(self._object.get("system_tags") or [])
|
|
341
364
|
|
|
342
|
-
if self._namespace_check and not self.
|
|
343
|
-
raise MetaflowNamespaceMismatch(
|
|
365
|
+
if self._namespace_check and not self._is_in_namespace(self._current_namespace):
|
|
366
|
+
raise MetaflowNamespaceMismatch(self._current_namespace)
|
|
344
367
|
|
|
345
368
|
def _get_object(self, *path_components):
|
|
346
369
|
result = self._metaflow.metadata.get_object(
|
|
@@ -365,15 +388,15 @@ class MetaflowObject(object):
|
|
|
365
388
|
query_filter = {}
|
|
366
389
|
|
|
367
390
|
# skip namespace filtering if _namespace_check is unset.
|
|
368
|
-
if self._namespace_check and
|
|
369
|
-
query_filter = {"any_tags":
|
|
391
|
+
if self._namespace_check and self._current_namespace:
|
|
392
|
+
query_filter = {"any_tags": self._current_namespace}
|
|
370
393
|
|
|
371
394
|
unfiltered_children = self._metaflow.metadata.get_object(
|
|
372
395
|
self._NAME,
|
|
373
396
|
_CLASSES[self._CHILD_CLASS]._NAME,
|
|
374
397
|
query_filter,
|
|
375
398
|
self._attempt,
|
|
376
|
-
*self.path_components
|
|
399
|
+
*self.path_components,
|
|
377
400
|
)
|
|
378
401
|
unfiltered_children = unfiltered_children if unfiltered_children else []
|
|
379
402
|
children = filter(
|
|
@@ -383,7 +406,11 @@ class MetaflowObject(object):
|
|
|
383
406
|
attempt=self._attempt,
|
|
384
407
|
_object=obj,
|
|
385
408
|
_parent=self,
|
|
386
|
-
|
|
409
|
+
_metaflow=self._metaflow,
|
|
410
|
+
_namespace_check=self._namespace_check,
|
|
411
|
+
_current_namespace=(
|
|
412
|
+
self._current_namespace if self._namespace_check else None
|
|
413
|
+
),
|
|
387
414
|
)
|
|
388
415
|
for obj in unfiltered_children
|
|
389
416
|
),
|
|
@@ -422,6 +449,23 @@ class MetaflowObject(object):
|
|
|
422
449
|
|
|
423
450
|
If the current namespace is None, this will always return True.
|
|
424
451
|
|
|
452
|
+
Returns
|
|
453
|
+
-------
|
|
454
|
+
bool
|
|
455
|
+
Whether or not the object is in the current namespace
|
|
456
|
+
"""
|
|
457
|
+
return self._is_in_namespace(current_namespace)
|
|
458
|
+
|
|
459
|
+
def _is_in_namespace(self, ns: str) -> bool:
|
|
460
|
+
"""
|
|
461
|
+
Returns whether this object is in namespace passed in.
|
|
462
|
+
|
|
463
|
+
If the current namespace is None, this will always return True.
|
|
464
|
+
|
|
465
|
+
Parameters
|
|
466
|
+
----------
|
|
467
|
+
ns : str
|
|
468
|
+
Namespace to check if the object is in.
|
|
425
469
|
Returns
|
|
426
470
|
-------
|
|
427
471
|
bool
|
|
@@ -430,7 +474,7 @@ class MetaflowObject(object):
|
|
|
430
474
|
if self._NAME == "flow":
|
|
431
475
|
return any(True for _ in self)
|
|
432
476
|
else:
|
|
433
|
-
return
|
|
477
|
+
return ns is None or ns in self._tags
|
|
434
478
|
|
|
435
479
|
def __str__(self):
|
|
436
480
|
if self._attempt is not None:
|
|
@@ -478,7 +522,11 @@ class MetaflowObject(object):
|
|
|
478
522
|
attempt=self._attempt,
|
|
479
523
|
_object=obj,
|
|
480
524
|
_parent=self,
|
|
525
|
+
_metaflow=self._metaflow,
|
|
481
526
|
_namespace_check=self._namespace_check,
|
|
527
|
+
_current_namespace=(
|
|
528
|
+
self._current_namespace if self._namespace_check else None
|
|
529
|
+
),
|
|
482
530
|
)
|
|
483
531
|
else:
|
|
484
532
|
raise KeyError(id)
|
|
@@ -509,7 +557,38 @@ class MetaflowObject(object):
|
|
|
509
557
|
pathspec=pathspec, attempt=attempt, _namespace_check=namespace_check
|
|
510
558
|
)
|
|
511
559
|
|
|
512
|
-
|
|
560
|
+
def _unpickle_2124(self, data):
|
|
561
|
+
if len(data) != 4:
|
|
562
|
+
raise MetaflowInternalError(
|
|
563
|
+
"Unexpected size of array: {}".format(len(data))
|
|
564
|
+
)
|
|
565
|
+
pathspec, attempt, ns, namespace_check = data
|
|
566
|
+
self.__init__(
|
|
567
|
+
pathspec=pathspec,
|
|
568
|
+
attempt=attempt,
|
|
569
|
+
_namespace_check=namespace_check,
|
|
570
|
+
_current_namespace=ns,
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
def _unpickle_21227(self, data):
|
|
574
|
+
if len(data) != 5:
|
|
575
|
+
raise MetaflowInternalError(
|
|
576
|
+
"Unexpected size of array: {}".format(len(data))
|
|
577
|
+
)
|
|
578
|
+
pathspec, attempt, md, ns, namespace_check = data
|
|
579
|
+
self.__init__(
|
|
580
|
+
pathspec=pathspec,
|
|
581
|
+
attempt=attempt,
|
|
582
|
+
_namespace_check=namespace_check,
|
|
583
|
+
_current_metadata=md,
|
|
584
|
+
_current_namespace=ns,
|
|
585
|
+
)
|
|
586
|
+
|
|
587
|
+
_UNPICKLE_FUNC = {
|
|
588
|
+
"2.8.4": _unpickle_284,
|
|
589
|
+
"2.12.4": _unpickle_2124,
|
|
590
|
+
"2.12.27": _unpickle_21227,
|
|
591
|
+
}
|
|
513
592
|
|
|
514
593
|
def __setstate__(self, state):
|
|
515
594
|
"""
|
|
@@ -529,12 +608,13 @@ class MetaflowObject(object):
|
|
|
529
608
|
self._UNPICKLE_FUNC[version](self, state["data"])
|
|
530
609
|
else:
|
|
531
610
|
# For backward compatibility: handles pickled objects that were serialized without a __getstate__ override
|
|
532
|
-
# We set namespace_check to False if it doesn't exist
|
|
533
|
-
#
|
|
611
|
+
# We set namespace_check to False if it doesn't exist so that the user can
|
|
612
|
+
# continue accessing this object once unpickled.
|
|
534
613
|
self.__init__(
|
|
535
614
|
pathspec=state.get("_pathspec", None),
|
|
536
615
|
attempt=state.get("_attempt", None),
|
|
537
616
|
_namespace_check=state.get("_namespace_check", False),
|
|
617
|
+
_current_namespace=None,
|
|
538
618
|
)
|
|
539
619
|
|
|
540
620
|
def __getstate__(self):
|
|
@@ -546,16 +626,18 @@ class MetaflowObject(object):
|
|
|
546
626
|
from this object) are pickled (serialized) in a later version of Metaflow, it may not be possible
|
|
547
627
|
to unpickle (deserialize) them in a previous version of Metaflow.
|
|
548
628
|
"""
|
|
549
|
-
# Note that we
|
|
550
|
-
#
|
|
551
|
-
#
|
|
552
|
-
#
|
|
629
|
+
# Note that we now record the namespace at the time of the object creation so
|
|
630
|
+
# we don't need to force namespace_check to be False and can properly continue
|
|
631
|
+
# checking for the namespace even after unpickling since we will know which
|
|
632
|
+
# namespace to check.
|
|
553
633
|
return {
|
|
554
|
-
"version": "2.
|
|
634
|
+
"version": "2.12.27",
|
|
555
635
|
"data": [
|
|
556
636
|
self.pathspec,
|
|
557
637
|
self._attempt,
|
|
558
|
-
|
|
638
|
+
self._metaflow.metadata.metadata_str(),
|
|
639
|
+
self._current_namespace,
|
|
640
|
+
self._namespace_check,
|
|
559
641
|
],
|
|
560
642
|
}
|
|
561
643
|
|
|
@@ -625,7 +707,7 @@ class MetaflowObject(object):
|
|
|
625
707
|
origin_pathspec = None
|
|
626
708
|
if self._NAME == "run":
|
|
627
709
|
latest_step = next(self.steps())
|
|
628
|
-
if latest_step:
|
|
710
|
+
if latest_step and latest_step.task:
|
|
629
711
|
# If we had a step
|
|
630
712
|
task = latest_step.task
|
|
631
713
|
origin_run_id = [
|
|
@@ -750,20 +832,29 @@ class MetaflowCode(object):
|
|
|
750
832
|
self._path = info["location"]
|
|
751
833
|
self._ds_type = info["ds_type"]
|
|
752
834
|
self._sha = info["sha"]
|
|
835
|
+
self._code_metadata = info.get(
|
|
836
|
+
"metadata",
|
|
837
|
+
'{"version": 0, "archive_format": "tgz", "mfcontent_version": 0}',
|
|
838
|
+
)
|
|
839
|
+
|
|
840
|
+
self._backend = MetaflowPackage.get_backend(self._code_metadata)
|
|
753
841
|
|
|
754
842
|
if filecache is None:
|
|
755
843
|
filecache = FileCache()
|
|
756
844
|
_, blobdata = filecache.get_data(
|
|
757
845
|
self._ds_type, self._flow_name, self._path, self._sha
|
|
758
846
|
)
|
|
759
|
-
|
|
760
|
-
self.
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
self.
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
847
|
+
self._code_obj = BytesIO(blobdata)
|
|
848
|
+
self._info = MetaflowPackage.cls_get_info(self._code_metadata, self._code_obj)
|
|
849
|
+
self._code_obj.seek(0)
|
|
850
|
+
if self._info:
|
|
851
|
+
self._flowspec = MetaflowPackage.cls_get_content(
|
|
852
|
+
self._code_metadata, self._code_obj, self._info["script"]
|
|
853
|
+
)
|
|
854
|
+
self._code_obj.seek(0)
|
|
855
|
+
else:
|
|
856
|
+
raise MetaflowInternalError("Code package metadata is invalid.")
|
|
857
|
+
self._tarball = None
|
|
767
858
|
|
|
768
859
|
@property
|
|
769
860
|
def path(self) -> str:
|
|
@@ -811,7 +902,69 @@ class MetaflowCode(object):
|
|
|
811
902
|
TarFile
|
|
812
903
|
TarFile for everything in this code package
|
|
813
904
|
"""
|
|
814
|
-
return
|
|
905
|
+
# We only return one tarball because the different TarFile objects share
|
|
906
|
+
# a common bytes buffer (self._code_obj).
|
|
907
|
+
if self._tarball is not None:
|
|
908
|
+
return self._tarball
|
|
909
|
+
if self._backend.type == "tgz":
|
|
910
|
+
self._tarball = self._backend.cls_open(self._code_obj)
|
|
911
|
+
return self._tarball
|
|
912
|
+
raise RuntimeError("Archive is not a tarball")
|
|
913
|
+
|
|
914
|
+
def extract(self) -> TemporaryDirectory:
|
|
915
|
+
"""
|
|
916
|
+
Extracts the code package to a temporary directory.
|
|
917
|
+
|
|
918
|
+
This creates a temporary directory containing all user code
|
|
919
|
+
files from the code package. The temporary directory is
|
|
920
|
+
automatically deleted when the returned TemporaryDirectory
|
|
921
|
+
object is garbage collected or when its cleanup() is called.
|
|
922
|
+
|
|
923
|
+
To preserve the contents to a permanent location, use
|
|
924
|
+
os.replace() which performs a zero-copy move on the same
|
|
925
|
+
filesystem:
|
|
926
|
+
|
|
927
|
+
```python
|
|
928
|
+
with task.code.extract() as tmp_dir:
|
|
929
|
+
# Move contents to permanent location
|
|
930
|
+
for item in os.listdir(tmp_dir):
|
|
931
|
+
src = os.path.join(tmp_dir, item)
|
|
932
|
+
dst = os.path.join('/path/to/permanent/dir', item)
|
|
933
|
+
os.makedirs(os.path.dirname(dst), exist_ok=True)
|
|
934
|
+
os.replace(src, dst) # Atomic move operation
|
|
935
|
+
```
|
|
936
|
+
Returns
|
|
937
|
+
-------
|
|
938
|
+
TemporaryDirectory
|
|
939
|
+
A temporary directory containing the extracted code files.
|
|
940
|
+
The directory and its contents are automatically deleted when
|
|
941
|
+
this object is garbage collected.
|
|
942
|
+
"""
|
|
943
|
+
tmp = TemporaryDirectory()
|
|
944
|
+
# We save the position we are in _code_obj -- in case tarball is using it at
|
|
945
|
+
# the same time -- so we can reset it to not perturb tarball.
|
|
946
|
+
pos = self._code_obj.tell()
|
|
947
|
+
self._code_obj.seek(0)
|
|
948
|
+
MetaflowPackage.cls_extract_into(
|
|
949
|
+
self._code_metadata, self._code_obj, tmp.name, ContentType.USER_CONTENT
|
|
950
|
+
)
|
|
951
|
+
self._code_obj.seek(pos)
|
|
952
|
+
return tmp
|
|
953
|
+
|
|
954
|
+
@property
|
|
955
|
+
def script_name(self) -> str:
|
|
956
|
+
"""
|
|
957
|
+
Returns the filename of the Python script containing the FlowSpec.
|
|
958
|
+
|
|
959
|
+
This is the main Python file that was used to execute the flow. For example,
|
|
960
|
+
if your flow is defined in 'myflow.py', this property will return 'myflow.py'.
|
|
961
|
+
|
|
962
|
+
Returns
|
|
963
|
+
-------
|
|
964
|
+
str
|
|
965
|
+
Name of the Python file containing the FlowSpec
|
|
966
|
+
"""
|
|
967
|
+
return self._info["script"]
|
|
815
968
|
|
|
816
969
|
def __str__(self):
|
|
817
970
|
return "<MetaflowCode: %s>" % self._info["script"]
|
|
@@ -982,6 +1135,8 @@ class MetaflowData(object):
|
|
|
982
1135
|
self._artifacts = dict((art.id, art) for art in artifacts)
|
|
983
1136
|
|
|
984
1137
|
def __getattr__(self, name: str):
|
|
1138
|
+
if name not in self._artifacts:
|
|
1139
|
+
raise AttributeError(name)
|
|
985
1140
|
return self._artifacts[name].data
|
|
986
1141
|
|
|
987
1142
|
def __contains__(self, var):
|
|
@@ -1049,13 +1204,198 @@ class Task(MetaflowObject):
|
|
|
1049
1204
|
_PARENT_CLASS = "step"
|
|
1050
1205
|
_CHILD_CLASS = "artifact"
|
|
1051
1206
|
|
|
1052
|
-
def __init__(self, *args, **kwargs):
|
|
1053
|
-
super(Task, self).__init__(*args, **kwargs)
|
|
1054
|
-
|
|
1055
1207
|
def _iter_filter(self, x):
|
|
1056
1208
|
# exclude private data artifacts
|
|
1057
1209
|
return x.id[0] != "_"
|
|
1058
1210
|
|
|
1211
|
+
def _get_matching_pathspecs(self, steps, metadata_key, metadata_pattern):
|
|
1212
|
+
"""
|
|
1213
|
+
Yield pathspecs of tasks from specified steps that match a given metadata pattern.
|
|
1214
|
+
|
|
1215
|
+
Parameters
|
|
1216
|
+
----------
|
|
1217
|
+
steps : List[str]
|
|
1218
|
+
List of Step objects to search for tasks.
|
|
1219
|
+
metadata_key : str
|
|
1220
|
+
Metadata key to filter tasks on (e.g., 'foreach-execution-path').
|
|
1221
|
+
metadata_pattern : str
|
|
1222
|
+
Regular expression pattern to match against the metadata value.
|
|
1223
|
+
|
|
1224
|
+
Yields
|
|
1225
|
+
------
|
|
1226
|
+
str
|
|
1227
|
+
Pathspec of each task whose metadata value for the specified key matches the pattern.
|
|
1228
|
+
"""
|
|
1229
|
+
flow_id, run_id, _, _ = self.path_components
|
|
1230
|
+
for step in steps:
|
|
1231
|
+
task_pathspecs = self._metaflow.metadata.filter_tasks_by_metadata(
|
|
1232
|
+
flow_id, run_id, step, metadata_key, metadata_pattern
|
|
1233
|
+
)
|
|
1234
|
+
for task_pathspec in task_pathspecs:
|
|
1235
|
+
yield task_pathspec
|
|
1236
|
+
|
|
1237
|
+
@staticmethod
|
|
1238
|
+
def _get_previous_steps(graph_info, step_name):
|
|
1239
|
+
# Get the parent steps
|
|
1240
|
+
steps = []
|
|
1241
|
+
for node_name, attributes in graph_info["steps"].items():
|
|
1242
|
+
if step_name in attributes["next"]:
|
|
1243
|
+
steps.append(node_name)
|
|
1244
|
+
return steps
|
|
1245
|
+
|
|
1246
|
+
@property
|
|
1247
|
+
def parent_task_pathspecs(self) -> Iterator[str]:
|
|
1248
|
+
"""
|
|
1249
|
+
Yields pathspecs of all parent tasks of the current task.
|
|
1250
|
+
|
|
1251
|
+
Yields
|
|
1252
|
+
------
|
|
1253
|
+
str
|
|
1254
|
+
Pathspec of the parent task of the current task
|
|
1255
|
+
"""
|
|
1256
|
+
_, _, step_name, _ = self.path_components
|
|
1257
|
+
metadata_dict = self.metadata_dict
|
|
1258
|
+
graph_info = self["_graph_info"].data
|
|
1259
|
+
|
|
1260
|
+
# Get the parent steps
|
|
1261
|
+
steps = self._get_previous_steps(graph_info, step_name)
|
|
1262
|
+
node_type = graph_info["steps"][step_name]["type"]
|
|
1263
|
+
metadata_key = "foreach-execution-path"
|
|
1264
|
+
current_path = metadata_dict.get(metadata_key)
|
|
1265
|
+
|
|
1266
|
+
if len(steps) > 1:
|
|
1267
|
+
# Static join - use exact path matching
|
|
1268
|
+
pattern = current_path or ".*"
|
|
1269
|
+
else:
|
|
1270
|
+
if not steps:
|
|
1271
|
+
return # No parent steps, yield nothing
|
|
1272
|
+
|
|
1273
|
+
if not current_path:
|
|
1274
|
+
# Current task is not part of a foreach
|
|
1275
|
+
# Pattern: ".*"
|
|
1276
|
+
pattern = ".*"
|
|
1277
|
+
else:
|
|
1278
|
+
current_depth = len(current_path.split(","))
|
|
1279
|
+
if node_type == "join":
|
|
1280
|
+
# Foreach join
|
|
1281
|
+
# (Current task, "A:10,B:13") and (Parent task, "A:10,B:13,C:21")
|
|
1282
|
+
# Pattern: "A:10,B:13,.*"
|
|
1283
|
+
pattern = f"{current_path},.*"
|
|
1284
|
+
else:
|
|
1285
|
+
# Foreach split or linear step
|
|
1286
|
+
# Pattern: "A:10,B:13"
|
|
1287
|
+
parent_step_type = graph_info["steps"][steps[0]]["type"]
|
|
1288
|
+
target_depth = current_depth
|
|
1289
|
+
if (
|
|
1290
|
+
parent_step_type == "split-foreach"
|
|
1291
|
+
or parent_step_type == "split-parallel"
|
|
1292
|
+
) and current_depth == 1:
|
|
1293
|
+
# (Current task, "A:10") and (Parent task, "")
|
|
1294
|
+
pattern = ".*"
|
|
1295
|
+
else:
|
|
1296
|
+
# (Current task, "A:10,B:13,C:21") and (Parent task, "A:10,B:13")
|
|
1297
|
+
# (Current task, "A:10,B:13") and (Parent task, "A:10,B:13")
|
|
1298
|
+
if (
|
|
1299
|
+
parent_step_type == "split-foreach"
|
|
1300
|
+
or parent_step_type == "split-parallel"
|
|
1301
|
+
):
|
|
1302
|
+
target_depth = current_depth - 1
|
|
1303
|
+
pattern = ",".join(current_path.split(",")[:target_depth])
|
|
1304
|
+
|
|
1305
|
+
for pathspec in self._get_matching_pathspecs(steps, metadata_key, pattern):
|
|
1306
|
+
yield pathspec
|
|
1307
|
+
|
|
1308
|
+
@property
|
|
1309
|
+
def child_task_pathspecs(self) -> Iterator[str]:
|
|
1310
|
+
"""
|
|
1311
|
+
Yields pathspecs of all child tasks of the current task.
|
|
1312
|
+
|
|
1313
|
+
Yields
|
|
1314
|
+
------
|
|
1315
|
+
str
|
|
1316
|
+
Pathspec of the child task of the current task
|
|
1317
|
+
"""
|
|
1318
|
+
flow_id, run_id, step_name, _ = self.path_components
|
|
1319
|
+
metadata_dict = self.metadata_dict
|
|
1320
|
+
graph_info = self["_graph_info"].data
|
|
1321
|
+
|
|
1322
|
+
# Get the child steps
|
|
1323
|
+
steps = graph_info["steps"][step_name]["next"]
|
|
1324
|
+
|
|
1325
|
+
node_type = graph_info["steps"][step_name]["type"]
|
|
1326
|
+
metadata_key = "foreach-execution-path"
|
|
1327
|
+
current_path = metadata_dict.get(metadata_key)
|
|
1328
|
+
|
|
1329
|
+
if len(steps) > 1:
|
|
1330
|
+
# Static split - use exact path matching
|
|
1331
|
+
pattern = current_path or ".*"
|
|
1332
|
+
else:
|
|
1333
|
+
if not steps:
|
|
1334
|
+
return # No child steps, yield nothing
|
|
1335
|
+
|
|
1336
|
+
if not current_path:
|
|
1337
|
+
# Current task is not part of a foreach
|
|
1338
|
+
# Pattern: ".*"
|
|
1339
|
+
pattern = ".*"
|
|
1340
|
+
else:
|
|
1341
|
+
current_depth = len(current_path.split(","))
|
|
1342
|
+
if node_type == "split-foreach" or node_type == "split-parallel":
|
|
1343
|
+
# Foreach split
|
|
1344
|
+
# (Current task, "A:10,B:13") and (Child task, "A:10,B:13,C:21")
|
|
1345
|
+
# Pattern: "A:10,B:13,.*"
|
|
1346
|
+
pattern = f"{current_path},.*"
|
|
1347
|
+
else:
|
|
1348
|
+
# Foreach join or linear step
|
|
1349
|
+
# Pattern: "A:10,B:13"
|
|
1350
|
+
child_step_type = graph_info["steps"][steps[0]]["type"]
|
|
1351
|
+
|
|
1352
|
+
# We need to know if the child step is a foreach join or a static join
|
|
1353
|
+
child_step_prev_steps = self._get_previous_steps(
|
|
1354
|
+
graph_info, steps[0]
|
|
1355
|
+
)
|
|
1356
|
+
if len(child_step_prev_steps) > 1:
|
|
1357
|
+
child_step_type = "static-join"
|
|
1358
|
+
target_depth = current_depth
|
|
1359
|
+
if child_step_type == "join" and current_depth == 1:
|
|
1360
|
+
# (Current task, "A:10") and (Child task, "")
|
|
1361
|
+
pattern = ".*"
|
|
1362
|
+
else:
|
|
1363
|
+
# (Current task, "A:10,B:13,C:21") and (Child task, "A:10,B:13")
|
|
1364
|
+
# (Current task, "A:10,B:13") and (Child task, "A:10,B:13")
|
|
1365
|
+
if child_step_type == "join":
|
|
1366
|
+
target_depth = current_depth - 1
|
|
1367
|
+
pattern = ",".join(current_path.split(",")[:target_depth])
|
|
1368
|
+
|
|
1369
|
+
for pathspec in self._get_matching_pathspecs(steps, metadata_key, pattern):
|
|
1370
|
+
yield pathspec
|
|
1371
|
+
|
|
1372
|
+
@property
|
|
1373
|
+
def parent_tasks(self) -> Iterator["Task"]:
|
|
1374
|
+
"""
|
|
1375
|
+
Yields all parent tasks of the current task if one exists.
|
|
1376
|
+
|
|
1377
|
+
Yields
|
|
1378
|
+
------
|
|
1379
|
+
Task
|
|
1380
|
+
Parent task of the current task
|
|
1381
|
+
"""
|
|
1382
|
+
parent_task_pathspecs = self.parent_task_pathspecs
|
|
1383
|
+
for pathspec in parent_task_pathspecs:
|
|
1384
|
+
yield Task(pathspec=pathspec, _namespace_check=False)
|
|
1385
|
+
|
|
1386
|
+
@property
|
|
1387
|
+
def child_tasks(self) -> Iterator["Task"]:
|
|
1388
|
+
"""
|
|
1389
|
+
Yields all child tasks of the current task if one exists.
|
|
1390
|
+
|
|
1391
|
+
Yields
|
|
1392
|
+
------
|
|
1393
|
+
Task
|
|
1394
|
+
Child task of the current task
|
|
1395
|
+
"""
|
|
1396
|
+
for pathspec in self.child_task_pathspecs:
|
|
1397
|
+
yield Task(pathspec=pathspec, _namespace_check=False)
|
|
1398
|
+
|
|
1059
1399
|
@property
|
|
1060
1400
|
def metadata(self) -> List[Metadata]:
|
|
1061
1401
|
"""
|
|
@@ -1770,6 +2110,41 @@ class Step(MetaflowObject):
|
|
|
1770
2110
|
for t in self:
|
|
1771
2111
|
return t.environment_info
|
|
1772
2112
|
|
|
2113
|
+
@property
|
|
2114
|
+
def parent_steps(self) -> Iterator["Step"]:
|
|
2115
|
+
"""
|
|
2116
|
+
Yields parent steps for the current step.
|
|
2117
|
+
|
|
2118
|
+
Yields
|
|
2119
|
+
------
|
|
2120
|
+
Step
|
|
2121
|
+
Parent step
|
|
2122
|
+
"""
|
|
2123
|
+
graph_info = self.task["_graph_info"].data
|
|
2124
|
+
|
|
2125
|
+
if self.id != "start":
|
|
2126
|
+
flow, run, _ = self.path_components
|
|
2127
|
+
for node_name, attributes in graph_info["steps"].items():
|
|
2128
|
+
if self.id in attributes["next"]:
|
|
2129
|
+
yield Step(f"{flow}/{run}/{node_name}", _namespace_check=False)
|
|
2130
|
+
|
|
2131
|
+
@property
|
|
2132
|
+
def child_steps(self) -> Iterator["Step"]:
|
|
2133
|
+
"""
|
|
2134
|
+
Yields child steps for the current step.
|
|
2135
|
+
|
|
2136
|
+
Yields
|
|
2137
|
+
------
|
|
2138
|
+
Step
|
|
2139
|
+
Child step
|
|
2140
|
+
"""
|
|
2141
|
+
graph_info = self.task["_graph_info"].data
|
|
2142
|
+
|
|
2143
|
+
if self.id != "end":
|
|
2144
|
+
flow, run, _ = self.path_components
|
|
2145
|
+
for next_step in graph_info["steps"][self.id]["next"]:
|
|
2146
|
+
yield Step(f"{flow}/{run}/{next_step}", _namespace_check=False)
|
|
2147
|
+
|
|
1773
2148
|
|
|
1774
2149
|
class Run(MetaflowObject):
|
|
1775
2150
|
"""
|
|
@@ -1841,9 +2216,10 @@ class Run(MetaflowObject):
|
|
|
1841
2216
|
# TODO: A more optimized way of figuring out if a run has remote steps (and thus a codepackage) available.
|
|
1842
2217
|
# This might require changes to the metadata-service as well.
|
|
1843
2218
|
for step in self:
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
2219
|
+
if step.task:
|
|
2220
|
+
code = step.task.code
|
|
2221
|
+
if code:
|
|
2222
|
+
return code
|
|
1847
2223
|
|
|
1848
2224
|
@property
|
|
1849
2225
|
def data(self) -> Optional[MetaflowData]:
|
|
@@ -2105,7 +2481,7 @@ class Run(MetaflowObject):
|
|
|
2105
2481
|
Trigger, optional
|
|
2106
2482
|
Container of triggering events
|
|
2107
2483
|
"""
|
|
2108
|
-
if "start" in self:
|
|
2484
|
+
if "start" in self and self["start"].task:
|
|
2109
2485
|
meta = self["start"].task.metadata_dict.get("execution-triggers")
|
|
2110
2486
|
if meta:
|
|
2111
2487
|
return Trigger(json.loads(meta))
|
|
@@ -2240,17 +2616,16 @@ class Metaflow(object):
|
|
|
2240
2616
|
if it has at least one run in the namespace.
|
|
2241
2617
|
"""
|
|
2242
2618
|
|
|
2243
|
-
def __init__(self):
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
self.metadata = current_metadata
|
|
2619
|
+
def __init__(self, _current_metadata: Optional[str] = None):
|
|
2620
|
+
if _current_metadata:
|
|
2621
|
+
provider, info = _metadata(_current_metadata)
|
|
2622
|
+
self.metadata = provider
|
|
2623
|
+
if info:
|
|
2624
|
+
self.metadata.INFO = info
|
|
2625
|
+
else:
|
|
2626
|
+
if current_metadata is False:
|
|
2627
|
+
default_metadata()
|
|
2628
|
+
self.metadata = current_metadata
|
|
2254
2629
|
|
|
2255
2630
|
@property
|
|
2256
2631
|
def flows(self) -> List[Flow]:
|
|
@@ -2287,7 +2662,7 @@ class Metaflow(object):
|
|
|
2287
2662
|
all_flows = all_flows if all_flows else []
|
|
2288
2663
|
for flow in all_flows:
|
|
2289
2664
|
try:
|
|
2290
|
-
v = Flow(_object=flow)
|
|
2665
|
+
v = Flow(_object=flow, _metaflow=self)
|
|
2291
2666
|
yield v
|
|
2292
2667
|
except MetaflowNamespaceMismatch:
|
|
2293
2668
|
continue
|
|
@@ -2311,7 +2686,26 @@ class Metaflow(object):
|
|
|
2311
2686
|
Flow
|
|
2312
2687
|
Flow with the given name.
|
|
2313
2688
|
"""
|
|
2314
|
-
return Flow(
|
|
2689
|
+
return Flow(name, _metaflow=self)
|
|
2690
|
+
|
|
2691
|
+
|
|
2692
|
+
def _metadata(ms: str) -> Tuple[Optional["MetadataProvider"], Optional[str]]:
|
|
2693
|
+
infos = ms.split("@", 1)
|
|
2694
|
+
types = [m.TYPE for m in METADATA_PROVIDERS]
|
|
2695
|
+
if infos[0] in types:
|
|
2696
|
+
provider = [m for m in METADATA_PROVIDERS if m.TYPE == infos[0]][0]
|
|
2697
|
+
if len(infos) > 1:
|
|
2698
|
+
return provider, infos[1]
|
|
2699
|
+
return provider, None
|
|
2700
|
+
# Deduce from ms; if starts with http, use service or else use local
|
|
2701
|
+
if ms.startswith("http"):
|
|
2702
|
+
metadata_type = "service"
|
|
2703
|
+
else:
|
|
2704
|
+
metadata_type = "local"
|
|
2705
|
+
res = [m for m in METADATA_PROVIDERS if m.TYPE == metadata_type]
|
|
2706
|
+
if not res:
|
|
2707
|
+
return None, None
|
|
2708
|
+
return res[0], ms
|
|
2315
2709
|
|
|
2316
2710
|
|
|
2317
2711
|
_CLASSES["flow"] = Flow
|