ob-metaflow 2.15.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/__init__.py +10 -3
- metaflow/_vendor/imghdr/__init__.py +186 -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 +4 -0
- metaflow/cli.py +125 -21
- metaflow/cli_components/init_cmd.py +1 -0
- metaflow/cli_components/run_cmds.py +204 -40
- metaflow/cli_components/step_cmd.py +160 -4
- metaflow/client/__init__.py +1 -0
- metaflow/client/core.py +198 -130
- metaflow/client/filecache.py +59 -32
- metaflow/cmd/code/__init__.py +2 -1
- metaflow/cmd/develop/stub_generator.py +49 -18
- metaflow/cmd/develop/stubs.py +9 -27
- metaflow/cmd/make_wrapper.py +30 -0
- metaflow/datastore/__init__.py +1 -0
- metaflow/datastore/content_addressed_store.py +40 -9
- metaflow/datastore/datastore_set.py +10 -1
- metaflow/datastore/flow_datastore.py +124 -4
- metaflow/datastore/spin_datastore.py +91 -0
- metaflow/datastore/task_datastore.py +92 -6
- metaflow/debug.py +5 -0
- metaflow/decorators.py +331 -82
- metaflow/extension_support/__init__.py +414 -356
- metaflow/extension_support/_empty_file.py +2 -2
- metaflow/flowspec.py +322 -82
- metaflow/graph.py +178 -15
- metaflow/includefile.py +25 -3
- metaflow/lint.py +94 -3
- metaflow/meta_files.py +13 -0
- metaflow/metadata_provider/metadata.py +13 -2
- metaflow/metaflow_config.py +66 -4
- metaflow/metaflow_environment.py +91 -25
- metaflow/metaflow_profile.py +18 -0
- metaflow/metaflow_version.py +16 -1
- 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 +6 -2
- metaflow/plugins/__init__.py +6 -0
- metaflow/plugins/airflow/airflow.py +11 -1
- metaflow/plugins/airflow/airflow_cli.py +16 -5
- metaflow/plugins/argo/argo_client.py +42 -20
- metaflow/plugins/argo/argo_events.py +6 -6
- metaflow/plugins/argo/argo_workflows.py +1023 -344
- metaflow/plugins/argo/argo_workflows_cli.py +396 -94
- metaflow/plugins/argo/argo_workflows_decorator.py +9 -0
- metaflow/plugins/argo/argo_workflows_deployer_objects.py +75 -49
- metaflow/plugins/argo/capture_error.py +5 -2
- metaflow/plugins/argo/conditional_input_paths.py +35 -0
- metaflow/plugins/argo/exit_hooks.py +209 -0
- metaflow/plugins/argo/param_val.py +19 -0
- metaflow/plugins/aws/aws_client.py +6 -0
- metaflow/plugins/aws/aws_utils.py +33 -1
- metaflow/plugins/aws/batch/batch.py +72 -5
- metaflow/plugins/aws/batch/batch_cli.py +24 -3
- metaflow/plugins/aws/batch/batch_decorator.py +57 -6
- metaflow/plugins/aws/step_functions/step_functions.py +28 -3
- metaflow/plugins/aws/step_functions/step_functions_cli.py +49 -4
- metaflow/plugins/aws/step_functions/step_functions_deployer.py +3 -0
- metaflow/plugins/aws/step_functions/step_functions_deployer_objects.py +30 -0
- metaflow/plugins/cards/card_cli.py +20 -1
- metaflow/plugins/cards/card_creator.py +24 -1
- metaflow/plugins/cards/card_datastore.py +21 -49
- metaflow/plugins/cards/card_decorator.py +58 -6
- metaflow/plugins/cards/card_modules/basic.py +38 -9
- metaflow/plugins/cards/card_modules/bundle.css +1 -1
- metaflow/plugins/cards/card_modules/chevron/renderer.py +1 -1
- metaflow/plugins/cards/card_modules/components.py +592 -3
- metaflow/plugins/cards/card_modules/convert_to_native_type.py +34 -5
- 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 +56 -41
- metaflow/plugins/cards/card_modules/test_cards.py +22 -6
- metaflow/plugins/cards/component_serializer.py +1 -8
- metaflow/plugins/cards/metadata.py +22 -0
- metaflow/plugins/catch_decorator.py +9 -0
- metaflow/plugins/datastores/local_storage.py +12 -6
- metaflow/plugins/datastores/spin_storage.py +12 -0
- metaflow/plugins/datatools/s3/s3.py +49 -17
- metaflow/plugins/datatools/s3/s3op.py +113 -66
- metaflow/plugins/env_escape/client_modules.py +102 -72
- metaflow/plugins/events_decorator.py +127 -121
- 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/kubernetes/kubernetes.py +12 -1
- metaflow/plugins/kubernetes/kubernetes_cli.py +11 -0
- metaflow/plugins/kubernetes/kubernetes_decorator.py +25 -6
- metaflow/plugins/kubernetes/kubernetes_job.py +12 -4
- metaflow/plugins/kubernetes/kubernetes_jobsets.py +31 -30
- metaflow/plugins/metadata_providers/local.py +76 -82
- metaflow/plugins/metadata_providers/service.py +13 -9
- metaflow/plugins/metadata_providers/spin.py +16 -0
- metaflow/plugins/package_cli.py +36 -24
- metaflow/plugins/parallel_decorator.py +11 -2
- metaflow/plugins/parsers.py +16 -0
- metaflow/plugins/pypi/bootstrap.py +7 -1
- metaflow/plugins/pypi/conda_decorator.py +41 -82
- metaflow/plugins/pypi/conda_environment.py +14 -6
- metaflow/plugins/pypi/micromamba.py +9 -1
- metaflow/plugins/pypi/pip.py +41 -5
- metaflow/plugins/pypi/pypi_decorator.py +4 -4
- metaflow/plugins/pypi/utils.py +22 -0
- metaflow/plugins/secrets/__init__.py +3 -0
- metaflow/plugins/secrets/secrets_decorator.py +14 -178
- 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/test_unbounded_foreach_decorator.py +2 -2
- metaflow/plugins/timeout_decorator.py +0 -1
- metaflow/plugins/uv/bootstrap.py +29 -1
- metaflow/plugins/uv/uv_environment.py +5 -3
- metaflow/pylint_wrapper.py +5 -1
- metaflow/runner/click_api.py +79 -26
- metaflow/runner/deployer.py +208 -6
- metaflow/runner/deployer_impl.py +32 -12
- metaflow/runner/metaflow_runner.py +266 -33
- metaflow/runner/subprocess_manager.py +21 -1
- metaflow/runner/utils.py +27 -16
- metaflow/runtime.py +660 -66
- metaflow/task.py +255 -26
- metaflow/user_configs/config_options.py +33 -21
- metaflow/user_configs/config_parameters.py +220 -58
- 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 +197 -7
- metaflow/vendor.py +23 -7
- metaflow/version.py +1 -1
- {ob_metaflow-2.15.13.1.data → ob_metaflow-2.19.7.1rc0.data}/data/share/metaflow/devtools/Makefile +13 -2
- {ob_metaflow-2.15.13.1.data → ob_metaflow-2.19.7.1rc0.data}/data/share/metaflow/devtools/Tiltfile +107 -7
- {ob_metaflow-2.15.13.1.data → ob_metaflow-2.19.7.1rc0.data}/data/share/metaflow/devtools/pick_services.sh +1 -0
- {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/METADATA +2 -3
- {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/RECORD +162 -121
- {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/WHEEL +1 -1
- 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/_vendor/v3_5/zipp.py +0 -329
- metaflow/info_file.py +0 -25
- metaflow/package.py +0 -203
- metaflow/user_configs/config_decorators.py +0 -568
- {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/entry_points.txt +0 -0
- {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/licenses/LICENSE +0 -0
- {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/top_level.txt +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
# This file serves as a __init__.py for metaflow_extensions
|
|
2
|
-
# and needs to remain empty.
|
|
1
|
+
# This file serves as a __init__.py for metaflow_extensions or metaflow
|
|
2
|
+
# packages when they are packaged and needs to remain empty.
|
metaflow/flowspec.py
CHANGED
|
@@ -4,10 +4,11 @@ import sys
|
|
|
4
4
|
import traceback
|
|
5
5
|
import reprlib
|
|
6
6
|
|
|
7
|
+
from collections.abc import MutableMapping
|
|
7
8
|
from enum import Enum
|
|
8
9
|
from itertools import islice
|
|
9
10
|
from types import FunctionType, MethodType
|
|
10
|
-
from typing import
|
|
11
|
+
from typing import Any, Callable, List, Optional, Tuple
|
|
11
12
|
|
|
12
13
|
from . import cmd_with_io, parameters
|
|
13
14
|
from .debug import debug
|
|
@@ -23,13 +24,14 @@ from .extension_support import extension_info
|
|
|
23
24
|
|
|
24
25
|
from .graph import FlowGraph
|
|
25
26
|
from .unbounded_foreach import UnboundedForeachInput
|
|
26
|
-
from .user_configs.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
+
|
|
33
35
|
from .util import to_pod
|
|
34
36
|
from .metaflow_config import INCLUDE_FOREACH_STACK, MAXIMUM_FOREACH_VALUE_CHARS
|
|
35
37
|
|
|
@@ -75,10 +77,84 @@ class ParallelUBF(UnboundedForeachInput):
|
|
|
75
77
|
return item or 0 # item is None for the control task, but it is also split 0
|
|
76
78
|
|
|
77
79
|
|
|
78
|
-
class
|
|
80
|
+
class FlowStateItems(Enum):
|
|
79
81
|
CONFIGS = 1
|
|
80
|
-
|
|
82
|
+
FLOW_MUTATORS = 2
|
|
81
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
|
+
)
|
|
82
158
|
|
|
83
159
|
|
|
84
160
|
class FlowSpecMeta(type):
|
|
@@ -87,6 +163,9 @@ class FlowSpecMeta(type):
|
|
|
87
163
|
if name == "FlowSpec":
|
|
88
164
|
return
|
|
89
165
|
|
|
166
|
+
cls._init_attrs()
|
|
167
|
+
|
|
168
|
+
def _init_attrs(cls):
|
|
90
169
|
from .decorators import (
|
|
91
170
|
DuplicateFlowDecoratorException,
|
|
92
171
|
) # Prevent circular import
|
|
@@ -96,16 +175,26 @@ class FlowSpecMeta(type):
|
|
|
96
175
|
# Runner/NBRunner. This is also created here in the meta class to avoid it being
|
|
97
176
|
# shared between different children classes.
|
|
98
177
|
|
|
99
|
-
#
|
|
100
|
-
|
|
101
|
-
|
|
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
|
+
)
|
|
102
188
|
|
|
103
|
-
#
|
|
104
|
-
|
|
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
|
|
105
194
|
|
|
106
195
|
# We inherit stuff from our parent classes as well -- we need to be careful
|
|
107
196
|
# in terms of the order; we will follow the MRO with the following rules:
|
|
108
|
-
# - decorators
|
|
197
|
+
# - decorators will cause an error if they do not
|
|
109
198
|
# support multiple and we see multiple instances of the same
|
|
110
199
|
# - config decorators will be joined
|
|
111
200
|
# - configs will be added later directly by the class; base class configs will
|
|
@@ -113,24 +202,55 @@ class FlowSpecMeta(type):
|
|
|
113
202
|
|
|
114
203
|
# We only need to do this for the base classes since the current class will
|
|
115
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
|
+
|
|
116
219
|
for base in cls.__mro__:
|
|
117
220
|
if base != cls and base != FlowSpec and issubclass(base, FlowSpec):
|
|
118
221
|
# Take care of decorators
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
cls._flow_decorators.setdefault(deco_name, []).extend(deco)
|
|
222
|
+
base_flow_decorators = base._flow_state.self_data[
|
|
223
|
+
FlowStateItems.FLOW_DECORATORS
|
|
224
|
+
]
|
|
123
225
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
cls._flow_state.setdefault(_FlowState.CONFIG_DECORATORS, []).extend(
|
|
128
|
-
base_configs
|
|
226
|
+
inherited_cls_flow_decorators = (
|
|
227
|
+
cls._flow_state.inherited_data.setdefault(
|
|
228
|
+
FlowStateItems.FLOW_DECORATORS, {}
|
|
129
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)
|
|
130
241
|
|
|
131
|
-
|
|
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)
|
|
132
250
|
|
|
133
|
-
|
|
251
|
+
cls._init_graph()
|
|
252
|
+
|
|
253
|
+
def _init_graph(cls):
|
|
134
254
|
# Graph and steps are specific to the class -- store here so we can access
|
|
135
255
|
# in class method _process_config_decorators
|
|
136
256
|
cls._graph = FlowGraph(cls)
|
|
@@ -156,7 +276,6 @@ class FlowSpec(metaclass=FlowSpecMeta):
|
|
|
156
276
|
"_datastore",
|
|
157
277
|
"_cached_input",
|
|
158
278
|
"_graph",
|
|
159
|
-
"_flow_decorators",
|
|
160
279
|
"_flow_state",
|
|
161
280
|
"_steps",
|
|
162
281
|
"index",
|
|
@@ -207,6 +326,11 @@ class FlowSpec(metaclass=FlowSpecMeta):
|
|
|
207
326
|
fname = fname[:-1]
|
|
208
327
|
return os.path.basename(fname)
|
|
209
328
|
|
|
329
|
+
@property
|
|
330
|
+
def _flow_decorators(self):
|
|
331
|
+
# Backward compatible method to access flow decorators
|
|
332
|
+
return self._flow_state[FlowStateItems.FLOW_DECORATORS]
|
|
333
|
+
|
|
210
334
|
@classmethod
|
|
211
335
|
def _check_parameters(cls, config_parameters=False):
|
|
212
336
|
seen = set()
|
|
@@ -224,26 +348,32 @@ class FlowSpec(metaclass=FlowSpecMeta):
|
|
|
224
348
|
|
|
225
349
|
@classmethod
|
|
226
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
|
|
227
355
|
|
|
228
356
|
# Fast path for no user configurations
|
|
229
357
|
if not process_configs or (
|
|
230
|
-
not cls._flow_state.
|
|
358
|
+
not cls._flow_state[FlowStateItems.FLOW_MUTATORS]
|
|
231
359
|
and all(len(step.config_decorators) == 0 for step in cls._steps)
|
|
232
360
|
):
|
|
233
361
|
# Process parameters to allow them to also use config values easily
|
|
234
362
|
for var, param in cls._get_parameters():
|
|
235
|
-
if param.IS_CONFIG_PARAMETER:
|
|
363
|
+
if isinstance(param, ConfigValue) or param.IS_CONFIG_PARAMETER:
|
|
236
364
|
continue
|
|
237
365
|
param.init(not process_configs)
|
|
238
366
|
return None
|
|
239
367
|
|
|
240
368
|
debug.userconf_exec("Processing mutating step/flow decorators")
|
|
241
369
|
# We need to convert all the user configurations from DelayedEvaluationParameters
|
|
242
|
-
# to actual values so they can be used as is in the
|
|
370
|
+
# to actual values so they can be used as is in the mutators.
|
|
243
371
|
|
|
244
|
-
# We
|
|
245
|
-
#
|
|
246
|
-
|
|
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 = []
|
|
247
377
|
cls._check_parameters(config_parameters=True)
|
|
248
378
|
for var, param in cls._get_parameters():
|
|
249
379
|
if not param.IS_CONFIG_PARAMETER:
|
|
@@ -255,77 +385,79 @@ class FlowSpec(metaclass=FlowSpecMeta):
|
|
|
255
385
|
# We store the value as well so that in _set_constants, we don't try
|
|
256
386
|
# to recompute (no guarantee that it is stable)
|
|
257
387
|
param._store_value(val)
|
|
258
|
-
|
|
388
|
+
to_save_configs.append((var, param))
|
|
259
389
|
debug.userconf_exec("Setting config %s to %s" % (var, str(val)))
|
|
260
390
|
setattr(cls, var, val)
|
|
261
391
|
|
|
262
|
-
|
|
263
|
-
#
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
deco.evaluate(MutableStep(cls, step))
|
|
277
|
-
else:
|
|
278
|
-
raise MetaflowInternalError(
|
|
279
|
-
"A non CustomFlowDecorator found in step custom decorators"
|
|
280
|
-
)
|
|
281
|
-
if step.config_decorators:
|
|
282
|
-
# We remove all mention of the custom step decorator
|
|
283
|
-
setattr(cls, step.name, step)
|
|
284
|
-
|
|
285
|
-
mutable_flow = MutableFlow(cls)
|
|
286
|
-
for deco in cls._flow_state.get(_FlowState.CONFIG_DECORATORS, []):
|
|
287
|
-
if isinstance(deco, CustomFlowDecorator):
|
|
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
|
+
)
|
|
288
406
|
# Sanity check to make sure we are applying the decorator to the right
|
|
289
407
|
# class
|
|
290
408
|
if not deco._flow_cls == cls and not issubclass(cls, deco._flow_cls):
|
|
291
409
|
raise MetaflowInternalError(
|
|
292
|
-
"
|
|
410
|
+
"FlowMutator registered on the wrong flow -- "
|
|
293
411
|
"expected %s but got %s"
|
|
294
412
|
% (deco._flow_cls.__name__, cls.__name__)
|
|
295
413
|
)
|
|
296
414
|
debug.userconf_exec(
|
|
297
|
-
"Evaluating flow level decorator %s"
|
|
415
|
+
"Evaluating flow level decorator %s (pre-mutate)"
|
|
416
|
+
% deco.__class__.__name__
|
|
298
417
|
)
|
|
299
|
-
deco.
|
|
418
|
+
deco.pre_mutate(mutable_flow)
|
|
300
419
|
# We reset cached_parameters on the very off chance that the user added
|
|
301
420
|
# more configurations based on the configuration
|
|
302
|
-
if
|
|
303
|
-
|
|
421
|
+
if cls._flow_state[FlowStateItems.CACHED_PARAMETERS] is not None:
|
|
422
|
+
cls._flow_state[FlowStateItems.CACHED_PARAMETERS] = None
|
|
304
423
|
else:
|
|
305
424
|
raise MetaflowInternalError(
|
|
306
|
-
"A non
|
|
425
|
+
"A non FlowMutator found in flow custom decorators"
|
|
307
426
|
)
|
|
308
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
|
+
|
|
309
450
|
# Process parameters to allow them to also use config values easily
|
|
310
451
|
for var, param in cls._get_parameters():
|
|
311
452
|
if param.IS_CONFIG_PARAMETER:
|
|
312
453
|
continue
|
|
313
454
|
param.init()
|
|
314
|
-
# Reset all configs that were already present in the class.
|
|
315
|
-
# TODO: This means that users can't override configs directly. Not sure if this
|
|
316
|
-
# is a pattern we want to support
|
|
317
|
-
for var, param in to_reset_configs:
|
|
318
|
-
setattr(cls, var, param)
|
|
319
|
-
|
|
320
|
-
# Reset cached parameters again since we added back the config parameters
|
|
321
|
-
if _FlowState.CACHED_PARAMETERS in cls._flow_state:
|
|
322
|
-
del cls._flow_state[_FlowState.CACHED_PARAMETERS]
|
|
323
455
|
|
|
324
456
|
# Set the current flow class we are in (the one we just created)
|
|
325
457
|
parameters.replace_flow_context(cls)
|
|
326
458
|
|
|
327
459
|
# Re-calculate class level attributes after modifying the class
|
|
328
|
-
cls.
|
|
460
|
+
cls._init_graph()
|
|
329
461
|
return cls
|
|
330
462
|
|
|
331
463
|
def _set_constants(self, graph, kwargs, config_options):
|
|
@@ -390,9 +522,19 @@ class FlowSpec(metaclass=FlowSpecMeta):
|
|
|
390
522
|
"name": deco.name,
|
|
391
523
|
"attributes": to_pod(deco.attributes),
|
|
392
524
|
"statically_defined": deco.statically_defined,
|
|
525
|
+
"inserted_by": deco.inserted_by,
|
|
393
526
|
}
|
|
394
527
|
for deco in flow_decorators(self)
|
|
395
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]
|
|
396
538
|
],
|
|
397
539
|
"extensions": extension_info(),
|
|
398
540
|
}
|
|
@@ -400,12 +542,20 @@ class FlowSpec(metaclass=FlowSpecMeta):
|
|
|
400
542
|
|
|
401
543
|
@classmethod
|
|
402
544
|
def _get_parameters(cls):
|
|
403
|
-
cached = cls._flow_state.
|
|
545
|
+
cached = cls._flow_state[FlowStateItems.CACHED_PARAMETERS]
|
|
546
|
+
returned = set()
|
|
404
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]
|
|
405
551
|
for var in cached:
|
|
406
|
-
|
|
552
|
+
if var not in returned:
|
|
553
|
+
yield var, getattr(cls, var)
|
|
407
554
|
return
|
|
408
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]
|
|
409
559
|
for var in dir(cls):
|
|
410
560
|
if var[0] == "_" or var in cls._NON_PARAMETERS:
|
|
411
561
|
continue
|
|
@@ -413,10 +563,10 @@ class FlowSpec(metaclass=FlowSpecMeta):
|
|
|
413
563
|
val = getattr(cls, var)
|
|
414
564
|
except:
|
|
415
565
|
continue
|
|
416
|
-
if isinstance(val, Parameter):
|
|
566
|
+
if isinstance(val, Parameter) and var not in returned:
|
|
417
567
|
build_list.append(var)
|
|
418
568
|
yield var, val
|
|
419
|
-
cls._flow_state[
|
|
569
|
+
cls._flow_state[FlowStateItems.CACHED_PARAMETERS] = build_list
|
|
420
570
|
|
|
421
571
|
def _set_datastore(self, datastore):
|
|
422
572
|
self._datastore = datastore
|
|
@@ -768,6 +918,15 @@ class FlowSpec(metaclass=FlowSpecMeta):
|
|
|
768
918
|
evaluates to an iterator. A task will be launched for each value in the iterator and
|
|
769
919
|
each task will execute the code specified by the step `foreach_step`.
|
|
770
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
|
+
|
|
771
930
|
Parameters
|
|
772
931
|
----------
|
|
773
932
|
dsts : Callable[..., None]
|
|
@@ -783,6 +942,7 @@ class FlowSpec(metaclass=FlowSpecMeta):
|
|
|
783
942
|
|
|
784
943
|
foreach = kwargs.pop("foreach", None)
|
|
785
944
|
num_parallel = kwargs.pop("num_parallel", None)
|
|
945
|
+
condition = kwargs.pop("condition", None)
|
|
786
946
|
if kwargs:
|
|
787
947
|
kw = next(iter(kwargs))
|
|
788
948
|
msg = (
|
|
@@ -799,6 +959,86 @@ class FlowSpec(metaclass=FlowSpecMeta):
|
|
|
799
959
|
)
|
|
800
960
|
raise InvalidNextException(msg)
|
|
801
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
|
+
|
|
802
1042
|
# check: all destinations are methods of this object
|
|
803
1043
|
funcs = []
|
|
804
1044
|
for i, dst in enumerate(dsts):
|
|
@@ -889,7 +1129,7 @@ class FlowSpec(metaclass=FlowSpecMeta):
|
|
|
889
1129
|
self._foreach_var = foreach
|
|
890
1130
|
|
|
891
1131
|
# check: non-keyword transitions are valid
|
|
892
|
-
if foreach is None:
|
|
1132
|
+
if foreach is None and condition is None:
|
|
893
1133
|
if len(dsts) < 1:
|
|
894
1134
|
msg = (
|
|
895
1135
|
"Step *{step}* has an invalid self.next() transition. "
|