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.
Files changed (74) hide show
  1. metaflow/__init__.py +7 -1
  2. metaflow/cli.py +16 -1
  3. metaflow/cli_components/init_cmd.py +1 -0
  4. metaflow/cli_components/run_cmds.py +6 -2
  5. metaflow/client/core.py +22 -30
  6. metaflow/datastore/task_datastore.py +0 -1
  7. metaflow/debug.py +5 -0
  8. metaflow/decorators.py +230 -70
  9. metaflow/extension_support/__init__.py +15 -8
  10. metaflow/extension_support/_empty_file.py +2 -2
  11. metaflow/flowspec.py +80 -53
  12. metaflow/graph.py +24 -2
  13. metaflow/meta_files.py +13 -0
  14. metaflow/metadata_provider/metadata.py +7 -1
  15. metaflow/metaflow_config.py +5 -0
  16. metaflow/metaflow_environment.py +82 -25
  17. metaflow/metaflow_version.py +1 -1
  18. metaflow/package/__init__.py +664 -0
  19. metaflow/packaging_sys/__init__.py +870 -0
  20. metaflow/packaging_sys/backend.py +113 -0
  21. metaflow/packaging_sys/distribution_support.py +153 -0
  22. metaflow/packaging_sys/tar_backend.py +86 -0
  23. metaflow/packaging_sys/utils.py +91 -0
  24. metaflow/packaging_sys/v1.py +476 -0
  25. metaflow/plugins/airflow/airflow.py +5 -1
  26. metaflow/plugins/airflow/airflow_cli.py +15 -4
  27. metaflow/plugins/argo/argo_workflows.py +15 -4
  28. metaflow/plugins/argo/argo_workflows_cli.py +16 -4
  29. metaflow/plugins/aws/batch/batch.py +22 -3
  30. metaflow/plugins/aws/batch/batch_cli.py +3 -0
  31. metaflow/plugins/aws/batch/batch_decorator.py +13 -5
  32. metaflow/plugins/aws/step_functions/step_functions.py +4 -1
  33. metaflow/plugins/aws/step_functions/step_functions_cli.py +15 -4
  34. metaflow/plugins/cards/card_decorator.py +0 -5
  35. metaflow/plugins/kubernetes/kubernetes.py +8 -1
  36. metaflow/plugins/kubernetes/kubernetes_cli.py +3 -0
  37. metaflow/plugins/kubernetes/kubernetes_decorator.py +13 -5
  38. metaflow/plugins/package_cli.py +25 -23
  39. metaflow/plugins/parallel_decorator.py +4 -2
  40. metaflow/plugins/pypi/bootstrap.py +8 -2
  41. metaflow/plugins/pypi/conda_decorator.py +39 -82
  42. metaflow/plugins/pypi/conda_environment.py +6 -2
  43. metaflow/plugins/pypi/pypi_decorator.py +4 -4
  44. metaflow/plugins/test_unbounded_foreach_decorator.py +2 -2
  45. metaflow/plugins/timeout_decorator.py +0 -1
  46. metaflow/plugins/uv/bootstrap.py +11 -0
  47. metaflow/plugins/uv/uv_environment.py +4 -2
  48. metaflow/pylint_wrapper.py +5 -1
  49. metaflow/runner/click_api.py +5 -4
  50. metaflow/runner/subprocess_manager.py +14 -2
  51. metaflow/runtime.py +37 -11
  52. metaflow/task.py +91 -7
  53. metaflow/user_configs/config_options.py +13 -8
  54. metaflow/user_configs/config_parameters.py +0 -4
  55. metaflow/user_decorators/__init__.py +0 -0
  56. metaflow/user_decorators/common.py +144 -0
  57. metaflow/user_decorators/mutable_flow.py +499 -0
  58. metaflow/user_decorators/mutable_step.py +424 -0
  59. metaflow/user_decorators/user_flow_decorator.py +263 -0
  60. metaflow/user_decorators/user_step_decorator.py +712 -0
  61. metaflow/util.py +4 -1
  62. metaflow/version.py +1 -1
  63. {metaflow-2.15.21.dist-info → metaflow-2.16.0.dist-info}/METADATA +2 -2
  64. {metaflow-2.15.21.dist-info → metaflow-2.16.0.dist-info}/RECORD +71 -60
  65. metaflow/info_file.py +0 -25
  66. metaflow/package.py +0 -203
  67. metaflow/user_configs/config_decorators.py +0 -568
  68. {metaflow-2.15.21.data → metaflow-2.16.0.data}/data/share/metaflow/devtools/Makefile +0 -0
  69. {metaflow-2.15.21.data → metaflow-2.16.0.data}/data/share/metaflow/devtools/Tiltfile +0 -0
  70. {metaflow-2.15.21.data → metaflow-2.16.0.data}/data/share/metaflow/devtools/pick_services.sh +0 -0
  71. {metaflow-2.15.21.dist-info → metaflow-2.16.0.dist-info}/WHEEL +0 -0
  72. {metaflow-2.15.21.dist-info → metaflow-2.16.0.dist-info}/entry_points.txt +0 -0
  73. {metaflow-2.15.21.dist-info → metaflow-2.16.0.dist-info}/licenses/LICENSE +0 -0
  74. {metaflow-2.15.21.dist-info → metaflow-2.16.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,499 @@
1
+ from functools import partial
2
+ from typing import Any, Dict, Generator, List, Optional, Tuple, TYPE_CHECKING, Union
3
+
4
+ from metaflow.debug import debug
5
+ from metaflow.exception import MetaflowException
6
+ from metaflow.user_configs.config_parameters import ConfigValue
7
+
8
+ if TYPE_CHECKING:
9
+ import metaflow.flowspec
10
+ import metaflow.parameters
11
+ import metaflow.user_decorators.mutable_step
12
+
13
+
14
+ class MutableFlow:
15
+ IGNORE = 1
16
+ ERROR = 2
17
+ OVERRIDE = 3
18
+
19
+ def __init__(
20
+ self,
21
+ flow_spec: "metaflow.flowspec.FlowSpec",
22
+ pre_mutate: bool = False,
23
+ statically_defined: bool = False,
24
+ inserted_by: Optional[str] = None,
25
+ ):
26
+ self._flow_cls = flow_spec
27
+ self._pre_mutate = pre_mutate
28
+ self._statically_defined = statically_defined
29
+ self._inserted_by = inserted_by
30
+ if self._inserted_by is None:
31
+ # This is an error because MutableSteps should only be created by
32
+ # StepMutators or FlowMutators. We need to catch it now because otherwise
33
+ # we may put stuff on the command line (with --with) that would get added
34
+ # twice and weird behavior may ensue.
35
+ raise MetaflowException(
36
+ "MutableFlow should only be created by StepMutators or FlowMutators. "
37
+ "This is an internal error."
38
+ )
39
+
40
+ @property
41
+ def decorator_specs(
42
+ self,
43
+ ) -> Generator[Tuple[str, str, List[Any], Dict[str, Any]], None, None]:
44
+ """
45
+ Iterate over all the decorator specifications of this flow. Note that the same
46
+ type of decorator may be present multiple times and no order is guaranteed.
47
+
48
+ The returned tuple contains:
49
+ - The decorator's name (shortest possible)
50
+ - The decorator's fully qualified name (in the case of Metaflow decorators, this
51
+ will indicate which extension the decorator comes from)
52
+ - A list of positional arguments
53
+ - A dictionary of keyword arguments
54
+
55
+ You can use the decorator specification to remove a decorator from the flow
56
+ for example.
57
+
58
+ Yields
59
+ ------
60
+ str, str, List[Any], Dict[str, Any]
61
+ A tuple containing the decorator name, it's fully qualified name,
62
+ a list of positional arguments, and a dictionary of keyword arguments.
63
+ """
64
+ for decos in self._flow_cls._flow_decorators.values():
65
+ for deco in decos:
66
+ # 3.7 does not support yield foo, *bar syntax so we
67
+ # work around
68
+
69
+ r = [
70
+ deco.name,
71
+ "%s.%s"
72
+ % (
73
+ deco.__class__.__module__,
74
+ deco.__class__.__name__,
75
+ ),
76
+ ]
77
+ r.extend(deco.get_args_kwargs())
78
+ yield tuple(r)
79
+
80
+ @property
81
+ def configs(self) -> Generator[Tuple[str, ConfigValue], None, None]:
82
+ """
83
+ Iterate over all user configurations in this flow
84
+
85
+ Use this to parameterize your flow based on configuration. As an example, the
86
+ `pre_mutate`/`mutate` methods can add decorators to steps in the flow that
87
+ depend on values in the configuration.
88
+
89
+ ```
90
+ class MyDecorator(FlowMutator):
91
+ def mutate(flow: MutableFlow):
92
+ val = next(flow.configs)[1].steps.start.cpu
93
+ flow.start.add_decorator(environment, vars={'mycpu': val})
94
+ return flow
95
+
96
+ @MyDecorator()
97
+ class TestFlow(FlowSpec):
98
+ config = Config('myconfig.json')
99
+
100
+ @step
101
+ def start(self):
102
+ pass
103
+ ```
104
+ can be used to add an environment decorator to the `start` step.
105
+
106
+ Yields
107
+ ------
108
+ Tuple[str, ConfigValue]
109
+ Iterates over the configurations of the flow
110
+ """
111
+ from metaflow.flowspec import _FlowState
112
+
113
+ # When configs are parsed, they are loaded in _flow_state[_FlowState.CONFIGS]
114
+ for name, value in self._flow_cls._flow_state.get(
115
+ _FlowState.CONFIGS, {}
116
+ ).items():
117
+ r = name, ConfigValue(value)
118
+ debug.userconf_exec("Mutable flow yielding config: %s" % str(r))
119
+ yield r
120
+
121
+ @property
122
+ def parameters(
123
+ self,
124
+ ) -> Generator[Tuple[str, "metaflow.parameters.Parameter"], None, None]:
125
+ """
126
+ Iterate over all the parameters in this flow.
127
+
128
+ Yields
129
+ ------
130
+ Tuple[str, Parameter]
131
+ Name of the parameter and parameter in the flow
132
+ """
133
+ for var, param in self._flow_cls._get_parameters():
134
+ if param.IS_CONFIG_PARAMETER:
135
+ continue
136
+ debug.userconf_exec(
137
+ "Mutable flow yielding parameter: %s" % str((var, param))
138
+ )
139
+ yield var, param
140
+
141
+ @property
142
+ def steps(
143
+ self,
144
+ ) -> Generator[
145
+ Tuple[str, "metaflow.user_decorators.mutable_step.MutableStep"], None, None
146
+ ]:
147
+ """
148
+ Iterate over all the steps in this flow. The order of the steps
149
+ returned is not guaranteed.
150
+
151
+ Yields
152
+ ------
153
+ Tuple[str, MutableStep]
154
+ A tuple with the step name and the step proxy
155
+ """
156
+ from .mutable_step import MutableStep
157
+
158
+ for var in dir(self._flow_cls):
159
+ potential_step = getattr(self._flow_cls, var)
160
+ if callable(potential_step) and hasattr(potential_step, "is_step"):
161
+ debug.userconf_exec("Mutable flow yielding step: %s" % var)
162
+ yield var, MutableStep(
163
+ self._flow_cls,
164
+ potential_step,
165
+ pre_mutate=self._pre_mutate,
166
+ statically_defined=self._statically_defined,
167
+ inserted_by=self._inserted_by,
168
+ )
169
+
170
+ def add_parameter(
171
+ self, name: str, value: "metaflow.parameters.Parameter", overwrite: bool = False
172
+ ) -> None:
173
+ """
174
+ Add a parameter to the flow. You can only add parameters in the `pre_mutate`
175
+ method.
176
+
177
+ Parameters
178
+ ----------
179
+ name : str
180
+ Name of the parameter
181
+ value : Parameter
182
+ Parameter to add to the flow
183
+ overwrite : bool, default False
184
+ If True, overwrite the parameter if it already exists
185
+ """
186
+ if not self._pre_mutate:
187
+ raise MetaflowException(
188
+ "Adding parameter '%s' from %s is only allowed in the `pre_mutate` "
189
+ "method and not the `mutate` method" % (name, self._inserted_by)
190
+ )
191
+ from metaflow.parameters import Parameter
192
+
193
+ if hasattr(self._flow_cls, name) and not overwrite:
194
+ raise MetaflowException(
195
+ "Flow '%s' already has a class member '%s' -- "
196
+ "set overwrite=True in add_parameter to overwrite it."
197
+ % (self._flow_cls.__name__, name)
198
+ )
199
+ if not isinstance(value, Parameter) or value.IS_CONFIG_PARAMETER:
200
+ raise MetaflowException(
201
+ "Only a Parameter or an IncludeFile can be added using `add_parameter`"
202
+ "; got %s" % type(value)
203
+ )
204
+ debug.userconf_exec("Mutable flow adding parameter %s to flow" % name)
205
+ setattr(self._flow_cls, name, value)
206
+
207
+ def remove_parameter(self, parameter_name: str) -> bool:
208
+ """
209
+ Remove a parameter from the flow.
210
+
211
+ The name given should match the name of the parameter (can be different
212
+ from the name of the parameter in the flow. You can not remove config parameters.
213
+ You can only remove parameters in the `pre_mutate` method.
214
+
215
+ Parameters
216
+ ----------
217
+ parameter_name : str
218
+ Name of the parameter
219
+
220
+ Returns
221
+ -------
222
+ bool
223
+ Returns True if the parameter was removed
224
+ """
225
+ if not self._pre_mutate:
226
+ raise MetaflowException(
227
+ "Removing parameter '%s' from %s is only allowed in the `pre_mutate` "
228
+ "method and not the `mutate` method"
229
+ % (parameter_name, " from ".join(self._inserted_by))
230
+ )
231
+ from metaflow.flowspec import _FlowState
232
+
233
+ for var, param in self._flow_cls._get_parameters():
234
+ if param.IS_CONFIG_PARAMETER:
235
+ continue
236
+ if param.name == parameter_name:
237
+ delattr(self._flow_cls, var)
238
+ debug.userconf_exec(
239
+ "Mutable flow removing parameter %s from flow" % var
240
+ )
241
+ # Reset so that we don't list it again
242
+ del self._flow_cls._flow_state[_FlowState.CACHED_PARAMETERS]
243
+ return True
244
+ debug.userconf_exec(
245
+ "Mutable flow failed to remove parameter %s from flow" % parameter_name
246
+ )
247
+ return False
248
+
249
+ def add_decorator(
250
+ self,
251
+ deco_type: Union[partial, str],
252
+ deco_args: Optional[List[Any]] = None,
253
+ deco_kwargs: Optional[Dict[str, Any]] = None,
254
+ duplicates: int = IGNORE,
255
+ ) -> None:
256
+ """
257
+ Add a Metaflow flow-decorator to a flow. You can only add decorators in the
258
+ `pre_mutate` method.
259
+
260
+ You can either add the decorator itself or its decorator specification for it
261
+ (the same you would get back from decorator_specs). You can also mix and match
262
+ but you cannot provide arguments both through the string and the
263
+ deco_args/deco_kwargs.
264
+
265
+ As an example:
266
+ ```
267
+ from metaflow import project
268
+
269
+ ...
270
+ my_flow.add_decorator(project, deco_kwargs={"name":"my_project"})
271
+ ```
272
+
273
+ is equivalent to:
274
+ ```
275
+ my_flow.add_decorator("project:name=my_project")
276
+ ```
277
+
278
+ Note in the later case, there is no need to import the flow decorator.
279
+
280
+ The latter syntax is useful to, for example, allow decorators to be stored as
281
+ strings in a configuration file.
282
+
283
+ In terms of precedence for decorators:
284
+ - if a decorator can be applied multiple times all decorators
285
+ added are kept (this is rare for flow-decorators).
286
+ - if `duplicates` is set to `MutableFlow.IGNORE`, then the decorator
287
+ being added is ignored (in other words, the existing decorator has precedence).
288
+ - if `duplicates` is set to `MutableFlow.OVERRIDE`, then the *existing*
289
+ decorator is removed and this newly added one replaces it (in other
290
+ words, the newly added decorator has precedence).
291
+ - if `duplicates` is set to `MutableFlow.ERROR`, then an error is raised but only
292
+ if the newly added decorator is *static* (ie: defined directly in the code).
293
+ If not, it is ignored.
294
+
295
+ Parameters
296
+ ----------
297
+ deco_type : Union[partial, str]
298
+ The decorator class to add to this flow. If using a string, you cannot specify
299
+ additional arguments as all argument will be parsed from the decorator
300
+ specification.
301
+ deco_args : List[Any], optional, default None
302
+ Positional arguments to pass to the decorator.
303
+ deco_kwargs : Dict[str, Any], optional, default None
304
+ Keyword arguments to pass to the decorator.
305
+ duplicates : int, default MutableFlow.IGNORE
306
+ Instruction on how to handle duplicates. It can be one of:
307
+ - `MutableFlow.IGNORE`: Ignore the decorator if it already exists.
308
+ - `MutableFlow.ERROR`: Raise an error if the decorator already exists.
309
+ - `MutableFlow.OVERRIDE`: Remove the existing decorator and add this one.
310
+
311
+ """
312
+ if not self._pre_mutate:
313
+ raise MetaflowException(
314
+ "Adding flow-decorator '%s' from %s is only allowed in the `pre_mutate` "
315
+ "method and not the `mutate` method"
316
+ % (
317
+ deco_type if isinstance(deco_type, str) else deco_type.name,
318
+ self._inserted_by,
319
+ )
320
+ )
321
+ # Prevent circular import
322
+ from metaflow.decorators import (
323
+ DuplicateFlowDecoratorException,
324
+ FlowDecorator,
325
+ extract_flow_decorator_from_decospec,
326
+ )
327
+
328
+ deco_args = deco_args or []
329
+ deco_kwargs = deco_kwargs or {}
330
+
331
+ def _add_flow_decorator(flow_deco):
332
+ if deco_args:
333
+ raise MetaflowException(
334
+ "Flow decorators do not take additional positional arguments"
335
+ )
336
+ # Update kwargs:
337
+ flow_deco.attributes.update(deco_kwargs)
338
+
339
+ # Check duplicates
340
+ def _do_add():
341
+ flow_deco.statically_defined = self._statically_defined
342
+ flow_deco.inserted_by = self._inserted_by
343
+ self._flow_cls._flow_decorators.setdefault(flow_deco.name, []).append(
344
+ flow_deco
345
+ )
346
+ debug.userconf_exec(
347
+ "Mutable flow adding flow decorator '%s'" % deco_type
348
+ )
349
+
350
+ existing_deco = [
351
+ d for d in self._flow_cls._flow_decorators if d.name == flow_deco.name
352
+ ]
353
+
354
+ if flow_deco.allow_multiple or not existing_deco:
355
+ _do_add()
356
+ elif duplicates == MutableFlow.IGNORE:
357
+ # If we ignore, we do not add the decorator
358
+ debug.userconf_exec(
359
+ "Mutable flow ignoring flow decorator '%s'"
360
+ "(already exists and duplicates are ignored)" % flow_deco.name
361
+ )
362
+ elif duplicates == MutableFlow.OVERRIDE:
363
+ # If we override, we remove the existing decorator and add this one
364
+ debug.userconf_exec(
365
+ "Mutable flow overriding flow decorator '%s' "
366
+ "(removing existing decorator and adding new one)" % flow_deco.name
367
+ )
368
+ self._flow_cls._flow_decorators = [
369
+ d
370
+ for d in self._flow_cls._flow_decorators
371
+ if d.name != flow_deco.name
372
+ ]
373
+ _do_add()
374
+ elif duplicates == MutableFlow.ERROR:
375
+ # If we error, we raise an exception
376
+ if self._statically_defined:
377
+ raise DuplicateFlowDecoratorException(flow_deco.name)
378
+ else:
379
+ debug.userconf_exec(
380
+ "Mutable flow ignoring flow decorator '%s' "
381
+ "(already exists and non statically defined)" % flow_deco.name
382
+ )
383
+ else:
384
+ raise ValueError("Invalid duplicates value: %s" % duplicates)
385
+
386
+ # If deco_type is a string, we want to parse it to a decospec
387
+ if isinstance(deco_type, str):
388
+ flow_deco, has_args_kwargs = extract_flow_decorator_from_decospec(deco_type)
389
+ if (deco_args or deco_kwargs) and has_args_kwargs:
390
+ raise MetaflowException(
391
+ "Cannot specify additional arguments when adding a flow decorator "
392
+ "using a decospec that already contains arguments"
393
+ )
394
+ _add_flow_decorator(flow_deco)
395
+ return
396
+
397
+ # Validate deco_type
398
+ if (
399
+ not isinstance(deco_type, partial)
400
+ or len(deco_type.args) != 1
401
+ or not issubclass(deco_type.args[0], FlowDecorator)
402
+ ):
403
+ raise TypeError("add_decorator takes a FlowDecorator")
404
+
405
+ deco_type = deco_type.args[0]
406
+ _add_flow_decorator(
407
+ deco_type(
408
+ attributes=deco_kwargs,
409
+ statically_defined=self._statically_defined,
410
+ inserted_by=self._inserted_by,
411
+ )
412
+ )
413
+
414
+ def remove_decorator(
415
+ self,
416
+ deco_name: str,
417
+ deco_args: Optional[List[Any]] = None,
418
+ deco_kwargs: Optional[Dict[str, Any]] = None,
419
+ ) -> bool:
420
+ """
421
+ Remove a flow-level decorator. To remove a decorator, you can pass the decorator
422
+ specification (obtained from `decorator_specs` for example).
423
+ Note that if multiple decorators share the same decorator specification
424
+ (very rare), they will all be removed.
425
+
426
+ You can only remove decorators in the `pre_mutate` method.
427
+
428
+ Parameters
429
+ ----------
430
+ deco_name : str
431
+ Decorator specification of the decorator to remove. If nothing else is
432
+ specified, all decorators matching that name will be removed.
433
+ deco_args : List[Any], optional, default None
434
+ Positional arguments to match the decorator specification.
435
+ deco_kwargs : Dict[str, Any], optional, default None
436
+ Keyword arguments to match the decorator specification.
437
+
438
+ Returns
439
+ -------
440
+ bool
441
+ Returns True if a decorator was removed.
442
+ """
443
+
444
+ if not self._pre_mutate:
445
+ raise MetaflowException(
446
+ "Removing flow-decorator '%s' from %s is only allowed in the `pre_mutate` "
447
+ "method and not the `mutate` method" % (deco_name, self._inserted_by)
448
+ )
449
+
450
+ do_all = deco_args is None and deco_kwargs is None
451
+ did_remove = False
452
+ if do_all and deco_name in self._flow_cls._flow_decorators:
453
+ del self._flow_cls._flow_decorators[deco_name]
454
+ return True
455
+ old_deco_list = self._flow_cls._flow_decorators.get(deco_name)
456
+ if not old_deco_list:
457
+ debug.userconf_exec(
458
+ "Mutable flow failed to remove decorator '%s' from flow (non present)"
459
+ % deco_name
460
+ )
461
+ return False
462
+ new_deco_list = []
463
+ for deco in old_deco_list:
464
+ if deco.get_args_kwargs() == (deco_args or [], deco_kwargs or {}):
465
+ did_remove = True
466
+ else:
467
+ new_deco_list.append(deco)
468
+ debug.userconf_exec(
469
+ "Mutable flow removed %d decorators from flow"
470
+ % (len(old_deco_list) - len(new_deco_list))
471
+ )
472
+ if new_deco_list:
473
+ self._flow_cls._flow_decorators[deconame] = new_deco_list
474
+ else:
475
+ del self._flow_cls._flow_decorators[deco_name]
476
+ return did_remove
477
+
478
+ def __getattr__(self, name):
479
+ # We allow direct access to the steps, configs and parameters but nothing else
480
+ from metaflow.parameters import Parameter
481
+
482
+ from .mutable_step import MutableStep
483
+
484
+ attr = getattr(self._flow_cls, name)
485
+ if attr:
486
+ # Steps
487
+ if callable(attr) and hasattr(attr, "is_step"):
488
+ return MutableStep(
489
+ self._flow_cls,
490
+ attr,
491
+ pre_mutate=self._pre_mutate,
492
+ statically_defined=self._statically_defined,
493
+ inserted_by=self._inserted_by,
494
+ )
495
+ if name[0] == "_" or name in self._flow_cls._NON_PARAMETERS:
496
+ raise AttributeError(self, name)
497
+ if isinstance(attr, (Parameter, ConfigValue)):
498
+ return attr
499
+ raise AttributeError(self, name)