ob-metaflow 2.12.36.2__py2.py3-none-any.whl → 2.12.36.3__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.
Potentially problematic release.
This version of ob-metaflow might be problematic. Click here for more details.
- metaflow/__init__.py +0 -3
- metaflow/cli.py +697 -84
- metaflow/cli_args.py +0 -17
- metaflow/cmd/develop/stub_generator.py +2 -9
- metaflow/decorators.py +2 -63
- metaflow/extension_support/plugins.py +27 -41
- metaflow/flowspec.py +16 -156
- metaflow/includefile.py +22 -50
- metaflow/metaflow_config.py +1 -1
- metaflow/package.py +3 -17
- metaflow/parameters.py +23 -80
- metaflow/plugins/__init__.py +0 -4
- metaflow/plugins/airflow/airflow_cli.py +0 -1
- metaflow/plugins/argo/argo_workflows.py +1 -41
- metaflow/plugins/argo/argo_workflows_cli.py +0 -1
- metaflow/plugins/argo/argo_workflows_deployer_objects.py +1 -5
- metaflow/plugins/aws/batch/batch_decorator.py +2 -2
- metaflow/plugins/aws/step_functions/step_functions.py +0 -32
- metaflow/plugins/aws/step_functions/step_functions_cli.py +0 -1
- metaflow/plugins/aws/step_functions/step_functions_deployer_objects.py +0 -3
- metaflow/plugins/datatools/s3/s3op.py +3 -3
- metaflow/plugins/kubernetes/kubernetes_cli.py +1 -1
- metaflow/plugins/kubernetes/kubernetes_decorator.py +2 -2
- metaflow/plugins/pypi/conda_decorator.py +10 -20
- metaflow/plugins/pypi/pypi_decorator.py +9 -11
- metaflow/plugins/timeout_decorator.py +2 -2
- metaflow/runner/click_api.py +19 -73
- metaflow/runner/deployer.py +1 -1
- metaflow/runner/deployer_impl.py +2 -2
- metaflow/runner/metaflow_runner.py +1 -4
- metaflow/runner/nbdeploy.py +0 -2
- metaflow/runner/nbrun.py +1 -1
- metaflow/runner/subprocess_manager.py +1 -3
- metaflow/runner/utils.py +20 -37
- metaflow/runtime.py +73 -111
- metaflow/sidecar/sidecar_worker.py +1 -1
- metaflow/util.py +0 -17
- metaflow/version.py +1 -1
- {ob_metaflow-2.12.36.2.dist-info → ob_metaflow-2.12.36.3.dist-info}/METADATA +2 -3
- {ob_metaflow-2.12.36.2.dist-info → ob_metaflow-2.12.36.3.dist-info}/RECORD +44 -54
- metaflow/cli_components/__init__.py +0 -0
- metaflow/cli_components/dump_cmd.py +0 -96
- metaflow/cli_components/init_cmd.py +0 -51
- metaflow/cli_components/run_cmds.py +0 -358
- metaflow/cli_components/step_cmd.py +0 -189
- metaflow/cli_components/utils.py +0 -140
- metaflow/user_configs/__init__.py +0 -0
- metaflow/user_configs/config_decorators.py +0 -563
- metaflow/user_configs/config_options.py +0 -495
- metaflow/user_configs/config_parameters.py +0 -386
- {ob_metaflow-2.12.36.2.dist-info → ob_metaflow-2.12.36.3.dist-info}/LICENSE +0 -0
- {ob_metaflow-2.12.36.2.dist-info → ob_metaflow-2.12.36.3.dist-info}/WHEEL +0 -0
- {ob_metaflow-2.12.36.2.dist-info → ob_metaflow-2.12.36.3.dist-info}/entry_points.txt +0 -0
- {ob_metaflow-2.12.36.2.dist-info → ob_metaflow-2.12.36.3.dist-info}/top_level.txt +0 -0
metaflow/cli_components/utils.py
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import importlib
|
|
2
|
-
from metaflow._vendor import click
|
|
3
|
-
from metaflow.extension_support.plugins import get_plugin
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class LazyPluginCommandCollection(click.CommandCollection):
|
|
7
|
-
# lazy_source should only point to things that are resolved as CLI plugins.
|
|
8
|
-
def __init__(self, *args, lazy_sources=None, **kwargs):
|
|
9
|
-
super().__init__(*args, **kwargs)
|
|
10
|
-
# lazy_sources is a list of strings in the form
|
|
11
|
-
# "{plugin_name}" -> "{module-name}.{command-object-name}"
|
|
12
|
-
self.lazy_sources = lazy_sources or {}
|
|
13
|
-
self._lazy_loaded = {}
|
|
14
|
-
|
|
15
|
-
def invoke(self, ctx):
|
|
16
|
-
# NOTE: This is copied from MultiCommand.invoke. The change is that we
|
|
17
|
-
# behave like chain in the sense that we evaluate the subcommand *after*
|
|
18
|
-
# invoking the base command but we don't chain the commands like self.chain
|
|
19
|
-
# would otherwise indicate.
|
|
20
|
-
# The goal of this is to make sure that the first command is properly executed
|
|
21
|
-
# *first* prior to loading the other subcommands. It's more a lazy_subcommand_load
|
|
22
|
-
# than a chain.
|
|
23
|
-
# Look for CHANGE HERE in this code to see where the changes are made.
|
|
24
|
-
# If click is updated, this may also need to be updated. This version is for
|
|
25
|
-
# click 7.1.2.
|
|
26
|
-
def _process_result(value):
|
|
27
|
-
if self.result_callback is not None:
|
|
28
|
-
value = ctx.invoke(self.result_callback, value, **ctx.params)
|
|
29
|
-
return value
|
|
30
|
-
|
|
31
|
-
if not ctx.protected_args:
|
|
32
|
-
# If we are invoked without command the chain flag controls
|
|
33
|
-
# how this happens. If we are not in chain mode, the return
|
|
34
|
-
# value here is the return value of the command.
|
|
35
|
-
# If however we are in chain mode, the return value is the
|
|
36
|
-
# return value of the result processor invoked with an empty
|
|
37
|
-
# list (which means that no subcommand actually was executed).
|
|
38
|
-
if self.invoke_without_command:
|
|
39
|
-
# CHANGE HERE: We behave like self.chain = False here
|
|
40
|
-
|
|
41
|
-
# if not self.chain:
|
|
42
|
-
return click.Command.invoke(self, ctx)
|
|
43
|
-
# with ctx:
|
|
44
|
-
# click.Command.invoke(self, ctx)
|
|
45
|
-
# return _process_result([])
|
|
46
|
-
|
|
47
|
-
ctx.fail("Missing command.")
|
|
48
|
-
|
|
49
|
-
# Fetch args back out
|
|
50
|
-
args = ctx.protected_args + ctx.args
|
|
51
|
-
ctx.args = []
|
|
52
|
-
ctx.protected_args = []
|
|
53
|
-
# CHANGE HERE: Add saved_args so we have access to it in the command to be
|
|
54
|
-
# able to infer what we are calling next
|
|
55
|
-
ctx.saved_args = args
|
|
56
|
-
|
|
57
|
-
# If we're not in chain mode, we only allow the invocation of a
|
|
58
|
-
# single command but we also inform the current context about the
|
|
59
|
-
# name of the command to invoke.
|
|
60
|
-
# CHANGE HERE: We change this block to do the invoke *before* the resolve_command
|
|
61
|
-
# Make sure the context is entered so we do not clean up
|
|
62
|
-
# resources until the result processor has worked.
|
|
63
|
-
with ctx:
|
|
64
|
-
ctx.invoked_subcommand = "*" if args else None
|
|
65
|
-
click.Command.invoke(self, ctx)
|
|
66
|
-
cmd_name, cmd, args = self.resolve_command(ctx, args)
|
|
67
|
-
sub_ctx = cmd.make_context(cmd_name, args, parent=ctx)
|
|
68
|
-
with sub_ctx:
|
|
69
|
-
return _process_result(sub_ctx.command.invoke(sub_ctx))
|
|
70
|
-
|
|
71
|
-
# CHANGE HERE: Removed all the part of chain mode.
|
|
72
|
-
|
|
73
|
-
def list_commands(self, ctx):
|
|
74
|
-
base = super().list_commands(ctx)
|
|
75
|
-
for source_name, source in self.lazy_sources.items():
|
|
76
|
-
subgroup = self._lazy_load(source_name, source)
|
|
77
|
-
base.extend(subgroup.list_commands(ctx))
|
|
78
|
-
return base
|
|
79
|
-
|
|
80
|
-
def get_command(self, ctx, cmd_name):
|
|
81
|
-
base_cmd = super().get_command(ctx, cmd_name)
|
|
82
|
-
if base_cmd is not None:
|
|
83
|
-
return base_cmd
|
|
84
|
-
for source_name, source in self.lazy_sources.items():
|
|
85
|
-
subgroup = self._lazy_load(source_name, source)
|
|
86
|
-
cmd = subgroup.get_command(ctx, cmd_name)
|
|
87
|
-
if cmd is not None:
|
|
88
|
-
return cmd
|
|
89
|
-
return None
|
|
90
|
-
|
|
91
|
-
def _lazy_load(self, source_name, source_path):
|
|
92
|
-
if source_name in self._lazy_loaded:
|
|
93
|
-
return self._lazy_loaded[source_name]
|
|
94
|
-
cmd_object = get_plugin("cli", source_path, source_name)
|
|
95
|
-
if not isinstance(cmd_object, click.Group):
|
|
96
|
-
raise ValueError(
|
|
97
|
-
f"Lazy loading of {source_name} failed by returning "
|
|
98
|
-
"a non-group object"
|
|
99
|
-
)
|
|
100
|
-
self._lazy_loaded[source_name] = cmd_object
|
|
101
|
-
return cmd_object
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
class LazyGroup(click.Group):
|
|
105
|
-
def __init__(self, *args, lazy_subcommands=None, **kwargs):
|
|
106
|
-
super().__init__(*args, **kwargs)
|
|
107
|
-
# lazy_subcommands is a list of strings in the form
|
|
108
|
-
# "{command} -> "{module-name}.{command-object-name}"
|
|
109
|
-
self.lazy_subcommands = lazy_subcommands or {}
|
|
110
|
-
self._lazy_loaded = {}
|
|
111
|
-
|
|
112
|
-
def list_commands(self, ctx):
|
|
113
|
-
base = super().list_commands(ctx)
|
|
114
|
-
lazy = sorted(self.lazy_subcommands.keys())
|
|
115
|
-
return base + lazy
|
|
116
|
-
|
|
117
|
-
def get_command(self, ctx, cmd_name):
|
|
118
|
-
if cmd_name in self.lazy_subcommands:
|
|
119
|
-
return self._lazy_load(cmd_name)
|
|
120
|
-
return super().get_command(ctx, cmd_name)
|
|
121
|
-
|
|
122
|
-
def _lazy_load(self, cmd_name):
|
|
123
|
-
if cmd_name in self._lazy_loaded:
|
|
124
|
-
return self._lazy_loaded[cmd_name]
|
|
125
|
-
|
|
126
|
-
import_path = self.lazy_subcommands[cmd_name]
|
|
127
|
-
modname, cmd = import_path.rsplit(".", 1)
|
|
128
|
-
# do the import
|
|
129
|
-
mod = importlib.import_module(modname)
|
|
130
|
-
# get the Command object from that module
|
|
131
|
-
cmd_object = getattr(mod, cmd)
|
|
132
|
-
# check the result to make debugging easier. note that wrapped BaseCommand
|
|
133
|
-
# can be functions
|
|
134
|
-
if not isinstance(cmd_object, click.BaseCommand):
|
|
135
|
-
raise ValueError(
|
|
136
|
-
f"Lazy loading of {import_path} failed by returning "
|
|
137
|
-
f"a non-command object {type(cmd_object)}"
|
|
138
|
-
)
|
|
139
|
-
self._lazy_loaded[cmd_name] = cmd_object
|
|
140
|
-
return cmd_object
|
|
File without changes
|
|
@@ -1,563 +0,0 @@
|
|
|
1
|
-
from functools import partial
|
|
2
|
-
from typing import Any, Callable, Generator, Optional, TYPE_CHECKING, Tuple, Union
|
|
3
|
-
|
|
4
|
-
from metaflow.debug import debug
|
|
5
|
-
from metaflow.exception import MetaflowException
|
|
6
|
-
from metaflow.user_configs.config_parameters import (
|
|
7
|
-
ConfigValue,
|
|
8
|
-
resolve_delayed_evaluator,
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
if TYPE_CHECKING:
|
|
12
|
-
import metaflow.decorators
|
|
13
|
-
import metaflow.flowspec
|
|
14
|
-
import metaflow.parameters
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class MutableStep:
|
|
18
|
-
"""
|
|
19
|
-
A MutableStep is a wrapper passed to the `CustomStepDecorator`'s `evaluate` method
|
|
20
|
-
to allow the decorator to interact with the step and providing easy methods to
|
|
21
|
-
modify the behavior of the step.
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
def __init__(
|
|
25
|
-
self,
|
|
26
|
-
flow_spec: "metaflow.flowspec.FlowSpec",
|
|
27
|
-
step: Union[
|
|
28
|
-
Callable[["metaflow.decorators.FlowSpecDerived"], None],
|
|
29
|
-
Callable[["metaflow.decorators.FlowSpecDerived", Any], None],
|
|
30
|
-
],
|
|
31
|
-
):
|
|
32
|
-
self._mutable_container = MutableFlow(flow_spec)
|
|
33
|
-
self._my_step = step
|
|
34
|
-
|
|
35
|
-
@property
|
|
36
|
-
def flow(self) -> "MutableFlow":
|
|
37
|
-
"""
|
|
38
|
-
The flow that contains this step
|
|
39
|
-
|
|
40
|
-
Returns
|
|
41
|
-
-------
|
|
42
|
-
MutableFlow
|
|
43
|
-
The flow that contains this step
|
|
44
|
-
"""
|
|
45
|
-
return self._mutable_container
|
|
46
|
-
|
|
47
|
-
@property
|
|
48
|
-
def decorators(self) -> Generator["metaflow.decorators.StepDecorator", None, None]:
|
|
49
|
-
"""
|
|
50
|
-
Iterate over all the decorators of this step. Note that the same type of decorator
|
|
51
|
-
may be present multiple times and no order is guaranteed.
|
|
52
|
-
|
|
53
|
-
Yields
|
|
54
|
-
------
|
|
55
|
-
metaflow.decorators.StepDecorator
|
|
56
|
-
A decorator of the step
|
|
57
|
-
"""
|
|
58
|
-
for deco in self._my_step.decorators:
|
|
59
|
-
yield deco
|
|
60
|
-
|
|
61
|
-
def add_decorator(self, deco_type: partial, **kwargs) -> None:
|
|
62
|
-
"""
|
|
63
|
-
Add a Metaflow decorator to a step.
|
|
64
|
-
|
|
65
|
-
Parameters
|
|
66
|
-
----------
|
|
67
|
-
deco_type : partial
|
|
68
|
-
The decorator class to add to this step
|
|
69
|
-
"""
|
|
70
|
-
# Prevent circular import
|
|
71
|
-
from metaflow.decorators import DuplicateStepDecoratorException, StepDecorator
|
|
72
|
-
|
|
73
|
-
# Validate deco_type
|
|
74
|
-
if (
|
|
75
|
-
not isinstance(deco_type, partial)
|
|
76
|
-
or len(deco_type.args) != 1
|
|
77
|
-
or not issubclass(deco_type.args[0], StepDecorator)
|
|
78
|
-
):
|
|
79
|
-
raise TypeError("add_decorator takes a StepDecorator")
|
|
80
|
-
|
|
81
|
-
deco_type = deco_type.args[0]
|
|
82
|
-
if (
|
|
83
|
-
deco_type.name in [deco.name for deco in self._my_step.decorators]
|
|
84
|
-
and not deco_type.allow_multiple
|
|
85
|
-
):
|
|
86
|
-
raise DuplicateStepDecoratorException(deco_type.name, self._my_step)
|
|
87
|
-
|
|
88
|
-
debug.userconf_exec(
|
|
89
|
-
"Mutable decorator adding step decorator %s to step %s"
|
|
90
|
-
% (deco_type.name, self._my_step.name)
|
|
91
|
-
)
|
|
92
|
-
self._my_step.decorators.append(
|
|
93
|
-
deco_type(attributes=kwargs, statically_defined=True)
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
def remove_decorator(self, deco_name: str, all: bool = True, **kwargs) -> bool:
|
|
97
|
-
"""
|
|
98
|
-
Remove one or more Metaflow decorators from a step.
|
|
99
|
-
|
|
100
|
-
Some decorators can be applied multiple times to a step. This method allows you
|
|
101
|
-
to choose which decorator to remove or just remove all of them or one of them.
|
|
102
|
-
|
|
103
|
-
Parameters
|
|
104
|
-
----------
|
|
105
|
-
deco_name : str
|
|
106
|
-
Name of the decorator to remove
|
|
107
|
-
all : bool, default True
|
|
108
|
-
If True, remove all instances of the decorator that match the filters
|
|
109
|
-
passed using kwargs (or all the instances of the decorator if no filters are
|
|
110
|
-
passed). If False, removes only the first found instance of the decorator.
|
|
111
|
-
|
|
112
|
-
Returns
|
|
113
|
-
-------
|
|
114
|
-
bool
|
|
115
|
-
Returns True if at least one decorator was removed.
|
|
116
|
-
"""
|
|
117
|
-
new_deco_list = []
|
|
118
|
-
did_remove = False
|
|
119
|
-
for deco in self._my_step.decorators:
|
|
120
|
-
if deco.name == deco_name:
|
|
121
|
-
# Evaluate all the configuration values if any
|
|
122
|
-
deco.init()
|
|
123
|
-
|
|
124
|
-
# Check filters
|
|
125
|
-
match_ok = True
|
|
126
|
-
if kwargs:
|
|
127
|
-
for k, v in kwargs.items():
|
|
128
|
-
match_ok = k in deco.attributes and deco.attributes[k] == v
|
|
129
|
-
if match_ok is False:
|
|
130
|
-
break
|
|
131
|
-
if match_ok:
|
|
132
|
-
did_remove = True
|
|
133
|
-
debug.userconf_exec(
|
|
134
|
-
"Mutable decorator removing step decorator %s from step %s"
|
|
135
|
-
% (deco.name, self._my_step.name)
|
|
136
|
-
)
|
|
137
|
-
else:
|
|
138
|
-
new_deco_list.append(deco)
|
|
139
|
-
else:
|
|
140
|
-
new_deco_list.append(deco)
|
|
141
|
-
if did_remove and not all:
|
|
142
|
-
break
|
|
143
|
-
|
|
144
|
-
self._my_step.decorators = new_deco_list
|
|
145
|
-
return did_remove
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
class MutableFlow:
|
|
149
|
-
def __init__(self, flow_spec: "metaflow.flowspec.FlowSpec"):
|
|
150
|
-
self._flow_cls = flow_spec
|
|
151
|
-
|
|
152
|
-
@property
|
|
153
|
-
def decorators(self) -> Generator["metaflow.decorators.FlowDecorator", None, None]:
|
|
154
|
-
"""
|
|
155
|
-
Iterate over all the decorators of this flow. Note that the same type of decorator
|
|
156
|
-
may be present multiple times and no order is guaranteed.
|
|
157
|
-
|
|
158
|
-
Yields
|
|
159
|
-
------
|
|
160
|
-
metaflow.decorators.FlowDecorator
|
|
161
|
-
A decorator of the flow
|
|
162
|
-
"""
|
|
163
|
-
for decos in self._flow_cls._flow_decorators.values():
|
|
164
|
-
for deco in decos:
|
|
165
|
-
yield deco
|
|
166
|
-
|
|
167
|
-
@property
|
|
168
|
-
def configs(self) -> Generator[Tuple[str, ConfigValue], None, None]:
|
|
169
|
-
"""
|
|
170
|
-
Iterate over all user configurations in this flow
|
|
171
|
-
|
|
172
|
-
Use this to parameterize your flow based on configuration. As an example, the
|
|
173
|
-
`evaluate` method of your `CustomFlowDecorator` can use this to add an
|
|
174
|
-
environment decorator.
|
|
175
|
-
```
|
|
176
|
-
class MyDecorator(CustomFlowDecorator):
|
|
177
|
-
def evaluate(flow: MutableFlow):
|
|
178
|
-
val = next(flow.configs)[1].steps.start.cpu
|
|
179
|
-
flow.start.add_decorator(environment, vars={'mycpu': val})
|
|
180
|
-
return flow
|
|
181
|
-
|
|
182
|
-
@MyDecorator()
|
|
183
|
-
class TestFlow(FlowSpec):
|
|
184
|
-
config = Config('myconfig.json')
|
|
185
|
-
|
|
186
|
-
@step
|
|
187
|
-
def start(self):
|
|
188
|
-
pass
|
|
189
|
-
```
|
|
190
|
-
can be used to add an environment decorator to the `start` step.
|
|
191
|
-
|
|
192
|
-
Yields
|
|
193
|
-
------
|
|
194
|
-
Tuple[str, ConfigValue]
|
|
195
|
-
Iterates over the configurations of the flow
|
|
196
|
-
"""
|
|
197
|
-
from metaflow.flowspec import _FlowState
|
|
198
|
-
|
|
199
|
-
# When configs are parsed, they are loaded in _flow_state[_FlowState.CONFIGS]
|
|
200
|
-
for name, value in self._flow_cls._flow_state.get(
|
|
201
|
-
_FlowState.CONFIGS, {}
|
|
202
|
-
).items():
|
|
203
|
-
yield name, ConfigValue(value)
|
|
204
|
-
|
|
205
|
-
@property
|
|
206
|
-
def parameters(self) -> Generator[Tuple[str, Any], None, None]:
|
|
207
|
-
for var, param in self._flow_cls._get_parameters():
|
|
208
|
-
if param.IS_CONFIG_PARAMETER:
|
|
209
|
-
continue
|
|
210
|
-
yield var, param
|
|
211
|
-
|
|
212
|
-
@property
|
|
213
|
-
def steps(self) -> Generator[Tuple[str, MutableStep], None, None]:
|
|
214
|
-
"""
|
|
215
|
-
Iterate over all the steps in this flow. The order of the steps
|
|
216
|
-
returned is not guaranteed.
|
|
217
|
-
|
|
218
|
-
Yields
|
|
219
|
-
------
|
|
220
|
-
Tuple[str, MutableStep]
|
|
221
|
-
A tuple with the step name and the step proxy
|
|
222
|
-
"""
|
|
223
|
-
for var in dir(self._flow_cls):
|
|
224
|
-
potential_step = getattr(self._flow_cls, var)
|
|
225
|
-
if callable(potential_step) and hasattr(potential_step, "is_step"):
|
|
226
|
-
yield var, MutableStep(self._flow_cls, potential_step)
|
|
227
|
-
|
|
228
|
-
def add_parameter(
|
|
229
|
-
self, name: str, value: "metaflow.parameters.Parameter", overwrite: bool = False
|
|
230
|
-
) -> None:
|
|
231
|
-
from metaflow.parameters import Parameter
|
|
232
|
-
|
|
233
|
-
if hasattr(self._flow_cls, name) and not overwrite:
|
|
234
|
-
raise MetaflowException(
|
|
235
|
-
"Flow '%s' already has a class member '%s' -- "
|
|
236
|
-
"set overwrite=True in add_parameter to overwrite it."
|
|
237
|
-
% (self._flow_cls.__name__, name)
|
|
238
|
-
)
|
|
239
|
-
if not isinstance(value, Parameter) or value.IS_CONFIG_PARAMETER:
|
|
240
|
-
raise MetaflowException(
|
|
241
|
-
"Only a Parameter or an IncludeFile can be added using `add_parameter`"
|
|
242
|
-
"; got %s" % type(value)
|
|
243
|
-
)
|
|
244
|
-
debug.userconf_exec("Mutable flow decorator adding parameter %s to flow" % name)
|
|
245
|
-
setattr(self._flow_cls, name, value)
|
|
246
|
-
|
|
247
|
-
def remove_parameter(self, parameter_name: str) -> bool:
|
|
248
|
-
"""
|
|
249
|
-
Remove a parameter from the flow.
|
|
250
|
-
|
|
251
|
-
The name given should match the name of the parameter (can be different
|
|
252
|
-
from the name of the parameter in the flow. You can not remove config parameters.
|
|
253
|
-
|
|
254
|
-
Parameters
|
|
255
|
-
----------
|
|
256
|
-
parameter_name : str
|
|
257
|
-
Name of the parameter
|
|
258
|
-
|
|
259
|
-
Returns
|
|
260
|
-
-------
|
|
261
|
-
bool
|
|
262
|
-
Returns True if the parameter was removed
|
|
263
|
-
"""
|
|
264
|
-
from metaflow.flowspec import _FlowState
|
|
265
|
-
|
|
266
|
-
for var, param in self._flow_cls._get_parameters():
|
|
267
|
-
if param.IS_CONFIG_PARAMETER:
|
|
268
|
-
continue
|
|
269
|
-
if param.name == parameter_name:
|
|
270
|
-
delattr(self._flow_cls, var)
|
|
271
|
-
debug.userconf_exec(
|
|
272
|
-
"Mutable flow decorator removing parameter %s from flow" % var
|
|
273
|
-
)
|
|
274
|
-
# Reset so that we don't list it again
|
|
275
|
-
del self._flow_cls._flow_state[_FlowState.CACHED_PARAMETERS]
|
|
276
|
-
return True
|
|
277
|
-
return False
|
|
278
|
-
|
|
279
|
-
def add_decorator(self, deco_type: partial, **kwargs) -> None:
|
|
280
|
-
"""
|
|
281
|
-
Add a Metaflow decorator to a flow.
|
|
282
|
-
|
|
283
|
-
Parameters
|
|
284
|
-
----------
|
|
285
|
-
deco_type : partial
|
|
286
|
-
The decorator class to add to this flow
|
|
287
|
-
"""
|
|
288
|
-
# Prevent circular import
|
|
289
|
-
from metaflow.decorators import DuplicateFlowDecoratorException, FlowDecorator
|
|
290
|
-
|
|
291
|
-
# Validate deco_type
|
|
292
|
-
if (
|
|
293
|
-
not isinstance(deco_type, partial)
|
|
294
|
-
or len(deco_type.args) != 1
|
|
295
|
-
or not issubclass(deco_type.args[0], FlowDecorator)
|
|
296
|
-
):
|
|
297
|
-
raise TypeError("add_decorator takes a FlowDecorator")
|
|
298
|
-
|
|
299
|
-
deco_type = deco_type.args[0]
|
|
300
|
-
if (
|
|
301
|
-
deco_type.name in self._flow_cls._flow_decorators
|
|
302
|
-
and not deco_type.allow_multiple
|
|
303
|
-
):
|
|
304
|
-
raise DuplicateFlowDecoratorException(deco_type.name)
|
|
305
|
-
|
|
306
|
-
self._flow_cls._flow_decorators.setdefault(deco_type.name, []).append(
|
|
307
|
-
deco_type(attributes=kwargs, statically_defined=True)
|
|
308
|
-
)
|
|
309
|
-
debug.userconf_exec(
|
|
310
|
-
"Mutable flow decorator adding decorator %s to flow" % deco_type.name
|
|
311
|
-
)
|
|
312
|
-
|
|
313
|
-
def remove_decorator(self, deco_name: str, all: bool = True, **kwargs) -> bool:
|
|
314
|
-
"""
|
|
315
|
-
Remove one or more Metaflow decorators from a flow.
|
|
316
|
-
|
|
317
|
-
Some decorators can be applied multiple times to a flow. This method allows you
|
|
318
|
-
to choose which decorator to remove or just remove all of them or one of them.
|
|
319
|
-
|
|
320
|
-
Parameters
|
|
321
|
-
----------
|
|
322
|
-
deco_name : str
|
|
323
|
-
Name of the decorator to remove
|
|
324
|
-
all : bool, default True
|
|
325
|
-
If True, remove all instances of the decorator that match the filters
|
|
326
|
-
passed using kwargs (or all the instances of the decorator if no filters are
|
|
327
|
-
passed). If False, removes only the first found instance of the decorator.
|
|
328
|
-
|
|
329
|
-
Returns
|
|
330
|
-
-------
|
|
331
|
-
bool
|
|
332
|
-
Returns True if at least one decorator was removed.
|
|
333
|
-
"""
|
|
334
|
-
new_deco_list = []
|
|
335
|
-
old_deco_list = self._flow_cls._flow_decorators.get(deco_name)
|
|
336
|
-
if old_deco_list is None:
|
|
337
|
-
return False
|
|
338
|
-
|
|
339
|
-
did_remove = False
|
|
340
|
-
for deco in old_deco_list:
|
|
341
|
-
# Evaluate all the configuration values if any
|
|
342
|
-
deco.init()
|
|
343
|
-
|
|
344
|
-
# Check filters
|
|
345
|
-
match_ok = True
|
|
346
|
-
if kwargs:
|
|
347
|
-
for k, v in kwargs.items():
|
|
348
|
-
match_ok = k in deco.attributes and deco.attributes[k] == v
|
|
349
|
-
if match_ok is False:
|
|
350
|
-
break
|
|
351
|
-
if match_ok:
|
|
352
|
-
did_remove = True
|
|
353
|
-
debug.userconf_exec(
|
|
354
|
-
"Mutable flow decorator removing decorator %s from flow" % deco.name
|
|
355
|
-
)
|
|
356
|
-
else:
|
|
357
|
-
new_deco_list.append(deco)
|
|
358
|
-
if did_remove and not all:
|
|
359
|
-
break
|
|
360
|
-
|
|
361
|
-
if new_deco_list:
|
|
362
|
-
self._flow_cls._flow_decorators[deco_name] = new_deco_list
|
|
363
|
-
else:
|
|
364
|
-
del self._flow_cls._flow_decorators[deco_name]
|
|
365
|
-
return did_remove
|
|
366
|
-
|
|
367
|
-
def __getattr__(self, name):
|
|
368
|
-
# We allow direct access to the steps, configs and parameters but nothing else
|
|
369
|
-
from metaflow.parameters import Parameter
|
|
370
|
-
|
|
371
|
-
attr = getattr(self._flow_cls, name)
|
|
372
|
-
if attr:
|
|
373
|
-
# Steps
|
|
374
|
-
if callable(attr) and hasattr(attr, "is_step"):
|
|
375
|
-
return MutableStep(self._flow_cls, attr)
|
|
376
|
-
if name[0] == "_" or name in self._flow_cls._NON_PARAMETERS:
|
|
377
|
-
raise AttributeError(self, name)
|
|
378
|
-
if isinstance(attr, (Parameter, ConfigValue)):
|
|
379
|
-
return attr
|
|
380
|
-
raise AttributeError(self, name)
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
class CustomFlowDecorator:
|
|
384
|
-
def __init__(self, *args, **kwargs):
|
|
385
|
-
from ..flowspec import FlowSpecMeta
|
|
386
|
-
|
|
387
|
-
if args and isinstance(args[0], (CustomFlowDecorator, FlowSpecMeta)):
|
|
388
|
-
# This means the decorator is bare like @MyDecorator
|
|
389
|
-
# and the first argument is the FlowSpec or another decorator (they
|
|
390
|
-
# can be stacked)
|
|
391
|
-
|
|
392
|
-
# If we have a init function, we call it with no arguments -- this can
|
|
393
|
-
# happen if the user defines a function with default parameters for example
|
|
394
|
-
try:
|
|
395
|
-
self.init()
|
|
396
|
-
except NotImplementedError:
|
|
397
|
-
pass
|
|
398
|
-
|
|
399
|
-
# Now set the flow class we apply to
|
|
400
|
-
if isinstance(args[0], FlowSpecMeta):
|
|
401
|
-
self._set_flow_cls(args[0])
|
|
402
|
-
else:
|
|
403
|
-
self._set_flow_cls(args[0]._flow_cls)
|
|
404
|
-
else:
|
|
405
|
-
# The arguments are actually passed to the init function for this decorator
|
|
406
|
-
self._args = args
|
|
407
|
-
self._kwargs = kwargs
|
|
408
|
-
|
|
409
|
-
def __get__(self, instance, owner):
|
|
410
|
-
# Required so that we "present" as a FlowSpec when the flow decorator is
|
|
411
|
-
# of the form
|
|
412
|
-
# @MyFlowDecorator
|
|
413
|
-
# class MyFlow(FlowSpec):
|
|
414
|
-
# pass
|
|
415
|
-
#
|
|
416
|
-
# In that case, if we don't have __get__, the object is a CustomFlowDecorator
|
|
417
|
-
# and not a FlowSpec. This is more critical for steps (and CustomStepDecorator)
|
|
418
|
-
# because other parts of the code rely on steps having is_step. There are
|
|
419
|
-
# other ways to solve this but this allowed for minimal changes going forward.
|
|
420
|
-
return self()
|
|
421
|
-
|
|
422
|
-
def __call__(
|
|
423
|
-
self, flow_spec: Optional["metaflow.flowspec.FlowSpecMeta"] = None
|
|
424
|
-
) -> "metaflow.flowspec.FlowSpecMeta":
|
|
425
|
-
if flow_spec:
|
|
426
|
-
# This is the case of a decorator @MyDecorator(foo=1, bar=2) and so
|
|
427
|
-
# we already called __init__ and saved foo and bar in self._args and
|
|
428
|
-
# self._kwargs and are now calling this on the flow itself.
|
|
429
|
-
|
|
430
|
-
# You can use config values in the arguments to a CustomFlowDecorator
|
|
431
|
-
# so we resolve those as well
|
|
432
|
-
new_args = [resolve_delayed_evaluator(arg) for arg in self._args]
|
|
433
|
-
new_kwargs = {
|
|
434
|
-
k: resolve_delayed_evaluator(v) for k, v in self._kwargs.items()
|
|
435
|
-
}
|
|
436
|
-
self.init(*new_args, **new_kwargs)
|
|
437
|
-
if hasattr(self, "_empty_init"):
|
|
438
|
-
raise MetaflowException(
|
|
439
|
-
"CustomFlowDecorator '%s' is used with arguments "
|
|
440
|
-
"but does not implement init" % str(self.__class__)
|
|
441
|
-
)
|
|
442
|
-
|
|
443
|
-
return self._set_flow_cls(flow_spec)
|
|
444
|
-
elif not self._flow_cls:
|
|
445
|
-
# This means that somehow the initialization did not happen properly
|
|
446
|
-
# so this may have been applied to a non flow
|
|
447
|
-
raise MetaflowException(
|
|
448
|
-
"A CustomFlowDecorator can only be applied to a FlowSpec"
|
|
449
|
-
)
|
|
450
|
-
return self._flow_cls()
|
|
451
|
-
|
|
452
|
-
def _set_flow_cls(
|
|
453
|
-
self, flow_spec: "metaflow.flowspec.FlowSpecMeta"
|
|
454
|
-
) -> "metaflow.flowspec.FlowSpecMeta":
|
|
455
|
-
from ..flowspec import _FlowState
|
|
456
|
-
|
|
457
|
-
flow_spec._flow_state.setdefault(_FlowState.CONFIG_DECORATORS, []).append(self)
|
|
458
|
-
self._flow_cls = flow_spec
|
|
459
|
-
return flow_spec
|
|
460
|
-
|
|
461
|
-
def init(self, *args, **kwargs):
|
|
462
|
-
"""
|
|
463
|
-
This method is intended to be optionally overridden if you need to
|
|
464
|
-
have an initializer.
|
|
465
|
-
"""
|
|
466
|
-
self._empty_init = True
|
|
467
|
-
|
|
468
|
-
def evaluate(self, mutable_flow: MutableFlow) -> None:
|
|
469
|
-
"""
|
|
470
|
-
Implement this method to act on the flow and modify it as needed.
|
|
471
|
-
|
|
472
|
-
Parameters
|
|
473
|
-
----------
|
|
474
|
-
mutable_flow : MutableFlow
|
|
475
|
-
Flow
|
|
476
|
-
|
|
477
|
-
Raises
|
|
478
|
-
------
|
|
479
|
-
NotImplementedError
|
|
480
|
-
_description_
|
|
481
|
-
"""
|
|
482
|
-
raise NotImplementedError()
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
class CustomStepDecorator:
|
|
486
|
-
def __init__(self, *args, **kwargs):
|
|
487
|
-
arg = None
|
|
488
|
-
if args:
|
|
489
|
-
if isinstance(args[0], CustomStepDecorator):
|
|
490
|
-
arg = args[0]._my_step
|
|
491
|
-
else:
|
|
492
|
-
arg = args[0]
|
|
493
|
-
if arg and callable(arg) and hasattr(arg, "is_step"):
|
|
494
|
-
# This means the decorator is bare like @MyDecorator
|
|
495
|
-
# and the first argument is the step
|
|
496
|
-
self._set_my_step(arg)
|
|
497
|
-
else:
|
|
498
|
-
# The arguments are actually passed to the init function for this decorator
|
|
499
|
-
self._args = args
|
|
500
|
-
self._kwargs = kwargs
|
|
501
|
-
|
|
502
|
-
def __get__(self, instance, owner):
|
|
503
|
-
# See explanation in CustomFlowDecorator.__get__
|
|
504
|
-
return self()
|
|
505
|
-
|
|
506
|
-
def __call__(
|
|
507
|
-
self,
|
|
508
|
-
step: Optional[
|
|
509
|
-
Union[
|
|
510
|
-
Callable[["metaflow.decorators.FlowSpecDerived"], None],
|
|
511
|
-
Callable[["metaflow.decorators.FlowSpecDerived", Any], None],
|
|
512
|
-
]
|
|
513
|
-
] = None,
|
|
514
|
-
) -> Union[
|
|
515
|
-
Callable[["metaflow.decorators.FlowSpecDerived"], None],
|
|
516
|
-
Callable[["metaflow.decorators.FlowSpecDerived", Any], None],
|
|
517
|
-
]:
|
|
518
|
-
if step:
|
|
519
|
-
# You can use config values in the arguments to a CustomFlowDecorator
|
|
520
|
-
# so we resolve those as well
|
|
521
|
-
new_args = [resolve_delayed_evaluator(arg) for arg in self._args]
|
|
522
|
-
new_kwargs = {
|
|
523
|
-
k: resolve_delayed_evaluator(v) for k, v in self._kwargs.items()
|
|
524
|
-
}
|
|
525
|
-
self.init(*new_args, **new_kwargs)
|
|
526
|
-
if hasattr(self, "_empty_init"):
|
|
527
|
-
raise MetaflowException(
|
|
528
|
-
"CustomStepDecorator '%s' is used with arguments "
|
|
529
|
-
"but does not implement init" % str(self.__class__)
|
|
530
|
-
)
|
|
531
|
-
return self._set_my_step(step)
|
|
532
|
-
elif not self._my_step:
|
|
533
|
-
# This means that somehow the initialization did not happen properly
|
|
534
|
-
# so this may have been applied to a non step
|
|
535
|
-
raise MetaflowException(
|
|
536
|
-
"A CustomStepDecorator can only be applied to a step function"
|
|
537
|
-
)
|
|
538
|
-
return self._my_step
|
|
539
|
-
|
|
540
|
-
def _set_my_step(
|
|
541
|
-
self,
|
|
542
|
-
step: Union[
|
|
543
|
-
Callable[["metaflow.decorators.FlowSpecDerived"], None],
|
|
544
|
-
Callable[["metaflow.decorators.FlowSpecDerived", Any], None],
|
|
545
|
-
],
|
|
546
|
-
) -> Union[
|
|
547
|
-
Callable[["metaflow.decorators.FlowSpecDerived"], None],
|
|
548
|
-
Callable[["metaflow.decorators.FlowSpecDerived", Any], None],
|
|
549
|
-
]:
|
|
550
|
-
|
|
551
|
-
self._my_step = step
|
|
552
|
-
self._my_step.config_decorators.append(self)
|
|
553
|
-
return self._my_step
|
|
554
|
-
|
|
555
|
-
def init(self, *args, **kwargs):
|
|
556
|
-
"""
|
|
557
|
-
This method is intended to be optionally overridden if you need to
|
|
558
|
-
have an initializer.
|
|
559
|
-
"""
|
|
560
|
-
self._empty_init = True
|
|
561
|
-
|
|
562
|
-
def evaluate(self, mutable_step: MutableStep) -> None:
|
|
563
|
-
raise NotImplementedError()
|