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.

Files changed (54) hide show
  1. metaflow/__init__.py +0 -3
  2. metaflow/cli.py +697 -84
  3. metaflow/cli_args.py +0 -17
  4. metaflow/cmd/develop/stub_generator.py +2 -9
  5. metaflow/decorators.py +2 -63
  6. metaflow/extension_support/plugins.py +27 -41
  7. metaflow/flowspec.py +16 -156
  8. metaflow/includefile.py +22 -50
  9. metaflow/metaflow_config.py +1 -1
  10. metaflow/package.py +3 -17
  11. metaflow/parameters.py +23 -80
  12. metaflow/plugins/__init__.py +0 -4
  13. metaflow/plugins/airflow/airflow_cli.py +0 -1
  14. metaflow/plugins/argo/argo_workflows.py +1 -41
  15. metaflow/plugins/argo/argo_workflows_cli.py +0 -1
  16. metaflow/plugins/argo/argo_workflows_deployer_objects.py +1 -5
  17. metaflow/plugins/aws/batch/batch_decorator.py +2 -2
  18. metaflow/plugins/aws/step_functions/step_functions.py +0 -32
  19. metaflow/plugins/aws/step_functions/step_functions_cli.py +0 -1
  20. metaflow/plugins/aws/step_functions/step_functions_deployer_objects.py +0 -3
  21. metaflow/plugins/datatools/s3/s3op.py +3 -3
  22. metaflow/plugins/kubernetes/kubernetes_cli.py +1 -1
  23. metaflow/plugins/kubernetes/kubernetes_decorator.py +2 -2
  24. metaflow/plugins/pypi/conda_decorator.py +10 -20
  25. metaflow/plugins/pypi/pypi_decorator.py +9 -11
  26. metaflow/plugins/timeout_decorator.py +2 -2
  27. metaflow/runner/click_api.py +19 -73
  28. metaflow/runner/deployer.py +1 -1
  29. metaflow/runner/deployer_impl.py +2 -2
  30. metaflow/runner/metaflow_runner.py +1 -4
  31. metaflow/runner/nbdeploy.py +0 -2
  32. metaflow/runner/nbrun.py +1 -1
  33. metaflow/runner/subprocess_manager.py +1 -3
  34. metaflow/runner/utils.py +20 -37
  35. metaflow/runtime.py +73 -111
  36. metaflow/sidecar/sidecar_worker.py +1 -1
  37. metaflow/util.py +0 -17
  38. metaflow/version.py +1 -1
  39. {ob_metaflow-2.12.36.2.dist-info → ob_metaflow-2.12.36.3.dist-info}/METADATA +2 -3
  40. {ob_metaflow-2.12.36.2.dist-info → ob_metaflow-2.12.36.3.dist-info}/RECORD +44 -54
  41. metaflow/cli_components/__init__.py +0 -0
  42. metaflow/cli_components/dump_cmd.py +0 -96
  43. metaflow/cli_components/init_cmd.py +0 -51
  44. metaflow/cli_components/run_cmds.py +0 -358
  45. metaflow/cli_components/step_cmd.py +0 -189
  46. metaflow/cli_components/utils.py +0 -140
  47. metaflow/user_configs/__init__.py +0 -0
  48. metaflow/user_configs/config_decorators.py +0 -563
  49. metaflow/user_configs/config_options.py +0 -495
  50. metaflow/user_configs/config_parameters.py +0 -386
  51. {ob_metaflow-2.12.36.2.dist-info → ob_metaflow-2.12.36.3.dist-info}/LICENSE +0 -0
  52. {ob_metaflow-2.12.36.2.dist-info → ob_metaflow-2.12.36.3.dist-info}/WHEEL +0 -0
  53. {ob_metaflow-2.12.36.2.dist-info → ob_metaflow-2.12.36.3.dist-info}/entry_points.txt +0 -0
  54. {ob_metaflow-2.12.36.2.dist-info → ob_metaflow-2.12.36.3.dist-info}/top_level.txt +0 -0
@@ -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()