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/flowspec.py CHANGED
@@ -4,19 +4,35 @@ import sys
4
4
  import traceback
5
5
  import reprlib
6
6
 
7
+ from collections.abc import MutableMapping
8
+ from enum import Enum
7
9
  from itertools import islice
8
10
  from types import FunctionType, MethodType
9
11
  from typing import Any, Callable, List, Optional, Tuple
10
12
 
11
13
  from . import cmd_with_io, parameters
14
+ from .debug import debug
12
15
  from .parameters import DelayedEvaluationParameter, Parameter
13
16
  from .exception import (
14
17
  MetaflowException,
15
18
  MissingInMergeArtifactsException,
19
+ MetaflowInternalError,
16
20
  UnhandledInMergeArtifactsException,
17
21
  )
22
+
23
+ from .extension_support import extension_info
24
+
18
25
  from .graph import FlowGraph
19
26
  from .unbounded_foreach import UnboundedForeachInput
27
+ from .user_configs.config_parameters import ConfigValue
28
+
29
+ from .user_decorators.mutable_flow import MutableFlow
30
+ from .user_decorators.mutable_step import MutableStep
31
+ from .user_decorators.user_flow_decorator import FlowMutator
32
+ from .user_decorators.user_step_decorator import StepMutator
33
+
34
+
35
+ from .util import to_pod
20
36
  from .metaflow_config import INCLUDE_FOREACH_STACK, MAXIMUM_FOREACH_VALUE_CHARS
21
37
 
22
38
  # For Python 3 compatibility
@@ -28,7 +44,15 @@ except NameError:
28
44
 
29
45
  from .datastore.inputs import Inputs
30
46
 
31
- INTERNAL_ARTIFACTS_SET = set(["_foreach_values"])
47
+ INTERNAL_ARTIFACTS_SET = set(
48
+ [
49
+ "_foreach_values",
50
+ "_unbounded_foreach",
51
+ "_control_mapper_tasks",
52
+ "_control_task_is_mapper_zero",
53
+ "_parallel_ubf_iter",
54
+ ]
55
+ )
32
56
 
33
57
 
34
58
  class InvalidNextException(MetaflowException):
@@ -53,7 +77,187 @@ class ParallelUBF(UnboundedForeachInput):
53
77
  return item or 0 # item is None for the control task, but it is also split 0
54
78
 
55
79
 
