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