metaflow 2.12.36__py2.py3-none-any.whl → 2.12.38__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 +3 -0
- metaflow/cli.py +84 -697
- 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 +51 -0
- metaflow/cli_components/run_cmds.py +358 -0
- metaflow/cli_components/step_cmd.py +189 -0
- metaflow/cli_components/utils.py +140 -0
- metaflow/cmd/develop/stub_generator.py +9 -2
- metaflow/decorators.py +63 -2
- metaflow/extension_support/plugins.py +41 -27
- metaflow/flowspec.py +156 -16
- metaflow/includefile.py +50 -22
- metaflow/metaflow_config.py +1 -1
- metaflow/package.py +17 -3
- metaflow/parameters.py +80 -23
- metaflow/plugins/__init__.py +4 -0
- metaflow/plugins/airflow/airflow_cli.py +1 -0
- metaflow/plugins/argo/argo_workflows.py +41 -1
- metaflow/plugins/argo/argo_workflows_cli.py +1 -0
- metaflow/plugins/aws/batch/batch_decorator.py +2 -2
- metaflow/plugins/aws/step_functions/step_functions.py +32 -0
- metaflow/plugins/aws/step_functions/step_functions_cli.py +1 -0
- 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 +22 -0
- metaflow/plugins/pypi/pypi_decorator.py +1 -0
- metaflow/plugins/timeout_decorator.py +2 -2
- metaflow/runner/click_api.py +73 -19
- metaflow/runtime.py +111 -73
- metaflow/sidecar/sidecar_worker.py +1 -1
- metaflow/user_configs/__init__.py +0 -0
- metaflow/user_configs/config_decorators.py +563 -0
- metaflow/user_configs/config_options.py +495 -0
- metaflow/user_configs/config_parameters.py +386 -0
- metaflow/util.py +17 -0
- metaflow/version.py +1 -1
- {metaflow-2.12.36.dist-info → metaflow-2.12.38.dist-info}/METADATA +3 -2
- {metaflow-2.12.36.dist-info → metaflow-2.12.38.dist-info}/RECORD +45 -35
- {metaflow-2.12.36.dist-info → metaflow-2.12.38.dist-info}/LICENSE +0 -0
- {metaflow-2.12.36.dist-info → metaflow-2.12.38.dist-info}/WHEEL +0 -0
- {metaflow-2.12.36.dist-info → metaflow-2.12.38.dist-info}/entry_points.txt +0 -0
- {metaflow-2.12.36.dist-info → metaflow-2.12.38.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,563 @@
|
|
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()
|