56
- class FlowSpec(object):
80
+ class FlowStateItems(Enum):
81
+ CONFIGS = 1
82
+ FLOW_MUTATORS = 2
83
+ CACHED_PARAMETERS = 3
84
+ SET_CONFIG_PARAMETERS = 4 # Parameters that now have a ConfigValue (converted)
85
+ FLOW_DECORATORS = 5
86
+
87
+
88
+ class _FlowState(MutableMapping):
89
+ # Dict like structure to hold state information about the flow but it holds
90
+ # the key/values in two sub dictionaries: the ones that are specific to the flow
91
+ # and the ones that are inherited from parent classes.
92
+ # This is NOT a general purpose class and is meant to only work with FlowSpec.
93
+ # For example, it assumes that items are only list, dicts or None and assumes that
94
+ # self._self_data has all keys properly initialized.
95
+
96
+ def __init__(self, *args, **kwargs):
97
+ self._self_data = dict(*args, **kwargs)
98
+ self._merged_data = {}
99
+ self._inherited = {}
100
+
101
+ def __getitem__(self, key):
102
+ # ORDER IS IMPORTANT: we use inherited first and extend by whatever is in
103
+ # the flowspec
104
+ if key in self._merged_data:
105
+ return self._merged_data[key]
106
+
107
+ # We haven't accessed this yet so compute it for the first time
108
+ self_value = self._self_data.get(key)
109
+ inherited_value = self._inherited.get(key)
110
+
111
+ if self_value is not None:
112
+ self._merged_data[key] = self._merge_value(inherited_value, self_value)
113
+ return self._merged_data[key]
114
+ elif key in self._self_data:
115
+ # Case of CACHED_PARAMETERS; a valid value is None. It is never inherited
116
+ self._merged_data[key] = None
117
+ return None
118
+ raise KeyError(key)
119
+
120
+ def __setitem__(self, key, value):
121
+ self._self_data[key] = value
122
+
123
+ def __delitem__(self, key):
124
+ if key in self._merged_data:
125
+ del self._merged_data[key]
126
+ else:
127
+ raise KeyError(key)
128
+
129
+ def __iter__(self):
130
+ # All keys are in self._self_data
131
+ for key in self._self_data:
132
+ yield self[key]
133
+
134
+ def __len__(self):
135
+ return len(self._self_data)
136
+
137
+ @property
138
+ def self_data(self):
139
+ self._merged_data.clear()
140
+ return self._self_data
141
+
142
+ @property
143
+ def inherited_data(self):
144
+ return self._inherited
145
+
146
+ def _merge_value(self, inherited_value, self_value):
147
+ if self_value is None:
148
+ return None
149
+ inherited_value = inherited_value or type(self_value)()
150
+ if isinstance(self_value, dict):
151
+ return {**inherited_value, **self_value}
152
+ elif isinstance(self_value, list):
153
+ return inherited_value + self_value
154
+ raise RuntimeError(
155
+ f"Cannot merge values of type {type(inherited_value)} and {type(self_value)} -- "
156
+ "please report this as a bug"
157
+ )
158
+
159
+
160
+ class FlowSpecMeta(type):
161
+ def __init__(cls, name, bases, attrs):
162
+ super().__init__(name, bases, attrs)
163
+ if name == "FlowSpec":
164
+ return
165
+
166
+ cls._init_attrs()
167
+
168
+ def _init_attrs(cls):
169
+ from .decorators import (
170
+ DuplicateFlowDecoratorException,
171
+ ) # Prevent circular import
172
+
173
+ # We store some state in the flow class itself. This is primarily used to
174
+ # attach global state to a flow. It is *not* an actual global because of
175
+ # Runner/NBRunner. This is also created here in the meta class to avoid it being
176
+ # shared between different children classes.
177
+
178
+ # Keys are FlowStateItems enum values
179
+ cls._flow_state = _FlowState(
180
+ {
181
+ FlowStateItems.CONFIGS: {},
182
+ FlowStateItems.FLOW_MUTATORS: [],
183
+ FlowStateItems.CACHED_PARAMETERS: None,
184
+ FlowStateItems.SET_CONFIG_PARAMETERS: [],
185
+ FlowStateItems.FLOW_DECORATORS: {},
186
+ }
187
+ )
188
+
189
+ # Keep track if configs have been processed -- this is particularly applicable
190
+ # for the Runner/Deployer where calling multiple APIs on the same flow could
191
+ # cause the configs to be processed multiple times. For a given flow, once
192
+ # the configs have been processed, we do not process them again.
193
+ cls._configs_processed = False
194
+
195
+ # We inherit stuff from our parent classes as well -- we need to be careful
196
+ # in terms of the order; we will follow the MRO with the following rules:
197
+ # - decorators will cause an error if they do not
198
+ # support multiple and we see multiple instances of the same
199
+ # - config decorators will be joined
200
+ # - configs will be added later directly by the class; base class configs will
201
+ # be taken into account as they would be inherited.
202
+
203
+ # We only need to do this for the base classes since the current class will
204
+ # get updated as decorators are parsed.
205
+
206
+ # We also need to be sure to not duplicate things. Consider something like
207
+ # class A(FlowSpec):
208
+ # pass
209
+ #
210
+ # class B(A):
211
+ # pass
212
+ #
213
+ # class C(B):
214
+ # pass
215
+ #
216
+ # C inherits from both B and A but we need to duplicate things from A only
217
+ # ONCE. To do this, we only propagate the self data from each class.
218
+
219
+ for base in cls.__mro__:
220
+ if base != cls and base != FlowSpec and issubclass(base, FlowSpec):
221
+ # Take care of decorators
222
+ base_flow_decorators = base._flow_state.self_data[
223
+ FlowStateItems.FLOW_DECORATORS
224
+ ]
225
+
226
+ inherited_cls_flow_decorators = (
227
+ cls._flow_state.inherited_data.setdefault(
228
+ FlowStateItems.FLOW_DECORATORS, {}
229
+ )
230
+ )
231
+ for deco_name, deco in base_flow_decorators.items():
232
+ if not deco:
233
+ continue
234
+ deco_allow_multiple = deco[0].allow_multiple
235
+ if (
236
+ deco_name in inherited_cls_flow_decorators
237
+ and not deco_allow_multiple
238
+ ):
239
+ raise DuplicateFlowDecoratorException(deco_name)
240
+ inherited_cls_flow_decorators.setdefault(deco_name, []).extend(deco)
241
+
242
+ # Take care of flow mutators -- configs are just objects in the class
243
+ # so they are naturally inherited. We do not need to do anything special
244
+ # for them.
245
+ base_mutators = base._flow_state.self_data[FlowStateItems.FLOW_MUTATORS]
246
+ if base_mutators:
247
+ cls._flow_state.inherited_data.setdefault(
248
+ FlowStateItems.FLOW_MUTATORS, []
249
+ ).extend(base_mutators)
250
+
251
+ cls._init_graph()
252
+
253
+ def _init_graph(cls):
254
+ # Graph and steps are specific to the class -- store here so we can access
255
+ # in class method _process_config_decorators
256
+ cls._graph = FlowGraph(cls)
257
+ cls._steps = [getattr(cls, node.name) for node in cls._graph]
258
+
259
+
260
+ class FlowSpec(metaclass=FlowSpecMeta):
57
261
  """
58
262
  Main class from which all Flows should inherit.
59
263
 
@@ -72,7 +276,7 @@ class FlowSpec(object):
72
276
  "_datastore",
73
277
  "_cached_input",
74
278
  "_graph",
75
- "_flow_decorators",
279
+ "_flow_state",
76
280
  "_steps",
77
281
  "index",
78
282
  "input",
@@ -83,8 +287,6 @@ class FlowSpec(object):
83
287
  # names starting with `_` as those are already excluded from `_get_parameters`.
84
288
  _NON_PARAMETERS = {"cmd", "foreach_stack", "index", "input", "script_name", "name"}
85
289
 
86
- _flow_decorators = {}
87
-
88
290
  def __init__(self, use_cli=True):
89
291
  """
