ob-metaflow 2.11.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 (289) hide show
  1. metaflow/R.py +10 -7
  2. metaflow/__init__.py +40 -25
  3. metaflow/_vendor/imghdr/__init__.py +186 -0
  4. metaflow/_vendor/importlib_metadata/__init__.py +1063 -0
  5. metaflow/_vendor/importlib_metadata/_adapters.py +68 -0
  6. metaflow/_vendor/importlib_metadata/_collections.py +30 -0
  7. metaflow/_vendor/importlib_metadata/_compat.py +71 -0
  8. metaflow/_vendor/importlib_metadata/_functools.py +104 -0
  9. metaflow/_vendor/importlib_metadata/_itertools.py +73 -0
  10. metaflow/_vendor/importlib_metadata/_meta.py +48 -0
  11. metaflow/_vendor/importlib_metadata/_text.py +99 -0
  12. metaflow/_vendor/importlib_metadata/py.typed +0 -0
  13. metaflow/_vendor/typeguard/__init__.py +48 -0
  14. metaflow/_vendor/typeguard/_checkers.py +1070 -0
  15. metaflow/_vendor/typeguard/_config.py +108 -0
  16. metaflow/_vendor/typeguard/_decorators.py +233 -0
  17. metaflow/_vendor/typeguard/_exceptions.py +42 -0
  18. metaflow/_vendor/typeguard/_functions.py +308 -0
  19. metaflow/_vendor/typeguard/_importhook.py +213 -0
  20. metaflow/_vendor/typeguard/_memo.py +48 -0
  21. metaflow/_vendor/typeguard/_pytest_plugin.py +127 -0
  22. metaflow/_vendor/typeguard/_suppression.py +86 -0
  23. metaflow/_vendor/typeguard/_transformer.py +1229 -0
  24. metaflow/_vendor/typeguard/_union_transformer.py +55 -0
  25. metaflow/_vendor/typeguard/_utils.py +173 -0
  26. metaflow/_vendor/typeguard/py.typed +0 -0
  27. metaflow/_vendor/typing_extensions.py +3641 -0
  28. metaflow/_vendor/v3_7/importlib_metadata/__init__.py +1063 -0
  29. metaflow/_vendor/v3_7/importlib_metadata/_adapters.py +68 -0
  30. metaflow/_vendor/v3_7/importlib_metadata/_collections.py +30 -0
  31. metaflow/_vendor/v3_7/importlib_metadata/_compat.py +71 -0
  32. metaflow/_vendor/v3_7/importlib_metadata/_functools.py +104 -0
  33. metaflow/_vendor/v3_7/importlib_metadata/_itertools.py +73 -0
  34. metaflow/_vendor/v3_7/importlib_metadata/_meta.py +48 -0
  35. metaflow/_vendor/v3_7/importlib_metadata/_text.py +99 -0
  36. metaflow/_vendor/v3_7/importlib_metadata/py.typed +0 -0
  37. metaflow/_vendor/v3_7/typeguard/__init__.py +48 -0
  38. metaflow/_vendor/v3_7/typeguard/_checkers.py +906 -0
  39. metaflow/_vendor/v3_7/typeguard/_config.py +108 -0
  40. metaflow/_vendor/v3_7/typeguard/_decorators.py +237 -0
  41. metaflow/_vendor/v3_7/typeguard/_exceptions.py +42 -0
  42. metaflow/_vendor/v3_7/typeguard/_functions.py +310 -0
  43. metaflow/_vendor/v3_7/typeguard/_importhook.py +213 -0
  44. metaflow/_vendor/v3_7/typeguard/_memo.py +48 -0
  45. metaflow/_vendor/v3_7/typeguard/_pytest_plugin.py +100 -0
  46. metaflow/_vendor/v3_7/typeguard/_suppression.py +88 -0
  47. metaflow/_vendor/v3_7/typeguard/_transformer.py +1207 -0
  48. metaflow/_vendor/v3_7/typeguard/_union_transformer.py +54 -0
  49. metaflow/_vendor/v3_7/typeguard/_utils.py +169 -0
  50. metaflow/_vendor/v3_7/typeguard/py.typed +0 -0
  51. metaflow/_vendor/v3_7/typing_extensions.py +3072 -0
  52. metaflow/_vendor/yaml/__init__.py +427 -0
  53. metaflow/_vendor/yaml/composer.py +139 -0
  54. metaflow/_vendor/yaml/constructor.py +748 -0
  55. metaflow/_vendor/yaml/cyaml.py +101 -0
  56. metaflow/_vendor/yaml/dumper.py +62 -0
  57. metaflow/_vendor/yaml/emitter.py +1137 -0
  58. metaflow/_vendor/yaml/error.py +75 -0
  59. metaflow/_vendor/yaml/events.py +86 -0
  60. metaflow/_vendor/yaml/loader.py +63 -0
  61. metaflow/_vendor/yaml/nodes.py +49 -0
  62. metaflow/_vendor/yaml/parser.py +589 -0
  63. metaflow/_vendor/yaml/reader.py +185 -0
  64. metaflow/_vendor/yaml/representer.py +389 -0
  65. metaflow/_vendor/yaml/resolver.py +227 -0
  66. metaflow/_vendor/yaml/scanner.py +1435 -0
  67. metaflow/_vendor/yaml/serializer.py +111 -0
  68. metaflow/_vendor/yaml/tokens.py +104 -0
  69. metaflow/cards.py +5 -0
  70. metaflow/cli.py +331 -785
  71. metaflow/cli_args.py +17 -0
  72. metaflow/cli_components/__init__.py +0 -0
  73. metaflow/cli_components/dump_cmd.py +96 -0
  74. metaflow/cli_components/init_cmd.py +52 -0
  75. metaflow/cli_components/run_cmds.py +546 -0
  76. metaflow/cli_components/step_cmd.py +334 -0
  77. metaflow/cli_components/utils.py +140 -0
  78. metaflow/client/__init__.py +1 -0
  79. metaflow/client/core.py +467 -73
  80. metaflow/client/filecache.py +75 -35
  81. metaflow/clone_util.py +7 -1
  82. metaflow/cmd/code/__init__.py +231 -0
  83. metaflow/cmd/develop/stub_generator.py +756 -288
  84. metaflow/cmd/develop/stubs.py +12 -28
  85. metaflow/cmd/main_cli.py +6 -4
  86. metaflow/cmd/make_wrapper.py +78 -0
  87. metaflow/datastore/__init__.py +1 -0
  88. metaflow/datastore/content_addressed_store.py +41 -10
  89. metaflow/datastore/datastore_set.py +11 -2
  90. metaflow/datastore/flow_datastore.py +156 -10
  91. metaflow/datastore/spin_datastore.py +91 -0
  92. metaflow/datastore/task_datastore.py +154 -39
  93. metaflow/debug.py +5 -0
  94. metaflow/decorators.py +404 -78
  95. metaflow/exception.py +8 -2
  96. metaflow/extension_support/__init__.py +527 -376
  97. metaflow/extension_support/_empty_file.py +2 -2
  98. metaflow/extension_support/plugins.py +49 -31
  99. metaflow/flowspec.py +482 -33
  100. metaflow/graph.py +210 -42
  101. metaflow/includefile.py +84 -40
  102. metaflow/lint.py +141 -22
  103. metaflow/meta_files.py +13 -0
  104. metaflow/{metadata → metadata_provider}/heartbeat.py +24 -8
  105. metaflow/{metadata → metadata_provider}/metadata.py +86 -1
  106. metaflow/metaflow_config.py +175 -28
  107. metaflow/metaflow_config_funcs.py +51 -3
  108. metaflow/metaflow_current.py +4 -10
  109. metaflow/metaflow_environment.py +139 -53
  110. metaflow/metaflow_git.py +115 -0
  111. metaflow/metaflow_profile.py +18 -0
  112. metaflow/metaflow_version.py +150 -66
  113. metaflow/mflog/__init__.py +4 -3
  114. metaflow/mflog/save_logs.py +2 -2
  115. metaflow/multicore_utils.py +31 -14
  116. metaflow/package/__init__.py +673 -0
  117. metaflow/packaging_sys/__init__.py +880 -0
  118. metaflow/packaging_sys/backend.py +128 -0
  119. metaflow/packaging_sys/distribution_support.py +153 -0
  120. metaflow/packaging_sys/tar_backend.py +99 -0
  121. metaflow/packaging_sys/utils.py +54 -0
  122. metaflow/packaging_sys/v1.py +527 -0
  123. metaflow/parameters.py +149 -28
  124. metaflow/plugins/__init__.py +74 -5
  125. metaflow/plugins/airflow/airflow.py +40 -25
  126. metaflow/plugins/airflow/airflow_cli.py +22 -5
  127. metaflow/plugins/airflow/airflow_decorator.py +1 -1
  128. metaflow/plugins/airflow/airflow_utils.py +5 -3
  129. metaflow/plugins/airflow/sensors/base_sensor.py +4 -4
  130. metaflow/plugins/airflow/sensors/external_task_sensor.py +2 -2
  131. metaflow/plugins/airflow/sensors/s3_sensor.py +2 -2
  132. metaflow/plugins/argo/argo_client.py +78 -33
  133. metaflow/plugins/argo/argo_events.py +6 -6
  134. metaflow/plugins/argo/argo_workflows.py +2410 -527
  135. metaflow/plugins/argo/argo_workflows_cli.py +571 -121
  136. metaflow/plugins/argo/argo_workflows_decorator.py +43 -12
  137. metaflow/plugins/argo/argo_workflows_deployer.py +106 -0
  138. metaflow/plugins/argo/argo_workflows_deployer_objects.py +453 -0
  139. metaflow/plugins/argo/capture_error.py +73 -0
  140. metaflow/plugins/argo/conditional_input_paths.py +35 -0
  141. metaflow/plugins/argo/exit_hooks.py +209 -0
  142. metaflow/plugins/argo/jobset_input_paths.py +15 -0
  143. metaflow/plugins/argo/param_val.py +19 -0
  144. metaflow/plugins/aws/aws_client.py +10 -3
  145. metaflow/plugins/aws/aws_utils.py +55 -2
  146. metaflow/plugins/aws/batch/batch.py +72 -5
  147. metaflow/plugins/aws/batch/batch_cli.py +33 -10
  148. metaflow/plugins/aws/batch/batch_client.py +4 -3
  149. metaflow/plugins/aws/batch/batch_decorator.py +102 -35
  150. metaflow/plugins/aws/secrets_manager/aws_secrets_manager_secrets_provider.py +13 -10
  151. metaflow/plugins/aws/step_functions/dynamo_db_client.py +0 -3
  152. metaflow/plugins/aws/step_functions/production_token.py +1 -1
  153. metaflow/plugins/aws/step_functions/step_functions.py +65 -8
  154. metaflow/plugins/aws/step_functions/step_functions_cli.py +101 -7
  155. metaflow/plugins/aws/step_functions/step_functions_decorator.py +1 -2
  156. metaflow/plugins/aws/step_functions/step_functions_deployer.py +97 -0
  157. metaflow/plugins/aws/step_functions/step_functions_deployer_objects.py +264 -0
  158. metaflow/plugins/azure/azure_exceptions.py +1 -1
  159. metaflow/plugins/azure/azure_secret_manager_secrets_provider.py +240 -0
  160. metaflow/plugins/azure/azure_tail.py +1 -1
  161. metaflow/plugins/azure/includefile_support.py +2 -0
  162. metaflow/plugins/cards/card_cli.py +66 -30
  163. metaflow/plugins/cards/card_creator.py +25 -1
  164. metaflow/plugins/cards/card_datastore.py +21 -49
  165. metaflow/plugins/cards/card_decorator.py +132 -8
  166. metaflow/plugins/cards/card_modules/basic.py +112 -17
  167. metaflow/plugins/cards/card_modules/bundle.css +1 -1
  168. metaflow/plugins/cards/card_modules/card.py +16 -1
  169. metaflow/plugins/cards/card_modules/chevron/renderer.py +1 -1
  170. metaflow/plugins/cards/card_modules/components.py +665 -28
  171. metaflow/plugins/cards/card_modules/convert_to_native_type.py +36 -7
  172. metaflow/plugins/cards/card_modules/json_viewer.py +232 -0
  173. metaflow/plugins/cards/card_modules/main.css +1 -0
  174. metaflow/plugins/cards/card_modules/main.js +68 -49
  175. metaflow/plugins/cards/card_modules/renderer_tools.py +1 -0
  176. metaflow/plugins/cards/card_modules/test_cards.py +26 -12
  177. metaflow/plugins/cards/card_server.py +39 -14
  178. metaflow/plugins/cards/component_serializer.py +2 -9
  179. metaflow/plugins/cards/metadata.py +22 -0
  180. metaflow/plugins/catch_decorator.py +9 -0
  181. metaflow/plugins/datastores/azure_storage.py +10 -1
  182. metaflow/plugins/datastores/gs_storage.py +6 -2
  183. metaflow/plugins/datastores/local_storage.py +12 -6
  184. metaflow/plugins/datastores/spin_storage.py +12 -0
  185. metaflow/plugins/datatools/local.py +2 -0
  186. metaflow/plugins/datatools/s3/s3.py +126 -75
  187. metaflow/plugins/datatools/s3/s3op.py +254 -121
  188. metaflow/plugins/env_escape/__init__.py +3 -3
  189. metaflow/plugins/env_escape/client_modules.py +102 -72
  190. metaflow/plugins/env_escape/server.py +7 -0
  191. metaflow/plugins/env_escape/stub.py +24 -5
  192. metaflow/plugins/events_decorator.py +343 -185
  193. metaflow/plugins/exit_hook/__init__.py +0 -0
  194. metaflow/plugins/exit_hook/exit_hook_decorator.py +46 -0
  195. metaflow/plugins/exit_hook/exit_hook_script.py +52 -0
  196. metaflow/plugins/gcp/__init__.py +1 -1
  197. metaflow/plugins/gcp/gcp_secret_manager_secrets_provider.py +11 -6
  198. metaflow/plugins/gcp/gs_tail.py +10 -6
  199. metaflow/plugins/gcp/includefile_support.py +3 -0
  200. metaflow/plugins/kubernetes/kube_utils.py +108 -0
  201. metaflow/plugins/kubernetes/kubernetes.py +411 -130
  202. metaflow/plugins/kubernetes/kubernetes_cli.py +168 -36
  203. metaflow/plugins/kubernetes/kubernetes_client.py +104 -2
  204. metaflow/plugins/kubernetes/kubernetes_decorator.py +246 -88
  205. metaflow/plugins/kubernetes/kubernetes_job.py +253 -581
  206. metaflow/plugins/kubernetes/kubernetes_jobsets.py +1071 -0
  207. metaflow/plugins/kubernetes/spot_metadata_cli.py +69 -0
  208. metaflow/plugins/kubernetes/spot_monitor_sidecar.py +109 -0
  209. metaflow/plugins/logs_cli.py +359 -0
  210. metaflow/plugins/{metadata → metadata_providers}/local.py +144 -84
  211. metaflow/plugins/{metadata → metadata_providers}/service.py +103 -26
  212. metaflow/plugins/metadata_providers/spin.py +16 -0
  213. metaflow/plugins/package_cli.py +36 -24
  214. metaflow/plugins/parallel_decorator.py +128 -11
  215. metaflow/plugins/parsers.py +16 -0
  216. metaflow/plugins/project_decorator.py +51 -5
  217. metaflow/plugins/pypi/bootstrap.py +357 -105
  218. metaflow/plugins/pypi/conda_decorator.py +82 -81
  219. metaflow/plugins/pypi/conda_environment.py +187 -52
  220. metaflow/plugins/pypi/micromamba.py +157 -47
  221. metaflow/plugins/pypi/parsers.py +268 -0
  222. metaflow/plugins/pypi/pip.py +88 -13
  223. metaflow/plugins/pypi/pypi_decorator.py +37 -1
  224. metaflow/plugins/pypi/utils.py +48 -2
  225. metaflow/plugins/resources_decorator.py +2 -2
  226. metaflow/plugins/secrets/__init__.py +3 -0
  227. metaflow/plugins/secrets/secrets_decorator.py +26 -181
  228. metaflow/plugins/secrets/secrets_func.py +49 -0
  229. metaflow/plugins/secrets/secrets_spec.py +101 -0
  230. metaflow/plugins/secrets/utils.py +74 -0
  231. metaflow/plugins/tag_cli.py +4 -7
  232. metaflow/plugins/test_unbounded_foreach_decorator.py +41 -6
  233. metaflow/plugins/timeout_decorator.py +3 -3
  234. metaflow/plugins/uv/__init__.py +0 -0
  235. metaflow/plugins/uv/bootstrap.py +128 -0
  236. metaflow/plugins/uv/uv_environment.py +72 -0
  237. metaflow/procpoll.py +1 -1
  238. metaflow/pylint_wrapper.py +5 -1
  239. metaflow/runner/__init__.py +0 -0
  240. metaflow/runner/click_api.py +717 -0
  241. metaflow/runner/deployer.py +470 -0
  242. metaflow/runner/deployer_impl.py +201 -0
  243. metaflow/runner/metaflow_runner.py +714 -0
  244. metaflow/runner/nbdeploy.py +132 -0
  245. metaflow/runner/nbrun.py +225 -0
  246. metaflow/runner/subprocess_manager.py +650 -0
  247. metaflow/runner/utils.py +335 -0
  248. metaflow/runtime.py +1078 -260
  249. metaflow/sidecar/sidecar_worker.py +1 -1
  250. metaflow/system/__init__.py +5 -0
  251. metaflow/system/system_logger.py +85 -0
  252. metaflow/system/system_monitor.py +108 -0
  253. metaflow/system/system_utils.py +19 -0
  254. metaflow/task.py +521 -225
  255. metaflow/tracing/__init__.py +7 -7
  256. metaflow/tracing/span_exporter.py +31 -38
  257. metaflow/tracing/tracing_modules.py +38 -43
  258. metaflow/tuple_util.py +27 -0
  259. metaflow/user_configs/__init__.py +0 -0
  260. metaflow/user_configs/config_options.py +563 -0
  261. metaflow/user_configs/config_parameters.py +598 -0
  262. metaflow/user_decorators/__init__.py +0 -0
  263. metaflow/user_decorators/common.py +144 -0
  264. metaflow/user_decorators/mutable_flow.py +512 -0
  265. metaflow/user_decorators/mutable_step.py +424 -0
  266. metaflow/user_decorators/user_flow_decorator.py +264 -0
  267. metaflow/user_decorators/user_step_decorator.py +749 -0
  268. metaflow/util.py +243 -27
  269. metaflow/vendor.py +23 -7
  270. metaflow/version.py +1 -1
  271. ob_metaflow-2.19.7.1rc0.data/data/share/metaflow/devtools/Makefile +355 -0
  272. ob_metaflow-2.19.7.1rc0.data/data/share/metaflow/devtools/Tiltfile +726 -0
  273. ob_metaflow-2.19.7.1rc0.data/data/share/metaflow/devtools/pick_services.sh +105 -0
  274. ob_metaflow-2.19.7.1rc0.dist-info/METADATA +87 -0
  275. ob_metaflow-2.19.7.1rc0.dist-info/RECORD +445 -0
  276. {ob_metaflow-2.11.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/WHEEL +1 -1
  277. {ob_metaflow-2.11.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/entry_points.txt +1 -0
  278. metaflow/_vendor/v3_5/__init__.py +0 -1
  279. metaflow/_vendor/v3_5/importlib_metadata/__init__.py +0 -644
  280. metaflow/_vendor/v3_5/importlib_metadata/_compat.py +0 -152
  281. metaflow/package.py +0 -188
  282. ob_metaflow-2.11.13.1.dist-info/METADATA +0 -85
  283. ob_metaflow-2.11.13.1.dist-info/RECORD +0 -308
  284. /metaflow/_vendor/{v3_5/zipp.py → zipp.py} +0 -0
  285. /metaflow/{metadata → metadata_provider}/__init__.py +0 -0
  286. /metaflow/{metadata → metadata_provider}/util.py +0 -0
  287. /metaflow/plugins/{metadata → metadata_providers}/__init__.py +0 -0
  288. {ob_metaflow-2.11.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info/licenses}/LICENSE +0 -0
  289. {ob_metaflow-2.11.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/top_level.txt +0 -0
metaflow/decorators.py CHANGED
@@ -1,24 +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
16
+ from .parameters import current_flow
17
+ from .user_configs.config_parameters import (
18
+ UNPACK_KEY,
19
+ resolve_delayed_evaluator,
20
+ unpack_delayed_evaluator,
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
14
31
  from metaflow._vendor import click
15
32
 
16
- try:
17
- unicode
18
- except NameError:
19
- unicode = str
20
- basestring = str
21
-
22
33
 
23
34
  class BadStepDecoratorException(MetaflowException):
24
35
  headline = "Syntax error"
@@ -29,7 +40,7 @@ class BadStepDecoratorException(MetaflowException):
29
40
  "not declared as a @step. Make sure you apply this decorator "
30
41
  "on a function which has @step on the line just before the "
31
42
  "function name and @{deco} is above @step.".format(
32
- deco=deco, func=func.__name__
43
+ deco=deco, func=getattr(func, "__name__", str(func))
33
44
  )
34
45
  )
35
46
  super(BadStepDecoratorException, self).__init__(msg)
@@ -50,11 +61,14 @@ class UnknownStepDecoratorException(MetaflowException):
50
61
  headline = "Unknown step decorator"
51
62
 
52
63
  def __init__(self, deconame):
53
- from .plugins import STEP_DECORATORS
54
-
55
64
  decos = ", ".join(
56
- 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
+ ]
57
70
  )
71
+
58
72
  msg = (
59
73
  "Unknown step decorator *{deconame}*. The following decorators are "
60
74
  "supported: *{decos}*".format(deconame=deconame, decos=decos)
@@ -79,9 +93,7 @@ class UnknownFlowDecoratorException(MetaflowException):
79
93
  headline = "Unknown flow decorator"
80
94
 
81
95
  def __init__(self, deconame):
82
- from .plugins import FLOW_DECORATORS
83
-
84
- decos = ", ".join(t.name for t in FLOW_DECORATORS)
96
+ decos = ", ".join(FlowMutatorMeta.all_decorators().keys())
85
97
  msg = (
86
98
  "Unknown flow decorator *{deconame}*. The following decorators are "
87
99
  "supported: *{decos}*".format(deconame=deconame, decos=decos)
@@ -110,21 +122,48 @@ class Decorator(object):
110
122
  # `allow_multiple` allows setting many decorators of the same type to a step/flow.
111
123
  allow_multiple = False
112
124
 
113
- def __init__(self, attributes=None, statically_defined=False):
125
+ def __init__(self, attributes=None, statically_defined=False, inserted_by=None):
114
126
  self.attributes = self.defaults.copy()
115
127
  self.statically_defined = statically_defined
128
+ self.inserted_by = inserted_by
129
+ self._user_defined_attributes = set()
130
+ self._ran_init = False
116
131
 
117
132
  if attributes:
118
133
  for k, v in attributes.items():
119
- if k in self.defaults:
134
+ if k in self.defaults or k.startswith(UNPACK_KEY):
120
135
  self.attributes[k] = v
136
+ if not k.startswith(UNPACK_KEY):
137
+ self._user_defined_attributes.add(k)
121
138
  else:
122
139
  raise InvalidDecoratorAttribute(self.name, k, self.defaults)
123
140
 
141
+ def init(self):
142
+ """
143
+ Initializes the decorator. In general, any operation you would do in __init__
144
+ should be done here.
145
+ """
146
+ pass
147
+
148
+ def external_init(self):
149
+ # In some cases (specifically when using remove_decorator), we may need to call
150
+ # init multiple times. Short-circuit re-evaluating.
151
+ if self._ran_init:
152
+ return
153
+
154
+ # Note that by design, later values override previous ones.
155
+ self.attributes, new_user_attributes = unpack_delayed_evaluator(self.attributes)
156
+ self._user_defined_attributes.update(new_user_attributes)
157
+ self.attributes = resolve_delayed_evaluator(self.attributes, to_dict=True)
158
+
159
+ if "init" in self.__class__.__dict__:
160
+ self.init()
161
+ self._ran_init = True
162
+
124
163
  @classmethod
125
- def _parse_decorator_spec(cls, deco_spec):
164
+ def extract_args_kwargs_from_decorator_spec(cls, deco_spec):
126
165
  if len(deco_spec) == 0:
127
- return cls()
166
+ return [], {}
128
167
 
129
168
  attrs = {}
130
169
  # TODO: Do we really want to allow spaces in the names of attributes?!?
@@ -144,9 +183,20 @@ class Decorator(object):
144
183
  val_parsed = val.strip()
145
184
 
146
185
  attrs[name.strip()] = val_parsed
147
- 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)
148
196
 
149
197
  def make_decorator_spec(self):
198
+ # Make sure all attributes are evaluated
199
+ self.external_init()
150
200
  attrs = {k: v for k, v in self.attributes.items() if v is not None}
151
201
  if attrs:
152
202
  attr_list = []
@@ -154,17 +204,31 @@ class Decorator(object):
154
204
  # escaping but for more complex types (typically dictionaries or lists),
155
205
  # we dump using JSON.
156
206
  for k, v in attrs.items():
157
- if isinstance(v, (int, float, unicode, basestring)):
207
+ if isinstance(v, (int, float, str)):
158
208
  attr_list.append("%s=%s" % (k, str(v)))
159
209
  else:
160
210
  attr_list.append("%s=%s" % (k, json.dumps(v).replace('"', '\\"')))
211
+
161
212
  attrstr = ",".join(attr_list)
162
213
  return "%s:%s" % (self.name, attrstr)
163
214
  else:
164
215
  return self.name
165
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
+
166
228
  def __str__(self):
167
- mode = "decorated" if self.statically_defined else "cli"
229
+ mode = "static" if self.statically_defined else "dynamic"
230
+ if self.inserted_by:
231
+ mode += " (inserted by %s)" % " from ".join(self.inserted_by)
168
232
  attrs = " ".join("%s=%s" % x for x in self.attributes.items())
169
233
  if attrs:
170
234
  attrs = " " + attrs
@@ -173,13 +237,9 @@ class Decorator(object):
173
237
 
174
238
 
175
239
  class FlowDecorator(Decorator):
176
- _flow_decorators = []
177
240
  options = {}
178
241
 
179
242
  def __init__(self, *args, **kwargs):
180
- # Note that this assumes we are executing one flow per process, so we have a global list of
181
- # _flow_decorators. A similar setup is used in parameters.
182
- self._flow_decorators.append(self)
183
243
  super(FlowDecorator, self).__init__(*args, **kwargs)
184
244
 
185
245
  def flow_init(
@@ -204,8 +264,14 @@ class FlowDecorator(Decorator):
204
264
 
205
265
  # compare this to parameters.add_custom_parameters
206
266
  def add_decorator_options(cmd):
267
+ flow_cls = getattr(current_flow, "flow_cls", None)
268
+ if flow_cls is None:
269
+ return cmd
270
+
207
271
  seen = {}
208
- for deco in flow_decorators():
272
+ existing_params = set(p.name.lower() for p in cmd.params)
273
+ # Add decorator options
274
+ for deco in flow_decorators(flow_cls):
209
275
  for option, kwargs in deco.options.items():
210
276
  if option in seen:
211
277
  msg = (
@@ -215,14 +281,24 @@ def add_decorator_options(cmd):
215
281
  % (deco.name, option, seen[option])
216
282
  )
217
283
  raise MetaflowInternalError(msg)
284
+ elif deco.name.lower() in existing_params:
285
+ raise MetaflowInternalError(
286
+ "Flow decorator '%s' uses an option '%s' which is a reserved "
287
+ "keyword. Please use a different option name." % (deco.name, option)
288
+ )
218
289
  else:
290
+ kwargs["envvar"] = "METAFLOW_FLOW_%s" % option.upper()
219
291
  seen[option] = deco.name
220
292
  cmd.params.insert(0, click.Option(("--" + option,), **kwargs))
221
293
  return cmd
222
294
 
223
295
 
224
- def flow_decorators():
225
- return FlowDecorator._flow_decorators
296
+ def flow_decorators(flow_cls):
297
+ return [
298
+ d
299
+ for deco_list in flow_cls._flow_state[FlowStateItems.FLOW_DECORATORS].values()
300
+ for d in deco_list
301
+ ]
226
302
 
227
303
 
228
304
  class StepDecorator(Decorator):
@@ -271,15 +347,36 @@ class StepDecorator(Decorator):
271
347
 
272
348
  def add_to_package(self):
273
349
  """
274
- 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
275
351
  called in the `MetaflowPackage` class where metaflow compiles the code package
276
- tarball. This hook is invoked in the `MetaflowPackage`'s `path_tuples`
277
- function. The `path_tuples` function is a generator that yields a tuple of
278
- `(file_path, arcname)`.`file_path` is the path of the file in the local file system;
279
- the `arcname` is the path of the file in the constructed tarball or the path of the file
280
- after decompressing the tarball.
281
-
282
- 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
283
380
  """
284
381
  return []
285
382
 
@@ -399,12 +496,20 @@ def _base_flow_decorator(decofunc, *args, **kwargs):
399
496
  cls = args[0]
400
497
  if isinstance(cls, type) and issubclass(cls, FlowSpec):
401
498
  # flow decorators add attributes in the class dictionary,
402
- # _flow_decorators. _flow_decorators is of type `{key:[decos]}`
403
- 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:
404
509
  raise DuplicateFlowDecoratorException(decofunc.name)
405
510
  else:
406
511
  deco_instance = decofunc(attributes=kwargs, statically_defined=True)
407
- cls._flow_decorators.setdefault(decofunc.name, []).append(deco_instance)
512
+ self_flow_decos.setdefault(decofunc.name, []).append(deco_instance)
408
513
  else:
409
514
  raise BadFlowDecoratorException(decofunc.name)
410
515
  return cls
@@ -423,10 +528,13 @@ def _base_step_decorator(decotype, *args, **kwargs):
423
528
  Decorator prototype for all step decorators. This function gets specialized
424
529
  and imported for all decorators types by _import_plugin_decorators().
425
530
  """
531
+
426
532
  if args:
427
533
  # No keyword arguments specified for the decorator, e.g. @foobar.
428
534
  # The first argument is the function to be decorated.
429
535
  func = args[0]
536
+ if isinstance(func, (StepMutator, UserStepDecoratorBase)):
537
+ func = func._my_step
430
538
  if not hasattr(func, "is_step"):
431
539
  raise BadStepDecoratorException(decotype.name, func)
432
540
 
@@ -450,6 +558,80 @@ def _base_step_decorator(decotype, *args, **kwargs):
450
558
  return wrap
451
559
 
452
560
 
561
+ _all_step_decos = None
562
+ _all_flow_decos = None
563
+
564
+
565
+ def get_all_step_decos():
566
+ global _all_step_decos
567
+ if _all_step_decos is None:
568
+ from .plugins import STEP_DECORATORS
569
+
570
+ _all_step_decos = {decotype.name: decotype for decotype in STEP_DECORATORS}
571
+ return _all_step_decos
572
+
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
+
453
635
  def _attach_decorators(flow, decospecs):
454
636
  """
455
637
  Attach decorators to all steps during runtime. This has the same
@@ -462,6 +644,7 @@ def _attach_decorators(flow, decospecs):
462
644
  #
463
645
  # Note that each step gets its own instance of the decorator class,
464
646
  # so decorator can maintain step-specific state.
647
+
465
648
  for step in flow:
466
649
  _attach_decorators_to_step(step, decospecs)
467
650
 
@@ -472,35 +655,95 @@ def _attach_decorators_to_step(step, decospecs):
472
655
  effect as if you defined the decorators statically in the source for
473
656
  the step.
474
657
  """
475
- from .plugins import STEP_DECORATORS
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
476
668
 
477
- decos = {decotype.name: decotype for decotype in STEP_DECORATORS}
669
+ else:
670
+ step_deco.add_or_raise(step, False, 1, None)
478
671
 
479
- for decospec in decospecs:
480
- splits = decospec.split(":", 1)
481
- deconame = splits[0]
482
- if deconame not in decos:
483
- raise UnknownStepDecoratorException(deconame)
484
- # Attach the decorator to step if it doesn't have the decorator
485
- # already. This means that statically defined decorators are always
486
- # preferred over runtime decorators.
487
- if (
488
- deconame not in [deco.name for deco in step.decorators]
489
- or decos[deconame].allow_multiple
490
- ):
491
- # if the decorator is present in a step and is of type allow_multiple
492
- # then add the decorator to the step
493
- deco = decos[deconame]._parse_decorator_spec(
494
- splits[1] if len(splits) > 1 else ""
495
- )
496
- 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
715
+
716
+
717
+ def _init(flow, only_non_static=False):
718
+ flow_decos = flow._flow_state[FlowStateItems.FLOW_DECORATORS]
719
+ for decorators in flow_decos.values():
720
+ for deco in decorators:
721
+ deco.external_init()
722
+
723
+ for flowstep in flow:
724
+ for deco in flowstep.decorators:
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()
497
730
 
498
731
 
499
732
  def _init_flow_decorators(
500
- 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,
501
743
  ):
502
744
  # Since all flow decorators are stored as `{key:[deco]}` we iterate through each of them.
503
- for decorators in flow._flow_decorators.values():
745
+ flow_decos = flow._flow_state[FlowStateItems.FLOW_DECORATORS]
746
+ for decorators in flow_decos.values():
504
747
  # First resolve the `options` for the flow decorator.
505
748
  # Options are passed from cli.
506
749
  # For example `@project` can take a `--name` / `--branch` from the cli as options.
@@ -517,11 +760,19 @@ def _init_flow_decorators(
517
760
  )
518
761
  else:
519
762
  # Each "non-multiple" flow decorator is only allowed to have one set of options
763
+ # Note that there may be no deco_options if a MutableFlow config injected
764
+ # the decorator.
520
765
  deco_flow_init_options = {
521
- option: deco_options[option.replace("-", "_")]
522
- for option in deco.options
766
+ option: deco_options.get(
767
+ option.replace("-", "_"), option_info["default"]
768
+ )
769
+ for option, option_info in deco.options.items()
523
770
  }
524
771
  for deco in decorators:
772
+ if _should_skip_decorator_for_spin(
773
+ deco, is_spin, skip_decorators, logger, "Flow decorator"
774
+ ):
775
+ continue
525
776
  deco.flow_init(
526
777
  flow,
527
778
  graph,
@@ -534,9 +785,89 @@ def _init_flow_decorators(
534
785
  )
535
786
 
536
787
 
537
- 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
+
538
865
  for step in flow:
539
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
540
871
  deco.step_init(
541
872
  flow,
542
873
  graph,
@@ -560,20 +891,18 @@ StepFlag = NewType("StepFlag", bool)
560
891
 
561
892
  @overload
562
893
  def step(
563
- f: Callable[[FlowSpecDerived], None]
564
- ) -> Callable[[FlowSpecDerived, StepFlag], None]:
565
- ...
894
+ f: Callable[[FlowSpecDerived], None],
895
+ ) -> Callable[[FlowSpecDerived, StepFlag], None]: ...
566
896
 
567
897
 
568
898
  @overload
569
899
  def step(
570
900
  f: Callable[[FlowSpecDerived, Any], None],
571
- ) -> Callable[[FlowSpecDerived, Any, StepFlag], None]:
572
- ...
901
+ ) -> Callable[[FlowSpecDerived, Any, StepFlag], None]: ...
573
902
 
574
903
 
575
904
  def step(
576
- f: Union[Callable[[FlowSpecDerived], None], Callable[[FlowSpecDerived, Any], None]]
905
+ f: Union[Callable[[FlowSpecDerived], None], Callable[[FlowSpecDerived, Any], None]],
577
906
  ):
578
907
  """
579
908
  Marks a method in a FlowSpec as a Metaflow Step. Note that this
@@ -608,12 +937,9 @@ def step(
608
937
  """
609
938
  f.is_step = True
610
939
  f.decorators = []
611
- try:
612
- # python 3
613
- f.name = f.__name__
614
- except:
615
- # python 2
616
- f.name = f.__func__.func_name
940
+ f.config_decorators = []
941
+ f.wrappers = []
942
+ f.name = f.__name__
617
943
  return f
618
944
 
619
945