ob-metaflow 2.15.13.1__py2.py3-none-any.whl → 2.19.7.1rc0__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 (169) hide show
  1. metaflow/__init__.py +10 -3
  2. metaflow/_vendor/imghdr/__init__.py +186 -0
  3. metaflow/_vendor/yaml/__init__.py +427 -0
  4. metaflow/_vendor/yaml/composer.py +139 -0
  5. metaflow/_vendor/yaml/constructor.py +748 -0
  6. metaflow/_vendor/yaml/cyaml.py +101 -0
  7. metaflow/_vendor/yaml/dumper.py +62 -0
  8. metaflow/_vendor/yaml/emitter.py +1137 -0
  9. metaflow/_vendor/yaml/error.py +75 -0
  10. metaflow/_vendor/yaml/events.py +86 -0
  11. metaflow/_vendor/yaml/loader.py +63 -0
  12. metaflow/_vendor/yaml/nodes.py +49 -0
  13. metaflow/_vendor/yaml/parser.py +589 -0
  14. metaflow/_vendor/yaml/reader.py +185 -0
  15. metaflow/_vendor/yaml/representer.py +389 -0
  16. metaflow/_vendor/yaml/resolver.py +227 -0
  17. metaflow/_vendor/yaml/scanner.py +1435 -0
  18. metaflow/_vendor/yaml/serializer.py +111 -0
  19. metaflow/_vendor/yaml/tokens.py +104 -0
  20. metaflow/cards.py +4 -0
  21. metaflow/cli.py +125 -21
  22. metaflow/cli_components/init_cmd.py +1 -0
  23. metaflow/cli_components/run_cmds.py +204 -40
  24. metaflow/cli_components/step_cmd.py +160 -4
  25. metaflow/client/__init__.py +1 -0
  26. metaflow/client/core.py +198 -130
  27. metaflow/client/filecache.py +59 -32
  28. metaflow/cmd/code/__init__.py +2 -1
  29. metaflow/cmd/develop/stub_generator.py +49 -18
  30. metaflow/cmd/develop/stubs.py +9 -27
  31. metaflow/cmd/make_wrapper.py +30 -0
  32. metaflow/datastore/__init__.py +1 -0
  33. metaflow/datastore/content_addressed_store.py +40 -9
  34. metaflow/datastore/datastore_set.py +10 -1
  35. metaflow/datastore/flow_datastore.py +124 -4
  36. metaflow/datastore/spin_datastore.py +91 -0
  37. metaflow/datastore/task_datastore.py +92 -6
  38. metaflow/debug.py +5 -0
  39. metaflow/decorators.py +331 -82
  40. metaflow/extension_support/__init__.py +414 -356
  41. metaflow/extension_support/_empty_file.py +2 -2
  42. metaflow/flowspec.py +322 -82
  43. metaflow/graph.py +178 -15
  44. metaflow/includefile.py +25 -3
  45. metaflow/lint.py +94 -3
  46. metaflow/meta_files.py +13 -0
  47. metaflow/metadata_provider/metadata.py +13 -2
  48. metaflow/metaflow_config.py +66 -4
  49. metaflow/metaflow_environment.py +91 -25
  50. metaflow/metaflow_profile.py +18 -0
  51. metaflow/metaflow_version.py +16 -1
  52. metaflow/package/__init__.py +673 -0
  53. metaflow/packaging_sys/__init__.py +880 -0
  54. metaflow/packaging_sys/backend.py +128 -0
  55. metaflow/packaging_sys/distribution_support.py +153 -0
  56. metaflow/packaging_sys/tar_backend.py +99 -0
  57. metaflow/packaging_sys/utils.py +54 -0
  58. metaflow/packaging_sys/v1.py +527 -0
  59. metaflow/parameters.py +6 -2
  60. metaflow/plugins/__init__.py +6 -0
  61. metaflow/plugins/airflow/airflow.py +11 -1
  62. metaflow/plugins/airflow/airflow_cli.py +16 -5
  63. metaflow/plugins/argo/argo_client.py +42 -20
  64. metaflow/plugins/argo/argo_events.py +6 -6
  65. metaflow/plugins/argo/argo_workflows.py +1023 -344
  66. metaflow/plugins/argo/argo_workflows_cli.py +396 -94
  67. metaflow/plugins/argo/argo_workflows_decorator.py +9 -0
  68. metaflow/plugins/argo/argo_workflows_deployer_objects.py +75 -49
  69. metaflow/plugins/argo/capture_error.py +5 -2
  70. metaflow/plugins/argo/conditional_input_paths.py +35 -0
  71. metaflow/plugins/argo/exit_hooks.py +209 -0
  72. metaflow/plugins/argo/param_val.py +19 -0
  73. metaflow/plugins/aws/aws_client.py +6 -0
  74. metaflow/plugins/aws/aws_utils.py +33 -1
  75. metaflow/plugins/aws/batch/batch.py +72 -5
  76. metaflow/plugins/aws/batch/batch_cli.py +24 -3
  77. metaflow/plugins/aws/batch/batch_decorator.py +57 -6
  78. metaflow/plugins/aws/step_functions/step_functions.py +28 -3
  79. metaflow/plugins/aws/step_functions/step_functions_cli.py +49 -4
  80. metaflow/plugins/aws/step_functions/step_functions_deployer.py +3 -0
  81. metaflow/plugins/aws/step_functions/step_functions_deployer_objects.py +30 -0
  82. metaflow/plugins/cards/card_cli.py +20 -1
  83. metaflow/plugins/cards/card_creator.py +24 -1
  84. metaflow/plugins/cards/card_datastore.py +21 -49
  85. metaflow/plugins/cards/card_decorator.py +58 -6
  86. metaflow/plugins/cards/card_modules/basic.py +38 -9
  87. metaflow/plugins/cards/card_modules/bundle.css +1 -1
  88. metaflow/plugins/cards/card_modules/chevron/renderer.py +1 -1
  89. metaflow/plugins/cards/card_modules/components.py +592 -3
  90. metaflow/plugins/cards/card_modules/convert_to_native_type.py +34 -5
  91. metaflow/plugins/cards/card_modules/json_viewer.py +232 -0
  92. metaflow/plugins/cards/card_modules/main.css +1 -0
  93. metaflow/plugins/cards/card_modules/main.js +56 -41
  94. metaflow/plugins/cards/card_modules/test_cards.py +22 -6
  95. metaflow/plugins/cards/component_serializer.py +1 -8
  96. metaflow/plugins/cards/metadata.py +22 -0
  97. metaflow/plugins/catch_decorator.py +9 -0
  98. metaflow/plugins/datastores/local_storage.py +12 -6
  99. metaflow/plugins/datastores/spin_storage.py +12 -0
  100. metaflow/plugins/datatools/s3/s3.py +49 -17
  101. metaflow/plugins/datatools/s3/s3op.py +113 -66
  102. metaflow/plugins/env_escape/client_modules.py +102 -72
  103. metaflow/plugins/events_decorator.py +127 -121
  104. metaflow/plugins/exit_hook/__init__.py +0 -0
  105. metaflow/plugins/exit_hook/exit_hook_decorator.py +46 -0
  106. metaflow/plugins/exit_hook/exit_hook_script.py +52 -0
  107. metaflow/plugins/kubernetes/kubernetes.py +12 -1
  108. metaflow/plugins/kubernetes/kubernetes_cli.py +11 -0
  109. metaflow/plugins/kubernetes/kubernetes_decorator.py +25 -6
  110. metaflow/plugins/kubernetes/kubernetes_job.py +12 -4
  111. metaflow/plugins/kubernetes/kubernetes_jobsets.py +31 -30
  112. metaflow/plugins/metadata_providers/local.py +76 -82
  113. metaflow/plugins/metadata_providers/service.py +13 -9
  114. metaflow/plugins/metadata_providers/spin.py +16 -0
  115. metaflow/plugins/package_cli.py +36 -24
  116. metaflow/plugins/parallel_decorator.py +11 -2
  117. metaflow/plugins/parsers.py +16 -0
  118. metaflow/plugins/pypi/bootstrap.py +7 -1
  119. metaflow/plugins/pypi/conda_decorator.py +41 -82
  120. metaflow/plugins/pypi/conda_environment.py +14 -6
  121. metaflow/plugins/pypi/micromamba.py +9 -1
  122. metaflow/plugins/pypi/pip.py +41 -5
  123. metaflow/plugins/pypi/pypi_decorator.py +4 -4
  124. metaflow/plugins/pypi/utils.py +22 -0
  125. metaflow/plugins/secrets/__init__.py +3 -0
  126. metaflow/plugins/secrets/secrets_decorator.py +14 -178
  127. metaflow/plugins/secrets/secrets_func.py +49 -0
  128. metaflow/plugins/secrets/secrets_spec.py +101 -0
  129. metaflow/plugins/secrets/utils.py +74 -0
  130. metaflow/plugins/test_unbounded_foreach_decorator.py +2 -2
  131. metaflow/plugins/timeout_decorator.py +0 -1
  132. metaflow/plugins/uv/bootstrap.py +29 -1
  133. metaflow/plugins/uv/uv_environment.py +5 -3
  134. metaflow/pylint_wrapper.py +5 -1
  135. metaflow/runner/click_api.py +79 -26
  136. metaflow/runner/deployer.py +208 -6
  137. metaflow/runner/deployer_impl.py +32 -12
  138. metaflow/runner/metaflow_runner.py +266 -33
  139. metaflow/runner/subprocess_manager.py +21 -1
  140. metaflow/runner/utils.py +27 -16
  141. metaflow/runtime.py +660 -66
  142. metaflow/task.py +255 -26
  143. metaflow/user_configs/config_options.py +33 -21
  144. metaflow/user_configs/config_parameters.py +220 -58
  145. metaflow/user_decorators/__init__.py +0 -0
  146. metaflow/user_decorators/common.py +144 -0
  147. metaflow/user_decorators/mutable_flow.py +512 -0
  148. metaflow/user_decorators/mutable_step.py +424 -0
  149. metaflow/user_decorators/user_flow_decorator.py +264 -0
  150. metaflow/user_decorators/user_step_decorator.py +749 -0
  151. metaflow/util.py +197 -7
  152. metaflow/vendor.py +23 -7
  153. metaflow/version.py +1 -1
  154. {ob_metaflow-2.15.13.1.data → ob_metaflow-2.19.7.1rc0.data}/data/share/metaflow/devtools/Makefile +13 -2
  155. {ob_metaflow-2.15.13.1.data → ob_metaflow-2.19.7.1rc0.data}/data/share/metaflow/devtools/Tiltfile +107 -7
  156. {ob_metaflow-2.15.13.1.data → ob_metaflow-2.19.7.1rc0.data}/data/share/metaflow/devtools/pick_services.sh +1 -0
  157. {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/METADATA +2 -3
  158. {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/RECORD +162 -121
  159. {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/WHEEL +1 -1
  160. metaflow/_vendor/v3_5/__init__.py +0 -1
  161. metaflow/_vendor/v3_5/importlib_metadata/__init__.py +0 -644
  162. metaflow/_vendor/v3_5/importlib_metadata/_compat.py +0 -152
  163. metaflow/_vendor/v3_5/zipp.py +0 -329
  164. metaflow/info_file.py +0 -25
  165. metaflow/package.py +0 -203
  166. metaflow/user_configs/config_decorators.py +0 -568
  167. {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/entry_points.txt +0 -0
  168. {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/licenses/LICENSE +0 -0
  169. {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/top_level.txt +0 -0
metaflow/decorators.py CHANGED
@@ -1,37 +1,35 @@
1
- from functools import partial
1
+ import importlib
2
2
  import json
3
3
  import re
4
4
 
5
- from typing import Any, Callable, NewType, TypeVar, Union, overload
5
+ from functools import partial
6
+ from typing import Any, Callable, Dict, List, NewType, Tuple, TypeVar, Union, overload
6
7
 
7
- from .flowspec import FlowSpec
8
+ from .flowspec import FlowSpec, FlowStateItems
8
9
  from .exception import (
9
10
  MetaflowInternalError,
10
11
  MetaflowException,
11
12
  InvalidDecoratorAttribute,
12
13
  )
13
14
 
15
+ from .debug import debug
14
16
  from .parameters import current_flow
15
- from .user_configs.config_decorators import CustomStepDecorator
16
17
  from .user_configs.config_parameters import (
17
18
  UNPACK_KEY,
18
19
  resolve_delayed_evaluator,
19
20
  unpack_delayed_evaluator,
20
21
  )
21
-
22
+ from .user_decorators.mutable_flow import MutableFlow
23
+ from .user_decorators.mutable_step import MutableStep
24
+ from .user_decorators.user_flow_decorator import FlowMutator, FlowMutatorMeta
25
+ from .user_decorators.user_step_decorator import (
26
+ StepMutator,
27
+ UserStepDecoratorBase,
28
+ UserStepDecoratorMeta,
29
+ )
30
+ from .metaflow_config import SPIN_ALLOWED_DECORATORS
22
31
  from metaflow._vendor import click
23
32
 
24
- try:
25
- unicode
26
- except NameError:
27
- unicode = str
28
- basestring = str
29
-
30
- # Contains the decorators on which _init was called. We want to ensure it is called
31
- # only once on each decorator and, as the _init() function below can be called in
32
- # several places, we need to track which decorator had their init function called
33
- _inited_decorators = set()
34
-
35
33
 
36
34
  class BadStepDecoratorException(MetaflowException):
37
35
  headline = "Syntax error"
@@ -42,7 +40,7 @@ class BadStepDecoratorException(MetaflowException):
42
40
  "not declared as a @step. Make sure you apply this decorator "
43
41
  "on a function which has @step on the line just before the "
44
42
  "function name and @{deco} is above @step.".format(
45
- deco=deco, func=func.__name__
43
+ deco=deco, func=getattr(func, "__name__", str(func))
46
44
  )
47
45
  )
48
46
  super(BadStepDecoratorException, self).__init__(msg)
@@ -63,11 +61,14 @@ class UnknownStepDecoratorException(MetaflowException):
63
61
  headline = "Unknown step decorator"
64
62
 
65
63
  def __init__(self, deconame):
66
- from .plugins import STEP_DECORATORS
67
-
68
64
  decos = ", ".join(
69
- t.name for t in STEP_DECORATORS if not t.name.endswith("_internal")
65
+ [
66
+ x
67
+ for x in UserStepDecoratorMeta.all_decorators().keys()
68
+ if not x.endswith("_internal")
69
+ ]
70
70
  )
71
+
71
72
  msg = (
72
73
  "Unknown step decorator *{deconame}*. The following decorators are "
73
74
  "supported: *{decos}*".format(deconame=deconame, decos=decos)
@@ -92,9 +93,7 @@ class UnknownFlowDecoratorException(MetaflowException):
92
93
  headline = "Unknown flow decorator"
93
94
 
94
95
  def __init__(self, deconame):
95
- from .plugins import FLOW_DECORATORS
96
-
97
- decos = ", ".join(t.name for t in FLOW_DECORATORS)
96
+ decos = ", ".join(FlowMutatorMeta.all_decorators().keys())
98
97
  msg = (
99
98
  "Unknown flow decorator *{deconame}*. The following decorators are "
100
99
  "supported: *{decos}*".format(deconame=deconame, decos=decos)
@@ -123,9 +122,10 @@ class Decorator(object):
123
122
  # `allow_multiple` allows setting many decorators of the same type to a step/flow.
124
123
  allow_multiple = False
125
124
 
126
- def __init__(self, attributes=None, statically_defined=False):
125
+ def __init__(self, attributes=None, statically_defined=False, inserted_by=None):
127
126
  self.attributes = self.defaults.copy()
128
127
  self.statically_defined = statically_defined
128
+ self.inserted_by = inserted_by
129
129
  self._user_defined_attributes = set()
130
130
  self._ran_init = False
131
131
 
@@ -143,7 +143,9 @@ class Decorator(object):
143
143
  Initializes the decorator. In general, any operation you would do in __init__
144
144
  should be done here.
145
145
  """
146
+ pass
146
147
 
148
+ def external_init(self):
147
149
  # In some cases (specifically when using remove_decorator), we may need to call
148
150
  # init multiple times. Short-circuit re-evaluating.
149
151
  if self._ran_init:
@@ -152,14 +154,16 @@ class Decorator(object):
152
154
  # Note that by design, later values override previous ones.
153
155
  self.attributes, new_user_attributes = unpack_delayed_evaluator(self.attributes)
154
156
  self._user_defined_attributes.update(new_user_attributes)
155
- self.attributes = resolve_delayed_evaluator(self.attributes)
157
+ self.attributes = resolve_delayed_evaluator(self.attributes, to_dict=True)
156
158
 
159
+ if "init" in self.__class__.__dict__:
160
+ self.init()
157
161
  self._ran_init = True
158
162
 
159
163
  @classmethod
160
- def _parse_decorator_spec(cls, deco_spec):
164
+ def extract_args_kwargs_from_decorator_spec(cls, deco_spec):
161
165
  if len(deco_spec) == 0:
162
- return cls()
166
+ return [], {}
163
167
 
164
168
  attrs = {}
165
169
  # TODO: Do we really want to allow spaces in the names of attributes?!?
@@ -179,9 +183,20 @@ class Decorator(object):
179
183
  val_parsed = val.strip()
180
184
 
181
185
  attrs[name.strip()] = val_parsed
182
- return cls(attributes=attrs)
186
+
187
+ return [], attrs
188
+
189
+ @classmethod
190
+ def parse_decorator_spec(cls, deco_spec):
191
+ if len(deco_spec) == 0:
192
+ return cls()
193
+
194
+ _, kwargs = cls.extract_args_kwargs_from_decorator_spec(deco_spec)
195
+ return cls(attributes=kwargs)
183
196
 
184
197
  def make_decorator_spec(self):
198
+ # Make sure all attributes are evaluated
199
+ self.external_init()
185
200
  attrs = {k: v for k, v in self.attributes.items() if v is not None}
186
201
  if attrs:
187
202
  attr_list = []
@@ -189,7 +204,7 @@ class Decorator(object):
189
204
  # escaping but for more complex types (typically dictionaries or lists),
190
205
  # we dump using JSON.
191
206
  for k, v in attrs.items():
192
- if isinstance(v, (int, float, unicode, basestring)):
207
+ if isinstance(v, (int, float, str)):
193
208
  attr_list.append("%s=%s" % (k, str(v)))
194
209
  else:
195
210
  attr_list.append("%s=%s" % (k, json.dumps(v).replace('"', '\\"')))
@@ -199,8 +214,21 @@ class Decorator(object):
199
214
  else:
200
215
  return self.name
201
216
 
217
+ def get_args_kwargs(self) -> Tuple[List[Any], Dict[str, Any]]:
218
+ """
219
+ Get the arguments and keyword arguments of the decorator.
220
+
221
+ Returns
222
+ -------
223
+ Tuple[List[Any], Dict[str, Any]]
224
+ A tuple containing a list of arguments and a dictionary of keyword arguments.
225
+ """
226
+ return [], dict(self.attributes)
227
+
202
228
  def __str__(self):
203
229
  mode = "static" if self.statically_defined else "dynamic"
230
+ if self.inserted_by:
231
+ mode += " (inserted by %s)" % " from ".join(self.inserted_by)
204
232
  attrs = " ".join("%s=%s" % x for x in self.attributes.items())
205
233
  if attrs:
206
234
  attrs = " " + attrs
@@ -266,7 +294,11 @@ def add_decorator_options(cmd):
266
294
 
267
295
 
268
296
  def flow_decorators(flow_cls):
269
- return [d for deco_list in flow_cls._flow_decorators.values() for d in deco_list]
297
+ return [
298
+ d
299
+ for deco_list in flow_cls._flow_state[FlowStateItems.FLOW_DECORATORS].values()
300
+ for d in deco_list
301
+ ]
270
302
 
271
303
 
272
304
  class StepDecorator(Decorator):
@@ -315,15 +347,36 @@ class StepDecorator(Decorator):
315
347
 
316
348
  def add_to_package(self):
317
349
  """
318
- Called to add custom packages needed for a decorator. This hook will be
350
+ Called to add custom files needed for this environment. This hook will be
319
351
  called in the `MetaflowPackage` class where metaflow compiles the code package
320
- tarball. This hook is invoked in the `MetaflowPackage`'s `path_tuples`
321
- function. The `path_tuples` function is a generator that yields a tuple of
322
- `(file_path, arcname)`.`file_path` is the path of the file in the local file system;
323
- the `arcname` is the path of the file in the constructed tarball or the path of the file
324
- after decompressing the tarball.
325
-
326
- Returns a list of tuples where each tuple represents (file_path, arcname)
352
+ tarball. This hook can return one of two things (the first is for backwards
353
+ compatibility -- move to the second):
354
+ - a generator yielding a tuple of `(file_path, arcname)` to add files to
355
+ the code package. `file_path` is the path to the file on the local filesystem
356
+ and `arcname` is the path relative to the packaged code.
357
+ - a generator yielding a tuple of `(content, arcname, type)` where:
358
+ - type is one of
359
+ ContentType.{USER_CONTENT, CODE_CONTENT, MODULE_CONTENT, OTHER_CONTENT}
360
+ - for USER_CONTENT:
361
+ - the file will be included relative to the directory containing the
362
+ user's flow file.
363
+ - content: path to the file to include
364
+ - arcname: path relative to the directory containing the user's flow file
365
+ - for CODE_CONTENT:
366
+ - the file will be included relative to the code directory in the package.
367
+ This will be the directory containing `metaflow`.
368
+ - content: path to the file to include
369
+ - arcname: path relative to the code directory in the package
370
+ - for MODULE_CONTENT:
371
+ - the module will be added to the code package as a python module. It will
372
+ be accessible as usual (import <module_name>)
373
+ - content: name of the module
374
+ - arcname: None (ignored)
375
+ - for OTHER_CONTENT:
376
+ - the file will be included relative to any other configuration/metadata
377
+ files for the flow
378
+ - content: path to the file to include
379
+ - arcname: path relative to the config directory in the package
327
380
  """
328
381
  return []
329
382
 
@@ -443,12 +496,20 @@ def _base_flow_decorator(decofunc, *args, **kwargs):
443
496
  cls = args[0]
444
497
  if isinstance(cls, type) and issubclass(cls, FlowSpec):
445
498
  # flow decorators add attributes in the class dictionary,
446
- # _flow_decorators. _flow_decorators is of type `{key:[decos]}`
447
- if decofunc.name in cls._flow_decorators and not decofunc.allow_multiple:
499
+ # cls._flow_state[FlowStateItems.FLOW_DECORATORS]. This is of type `{key:[decos]}`
500
+ self_flow_decos = cls._flow_state.self_data[FlowStateItems.FLOW_DECORATORS]
501
+ inherited_flow_decos = cls._flow_state.inherited_data.get(
502
+ FlowStateItems.FLOW_DECORATORS, {}
503
+ )
504
+
505
+ if (
506
+ decofunc.name in self_flow_decos
507
+ or decofunc.name in inherited_flow_decos
508
+ ) and not decofunc.allow_multiple:
448
509
  raise DuplicateFlowDecoratorException(decofunc.name)
449
510
  else:
450
511
  deco_instance = decofunc(attributes=kwargs, statically_defined=True)
451
- cls._flow_decorators.setdefault(decofunc.name, []).append(deco_instance)
512
+ self_flow_decos.setdefault(decofunc.name, []).append(deco_instance)
452
513
  else:
453
514
  raise BadFlowDecoratorException(decofunc.name)
454
515
  return cls
@@ -472,7 +533,7 @@ def _base_step_decorator(decotype, *args, **kwargs):
472
533
  # No keyword arguments specified for the decorator, e.g. @foobar.
473
534
  # The first argument is the function to be decorated.
474
535
  func = args[0]
475
- if isinstance(func, CustomStepDecorator):
536
+ if isinstance(func, (StepMutator, UserStepDecoratorBase)):
476
537
  func = func._my_step
477
538
  if not hasattr(func, "is_step"):
478
539
  raise BadStepDecoratorException(decotype.name, func)
@@ -498,9 +559,10 @@ def _base_step_decorator(decotype, *args, **kwargs):
498
559
 
499
560
 
500
561
  _all_step_decos = None
562
+ _all_flow_decos = None
501
563
 
502
564
 
503
- def _get_all_step_decos():
565
+ def get_all_step_decos():
504
566
  global _all_step_decos
505
567
  if _all_step_decos is None:
506
568
  from .plugins import STEP_DECORATORS
@@ -509,6 +571,67 @@ def _get_all_step_decos():
509
571
  return _all_step_decos
510
572
 
511
573
 
574
+ def get_all_flow_decos():
575
+ global _all_flow_decos
576
+ if _all_flow_decos is None:
577
+ from .plugins import FLOW_DECORATORS
578
+
579
+ _all_flow_decos = {decotype.name: decotype for decotype in FLOW_DECORATORS}
580
+ return _all_flow_decos
581
+
582
+
583
+ def extract_step_decorator_from_decospec(decospec: str):
584
+ splits = decospec.split(":", 1)
585
+ deconame = splits[0]
586
+
587
+ # Check if it is a user-defined decorator or metaflow decorator
588
+ deco_cls = UserStepDecoratorMeta.get_decorator_by_name(deconame)
589
+ if deco_cls is not None:
590
+ return (
591
+ deco_cls.parse_decorator_spec(splits[1] if len(splits) > 1 else ""),
592
+ len(splits) > 1,
593
+ )
594
+
595
+ # Check if this is a decorator we can import
596
+ if "." in deconame:
597
+ # We consider this to be a import path to a user decorator so
598
+ # something like "my_package.my_decorator"
599
+ module_name, class_name = deconame.rsplit(".", 1)
600
+ try:
601
+ module = importlib.import_module(module_name)
602
+ except ImportError as e:
603
+ raise MetaflowException(
604
+ "Could not import user decorator %s" % deconame
605
+ ) from e
606
+ deco_cls = getattr(module, class_name, None)
607
+ if (
608
+ deco_cls is None
609
+ or not isinstance(deco_cls, type)
610
+ or not issubclass(deco_cls, UserStepDecoratorBase)
611
+ ):
612
+ raise UnknownStepDecoratorException(deconame)
613
+ return (
614
+ deco_cls.parse_decorator_spec(splits[1] if len(splits) > 1 else ""),
615
+ len(splits) > 1,
616
+ )
617
+
618
+ raise UnknownStepDecoratorException(deconame)
619
+
620
+
621
+ def extract_flow_decorator_from_decospec(decospec: str):
622
+ splits = decospec.split(":", 1)
623
+ deconame = splits[0]
624
+ # Check if it is a user-defined decorator or metaflow decorator
625
+ deco_cls = FlowMutatorMeta.get_decorator_by_name(deconame)
626
+ if deco_cls is not None:
627
+ return (
628
+ deco_cls.parse_decorator_spec(splits[1] if len(splits) > 1 else ""),
629
+ len(splits) > 1,
630
+ )
631
+ else:
632
+ raise UnknownFlowDecoratorException(deconame)
633
+
634
+
512
635
  def _attach_decorators(flow, decospecs):
513
636
  """
514
637
  Attach decorators to all steps during runtime. This has the same
@@ -532,49 +655,95 @@ def _attach_decorators_to_step(step, decospecs):
532
655
  effect as if you defined the decorators statically in the source for
533
656
  the step.
534
657
  """
658
+ for decospec in decospecs:
659
+ step_deco, _ = extract_step_decorator_from_decospec(decospec)
660
+ if isinstance(step_deco, StepDecorator):
661
+ # Check multiple
662
+ if (
663
+ step_deco.name not in [deco.name for deco in step.decorators]
664
+ or step_deco.allow_multiple
665
+ ):
666
+ step.decorators.append(step_deco)
667
+ # Else it is ignored -- this is a non-static decorator
535
668
 
536
- decos = _get_all_step_decos()
669
+ else:
670
+ step_deco.add_or_raise(step, False, 1, None)
537
671
 
538
- for decospec in decospecs:
539
- splits = decospec.split(":", 1)
540
- deconame = splits[0]
541
- if deconame not in decos:
542
- raise UnknownStepDecoratorException(deconame)
543
- # Attach the decorator to step if it doesn't have the decorator
544
- # already. This means that statically defined decorators are always
545
- # preferred over runtime decorators.
546
- if (
547
- deconame not in [deco.name for deco in step.decorators]
548
- or decos[deconame].allow_multiple
549
- ):
550
- # if the decorator is present in a step and is of type allow_multiple
551
- # then add the decorator to the step
552
- deco = decos[deconame]._parse_decorator_spec(
553
- splits[1] if len(splits) > 1 else ""
554
- )
555
- step.decorators.append(deco)
672
+
673
+ def _should_skip_decorator_for_spin(
674
+ deco, is_spin, skip_decorators, logger, decorator_type="decorator"
675
+ ):
676
+ """
677
+ Determine if a decorator should be skipped for spin steps.
678
+
679
+ Parameters:
680
+ -----------
681
+ deco : Decorator
682
+ The decorator instance to check
683
+ is_spin : bool
684
+ Whether this is a spin step
685
+ skip_decorators : bool
686
+ Whether to skip all decorators
687
+ logger : callable
688
+ Logger function for warnings
689
+ decorator_type : str
690
+ Type of decorator ("Flow decorator" or "Step decorator") for logging
691
+
692
+ Returns:
693
+ --------
694
+ bool
695
+ True if the decorator should be skipped, False otherwise
696
+ """
697
+ if not is_spin:
698
+ return False
699
+
700
+ # Skip all decorator hooks if skip_decorators is True
701
+ if skip_decorators:
702
+ return True
703
+
704
+ # Run decorator hooks for spin steps only if they are in the whitelist
705
+ if deco.name not in SPIN_ALLOWED_DECORATORS:
706
+ logger(
707
+ f"[Warning] Ignoring {decorator_type} '{deco.name}' as it is not supported in spin steps.",
708
+ system_msg=True,
709
+ timestamp=False,
710
+ bad=True,
711
+ )
712
+ return True
713
+
714
+ return False
556
715
 
557
716
 
558
717
  def _init(flow, only_non_static=False):
559
- for decorators in flow._flow_decorators.values():
718
+ flow_decos = flow._flow_state[FlowStateItems.FLOW_DECORATORS]
719
+ for decorators in flow_decos.values():
560
720
  for deco in decorators:
561
- if deco in _inited_decorators:
562
- continue
563
- deco.init()
564
- _inited_decorators.add(deco)
721
+ deco.external_init()
722
+
565
723
  for flowstep in flow:
566
724
  for deco in flowstep.decorators:
567
- if deco in _inited_decorators:
568
- continue
569
- deco.init()
570
- _inited_decorators.add(deco)
725
+ deco.external_init()
726
+ for deco in flowstep.config_decorators or []:
727
+ deco.external_init()
728
+ for deco in flowstep.wrappers or []:
729
+ deco.external_init()
571
730
 
572
731
 
573
732
  def _init_flow_decorators(
574
- flow, graph, environment, flow_datastore, metadata, logger, echo, deco_options
733
+ flow,
734
+ graph,
735
+ environment,
736
+ flow_datastore,
737
+ metadata,
738
+ logger,
739
+ echo,
740
+ deco_options,
741
+ is_spin=False,
742
+ skip_decorators=False,
575
743
  ):
576
744
  # Since all flow decorators are stored as `{key:[deco]}` we iterate through each of them.
577
- for decorators in flow._flow_decorators.values():
745
+ flow_decos = flow._flow_state[FlowStateItems.FLOW_DECORATORS]
746
+ for decorators in flow_decos.values():
578
747
  # First resolve the `options` for the flow decorator.
579
748
  # Options are passed from cli.
580
749
  # For example `@project` can take a `--name` / `--branch` from the cli as options.
@@ -600,6 +769,10 @@ def _init_flow_decorators(
600
769
  for option, option_info in deco.options.items()
601
770
  }
602
771
  for deco in decorators:
772
+ if _should_skip_decorator_for_spin(
773
+ deco, is_spin, skip_decorators, logger, "Flow decorator"
774
+ ):
775
+ continue
603
776
  deco.flow_init(
604
777
  flow,
605
778
  graph,
@@ -612,9 +785,89 @@ def _init_flow_decorators(
612
785
  )
613
786
 
614
787
 
615
- def _init_step_decorators(flow, graph, environment, flow_datastore, logger):
788
+ def _init_step_decorators(
789
+ flow,
790
+ graph,
791
+ environment,
792
+ flow_datastore,
793
+ logger,
794
+ is_spin=False,
795
+ skip_decorators=False,
796
+ ):
797
+ # NOTE: We don't need the graph but keeping it for backwards compatibility with
798
+ # extensions that use it directly. We will remove it at some point.
799
+
800
+ # We call the mutate method for both the flow and step mutators.
801
+ cls = flow.__class__
802
+ # Run all the decorators. We first run the flow-level decorators
803
+ # and then the step level ones to maintain a consistent order with how
804
+ # other decorators are run.
805
+
806
+ for deco in cls._flow_state[FlowStateItems.FLOW_MUTATORS]:
807
+ if isinstance(deco, FlowMutator):
808
+ inserted_by_value = [deco.decorator_name] + (deco.inserted_by or [])
809
+ mutable_flow = MutableFlow(
810
+ cls,
811
+ pre_mutate=False,
812
+ statically_defined=deco.statically_defined,
813
+ inserted_by=inserted_by_value,
814
+ )
815
+ # Sanity check to make sure we are applying the decorator to the right
816
+ # class
817
+ if not deco._flow_cls == cls and not issubclass(cls, deco._flow_cls):
818
+ raise MetaflowInternalError(
819
+ "FlowMutator registered on the wrong flow -- "
820
+ "expected %s but got %s" % (deco._flow_cls.__name__, cls.__name__)
821
+ )
822
+ debug.userconf_exec(
823
+ "Evaluating flow level decorator %s (mutate)" % deco.__class__.__name__
824
+ )
825
+ deco.mutate(mutable_flow)
826
+ # We reset cached_parameters on the very off chance that the user added
827
+ # more configurations based on the configuration
828
+ if cls._flow_state[FlowStateItems.CACHED_PARAMETERS] is not None:
829
+ cls._flow_state[FlowStateItems.CACHED_PARAMETERS] = None
830
+ else:
831
+ raise MetaflowInternalError(
832
+ "A non FlowMutator found in flow custom decorators"
833
+ )
834
+
835
+ for step in cls._steps:
836
+ for deco in step.config_decorators:
837
+ inserted_by_value = [deco.decorator_name] + (deco.inserted_by or [])
838
+
839
+ if isinstance(deco, StepMutator):
840
+ debug.userconf_exec(
841
+ "Evaluating step level decorator %s for %s (mutate)"
842
+ % (deco.__class__.__name__, step.name)
843
+ )
844
+ deco.mutate(
845
+ MutableStep(
846
+ cls,
847
+ step,
848
+ pre_mutate=False,
849
+ statically_defined=deco.statically_defined,
850
+ inserted_by=inserted_by_value,
851
+ )
852
+ )
853
+ else:
854
+ raise MetaflowInternalError(
855
+ "A non StepMutator found in step custom decorators"
856
+ )
857
+
858
+ if step.config_decorators:
859
+ # We remove all mention of the custom step decorator
860
+ setattr(cls, step.name, step)
861
+
862
+ cls._init_graph()
863
+ graph = flow._graph
864
+
616
865
  for step in flow:
617
866
  for deco in step.decorators:
867
+ if _should_skip_decorator_for_spin(
868
+ deco, is_spin, skip_decorators, logger, "Step decorator"
869
+ ):
870
+ continue
618
871
  deco.step_init(
619
872
  flow,
620
873
  graph,
@@ -638,7 +891,7 @@ StepFlag = NewType("StepFlag", bool)
638
891
 
639
892
  @overload
640
893
  def step(
641
- f: Callable[[FlowSpecDerived], None]
894
+ f: Callable[[FlowSpecDerived], None],
642
895
  ) -> Callable[[FlowSpecDerived, StepFlag], None]: ...
643
896
 
644
897
 
@@ -649,7 +902,7 @@ def step(
649
902
 
650
903
 
651
904
  def step(
652
- f: Union[Callable[[FlowSpecDerived], None], Callable[[FlowSpecDerived, Any], None]]
905
+ f: Union[Callable[[FlowSpecDerived], None], Callable[[FlowSpecDerived, Any], None]],
653
906
  ):
654
907
  """
655
908
  Marks a method in a FlowSpec as a Metaflow Step. Note that this
@@ -685,12 +938,8 @@ def step(
685
938
  f.is_step = True
686
939
  f.decorators = []
687
940
  f.config_decorators = []
688
- try:
689
- # python 3
690
- f.name = f.__name__
691
- except:
692
- # python 2
693
- f.name = f.__func__.func_name
941
+ f.wrappers = []
942
+ f.name = f.__name__
694
943
  return f
695
944
 
696
945