90
292
  Construct a FlowSpec
@@ -101,18 +303,11 @@ class FlowSpec(object):
101
303
  self._transition = None
102
304
  self._cached_input = {}
103
305
 
104
- self._graph = FlowGraph(self.__class__)
105
- self._steps = [getattr(self, node.name) for node in self._graph]
106
-
107
- # This must be set before calling cli.main() below (or specifically, add_custom_parameters)
108
- parameters.parameters = [p for _, p in self._get_parameters()]
109
-
110
306
  if use_cli:
111
- # we import cli here to make sure custom parameters in
112
- # args.py get fully evaluated before cli.py is imported.
113
- from . import cli
307
+ with parameters.flow_context(self.__class__) as _:
308
+ from . import cli
114
309
 
115
- cli.main(self)
310
+ cli.main(self)
116
311
 
117
312
  @property
118
313
  def script_name(self) -> str:
@@ -131,16 +326,17 @@ class FlowSpec(object):
131
326
  fname = fname[:-1]
132
327
  return os.path.basename(fname)
133
328
 
134
- def _set_constants(self, graph, kwargs):
135
- from metaflow.decorators import (
136
- flow_decorators,
137
- ) # To prevent circular dependency
329
+ @property
330
+ def _flow_decorators(self):
331
+ # Backward compatible method to access flow decorators
332
+ return self._flow_state[FlowStateItems.FLOW_DECORATORS]
138
333
 
139
- # Persist values for parameters and other constants (class level variables)
140
- # only once. This method is called before persist_constants is called to
141
- # persist all values set using setattr
334
+ @classmethod
335
+ def _check_parameters(cls, config_parameters=False):
142
336
  seen = set()
143
- for var, param in self._get_parameters():
337
+ for _, param in cls._get_parameters():
338
+ if param.IS_CONFIG_PARAMETER != config_parameters:
339
+ continue
144
340
  norm = param.name.lower()
145
341
  if norm in seen:
146
342
  raise MetaflowException(
@@ -149,17 +345,152 @@ class FlowSpec(object):
149
345
  "case-insensitive." % param.name
150
346
  )
151
347
  seen.add(norm)
