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/flowspec.py
CHANGED
|
@@ -4,19 +4,35 @@ import sys
|
|
|
4
4
|
import traceback
|
|
5
5
|
import reprlib
|
|
6
6
|
|
|
7
|
+
from collections.abc import MutableMapping
|
|
8
|
+
from enum import Enum
|
|
7
9
|
from itertools import islice
|
|
8
10
|
from types import FunctionType, MethodType
|
|
9
11
|
from typing import Any, Callable, List, Optional, Tuple
|
|
10
12
|
|
|
11
13
|
from . import cmd_with_io, parameters
|
|
14
|
+
from .debug import debug
|
|
12
15
|
from .parameters import DelayedEvaluationParameter, Parameter
|
|
13
16
|
from .exception import (
|
|
14
17
|
MetaflowException,
|
|
15
18
|
MissingInMergeArtifactsException,
|
|
19
|
+
MetaflowInternalError,
|
|
16
20
|
UnhandledInMergeArtifactsException,
|
|
17
21
|
)
|
|
22
|
+
|
|
23
|
+
from .extension_support import extension_info
|
|
24
|
+
|
|
18
25
|
from .graph import FlowGraph
|
|
19
26
|
from .unbounded_foreach import UnboundedForeachInput
|
|
27
|
+
from .user_configs.config_parameters import ConfigValue
|
|
28
|
+
|
|
29
|
+
from .user_decorators.mutable_flow import MutableFlow
|
|
30
|
+
from .user_decorators.mutable_step import MutableStep
|
|
31
|
+
from .user_decorators.user_flow_decorator import FlowMutator
|
|
32
|
+
from .user_decorators.user_step_decorator import StepMutator
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
from .util import to_pod
|
|
20
36
|
from .metaflow_config import INCLUDE_FOREACH_STACK, MAXIMUM_FOREACH_VALUE_CHARS
|
|
21
37
|
|
|
22
38
|
# For Python 3 compatibility
|
|
@@ -28,7 +44,15 @@ except NameError:
|
|
|
28
44
|
|
|
29
45
|
from .datastore.inputs import Inputs
|
|
30
46
|
|
|
31
|
-
INTERNAL_ARTIFACTS_SET = set(
|
|
47
|
+
INTERNAL_ARTIFACTS_SET = set(
|
|
48
|
+
[
|
|
49
|
+
"_foreach_values",
|
|
50
|
+
"_unbounded_foreach",
|
|
51
|
+
"_control_mapper_tasks",
|
|
52
|
+
"_control_task_is_mapper_zero",
|
|
53
|
+
"_parallel_ubf_iter",
|
|
54
|
+
]
|
|
55
|
+
)
|
|
32
56
|
|
|
33
57
|
|
|
34
58
|
class InvalidNextException(MetaflowException):
|
|
@@ -53,7 +77,187 @@ class ParallelUBF(UnboundedForeachInput):
|
|
|
53
77
|
return item or 0 # item is None for the control task, but it is also split 0
|
|
54
78
|
|
|
55
79
|
|
|
56
|
-
class
|
|
80
|
+
class FlowStateItems(Enum):
|
|
81
|
+
CONFIGS = 1
|
|
82
|
+
FLOW_MUTATORS = 2
|
|
83
|
+
CACHED_PARAMETERS = 3
|
|
84
|
+
SET_CONFIG_PARAMETERS = 4 # Parameters that now have a ConfigValue (converted)
|
|
85
|
+
FLOW_DECORATORS = 5
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class _FlowState(MutableMapping):
|
|
89
|
+
# Dict like structure to hold state information about the flow but it holds
|
|
90
|
+
# the key/values in two sub dictionaries: the ones that are specific to the flow
|
|
91
|
+
# and the ones that are inherited from parent classes.
|
|
92
|
+
# This is NOT a general purpose class and is meant to only work with FlowSpec.
|
|
93
|
+
# For example, it assumes that items are only list, dicts or None and assumes that
|
|
94
|
+
# self._self_data has all keys properly initialized.
|
|
95
|
+
|
|
96
|
+
def __init__(self, *args, **kwargs):
|
|
97
|
+
self._self_data = dict(*args, **kwargs)
|
|
98
|
+
self._merged_data = {}
|
|
99
|
+
self._inherited = {}
|
|
100
|
+
|
|
101
|
+
def __getitem__(self, key):
|
|
102
|
+
# ORDER IS IMPORTANT: we use inherited first and extend by whatever is in
|
|
103
|
+
# the flowspec
|
|
104
|
+
if key in self._merged_data:
|
|
105
|
+
return self._merged_data[key]
|
|
106
|
+
|
|
107
|
+
# We haven't accessed this yet so compute it for the first time
|
|
108
|
+
self_value = self._self_data.get(key)
|
|
109
|
+
inherited_value = self._inherited.get(key)
|
|
110
|
+
|
|
111
|
+
if self_value is not None:
|
|
112
|
+
self._merged_data[key] = self._merge_value(inherited_value, self_value)
|
|
113
|
+
return self._merged_data[key]
|
|
114
|
+
elif key in self._self_data:
|
|
115
|
+
# Case of CACHED_PARAMETERS; a valid value is None. It is never inherited
|
|
116
|
+
self._merged_data[key] = None
|
|
117
|
+
return None
|
|
118
|
+
raise KeyError(key)
|
|
119
|
+
|
|
120
|
+
def __setitem__(self, key, value):
|
|
121
|
+
self._self_data[key] = value
|
|
122
|
+
|
|
123
|
+
def __delitem__(self, key):
|
|
124
|
+
if key in self._merged_data:
|
|
125
|
+
del self._merged_data[key]
|
|
126
|
+
else:
|
|
127
|
+
raise KeyError(key)
|
|
128
|
+
|
|
129
|
+
def __iter__(self):
|
|
130
|
+
# All keys are in self._self_data
|
|
131
|
+
for key in self._self_data:
|
|
132
|
+
yield self[key]
|
|
133
|
+
|
|
134
|
+
def __len__(self):
|
|
135
|
+
return len(self._self_data)
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def self_data(self):
|
|
139
|
+
self._merged_data.clear()
|
|
140
|
+
return self._self_data
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def inherited_data(self):
|
|
144
|
+
return self._inherited
|
|
145
|
+
|
|
146
|
+
def _merge_value(self, inherited_value, self_value):
|
|
147
|
+
if self_value is None:
|
|
148
|
+
return None
|
|
149
|
+
inherited_value = inherited_value or type(self_value)()
|
|
150
|
+
if isinstance(self_value, dict):
|
|
151
|
+
return {**inherited_value, **self_value}
|
|
152
|
+
elif isinstance(self_value, list):
|
|
153
|
+
return inherited_value + self_value
|
|
154
|
+
raise RuntimeError(
|
|
155
|
+
f"Cannot merge values of type {type(inherited_value)} and {type(self_value)} -- "
|
|
156
|
+
"please report this as a bug"
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class FlowSpecMeta(type):
|
|
161
|
+
def __init__(cls, name, bases, attrs):
|
|
162
|
+
super().__init__(name, bases, attrs)
|
|
163
|
+
if name == "FlowSpec":
|
|
164
|
+
return
|
|
165
|
+
|
|
166
|
+
cls._init_attrs()
|
|
167
|
+
|
|
168
|
+
def _init_attrs(cls):
|
|
169
|
+
from .decorators import (
|
|
170
|
+
DuplicateFlowDecoratorException,
|
|
171
|
+
) # Prevent circular import
|
|
172
|
+
|
|
173
|
+
# We store some state in the flow class itself. This is primarily used to
|
|
174
|
+
# attach global state to a flow. It is *not* an actual global because of
|
|
175
|
+
# Runner/NBRunner. This is also created here in the meta class to avoid it being
|
|
176
|
+
# shared between different children classes.
|
|
177
|
+
|
|
178
|
+
# Keys are FlowStateItems enum values
|
|
179
|
+
cls._flow_state = _FlowState(
|
|
180
|
+
{
|
|
181
|
+
FlowStateItems.CONFIGS: {},
|
|
182
|
+
FlowStateItems.FLOW_MUTATORS: [],
|
|
183
|
+
FlowStateItems.CACHED_PARAMETERS: None,
|
|
184
|
+
FlowStateItems.SET_CONFIG_PARAMETERS: [],
|
|
185
|
+
FlowStateItems.FLOW_DECORATORS: {},
|
|
186
|
+
}
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# Keep track if configs have been processed -- this is particularly applicable
|
|
190
|
+
# for the Runner/Deployer where calling multiple APIs on the same flow could
|
|
191
|
+
# cause the configs to be processed multiple times. For a given flow, once
|
|
192
|
+
# the configs have been processed, we do not process them again.
|
|
193
|
+
cls._configs_processed = False
|
|
194
|
+
|
|
195
|
+
# We inherit stuff from our parent classes as well -- we need to be careful
|
|
196
|
+
# in terms of the order; we will follow the MRO with the following rules:
|
|
197
|
+
# - decorators will cause an error if they do not
|
|
198
|
+
# support multiple and we see multiple instances of the same
|
|
199
|
+
# - config decorators will be joined
|
|
200
|
+
# - configs will be added later directly by the class; base class configs will
|
|
201
|
+
# be taken into account as they would be inherited.
|
|
202
|
+
|
|
203
|
+
# We only need to do this for the base classes since the current class will
|
|
204
|
+
# get updated as decorators are parsed.
|
|
205
|
+
|
|
206
|
+
# We also need to be sure to not duplicate things. Consider something like
|
|
207
|
+
# class A(FlowSpec):
|
|
208
|
+
# pass
|
|
209
|
+
#
|
|
210
|
+
# class B(A):
|
|
211
|
+
# pass
|
|
212
|
+
#
|
|
213
|
+
# class C(B):
|
|
214
|
+
# pass
|
|
215
|
+
#
|
|
216
|
+
# C inherits from both B and A but we need to duplicate things from A only
|
|
217
|
+
# ONCE. To do this, we only propagate the self data from each class.
|
|
218
|
+
|
|
219
|
+
for base in cls.__mro__:
|
|
220
|
+
if base != cls and base != FlowSpec and issubclass(base, FlowSpec):
|
|
221
|
+
# Take care of decorators
|
|
222
|
+
base_flow_decorators = base._flow_state.self_data[
|
|
223
|
+
FlowStateItems.FLOW_DECORATORS
|
|
224
|
+
]
|
|
225
|
+
|
|
226
|
+
inherited_cls_flow_decorators = (
|
|
227
|
+
cls._flow_state.inherited_data.setdefault(
|
|
228
|
+
FlowStateItems.FLOW_DECORATORS, {}
|
|
229
|
+
)
|
|
230
|
+
)
|
|
231
|
+
for deco_name, deco in base_flow_decorators.items():
|
|
232
|
+
if not deco:
|
|
233
|
+
continue
|
|
234
|
+
deco_allow_multiple = deco[0].allow_multiple
|
|
235
|
+
if (
|
|
236
|
+
deco_name in inherited_cls_flow_decorators
|
|
237
|
+
and not deco_allow_multiple
|
|
238
|
+
):
|
|
239
|
+
raise DuplicateFlowDecoratorException(deco_name)
|
|
240
|
+
inherited_cls_flow_decorators.setdefault(deco_name, []).extend(deco)
|
|
241
|
+
|
|
242
|
+
# Take care of flow mutators -- configs are just objects in the class
|
|
243
|
+
# so they are naturally inherited. We do not need to do anything special
|
|
244
|
+
# for them.
|
|
245
|
+
base_mutators = base._flow_state.self_data[FlowStateItems.FLOW_MUTATORS]
|
|
246
|
+
if base_mutators:
|
|
247
|
+
cls._flow_state.inherited_data.setdefault(
|
|
248
|
+
FlowStateItems.FLOW_MUTATORS, []
|
|
249
|
+
).extend(base_mutators)
|
|
250
|
+
|
|
251
|
+
cls._init_graph()
|
|
252
|
+
|
|
253
|
+
def _init_graph(cls):
|
|
254
|
+
# Graph and steps are specific to the class -- store here so we can access
|
|
255
|
+
# in class method _process_config_decorators
|
|
256
|
+
cls._graph = FlowGraph(cls)
|
|
257
|
+
cls._steps = [getattr(cls, node.name) for node in cls._graph]
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
class FlowSpec(metaclass=FlowSpecMeta):
|
|
57
261
|
"""
|
|
58
262
|
Main class from which all Flows should inherit.
|
|
59
263
|
|
|
@@ -72,7 +276,7 @@ class FlowSpec(object):
|
|
|
72
276
|
"_datastore",
|
|
73
277
|
"_cached_input",
|
|
74
278
|
"_graph",
|
|
75
|
-
"
|
|
279
|
+
"_flow_state",
|
|
76
280
|
"_steps",
|
|
77
281
|
"index",
|
|
78
282
|
"input",
|
|
@@ -83,8 +287,6 @@ class FlowSpec(object):
|
|
|
83
287
|
# names starting with `_` as those are already excluded from `_get_parameters`.
|
|
84
288
|
_NON_PARAMETERS = {"cmd", "foreach_stack", "index", "input", "script_name", "name"}
|
|
85
289
|
|
|
86
|
-
_flow_decorators = {}
|
|
87
|
-
|
|
88
290
|
def __init__(self, use_cli=True):
|
|
89
291
|
"""
|
|
90
292
|
Construct a FlowSpec
|
|
@@ -101,18 +303,11 @@ class FlowSpec(object):
|
|
|
101
303
|
self._transition = None
|
|
102
304
|
self._cached_input = {}
|
|
103
305
|
|
|
104
|
-
self._graph = FlowGraph(self.__class__)
|
|
105
|
-
self._steps = [getattr(self, node.name) for node in self._graph]
|
|
106
|
-
|
|
107
|
-
# This must be set before calling cli.main() below (or specifically, add_custom_parameters)
|
|
108
|
-
parameters.parameters = [p for _, p in self._get_parameters()]
|
|
109
|
-
|
|
110
306
|
if use_cli:
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
from . import cli
|
|
307
|
+
with parameters.flow_context(self.__class__) as _:
|
|
308
|
+
from . import cli
|
|
114
309
|
|
|
115
|
-
|
|
310
|
+
cli.main(self)
|
|
116
311
|
|
|
117
312
|
@property
|
|
118
313
|
def script_name(self) -> str:
|
|
@@ -131,16 +326,17 @@ class FlowSpec(object):
|
|
|
131
326
|
fname = fname[:-1]
|
|
132
327
|
return os.path.basename(fname)
|
|
133
328
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
329
|
+
@property
|
|
330
|
+
def _flow_decorators(self):
|
|
331
|
+
# Backward compatible method to access flow decorators
|
|
332
|
+
return self._flow_state[FlowStateItems.FLOW_DECORATORS]
|
|
138
333
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
# persist all values set using setattr
|
|
334
|
+
@classmethod
|
|
335
|
+
def _check_parameters(cls, config_parameters=False):
|
|
142
336
|
seen = set()
|
|
143
|
-
for
|
|
337
|
+
for _, param in cls._get_parameters():
|
|
338
|
+
if param.IS_CONFIG_PARAMETER != config_parameters:
|
|
339
|
+
continue
|
|
144
340
|
norm = param.name.lower()
|
|
145
341
|
if norm in seen:
|
|
146
342
|
raise MetaflowException(
|
|
@@ -149,17 +345,152 @@ class FlowSpec(object):
|
|
|
149
345
|
"case-insensitive." % param.name
|
|
150
346
|
)
|
|
151
347
|
seen.add(norm)
|
|
152
|
-
|
|
348
|
+
|
|
349
|
+
@classmethod
|
|
350
|
+
def _process_config_decorators(cls, config_options, process_configs=True):
|
|
351
|
+
if cls._configs_processed:
|
|
352
|
+
debug.userconf_exec("Mutating step/flow decorators already processed")
|
|
353
|
+
return None
|
|
354
|
+
cls._configs_processed = True
|
|
355
|
+
|
|
356
|
+
# Fast path for no user configurations
|
|
357
|
+
if not process_configs or (
|
|
358
|
+
not cls._flow_state[FlowStateItems.FLOW_MUTATORS]
|
|
359
|
+
and all(len(step.config_decorators) == 0 for step in cls._steps)
|
|
360
|
+
):
|
|
361
|
+
# Process parameters to allow them to also use config values easily
|
|
362
|
+
for var, param in cls._get_parameters():
|
|
363
|
+
if isinstance(param, ConfigValue) or param.IS_CONFIG_PARAMETER:
|
|
364
|
+
continue
|
|
365
|
+
param.init(not process_configs)
|
|
366
|
+
return None
|
|
367
|
+
|
|
368
|
+
debug.userconf_exec("Processing mutating step/flow decorators")
|
|
369
|
+
# We need to convert all the user configurations from DelayedEvaluationParameters
|
|
370
|
+
# to actual values so they can be used as is in the mutators.
|
|
371
|
+
|
|
372
|
+
# We, however, need to make sure _get_parameters still works properly so
|
|
373
|
+
# we store what was a config and has been set to a specific value.
|
|
374
|
+
# This is safe to do for now because all other uses of _get_parameters typically
|
|
375
|
+
# do not rely on the variable itself but just the parameter.
|
|
376
|
+
to_save_configs = []
|
|
377
|
+
cls._check_parameters(config_parameters=True)
|
|
378
|
+
for var, param in cls._get_parameters():
|
|
379
|
+
if not param.IS_CONFIG_PARAMETER:
|
|
380
|
+
continue
|
|
381
|
+
# Note that a config with no default and not required will be None
|
|
382
|
+
val = config_options.get(param.name.replace("-", "_").lower())
|
|
383
|
+
if isinstance(val, DelayedEvaluationParameter):
|
|
384
|
+
val = val()
|
|
385
|
+
# We store the value as well so that in _set_constants, we don't try
|
|
386
|
+
# to recompute (no guarantee that it is stable)
|
|
387
|
+
param._store_value(val)
|
|
388
|
+
to_save_configs.append((var, param))
|
|
389
|
+
debug.userconf_exec("Setting config %s to %s" % (var, str(val)))
|
|
390
|
+
setattr(cls, var, val)
|
|
391
|
+
|
|
392
|
+
cls._flow_state[FlowStateItems.SET_CONFIG_PARAMETERS] = to_save_configs
|
|
393
|
+
# Run all the decorators. We first run the flow-level decorators
|
|
394
|
+
# and then the step level ones to maintain a consistent order with how
|
|
395
|
+
# other decorators are run.
|
|
396
|
+
|
|
397
|
+
for deco in cls._flow_state[FlowStateItems.FLOW_MUTATORS]:
|
|
398
|
+
if isinstance(deco, FlowMutator):
|
|
399
|
+
inserted_by_value = [deco.decorator_name] + (deco.inserted_by or [])
|
|
400
|
+
mutable_flow = MutableFlow(
|
|
401
|
+
cls,
|
|
402
|
+
pre_mutate=True,
|
|
403
|
+
statically_defined=deco.statically_defined,
|
|
404
|
+
inserted_by=inserted_by_value,
|
|
405
|
+
)
|
|
406
|
+
# Sanity check to make sure we are applying the decorator to the right
|
|
407
|
+
# class
|
|
408
|
+
if not deco._flow_cls == cls and not issubclass(cls, deco._flow_cls):
|
|
409
|
+
raise MetaflowInternalError(
|
|
410
|
+
"FlowMutator registered on the wrong flow -- "
|
|
411
|
+
"expected %s but got %s"
|
|
412
|
+
% (deco._flow_cls.__name__, cls.__name__)
|
|
413
|
+
)
|
|
414
|
+
debug.userconf_exec(
|
|
415
|
+
"Evaluating flow level decorator %s (pre-mutate)"
|
|
416
|
+
% deco.__class__.__name__
|
|
417
|
+
)
|
|
418
|
+
deco.pre_mutate(mutable_flow)
|
|
419
|
+
# We reset cached_parameters on the very off chance that the user added
|
|
420
|
+
# more configurations based on the configuration
|
|
421
|
+
if cls._flow_state[FlowStateItems.CACHED_PARAMETERS] is not None:
|
|
422
|
+
cls._flow_state[FlowStateItems.CACHED_PARAMETERS] = None
|
|
423
|
+
else:
|
|
424
|
+
raise MetaflowInternalError(
|
|
425
|
+
"A non FlowMutator found in flow custom decorators"
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
for step in cls._steps:
|
|
429
|
+
for deco in step.config_decorators:
|
|
430
|
+
if isinstance(deco, StepMutator):
|
|
431
|
+
inserted_by_value = [deco.decorator_name] + (deco.inserted_by or [])
|
|
432
|
+
debug.userconf_exec(
|
|
433
|
+
"Evaluating step level decorator %s for %s (pre-mutate)"
|
|
434
|
+
% (deco.__class__.__name__, step.name)
|
|
435
|
+
)
|
|
436
|
+
deco.pre_mutate(
|
|
437
|
+
MutableStep(
|
|
438
|
+
cls,
|
|
439
|
+
step,
|
|
440
|
+
pre_mutate=True,
|
|
441
|
+
statically_defined=deco.statically_defined,
|
|
442
|
+
inserted_by=inserted_by_value,
|
|
443
|
+
)
|
|
444
|
+
)
|
|
445
|
+
else:
|
|
446
|
+
raise MetaflowInternalError(
|
|
447
|
+
"A non StepMutator found in step custom decorators"
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
# Process parameters to allow them to also use config values easily
|
|
451
|
+
for var, param in cls._get_parameters():
|
|
452
|
+
if param.IS_CONFIG_PARAMETER:
|
|
453
|
+
continue
|
|
454
|
+
param.init()
|
|
455
|
+
|
|
456
|
+
# Set the current flow class we are in (the one we just created)
|
|
457
|
+
parameters.replace_flow_context(cls)
|
|
458
|
+
|
|
459
|
+
# Re-calculate class level attributes after modifying the class
|
|
460
|
+
cls._init_graph()
|
|
461
|
+
return cls
|
|
462
|
+
|
|
463
|
+
def _set_constants(self, graph, kwargs, config_options):
|
|
464
|
+
from metaflow.decorators import (
|
|
465
|
+
flow_decorators,
|
|
466
|
+
) # To prevent circular dependency
|
|
467
|
+
|
|
468
|
+
# Persist values for parameters and other constants (class level variables)
|
|
469
|
+
# only once. This method is called before persist_constants is called to
|
|
470
|
+
# persist all values set using setattr
|
|
471
|
+
self._check_parameters(config_parameters=False)
|
|
472
|
+
|
|
473
|
+
seen = set()
|
|
153
474
|
self._success = True
|
|
154
475
|
|
|
155
476
|
parameters_info = []
|
|
156
477
|
for var, param in self._get_parameters():
|
|
157
478
|
seen.add(var)
|
|
158
|
-
|
|
479
|
+
if param.IS_CONFIG_PARAMETER:
|
|
480
|
+
# Use computed value if already evaluated, else get from config_options
|
|
481
|
+
val = param._computed_value or config_options.get(
|
|
482
|
+
param.name.replace("-", "_").lower()
|
|
483
|
+
)
|
|
484
|
+
else:
|
|
485
|
+
val = kwargs[param.name.replace("-", "_").lower()]
|
|
159
486
|
# Support for delayed evaluation of parameters.
|
|
160
487
|
if isinstance(val, DelayedEvaluationParameter):
|
|
161
488
|
val = val()
|
|
162
489
|
val = val.split(param.separator) if val and param.separator else val
|
|
490
|
+
if isinstance(val, ConfigValue):
|
|
491
|
+
# We store config values as dict so they are accessible with older
|
|
492
|
+
# metaflow clients. It also makes it easier to access.
|
|
493
|
+
val = val.to_dict()
|
|
163
494
|
setattr(self, var, val)
|
|
164
495
|
parameters_info.append({"name": var, "type": param.__class__.__name__})
|
|
165
496
|
|
|
@@ -189,25 +520,53 @@ class FlowSpec(object):
|
|
|
189
520
|
"decorators": [
|
|
190
521
|
{
|
|
191
522
|
"name": deco.name,
|
|
192
|
-
"attributes": deco.attributes,
|
|
523
|
+
"attributes": to_pod(deco.attributes),
|
|
193
524
|
"statically_defined": deco.statically_defined,
|
|
525
|
+
"inserted_by": deco.inserted_by,
|
|
194
526
|
}
|
|
195
|
-
for deco in flow_decorators()
|
|
527
|
+
for deco in flow_decorators(self)
|
|
196
528
|
if not deco.name.startswith("_")
|
|
529
|
+
]
|
|
530
|
+
+ [
|
|
531
|
+
{
|
|
532
|
+
"name": deco.__class__.__name__,
|
|
533
|
+
"attributes": {},
|
|
534
|
+
"statically_defined": deco.statically_defined,
|
|
535
|
+
"inserted_by": deco.inserted_by,
|
|
536
|
+
}
|
|
537
|
+
for deco in self._flow_state[FlowStateItems.FLOW_MUTATORS]
|
|
197
538
|
],
|
|
539
|
+
"extensions": extension_info(),
|
|
198
540
|
}
|
|
199
541
|
self._graph_info = graph_info
|
|
200
542
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
543
|
+
@classmethod
|
|
544
|
+
def _get_parameters(cls):
|
|
545
|
+
cached = cls._flow_state[FlowStateItems.CACHED_PARAMETERS]
|
|
546
|
+
returned = set()
|
|
547
|
+
if cached is not None:
|
|
548
|
+
for set_config in cls._flow_state[FlowStateItems.SET_CONFIG_PARAMETERS]:
|
|
549
|
+
returned.add(set_config[0])
|
|
550
|
+
yield set_config[0], set_config[1]
|
|
551
|
+
for var in cached:
|
|
552
|
+
if var not in returned:
|
|
553
|
+
yield var, getattr(cls, var)
|
|
554
|
+
return
|
|
555
|
+
build_list = []
|
|
556
|
+
for set_config in cls._flow_state[FlowStateItems.SET_CONFIG_PARAMETERS]:
|
|
557
|
+
returned.add(set_config[0])
|
|
558
|
+
yield set_config[0], set_config[1]
|
|
559
|
+
for var in dir(cls):
|
|
560
|
+
if var[0] == "_" or var in cls._NON_PARAMETERS:
|
|
204
561
|
continue
|
|
205
562
|
try:
|
|
206
|
-
val = getattr(
|
|
563
|
+
val = getattr(cls, var)
|
|
207
564
|
except:
|
|
208
565
|
continue
|
|
209
|
-
if isinstance(val, Parameter):
|
|
566
|
+
if isinstance(val, Parameter) and var not in returned:
|
|
567
|
+
build_list.append(var)
|
|
210
568
|
yield var, val
|
|
569
|
+
cls._flow_state[FlowStateItems.CACHED_PARAMETERS] = build_list
|
|
211
570
|
|
|
212
571
|
def _set_datastore(self, datastore):
|
|
213
572
|
self._datastore = datastore
|
|
@@ -559,6 +918,15 @@ class FlowSpec(object):
|
|
|
559
918
|
evaluates to an iterator. A task will be launched for each value in the iterator and
|
|
560
919
|
each task will execute the code specified by the step `foreach_step`.
|
|
561
920
|
|
|
921
|
+
- Switch statement:
|
|
922
|
+
```
|
|
923
|
+
self.next({"case1": self.step_a, "case2": self.step_b}, condition='condition_variable')
|
|
924
|
+
```
|
|
925
|
+
In this situation, `step_a` and `step_b` are methods in the current class decorated
|
|
926
|
+
with the `@step` decorator and `condition_variable` is a variable name in the current
|
|
927
|
+
class. The value of the condition variable determines which step to execute. If the
|
|
928
|
+
value doesn't match any of the dictionary keys, a RuntimeError is raised.
|
|
929
|
+
|
|
562
930
|
Parameters
|
|
563
931
|
----------
|
|
564
932
|
dsts : Callable[..., None]
|
|
@@ -574,6 +942,7 @@ class FlowSpec(object):
|
|
|
574
942
|
|
|
575
943
|
foreach = kwargs.pop("foreach", None)
|
|
576
944
|
num_parallel = kwargs.pop("num_parallel", None)
|
|
945
|
+
condition = kwargs.pop("condition", None)
|
|
577
946
|
if kwargs:
|
|
578
947
|
kw = next(iter(kwargs))
|
|
579
948
|
msg = (
|
|
@@ -590,6 +959,86 @@ class FlowSpec(object):
|
|
|
590
959
|
)
|
|
591
960
|
raise InvalidNextException(msg)
|
|
592
961
|
|
|
962
|
+
# check: switch case using condition
|
|
963
|
+
if condition is not None:
|
|
964
|
+
if len(dsts) != 1 or not isinstance(dsts[0], dict) or not dsts[0]:
|
|
965
|
+
msg = (
|
|
966
|
+
"Step *{step}* has an invalid self.next() transition. "
|
|
967
|
+
"When using 'condition', the transition must be to a single, "
|
|
968
|
+
"non-empty dictionary mapping condition values to step methods.".format(
|
|
969
|
+
step=step
|
|
970
|
+
)
|
|
971
|
+
)
|
|
972
|
+
raise InvalidNextException(msg)
|
|
973
|
+
|
|
974
|
+
if not isinstance(condition, basestring):
|
|
975
|
+
msg = (
|
|
976
|
+
"Step *{step}* has an invalid self.next() transition. "
|
|
977
|
+
"The argument to 'condition' must be a string.".format(step=step)
|
|
978
|
+
)
|
|
979
|
+
raise InvalidNextException(msg)
|
|
980
|
+
|
|
981
|
+
if foreach is not None or num_parallel is not None:
|
|
982
|
+
msg = (
|
|
983
|
+
"Step *{step}* has an invalid self.next() transition. "
|
|
984
|
+
"Switch statements cannot be combined with foreach or num_parallel.".format(
|
|
985
|
+
step=step
|
|
986
|
+
)
|
|
987
|
+
)
|
|
988
|
+
raise InvalidNextException(msg)
|
|
989
|
+
|
|
990
|
+
switch_cases = dsts[0]
|
|
991
|
+
|
|
992
|
+
# Validate that condition variable exists
|
|
993
|
+
try:
|
|
994
|
+
condition_value = getattr(self, condition)
|
|
995
|
+
except AttributeError:
|
|
996
|
+
msg = (
|
|
997
|
+
"Condition variable *self.{var}* in step *{step}* "
|
|
998
|
+
"does not exist. Make sure you set self.{var} in this step.".format(
|
|
999
|
+
step=step, var=condition
|
|
1000
|
+
)
|
|
1001
|
+
)
|
|
1002
|
+
raise InvalidNextException(msg)
|
|
1003
|
+
|
|
1004
|
+
if condition_value not in switch_cases:
|
|
1005
|
+
available_cases = list(switch_cases.keys())
|
|
1006
|
+
raise RuntimeError(
|
|
1007
|
+
f"Switch condition variable '{condition}' has value '{condition_value}' "
|
|
1008
|
+
f"which is not in the available cases: {available_cases}"
|
|
1009
|
+
)
|
|
1010
|
+
|
|
1011
|
+
# Get the chosen step and set transition directly
|
|
1012
|
+
chosen_step_func = switch_cases[condition_value]
|
|
1013
|
+
|
|
1014
|
+
# Validate that the chosen step exists
|
|
1015
|
+
try:
|
|
1016
|
+
name = chosen_step_func.__func__.__name__
|
|
1017
|
+
except:
|
|
1018
|
+
msg = (
|
|
1019
|
+
"Step *{step}* specifies a switch transition that is not a function. "
|
|
1020
|
+
"Make sure the value in the dictionary is a method "
|
|
1021
|
+
"of the Flow class.".format(step=step)
|
|
1022
|
+
)
|
|
1023
|
+
raise InvalidNextException(msg)
|
|
1024
|
+
if not hasattr(self, name):
|
|
1025
|
+
msg = (
|
|
1026
|
+
"Step *{step}* specifies a switch transition to an "
|
|
1027
|
+
"unknown step, *{name}*.".format(step=step, name=name)
|
|
1028
|
+
)
|
|
1029
|
+
raise InvalidNextException(msg)
|
|
1030
|
+
|
|
1031
|
+
self._transition = ([name], None)
|
|
1032
|
+
return
|
|
1033
|
+
|
|
1034
|
+
# Check for an invalid transition: a dictionary used without a 'condition' parameter.
|
|
1035
|
+
if len(dsts) == 1 and isinstance(dsts[0], dict):
|
|
1036
|
+
msg = (
|
|
1037
|
+
"Step *{step}* has an invalid self.next() transition. "
|
|
1038
|
+
"Dictionary argument requires 'condition' parameter.".format(step=step)
|
|
1039
|
+
)
|
|
1040
|
+
raise InvalidNextException(msg)
|
|
1041
|
+
|
|
593
1042
|
# check: all destinations are methods of this object
|
|
594
1043
|
funcs = []
|
|
595
1044
|
for i, dst in enumerate(dsts):
|
|
@@ -680,7 +1129,7 @@ class FlowSpec(object):
|
|
|
680
1129
|
self._foreach_var = foreach
|
|
681
1130
|
|
|
682
1131
|
# check: non-keyword transitions are valid
|
|
683
|
-
if foreach is None:
|
|
1132
|
+
if foreach is None and condition is None:
|
|
684
1133
|
if len(dsts) < 1:
|
|
685
1134
|
msg = (
|
|
686
1135
|
"Step *{step}* has an invalid self.next() transition. "
|