metaflow 2.15.21__py2.py3-none-any.whl → 2.16.0__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 +7 -1
- metaflow/cli.py +16 -1
- metaflow/cli_components/init_cmd.py +1 -0
- metaflow/cli_components/run_cmds.py +6 -2
- metaflow/client/core.py +22 -30
- metaflow/datastore/task_datastore.py +0 -1
- metaflow/debug.py +5 -0
- metaflow/decorators.py +230 -70
- metaflow/extension_support/__init__.py +15 -8
- metaflow/extension_support/_empty_file.py +2 -2
- metaflow/flowspec.py +80 -53
- metaflow/graph.py +24 -2
- metaflow/meta_files.py +13 -0
- metaflow/metadata_provider/metadata.py +7 -1
- metaflow/metaflow_config.py +5 -0
- metaflow/metaflow_environment.py +82 -25
- metaflow/metaflow_version.py +1 -1
- metaflow/package/__init__.py +664 -0
- metaflow/packaging_sys/__init__.py +870 -0
- metaflow/packaging_sys/backend.py +113 -0
- metaflow/packaging_sys/distribution_support.py +153 -0
- metaflow/packaging_sys/tar_backend.py +86 -0
- metaflow/packaging_sys/utils.py +91 -0
- metaflow/packaging_sys/v1.py +476 -0
- metaflow/plugins/airflow/airflow.py +5 -1
- metaflow/plugins/airflow/airflow_cli.py +15 -4
- metaflow/plugins/argo/argo_workflows.py +15 -4
- metaflow/plugins/argo/argo_workflows_cli.py +16 -4
- metaflow/plugins/aws/batch/batch.py +22 -3
- metaflow/plugins/aws/batch/batch_cli.py +3 -0
- metaflow/plugins/aws/batch/batch_decorator.py +13 -5
- metaflow/plugins/aws/step_functions/step_functions.py +4 -1
- metaflow/plugins/aws/step_functions/step_functions_cli.py +15 -4
- metaflow/plugins/cards/card_decorator.py +0 -5
- metaflow/plugins/kubernetes/kubernetes.py +8 -1
- metaflow/plugins/kubernetes/kubernetes_cli.py +3 -0
- metaflow/plugins/kubernetes/kubernetes_decorator.py +13 -5
- metaflow/plugins/package_cli.py +25 -23
- metaflow/plugins/parallel_decorator.py +4 -2
- metaflow/plugins/pypi/bootstrap.py +8 -2
- metaflow/plugins/pypi/conda_decorator.py +39 -82
- metaflow/plugins/pypi/conda_environment.py +6 -2
- metaflow/plugins/pypi/pypi_decorator.py +4 -4
- metaflow/plugins/test_unbounded_foreach_decorator.py +2 -2
- metaflow/plugins/timeout_decorator.py +0 -1
- metaflow/plugins/uv/bootstrap.py +11 -0
- metaflow/plugins/uv/uv_environment.py +4 -2
- metaflow/pylint_wrapper.py +5 -1
- metaflow/runner/click_api.py +5 -4
- metaflow/runner/subprocess_manager.py +14 -2
- metaflow/runtime.py +37 -11
- metaflow/task.py +91 -7
- metaflow/user_configs/config_options.py +13 -8
- metaflow/user_configs/config_parameters.py +0 -4
- metaflow/user_decorators/__init__.py +0 -0
- metaflow/user_decorators/common.py +144 -0
- metaflow/user_decorators/mutable_flow.py +499 -0
- metaflow/user_decorators/mutable_step.py +424 -0
- metaflow/user_decorators/user_flow_decorator.py +263 -0
- metaflow/user_decorators/user_step_decorator.py +712 -0
- metaflow/util.py +4 -1
- metaflow/version.py +1 -1
- {metaflow-2.15.21.dist-info → metaflow-2.16.0.dist-info}/METADATA +2 -2
- {metaflow-2.15.21.dist-info → metaflow-2.16.0.dist-info}/RECORD +71 -60
- metaflow/info_file.py +0 -25
- metaflow/package.py +0 -203
- metaflow/user_configs/config_decorators.py +0 -568
- {metaflow-2.15.21.data → metaflow-2.16.0.data}/data/share/metaflow/devtools/Makefile +0 -0
- {metaflow-2.15.21.data → metaflow-2.16.0.data}/data/share/metaflow/devtools/Tiltfile +0 -0
- {metaflow-2.15.21.data → metaflow-2.16.0.data}/data/share/metaflow/devtools/pick_services.sh +0 -0
- {metaflow-2.15.21.dist-info → metaflow-2.16.0.dist-info}/WHEEL +0 -0
- {metaflow-2.15.21.dist-info → metaflow-2.16.0.dist-info}/entry_points.txt +0 -0
- {metaflow-2.15.21.dist-info → metaflow-2.16.0.dist-info}/licenses/LICENSE +0 -0
- {metaflow-2.15.21.dist-info → metaflow-2.16.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,424 @@
|
|
1
|
+
from functools import partial
|
2
|
+
from typing import (
|
3
|
+
Any,
|
4
|
+
Callable,
|
5
|
+
Dict,
|
6
|
+
Generator,
|
7
|
+
List,
|
8
|
+
Optional,
|
9
|
+
TYPE_CHECKING,
|
10
|
+
Tuple,
|
11
|
+
Union,
|
12
|
+
)
|
13
|
+
|
14
|
+
from metaflow.debug import debug
|
15
|
+
from metaflow.exception import MetaflowException
|
16
|
+
|
17
|
+
from .user_step_decorator import StepMutator, UserStepDecoratorBase
|
18
|
+
|
19
|
+
if TYPE_CHECKING:
|
20
|
+
import metaflow.decorators
|
21
|
+
import metaflow.flowspec
|
22
|
+
|
23
|
+
|
24
|
+
class MutableStep:
|
25
|
+
IGNORE = 1
|
26
|
+
ERROR = 2
|
27
|
+
OVERRIDE = 3
|
28
|
+
|
29
|
+
def __init__(
|
30
|
+
self,
|
31
|
+
flow_spec: "metaflow.flowspec.FlowSpec",
|
32
|
+
step: Union[
|
33
|
+
Callable[["metaflow.decorators.FlowSpecDerived"], None],
|
34
|
+
Callable[["metaflow.decorators.FlowSpecDerived", Any], None],
|
35
|
+
],
|
36
|
+
pre_mutate: bool = False,
|
37
|
+
statically_defined: bool = False,
|
38
|
+
inserted_by: Optional[str] = None,
|
39
|
+
):
|
40
|
+
from .mutable_flow import MutableFlow
|
41
|
+
|
42
|
+
self._mutable_container = MutableFlow(
|
43
|
+
flow_spec,
|
44
|
+
pre_mutate=pre_mutate,
|
45
|
+
statically_defined=statically_defined,
|
46
|
+
inserted_by=inserted_by,
|
47
|
+
)
|
48
|
+
self._flow_cls = flow_spec.__class__
|
49
|
+
self._my_step = step
|
50
|
+
self._pre_mutate = pre_mutate
|
51
|
+
self._statically_defined = statically_defined
|
52
|
+
self._inserted_by = inserted_by
|
53
|
+
if self._inserted_by is None:
|
54
|
+
# This is an error because MutableSteps should only be created by
|
55
|
+
# StepMutators or FlowMutators. We need to catch it now because otherwise
|
56
|
+
# we may put stuff on the command line (with --with) that would get added
|
57
|
+
# twice and weird behavior may ensue.
|
58
|
+
raise MetaflowException(
|
59
|
+
"MutableStep should only be created by StepMutators or FlowMutators. "
|
60
|
+
"This is an internal error."
|
61
|
+
)
|
62
|
+
|
63
|
+
@property
|
64
|
+
def flow(self) -> "metaflow.user_decorator.mutable_flow.MutableFlow":
|
65
|
+
"""
|
66
|
+
The flow that contains this step
|
67
|
+
|
68
|
+
Returns
|
69
|
+
-------
|
70
|
+
MutableFlow
|
71
|
+
The flow that contains this step
|
72
|
+
"""
|
73
|
+
return self._mutable_container
|
74
|
+
|
75
|
+
@property
|
76
|
+
def decorator_specs(
|
77
|
+
self,
|
78
|
+
) -> Generator[Tuple[str, str, List[Any], Dict[str, Any]], None, None]:
|
79
|
+
"""
|
80
|
+
Iterate over all the decorator specifications of this step. Note that the same
|
81
|
+
type of decorator may be present multiple times and no order is guaranteed.
|
82
|
+
|
83
|
+
The returned tuple contains:
|
84
|
+
- The decorator's name (shortest possible)
|
85
|
+
- The decorator's fully qualified name (in the case of Metaflow decorators, this
|
86
|
+
will indicate which extension the decorator comes from)
|
87
|
+
- A list of positional arguments
|
88
|
+
- A dictionary of keyword arguments
|
89
|
+
|
90
|
+
You can use the resulting tuple to remove a decorator for example
|
91
|
+
|
92
|
+
Yields
|
93
|
+
------
|
94
|
+
str, str, List[Any], Dict[str, Any]
|
95
|
+
A tuple containing the decorator name, it's fully qualified name,
|
96
|
+
a list of positional arguments, and a dictionary of keyword arguments.
|
97
|
+
"""
|
98
|
+
for deco in self._my_step.decorators:
|
99
|
+
# 3.7 does not support yield foo, *bar syntax so we
|
100
|
+
# work around
|
101
|
+
r = [
|
102
|
+
deco.name,
|
103
|
+
"%s.%s"
|
104
|
+
% (
|
105
|
+
deco.__class__.__module__,
|
106
|
+
deco.__class__.__name__,
|
107
|
+
),
|
108
|
+
]
|
109
|
+
r.extend(deco.get_args_kwargs())
|
110
|
+
yield tuple(r)
|
111
|
+
|
112
|
+
for deco in self._my_step.wrappers:
|
113
|
+
r = [
|
114
|
+
UserStepDecoratorBase.get_decorator_name(deco.__class__),
|
115
|
+
deco.decorator_name,
|
116
|
+
]
|
117
|
+
r.extend(deco.get_args_kwargs())
|
118
|
+
yield tuple(r)
|
119
|
+
|
120
|
+
for deco in self._my_step.config_decorators:
|
121
|
+
r = [
|
122
|
+
UserStepDecoratorBase.get_decorator_name(deco.__class__),
|
123
|
+
deco.decorator_name,
|
124
|
+
]
|
125
|
+
r.extend(deco.get_args_kwargs())
|
126
|
+
yield tuple(r)
|
127
|
+
|
128
|
+
def add_decorator(
|
129
|
+
self,
|
130
|
+
deco_type: Union[partial, UserStepDecoratorBase, str],
|
131
|
+
deco_args: Optional[List[Any]] = None,
|
132
|
+
deco_kwargs: Optional[Dict[str, Any]] = None,
|
133
|
+
duplicates: int = IGNORE,
|
134
|
+
) -> None:
|
135
|
+
"""
|
136
|
+
Add a Metaflow step-decorator or a user step-decorator to a step.
|
137
|
+
|
138
|
+
You can either add the decorator itself or its decorator specification for it
|
139
|
+
(the same you would get back from decorator_specs). You can also mix and match
|
140
|
+
but you cannot provide arguments both through the string and the
|
141
|
+
deco_args/deco_kwargs.
|
142
|
+
|
143
|
+
As an example:
|
144
|
+
```
|
145
|
+
from metaflow import environment
|
146
|
+
...
|
147
|
+
my_step.add_decorator(environment, deco_kwargs={"vars": {"foo": 42})}
|
148
|
+
```
|
149
|
+
|
150
|
+
is equivalent to:
|
151
|
+
```
|
152
|
+
my_step.add_decorator('environment:vars={"foo": 42}')
|
153
|
+
```
|
154
|
+
|
155
|
+
is equivalent to:
|
156
|
+
```
|
157
|
+
my_step.add_decorator('environment', deco_kwargs={"vars":{"foo": 42}})
|
158
|
+
```
|
159
|
+
|
160
|
+
but this is not allowed:
|
161
|
+
```
|
162
|
+
my_step.add_decorator('environment:vars={"bar" 43}', deco_kwargs={"vars":{"foo": 42}})
|
163
|
+
```
|
164
|
+
|
165
|
+
Note in the case where you specify a
|
166
|
+
string for the decorator, there is no need to import the decorator.
|
167
|
+
|
168
|
+
The string syntax is useful to, for example, allow decorators to be stored as
|
169
|
+
strings in a configuration file.
|
170
|
+
|
171
|
+
You can only add StepMutators in the pre_mutate stage.
|
172
|
+
|
173
|
+
In terms of precedence for decorators:
|
174
|
+
- if a decorator can be applied multiple times (like `@card`) all decorators
|
175
|
+
added are kept.
|
176
|
+
- if `duplicates` is set to `MutableStep.IGNORE`, then the decorator
|
177
|
+
being added is ignored (in other words, the existing decorator has precedence).
|
178
|
+
- if `duplicates` is set to `MutableStep.OVERRIDE`, then the *existing*
|
179
|
+
decorator is removed and this newly added one replaces it (in other
|
180
|
+
words, the newly added decorator has precedence).
|
181
|
+
- if `duplicates` is set to `MutableStep.ERROR`, then an error is raised but only
|
182
|
+
if the newly added decorator is *static* (ie: defined directly in the code).
|
183
|
+
If not, it is ignored.
|
184
|
+
|
185
|
+
Parameters
|
186
|
+
----------
|
187
|
+
deco_type : Union[partial, UserStepDecoratorBase, str]
|
188
|
+
The decorator class to add to this step.
|
189
|
+
deco_args : List[Any], optional, default None
|
190
|
+
Positional arguments to pass to the decorator.
|
191
|
+
deco_kwargs : Dict[str, Any], optional, default None
|
192
|
+
Keyword arguments to pass to the decorator.
|
193
|
+
duplicates : int, default MutableStep.IGNORE
|
194
|
+
Instruction on how to handle duplicates. It can be one of:
|
195
|
+
- `MutableStep.IGNORE`: Ignore the decorator if it already exists.
|
196
|
+
- `MutableStep.ERROR`: Raise an error if the decorator already exists.
|
197
|
+
- `MutableStep.OVERRIDE`: Remove the existing decorator and add this one.
|
198
|
+
"""
|
199
|
+
# Prevent circular import
|
200
|
+
from metaflow.decorators import (
|
201
|
+
DuplicateStepDecoratorException,
|
202
|
+
StepDecorator,
|
203
|
+
extract_step_decorator_from_decospec,
|
204
|
+
)
|
205
|
+
|
206
|
+
deco_args = deco_args or []
|
207
|
+
deco_kwargs = deco_kwargs or {}
|
208
|
+
|
209
|
+
def _add_step_decorator(step_deco):
|
210
|
+
if deco_args:
|
211
|
+
raise MetaflowException(
|
212
|
+
"Step decorators do not take additional positional arguments"
|
213
|
+
)
|
214
|
+
# Update kwargs:
|
215
|
+
step_deco.attributes.update(deco_kwargs)
|
216
|
+
|
217
|
+
# Check duplicates
|
218
|
+
def _do_add():
|
219
|
+
step_deco.statically_defined = self._statically_defined
|
220
|
+
step_deco.inserted_by = self._inserted_by
|
221
|
+
self._my_step.decorators.append(step_deco)
|
222
|
+
debug.userconf_exec(
|
223
|
+
"Mutable step adding step decorator '%s' to step '%s'"
|
224
|
+
% (deco_type, self._my_step.name)
|
225
|
+
)
|
226
|
+
|
227
|
+
existing_deco = [
|
228
|
+
d for d in self._my_step.decorators if d.name == step_deco.name
|
229
|
+
]
|
230
|
+
|
231
|
+
if step_deco.allow_multiple or not existing_deco:
|
232
|
+
_do_add()
|
233
|
+
elif duplicates == MutableStep.IGNORE:
|
234
|
+
# If we ignore, we do not add the decorator
|
235
|
+
debug.userconf_exec(
|
236
|
+
"Mutable step ignoring step decorator '%s' on step '%s' "
|
237
|
+
"(already exists and duplicates are ignored)"
|
238
|
+
% (step_deco.name, self._my_step.name)
|
239
|
+
)
|
240
|
+
elif duplicates == MutableStep.OVERRIDE:
|
241
|
+
# If we override, we remove the existing decorator and add this one
|
242
|
+
debug.userconf_exec(
|
243
|
+
"Mutable step overriding step decorator '%s' on step '%s' "
|
244
|
+
"(removing existing decorator and adding new one)"
|
245
|
+
% (step_deco.name, self._my_step.name)
|
246
|
+
)
|
247
|
+
self._my_step.decorators = [
|
248
|
+
d for d in self._my_step.decorators if d.name != step_deco.name
|
249
|
+
]
|
250
|
+
_do_add()
|
251
|
+
elif duplicates == MutableStep.ERROR:
|
252
|
+
# If we error, we raise an exception
|
253
|
+
if self._statically_defined:
|
254
|
+
raise DuplicateStepDecoratorException(step_deco.name, self._my_step)
|
255
|
+
else:
|
256
|
+
debug.userconf_exec(
|
257
|
+
"Mutable step ignoring step decorator '%s' on step '%s' "
|
258
|
+
"(already exists and non statically defined)"
|
259
|
+
% (step_deco.name, self._my_step.name)
|
260
|
+
)
|
261
|
+
else:
|
262
|
+
raise ValueError("Invalid duplicates value: %s" % duplicates)
|
263
|
+
|
264
|
+
if isinstance(deco_type, str):
|
265
|
+
step_deco, has_args_kwargs = extract_step_decorator_from_decospec(deco_type)
|
266
|
+
if (deco_args or deco_kwargs) and has_args_kwargs:
|
267
|
+
raise MetaflowException(
|
268
|
+
"Cannot specify additional arguments when adding a user step "
|
269
|
+
"decorator using a decospec that already has arguments"
|
270
|
+
)
|
271
|
+
|
272
|
+
if isinstance(step_deco, StepDecorator):
|
273
|
+
_add_step_decorator(step_deco)
|
274
|
+
else:
|
275
|
+
# User defined decorator.
|
276
|
+
if not self._pre_mutate and isinstance(step_deco, StepMutator):
|
277
|
+
raise MetaflowException(
|
278
|
+
"Adding step mutator '%s' from %s is only allowed in the "
|
279
|
+
"`pre_mutate` method and not the `mutate` method"
|
280
|
+
% (step_deco.decorator_name, self._inserted_by)
|
281
|
+
)
|
282
|
+
|
283
|
+
if deco_args or deco_kwargs:
|
284
|
+
# We need to recreate the object if there were args or kwargs
|
285
|
+
# since they were not in the string
|
286
|
+
step_deco = step_deco.__class__(*deco_args, **deco_kwargs)
|
287
|
+
|
288
|
+
step_deco.add_or_raise(
|
289
|
+
self._my_step,
|
290
|
+
self._statically_defined,
|
291
|
+
duplicates,
|
292
|
+
self._inserted_by,
|
293
|
+
)
|
294
|
+
return
|
295
|
+
|
296
|
+
if isinstance(deco_type, type) and issubclass(deco_type, UserStepDecoratorBase):
|
297
|
+
# We can only add step mutators in the pre mutate stage.
|
298
|
+
if not self._pre_mutate and issubclass(deco_type, StepMutator):
|
299
|
+
raise MetaflowException(
|
300
|
+
"Adding step mutator '%s' from %s is only allowed in the "
|
301
|
+
"`pre_mutate` method and not the `mutate` method"
|
302
|
+
% (step_deco.decorator_name, self._inserted_by)
|
303
|
+
)
|
304
|
+
debug.userconf_exec(
|
305
|
+
"Mutable step adding decorator %s to step %s"
|
306
|
+
% (deco_type, self._my_step.name)
|
307
|
+
)
|
308
|
+
|
309
|
+
d = deco_type(*deco_args, **deco_kwargs)
|
310
|
+
# add_or_raise properly registers the decorator
|
311
|
+
d.add_or_raise(
|
312
|
+
self._my_step, self._statically_defined, duplicates, self._inserted_by
|
313
|
+
)
|
314
|
+
return
|
315
|
+
|
316
|
+
# At this point, it should be a regular Metaflow step decorator
|
317
|
+
if (
|
318
|
+
not isinstance(deco_type, partial)
|
319
|
+
or len(deco_type.args) != 1
|
320
|
+
or not issubclass(deco_type.args[0], StepDecorator)
|
321
|
+
):
|
322
|
+
raise TypeError(
|
323
|
+
"add_decorator takes a metaflow decorator or user StepDecorator"
|
324
|
+
)
|
325
|
+
|
326
|
+
deco_type = deco_type.args[0]
|
327
|
+
_add_step_decorator(
|
328
|
+
deco_type(
|
329
|
+
attributes=deco_kwargs,
|
330
|
+
statically_defined=self._statically_defined,
|
331
|
+
inserted_by=self._inserted_by,
|
332
|
+
)
|
333
|
+
)
|
334
|
+
|
335
|
+
def remove_decorator(
|
336
|
+
self,
|
337
|
+
deco_name: str,
|
338
|
+
deco_args: Optional[List[Any]] = None,
|
339
|
+
deco_kwargs: Optional[Dict[str, Any]] = None,
|
340
|
+
) -> bool:
|
341
|
+
"""
|
342
|
+
Remove a step-level decorator. To remove a decorator, you can pass the decorator
|
343
|
+
specification (obtained from `decorator_specs` for example).
|
344
|
+
Note that if multiple decorators share the same decorator specification
|
345
|
+
(very rare), they will all be removed.
|
346
|
+
|
347
|
+
You can only remove StepMutators in the `pre_mutate` method.
|
348
|
+
|
349
|
+
Parameters
|
350
|
+
----------
|
351
|
+
deco_name : str
|
352
|
+
Decorator specification of the decorator to remove. If nothing else is
|
353
|
+
specified, all decorators matching that name will be removed.
|
354
|
+
deco_args : List[Any], optional, default None
|
355
|
+
Positional arguments to match the decorator specification.
|
356
|
+
deco_kwargs : Dict[str, Any], optional, default None
|
357
|
+
Keyword arguments to match the decorator specification.
|
358
|
+
|
359
|
+
Returns
|
360
|
+
-------
|
361
|
+
bool
|
362
|
+
Returns True if a decorator was removed.
|
363
|
+
"""
|
364
|
+
|
365
|
+
do_all = deco_args is None and deco_kwargs is None
|
366
|
+
did_remove = False
|
367
|
+
canonical_deco_type = UserStepDecoratorBase.get_decorator_by_name(deco_name)
|
368
|
+
if issubclass(canonical_deco_type, UserStepDecoratorBase):
|
369
|
+
for attr in ["config_decorators", "wrappers"]:
|
370
|
+
new_deco_list = []
|
371
|
+
|
372
|
+
for deco in getattr(self._my_step, attr):
|
373
|
+
if deco.decorator_name == canonical_deco_type.decorator_name:
|
374
|
+
if do_all:
|
375
|
+
continue # We remove all decorators with this name
|
376
|
+
if deco.get_args_kwargs() == (
|
377
|
+
deco_args or [],
|
378
|
+
deco_kwargs or {},
|
379
|
+
):
|
380
|
+
if not self._pre_mutate and isinstance(deco, StepMutator):
|
381
|
+
raise MetaflowException(
|
382
|
+
"Removing step mutator '%s' from %s is only allowed in the "
|
383
|
+
"`pre_mutate` method and not the `mutate` method"
|
384
|
+
% (deco.decorator_name, self._inserted_by)
|
385
|
+
)
|
386
|
+
did_remove = True
|
387
|
+
debug.userconf_exec(
|
388
|
+
"Mutable step removing user step decorator '%s' from step '%s'"
|
389
|
+
% (deco.decorator_name, self._my_step.name)
|
390
|
+
)
|
391
|
+
else:
|
392
|
+
new_deco_list.append(deco)
|
393
|
+
else:
|
394
|
+
new_deco_list.append(deco)
|
395
|
+
setattr(self._my_step, attr, new_deco_list)
|
396
|
+
|
397
|
+
if did_remove:
|
398
|
+
return True
|
399
|
+
for deco in self._my_step.decorators:
|
400
|
+
if deco.name == deco_name:
|
401
|
+
if do_all:
|
402
|
+
continue # We remove all decorators with this name
|
403
|
+
# Check if the decorator specification matches
|
404
|
+
if deco.get_args_kwargs() == (deco_args, deco_kwargs):
|
405
|
+
did_remove = True
|
406
|
+
debug.userconf_exec(
|
407
|
+
"Mutable step removing step decorator '%s' from step '%s'"
|
408
|
+
% (deco.name, self._my_step.name)
|
409
|
+
)
|
410
|
+
else:
|
411
|
+
new_deco_list.append(deco)
|
412
|
+
else:
|
413
|
+
new_deco_list.append(deco)
|
414
|
+
|
415
|
+
self._my_step.decorators = new_deco_list
|
416
|
+
|
417
|
+
if did_remove:
|
418
|
+
return True
|
419
|
+
|
420
|
+
debug.userconf_exec(
|
421
|
+
"Mutable step did not find decorator '%s' to remove from step '%s'"
|
422
|
+
% (deco_name, self._my_step.name)
|
423
|
+
)
|
424
|
+
return False
|
@@ -0,0 +1,263 @@
|
|
1
|
+
from typing import Dict, Optional, Union, TYPE_CHECKING
|
2
|
+
|
3
|
+
from metaflow.exception import MetaflowException
|
4
|
+
from metaflow.user_configs.config_parameters import (
|
5
|
+
resolve_delayed_evaluator,
|
6
|
+
unpack_delayed_evaluator,
|
7
|
+
)
|
8
|
+
|
9
|
+
from .common import ClassPath_Trie
|
10
|
+
|
11
|
+
if TYPE_CHECKING:
|
12
|
+
import metaflow.flowspec
|
13
|
+
import metaflow.user_decorators.mutable_flow
|
14
|
+
|
15
|
+
|
16
|
+
class FlowMutatorMeta(type):
|
17
|
+
_all_registered_decorators = ClassPath_Trie()
|
18
|
+
_do_not_register = set()
|
19
|
+
_import_modules = set()
|
20
|
+
|
21
|
+
def __new__(mcs, name, bases, namespace):
|
22
|
+
cls = super().__new__(mcs, name, bases, namespace)
|
23
|
+
cls.decorator_name = getattr(
|
24
|
+
cls, "_decorator_name", f"{cls.__module__}.{cls.__name__}"
|
25
|
+
)
|
26
|
+
if not cls.__module__.startswith("metaflow.") and not cls.__module__.startswith(
|
27
|
+
"metaflow_extensions."
|
28
|
+
):
|
29
|
+
mcs._import_modules.add(cls.__module__)
|
30
|
+
|
31
|
+
if name == "FlowMutator" or cls.decorator_name in mcs._do_not_register:
|
32
|
+
return cls
|
33
|
+
|
34
|
+
# We inject a __init_subclass__ method so we can figure out if there
|
35
|
+
# are subclasses. We want to register as decorators only the ones that do
|
36
|
+
# not have a subclass. The logic is that everything is registered and if
|
37
|
+
# a subclass shows up, we will unregister the parent class leaving only those
|
38
|
+
# classes that do not have any subclasses registered.
|
39
|
+
@classmethod
|
40
|
+
def do_unregister(cls_, **_kwargs):
|
41
|
+
for base in cls_.__bases__:
|
42
|
+
if isinstance(base, FlowMutatorMeta):
|
43
|
+
# If the base is a FlowMutatorMeta, we unregister it
|
44
|
+
# so that we don't have any decorators that are not the
|
45
|
+
# most derived one.
|
46
|
+
mcs._all_registered_decorators.remove(base.decorator_name)
|
47
|
+
# Also make sure we don't register again
|
48
|
+
mcs._do_not_register.add(base.decorator_name)
|
49
|
+
|
50
|
+
cls.__init_subclass__ = do_unregister
|
51
|
+
mcs._all_registered_decorators.insert(cls.decorator_name, cls)
|
52
|
+
return cls
|
53
|
+
|
54
|
+
@classmethod
|
55
|
+
def all_decorators(mcs) -> Dict[str, "FlowMutatorMeta"]:
|
56
|
+
mcs._check_init()
|
57
|
+
return mcs._all_registered_decorators.get_unique_prefixes()
|
58
|
+
|
59
|
+
def __str__(cls):
|
60
|
+
return "FlowMutator(%s)" % cls.decorator_name
|
61
|
+
|
62
|
+
@classmethod
|
63
|
+
def get_decorator_by_name(
|
64
|
+
mcs, decorator_name: str
|
65
|
+
) -> Optional[Union["FlowDecoratorMeta", "metaflow.decorators.Decorator"]]:
|
66
|
+
"""
|
67
|
+
Get a decorator by its name.
|
68
|
+
|
69
|
+
Parameters
|
70
|
+
----------
|
71
|
+
decorator_name: str
|
72
|
+
The name of the decorator to retrieve.
|
73
|
+
|
74
|
+
Returns
|
75
|
+
-------
|
76
|
+
Optional[FlowDecoratorMeta]
|
77
|
+
The decorator class if found, None otherwise.
|
78
|
+
"""
|
79
|
+
mcs._check_init()
|
80
|
+
return mcs._all_registered_decorators.unique_prefix_value(decorator_name)
|
81
|
+
|
82
|
+
@classmethod
|
83
|
+
def get_decorator_name(mcs, decorator_type: type) -> Optional[str]:
|
84
|
+
"""
|
85
|
+
Get the minimally unique classpath name for a decorator type.
|
86
|
+
|
87
|
+
Parameters
|
88
|
+
----------
|
89
|
+
decorator_type: type
|
90
|
+
The type of the decorator to retrieve the name for.
|
91
|
+
|
92
|
+
Returns
|
93
|
+
-------
|
94
|
+
Optional[str]
|
95
|
+
The minimally unique classpath name if found, None otherwise.
|
96
|
+
"""
|
97
|
+
mcs._check_init()
|
98
|
+
return mcs._all_registered_decorators.unique_prefix_for_type(decorator_type)
|
99
|
+
|
100
|
+
@classmethod
|
101
|
+
def _check_init(mcs):
|
102
|
+
# Delay importing STEP_DECORATORS until we actually need it
|
103
|
+
if not mcs._all_registered_decorators.inited:
|
104
|
+
from metaflow.plugins import FLOW_DECORATORS
|
105
|
+
|
106
|
+
mcs._all_registered_decorators.init([(t.name, t) for t in FLOW_DECORATORS])
|
107
|
+
|
108
|
+
|
109
|
+
class FlowMutator(metaclass=FlowMutatorMeta):
|
110
|
+
"""
|
111
|
+
Derive from this class to implement a flow mutator.
|
112
|
+
|
113
|
+
A flow mutator allows you to introspect a flow and its included steps. You can
|
114
|
+
then add parameters, configurations and decorators to the flow as well as modify
|
115
|
+
any of its steps.
|
116
|
+
use values available through configurations to determine how to mutate the flow.
|
117
|
+
|
118
|
+
There are two main methods provided:
|
119
|
+
- pre_mutate: called as early as possible right after configuration values are read.
|
120
|
+
- mutate: called right after all the command line is parsed but before any
|
121
|
+
Metaflow decorators are applied.
|
122
|
+
|
123
|
+
The `mutate` method does not allow you to modify the flow itself but you can still
|
124
|
+
modify the steps.
|
125
|
+
"""
|
126
|
+
|
127
|
+
def __init__(self, *args, **kwargs):
|
128
|
+
from ..flowspec import FlowSpecMeta
|
129
|
+
|
130
|
+
self._flow_cls = None
|
131
|
+
# Flow mutators are always statically defined (no way of passing them
|
132
|
+
# on the command line or adding them via configs)
|
133
|
+
self.statically_defined = True
|
134
|
+
self.inserted_by = None
|
135
|
+
|
136
|
+
# The arguments are actually passed to the init function for this decorator
|
137
|
+
# and used in _graph_info
|
138
|
+
self._args = args
|
139
|
+
self._kwargs = kwargs
|
140
|
+
if args and isinstance(args[0], (FlowMutator, FlowSpecMeta)):
|
141
|
+
# This means the decorator is bare like @MyDecorator
|
142
|
+
# and the first argument is the FlowSpec or another decorator (they
|
143
|
+
# can be stacked)
|
144
|
+
|
145
|
+
# Now set the flow class we apply to
|
146
|
+
if isinstance(args[0], FlowSpecMeta):
|
147
|
+
self._set_flow_cls(args[0])
|
148
|
+
else:
|
149
|
+
self._set_flow_cls(args[0]._flow_cls)
|
150
|
+
self._args = self._args[1:] # Remove the first argument
|
151
|
+
|
152
|
+
def __mro_entries__(self, bases):
|
153
|
+
# This is called in the following case:
|
154
|
+
# @MyMutator
|
155
|
+
# class MyBaseFlowSpec(FlowSpec):
|
156
|
+
# pass
|
157
|
+
#
|
158
|
+
# class MyFlow(MyBaseFlowSpec):
|
159
|
+
# pass
|
160
|
+
#
|
161
|
+
# MyBaseFlowSpec will be an object of type MyMutator which is not
|
162
|
+
# great when inheriting from it. With this method, we ensure that the
|
163
|
+
# base class will actually be MyBaseFlowSpec
|
164
|
+
return (self._flow_cls,)
|
165
|
+
|
166
|
+
def __call__(
|
167
|
+
self, flow_spec: Optional["metaflow.flowspec.FlowSpecMeta"] = None
|
168
|
+
) -> "metaflow.flowspec.FlowSpecMeta":
|
169
|
+
if flow_spec:
|
170
|
+
if isinstance(flow_spec, FlowMutator):
|
171
|
+
flow_spec = flow_spec._flow_cls
|
172
|
+
return self._set_flow_cls(flow_spec)
|
173
|
+
elif not self._flow_cls:
|
174
|
+
# This means that somehow the initialization did not happen properly
|
175
|
+
# so this may have been applied to a non flow
|
176
|
+
raise MetaflowException("A FlowMutator can only be applied to a FlowSpec")
|
177
|
+
# NOTA: This returns self._flow_cls() because the object in the case of
|
178
|
+
# @FlowDecorator
|
179
|
+
# class MyFlow(FlowSpec):
|
180
|
+
# pass
|
181
|
+
# the object is a FlowDecorator and when the main function calls it, we end up
|
182
|
+
# here and need to actually call the FlowSpec. This is not the case when using
|
183
|
+
# a decorator with arguments because in the line above, we will have returned a
|
184
|
+
# FlowSpec object. Previous solution was to use __get__ but this does not seem
|
185
|
+
# to work properly.
|
186
|
+
return self._flow_cls()
|
187
|
+
|
188
|
+
def _set_flow_cls(
|
189
|
+
self, flow_spec: "metaflow.flowspec.FlowSpecMeta"
|
190
|
+
) -> "metaflow.flowspec.FlowSpecMeta":
|
191
|
+
from ..flowspec import _FlowState
|
192
|
+
|
193
|
+
flow_spec._flow_state.setdefault(_FlowState.CONFIG_DECORATORS, []).append(self)
|
194
|
+
self._flow_cls = flow_spec
|
195
|
+
return flow_spec
|
196
|
+
|
197
|
+
def __str__(self):
|
198
|
+
return str(self.__class__)
|
199
|
+
|
200
|
+
def init(self, *args, **kwargs):
|
201
|
+
"""
|
202
|
+
Implement this method if you wish for your FlowMutator to take in arguments.
|
203
|
+
|
204
|
+
Your flow-mutator can then look like:
|
205
|
+
|
206
|
+
@MyMutator(arg1, arg2)
|
207
|
+
class MyFlow(FlowSpec):
|
208
|
+
pass
|
209
|
+
|
210
|
+
It is an error to use your mutator with arguments but not implement this method.
|
211
|
+
|
212
|
+
"""
|
213
|
+
pass
|
214
|
+
|
215
|
+
def external_init(self):
|
216
|
+
# You can use config values in the arguments to a FlowMutator
|
217
|
+
# so we resolve those as well
|
218
|
+
self._args = [resolve_delayed_evaluator(arg) for arg in self._args]
|
219
|
+
self._kwargs, _ = unpack_delayed_evaluator(self._kwargs)
|
220
|
+
self._kwargs = {
|
221
|
+
k: resolve_delayed_evaluator(v) for k, v in self._kwargs.items()
|
222
|
+
}
|
223
|
+
if self._args or self._kwargs:
|
224
|
+
if "init" not in self.__class__.__dict__:
|
225
|
+
raise MetaflowException(
|
226
|
+
"%s is used with arguments but does not implement init" % self
|
227
|
+
)
|
228
|
+
self.init(*self._args, **self._kwargs)
|
229
|
+
|
230
|
+
def pre_mutate(
|
231
|
+
self, mutable_flow: "metaflow.user_decorators.mutable_flow.MutableFlow"
|
232
|
+
) -> None:
|
233
|
+
"""
|
234
|
+
Method called right after all configuration values are read.
|
235
|
+
|
236
|
+
Parameters
|
237
|
+
----------
|
238
|
+
mutable_flow : metaflow.user_decorators.mutable_flow.MutableFlow
|
239
|
+
A representation of this flow
|
240
|
+
"""
|
241
|
+
return None
|
242
|
+
|
243
|
+
def mutate(
|
244
|
+
self, mutable_flow: "metaflow.user_decorators.mutable_flow.MutableFlow"
|
245
|
+
) -> None:
|
246
|
+
"""
|
247
|
+
Method called right before the first Metaflow step decorator is applied. This
|
248
|
+
means that the command line, including all `--with` options has been parsed.
|
249
|
+
|
250
|
+
Given how late this function is called, there are a few restrictions on what
|
251
|
+
you can do; the following methods on MutableFlow are not allowed and calling
|
252
|
+
them will result in an error:
|
253
|
+
- add_parameter/remove_parameter
|
254
|
+
- add_decorator/remove_decorator
|
255
|
+
|
256
|
+
To call these methods, use the `pre_mutate` method instead.
|
257
|
+
|
258
|
+
Parameters
|
259
|
+
----------
|
260
|
+
mutable_flow : metaflow.user_decorators.mutable_flow.MutableFlow
|
261
|
+
A representation of this flow
|
262
|
+
"""
|
263
|
+
return None
|