152
- seen.clear()
348
+
349
+ @classmethod
350
+ def _process_config_decorators(cls, config_options, process_configs=True):
351
+ if cls._configs_processed:
352
+ debug.userconf_exec("Mutating step/flow decorators already processed")
353
+ return None
354
+ cls._configs_processed = True
355
+
356
+ # Fast path for no user configurations
357
+ if not process_configs or (
358
+ not cls._flow_state[FlowStateItems.FLOW_MUTATORS]
359
+ and all(len(step.config_decorators) == 0 for step in cls._steps)
360
+ ):
361
+ # Process parameters to allow them to also use config values easily
362
+ for var, param in cls._get_parameters():
363
+ if isinstance(param, ConfigValue) or param.IS_CONFIG_PARAMETER:
364
+ continue
365
+ param.init(not process_configs)
366
+ return None
367
+
368
+ debug.userconf_exec("Processing mutating step/flow decorators")
369
+ # We need to convert all the user configurations from DelayedEvaluationParameters
370
+ # to actual values so they can be used as is in the mutators.
371
+
372
+ # We, however, need to make sure _get_parameters still works properly so
373
+ # we store what was a config and has been set to a specific value.
374
+ # This is safe to do for now because all other uses of _get_parameters typically
375
+ # do not rely on the variable itself but just the parameter.
376
+ to_save_configs = []
377
+ cls._check_parameters(config_parameters=True)
378
+ for var, param in cls._get_parameters():
379
+ if not param.IS_CONFIG_PARAMETER:
380
+ continue
381
+ # Note that a config with no default and not required will be None
382
+ val = config_options.get(param.name.replace("-", "_").lower())
383
+ if isinstance(val, DelayedEvaluationParameter):
384
+ val = val()
385
+ # We store the value as well so that in _set_constants, we don't try
386
+ # to recompute (no guarantee that it is stable)
387
+ param._store_value(val)
388
+ to_save_configs.append((var, param))
389
+ debug.userconf_exec("Setting config %s to %s" % (var, str(val)))
390
+ setattr(cls, var, val)
391
+
392
+ cls._flow_state[FlowStateItems.SET_CONFIG_PARAMETERS] = to_save_configs
393
+ # Run all the decorators. We first run the flow-level decorators
394
+ # and then the step level ones to maintain a consistent order with how
395
+ # other decorators are run.
396
+
397
+ for deco in cls._flow_state[FlowStateItems.FLOW_MUTATORS]:
398
+ if isinstance(deco, FlowMutator):
399
+ inserted_by_value = [deco.decorator_name] + (deco.inserted_by or [])
400
+ mutable_flow = MutableFlow(
401
+ cls,
402
+ pre_mutate=True,
403
+ statically_defined=deco.statically_defined,
404
+ inserted_by=inserted_by_value,
405
+ )
406
+ # Sanity check to make sure we are applying the decorator to the right
407
+ # class
408
+ if not deco._flow_cls == cls and not issubclass(cls, deco._flow_cls):
409
+ raise MetaflowInternalError(
410
+ "FlowMutator registered on the wrong flow -- "
411
+ "expected %s but got %s"
412
+ % (deco._flow_cls.__name__, cls.__name__)
413
+ )
414
+ debug.userconf_exec(
415
+ "Evaluating flow level decorator %s (pre-mutate)"
416
+ % deco.__class__.__name__
417
+ )
418
+ deco.pre_mutate(mutable_flow)
419
+ # We reset cached_parameters on the very off chance that the user added
420
+ # more configurations based on the configuration
421
+ if cls._flow_state[FlowStateItems.CACHED_PARAMETERS] is not None:
422
+ cls._flow_state[FlowStateItems.CACHED_PARAMETERS] = None
423
+ else:
424
+ raise MetaflowInternalError(
425
+ "A non FlowMutator found in flow custom decorators"
426
+ )
427
+
428
+ for step in cls._steps:
429
+ for deco in step.config_decorators:
430
+ if isinstance(deco, StepMutator):
431
+ inserted_by_value = [deco.decorator_name] + (deco.inserted_by or [])
432
+ debug.userconf_exec(
433
+ "Evaluating step level decorator %s for %s (pre-mutate)"
434
+ % (deco.__class__.__name__, step.name)
435
+ )
436
+ deco.pre_mutate(
437
+ MutableStep(
438
+ cls,
439
+ step,
440
+ pre_mutate=True,
441
+ statically_defined=deco.statically_defined,
442
+ inserted_by=inserted_by_value,
443
+ )
444
+ )
445
+ else:
446
+ raise MetaflowInternalError(
447
+ "A non StepMutator found in step custom decorators"
448
+ )
449
+
450
+ # Process parameters to allow them to also use config values easily
451
+ for var, param in cls._get_parameters():
452
+ if param.IS_CONFIG_PARAMETER:
453
+ continue
454
+ param.init()
455
+
456
+ # Set the current flow class we are in (the one we just created)
457
+ parameters.replace_flow_context(cls)
458
+
459
+ # Re-calculate class level attributes after modifying the class
460
+ cls._init_graph()
461
+ return cls
462
+
463
+ def _set_constants(self, graph, kwargs, config_options):
464
+ from metaflow.decorators import (
465
+ flow_decorators,
466
+ ) # To prevent circular dependency
467
+
468
+ # Persist values for parameters and other constants (class level variables)
469
+ # only once. This method is called before persist_constants is called to
470
+ # persist all values set using setattr
471
+ self._check_parameters(config_parameters=False)
472
+
473
+ seen = set()
153
474
  self._success = True
154
475
 
155
476
  parameters_info = []
156
477
  for var, param in self._get_parameters():
157
478
  seen.add(var)
158
- val = kwargs[param.name.replace("-", "_").lower()]
479
+ if param.IS_CONFIG_PARAMETER:
480
+ # Use computed value if already evaluated, else get from config_options
481
+ val = param._computed_value or config_options.get(
482
+ param.name.replace("-", "_").lower()
483
+ )
484
+ else:
485
+ val = kwargs[param.name.replace("-", "_").lower()]
159
486
  # Support for delayed evaluation of parameters.
160
487
  if isinstance(val, DelayedEvaluationParameter):
161
488
  val = val()
162
489
  val = val.split(param.separator) if val and param.separator else val
490
+ if isinstance(val, ConfigValue):
491
+ # We store config values as dict so they are accessible with older
492
+ # metaflow clients. It also makes it easier to access.
493
+ val = val.to_dict()
163
494
  setattr(self, var, val)
164
495
  parameters_info.append({"name": var, "type": param.__class__.__name__})
165
496
 
@@ -189,25 +520,53 @@ class FlowSpec(object):
189
520
  "decorators": [
190
521
  {
191
522
  "name": deco.name,
192
- "attributes": deco.attributes,
523
+ "attributes": to_pod(deco.attributes),
193
524
  "statically_defined": deco.statically_defined,
525
+ "inserted_by": deco.inserted_by,
194
526
  }
195
- for deco in flow_decorators()
527
+ for deco in flow_decorators(self)
196
528
  if not deco.name.startswith("_")
529
+ ]
530
+ + [
531
+ {
532
+ "name": deco.__class__.__name__,
533
+ "attributes": {},
534
+ "statically_defined": deco.statically_defined,
535
+ "inserted_by": deco.inserted_by,
536
+ }
537
+ for deco in self._flow_state[FlowStateItems.FLOW_MUTATORS]
197
538
  ],
539
+ "extensions": extension_info(),
198
540
  }
199
541
  self._graph_info = graph_info
200
542
 
201
- def _get_parameters(self):
202
- for var in dir(self):
203
- if var[0] == "_" or var in self._NON_PARAMETERS:
543
+ @classmethod
544
+ def _get_parameters(cls):
545
+ cached = cls._flow_state[FlowStateItems.CACHED_PARAMETERS]
546
+ returned = set()
547
+ if cached is not None:
548
+ for set_config in cls._flow_state[FlowStateItems.SET_CONFIG_PARAMETERS]:
549
+ returned.add(set_config[0])
550
+ yield set_config[0], set_config[1]
551
+ for var in cached:
552
+ if var not in returned:
553
+ yield var, getattr(cls, var)
554
+ return
555
+ build_list = []
556
+ for set_config in cls._flow_state[FlowStateItems.SET_CONFIG_PARAMETERS]:
557
+ returned.add(set_config[0])
558
+ yield set_config[0], set_config[1]
559
+ for var in dir(cls):
560
+ if var[0] == "_" or var in cls._NON_PARAMETERS:
204
561
  continue
205
562
  try:
206
- val = getattr(self, var)
563
+ val = getattr(cls, var)
207
564
  except:
208
565
  continue
209
- if isinstance(val, Parameter):
566
+ if isinstance(val, Parameter) and var not in returned:
567
+ build_list.append(var)
210
568
  yield var, val
569
+ cls._flow_state[FlowStateItems.CACHED_PARAMETERS] = build_list
211
570
 
212
571
  def _set_datastore(self, datastore):
213
572
  self._datastore = datastore
@@ -559,6 +918,15 @@ class FlowSpec(object):
559
918
  evaluates to an iterator. A task will be launched for each value in the iterator and
560
919
  each task will execute the code specified by the step `foreach_step`.
561
920
 
921
+ - Switch statement:
922
+ ```
923
+ self.next({"case1": self.step_a, "case2": self.step_b}, condition='condition_variable')
924
+ ```
925
+ In this situation, `step_a` and `step_b` are methods in the current class decorated
926
+ with the `@step` decorator and `condition_variable` is a variable name in the current
927
+ class. The value of the condition variable determines which step to execute. If the
928
+ value doesn't match any of the dictionary keys, a RuntimeError is raised.
929
+
562
930
  Parameters
563
931
  ----------
564
932
  dsts : Callable[..., None]
@@ -574,6 +942,7 @@ class FlowSpec(object):
574
942
 
575
943
  foreach = kwargs.pop("foreach", None)
576
944
  num_parallel = kwargs.pop("num_parallel", None)
945
+ condition = kwargs.pop("condition", None)
577
946
  if kwargs:
578
947
  kw = next(iter(kwargs))
579
948
  msg = (
@@ -590,6 +959,86 @@ class FlowSpec(object):
590
959
  )
591
960
  raise InvalidNextException(msg)
592
961
 
962
+ # check: switch case using condition
963
+ if condition is not None:
964
+ if len(dsts) != 1 or not isinstance(dsts[0], dict) or not dsts[0]:
965
+ msg = (
966
+ "Step *{step}* has an invalid self.next() transition. "
967
+ "When using 'condition', the transition must be to a single, "
968
+ "non-empty dictionary mapping condition values to step methods.".format(
969
+ step=step
970
+ )
971
+ )
972
+ raise InvalidNextException(msg)
973
+
974
+ if not isinstance(condition, basestring):
975
+ msg = (
976
+ "Step *{step}* has an invalid self.next() transition. "
977
+ "The argument to 'condition' must be a string.".format(step=step)
978
+ )
979
+ raise InvalidNextException(msg)
980
+
981
+ if foreach is not None or num_parallel is not None:
982
+ msg = (
983
+ "Step *{step}* has an invalid self.next() transition. "
984
+ "Switch statements cannot be combined with foreach or num_parallel.".format(
985
+ step=step
986
+ )
987
+ )
988
+ raise InvalidNextException(msg)
989
+
990
+ switch_cases = dsts[0]
991
+
992
+ # Validate that condition variable exists
993
+ try:
994
+ condition_value = getattr(self, condition)
995
+ except AttributeError:
996
+ msg = (
997
+ "Condition variable *self.{var}* in step *{step}* "
998
+ "does not exist. Make sure you set self.{var} in this step.".format(
999
+ step=step, var=condition
1000
+ )
1001
+ )
1002
+ raise InvalidNextException(msg)
1003
+
1004
+ if condition_value not in switch_cases:
1005
+ available_cases = list(switch_cases.keys())
1006
+ raise RuntimeError(
1007
+ f"Switch condition variable '{condition}' has value '{condition_value}' "
1008
+ f"which is not in the available cases: {available_cases}"
1009
+ )
1010
+
1011
+ # Get the chosen step and set transition directly
1012
+ chosen_step_func = switch_cases[condition_value]
1013
+
1014
+ # Validate that the chosen step exists
1015
+ try:
1016
+ name = chosen_step_func.__func__.__name__
1017
+ except:
1018
+ msg = (
1019
+ "Step *{step}* specifies a switch transition that is not a function. "
1020
+ "Make sure the value in the dictionary is a method "
1021
+ "of the Flow class.".format(step=step)
1022
+ )
1023
+ raise InvalidNextException(msg)
1024
+ if not hasattr(self, name):
1025
+ msg = (
1026
+ "Step *{step}* specifies a switch transition to an "
1027
+ "unknown step, *{name}*.".format(step=step, name=name)
1028
+ )
1029
+ raise InvalidNextException(msg)
1030
+
1031
+ self._transition = ([name], None)
1032
+ return
1033
+
1034
+ # Check for an invalid transition: a dictionary used without a 'condition' parameter.
1035
+ if len(dsts) == 1 and isinstance(dsts[0], dict):
1036
+ msg = (
1037
+ "Step *{step}* has an invalid self.next() transition. "
1038
+ "Dictionary argument requires 'condition' parameter.".format(step=step)
1039
+ )
1040
+ raise InvalidNextException(msg)
1041
+
593
1042
  # check: all destinations are methods of this object
594
1043
  funcs = []
595
1044
  for i, dst in enumerate(dsts):
@@ -680,7 +1129,7 @@ class FlowSpec(object):
680
1129
  self._foreach_var = foreach
681
1130
 
682
1131
  # check: non-keyword transitions are valid
683
- if foreach is None:
1132
+ if foreach is None and condition is None:
684
1133
  if len(dsts) < 1:
685
1134
  msg = (
686
1135
  "Step *{step}* has an invalid self.next() transition. "