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,563 @@
1
+ import importlib
2
+ import json
3
+ import os
4
+
5
+ from typing import Any, Callable, Dict, List, Optional, Tuple, Union
6
+
7
+ from metaflow._vendor import click
8
+ from metaflow.debug import debug
9
+
10
+ from .config_parameters import ConfigValue
11
+ from ..exception import MetaflowException, MetaflowInternalError
12
+ from ..packaging_sys import MetaflowCodeContent
13
+ from ..parameters import DeployTimeField, ParameterContext, current_flow
14
+ from ..util import get_username
15
+
16
+
17
+ _CONVERT_PREFIX = "@!c!@:"
18
+ _DEFAULT_PREFIX = "@!d!@:"
19
+ _NO_FILE = "@!n!@:"
20
+
21
+ _CONVERTED_DEFAULT = _CONVERT_PREFIX + _DEFAULT_PREFIX
22
+ _CONVERTED_NO_FILE = _CONVERT_PREFIX + _NO_FILE
23
+ _CONVERTED_DEFAULT_NO_FILE = _CONVERTED_DEFAULT + _NO_FILE
24
+
25
+
26
+ def _load_config_values(info_file: Optional[str] = None) -> Optional[Dict[Any, Any]]:
27
+ if info_file is None:
28
+ config_content = MetaflowCodeContent.get_config()
29
+ else:
30
+ try:
31
+ with open(info_file, encoding="utf-8") as f:
32
+ config_content = json.load(f)
33
+ except IOError:
34
+ return None
35
+ if config_content:
36
+ return config_content.get("user_configs", {})
37
+ return None
38
+
39
+
40
+ class ConvertPath(click.Path):
41
+ name = "ConvertPath"
42
+
43
+ def convert(self, value, param, ctx):
44
+ if isinstance(value, str) and value.startswith(_CONVERT_PREFIX):
45
+ return value
46
+ is_default = False
47
+ if value and value.startswith(_DEFAULT_PREFIX):
48
+ is_default = True
49
+ value = value[len(_DEFAULT_PREFIX) :]
50
+ value = super().convert(value, param, ctx)
51
+ return self.convert_value(value, is_default)
52
+
53
+ @staticmethod
54
+ def mark_as_default(value):
55
+ if value is None:
56
+ return None
57
+ return _DEFAULT_PREFIX + str(value)
58
+
59
+ @staticmethod
60
+ def convert_value(value, is_default):
61
+ default_str = _DEFAULT_PREFIX if is_default else ""
62
+ if value is None:
63
+ return None
64
+ try:
65
+ with open(value, "r", encoding="utf-8") as f:
66
+ content = f.read()
67
+ except OSError:
68
+ return _CONVERT_PREFIX + default_str + _NO_FILE + value
69
+ return _CONVERT_PREFIX + default_str + content
70
+
71
+
72
+ class ConvertDictOrStr(click.ParamType):
73
+ name = "ConvertDictOrStr"
74
+
75
+ def convert(self, value, param, ctx):
76
+ is_default = False
77
+ if isinstance(value, str):
78
+ if value.startswith(_CONVERT_PREFIX):
79
+ return value
80
+ if value.startswith(_DEFAULT_PREFIX):
81
+ is_default = True
82
+ value = value[len(_DEFAULT_PREFIX) :]
83
+
84
+ return self.convert_value(value, is_default)
85
+
86
+ @staticmethod
87
+ def convert_value(value, is_default):
88
+ default_str = _DEFAULT_PREFIX if is_default else ""
89
+ if value is None:
90
+ return None
91
+
92
+ if isinstance(value, dict):
93
+ return _CONVERT_PREFIX + default_str + json.dumps(value)
94
+
95
+ if value.startswith(_CONVERT_PREFIX):
96
+ return value
97
+
98
+ return _CONVERT_PREFIX + default_str + value
99
+
100
+ @staticmethod
101
+ def mark_as_default(value):
102
+ if value is None:
103
+ return None
104
+ if isinstance(value, dict):
105
+ return _DEFAULT_PREFIX + json.dumps(value)
106
+ return _DEFAULT_PREFIX + str(value)
107
+
108
+
109
+ class MultipleTuple(click.Tuple):
110
+ # Small wrapper around a click.Tuple to allow the environment variable for
111
+ # configurations to be a JSON string. Otherwise the default behavior is splitting
112
+ # by whitespace which is totally not what we want
113
+ # You can now pass multiple configuration options through an environment variable
114
+ # using something like:
115
+ # METAFLOW_FLOW_CONFIG_VALUE='{"config1": {"key0": "value0"}, "config2": {"key1": "value1"}}'
116
+ # or METAFLOW_FLOW_CONFIG='{"config1": "file1", "config2": "file2"}'
117
+
118
+ def split_envvar_value(self, rv):
119
+ loaded = json.loads(rv)
120
+ return list(
121
+ item if isinstance(item, str) else json.dumps(item)
122
+ for pair in loaded.items()
123
+ for item in pair
124
+ )
125
+
126
+
127
+ class ConfigInput:
128
+ # ConfigInput is an internal class responsible for processing all the --config and
129
+ # --config-value options.
130
+ # It gathers information from the --local-config-file (to figure out
131
+ # where options are stored) and is also responsible for processing any `--config` or
132
+ # `--config-value` options. Note that the process_configs function will be called
133
+ # *twice* (once for the configs and another for the config-values). This makes
134
+ # this function a little bit more tricky. We need to wait for both calls before
135
+ # being able to process anything.
136
+
137
+ # It will then store this information in the flow spec for use later in processing.
138
+ # It is stored in the flow spec to avoid being global to support the Runner.
139
+
140
+ loaded_configs = None # type: Optional[Dict[str, Dict[Any, Any]]]
141
+ config_file = None # type: Optional[str]
142
+
143
+ def __init__(
144
+ self,
145
+ req_configs: List[str],
146
+ defaults: Dict[str, Tuple[Union[str, Dict[Any, Any]], bool]],
147
+ parsers: Dict[str, Union[str, Callable[[str], Dict[Any, Any]]]],
148
+ ):
149
+ self._req_configs = set(req_configs)
150
+ self._defaults = defaults
151
+ self._parsers = parsers
152
+ self._path_values = None
153
+ self._value_values = None
154
+
155
+ @staticmethod
156
+ def make_key_name(name: str) -> str:
157
+ # Special mark to indicate that the configuration value is not content or a file
158
+ # name but a value that should be read in the config file (effectively where
159
+ # the value has already been materialized).
160
+ return "kv." + name.lower()
161
+
162
+ @classmethod
163
+ def set_config_file(cls, config_file: str):
164
+ cls.config_file = config_file
165
+
166
+ @classmethod
167
+ def get_config(cls, config_name: str) -> Optional[Dict[Any, Any]]:
168
+ if cls.loaded_configs is None:
169
+ all_configs = _load_config_values(cls.config_file)
170
+ if all_configs is None:
171
+ raise MetaflowException(
172
+ "Could not load expected configuration values "
173
+ "from the CONFIG_PARAMETERS file. This is a Metaflow bug. "
174
+ "Please contact support."
175
+ )
176
+ cls.loaded_configs = all_configs
177
+ return cls.loaded_configs[config_name]
178
+
179
+ def process_configs(
180
+ self,
181
+ flow_name: str,
182
+ param_name: str,
183
+ param_value: Dict[str, Optional[str]],
184
+ quiet: bool,
185
+ datastore: str,
186
+ click_obj: Optional[Any] = None,
187
+ ):
188
+ from ..cli import echo_always, echo_dev_null # Prevent circular import
189
+ from ..flowspec import FlowStateItems # Prevent circular import
190
+
191
+ flow_cls = getattr(current_flow, "flow_cls", None)
192
+ if flow_cls is None:
193
+ # This is an error
194
+ raise MetaflowInternalError(
195
+ "Config values should be processed for a FlowSpec"
196
+ )
197
+
198
+ # This function is called by click when processing all the --config and
199
+ # --config-value options.
200
+ # The value passed in is a list of tuples (name, value).
201
+ # Click will provide:
202
+ # - all the defaults if nothing is provided on the command line
203
+ # - provide *just* the passed in value if anything is provided on the command
204
+ # line.
205
+ #
206
+ # We need to get all config and config-value options and click will call this
207
+ # function twice. We will first get all the values on the command line and
208
+ # *then* merge with the defaults to form a full set of values.
209
+ # We therefore get a full set of values where:
210
+ # - the name will correspond to the configuration name
211
+ # - the value will be:
212
+ # - the default (including None if there is no default). If the default is
213
+ # not None, it will start with _CONVERTED_DEFAULT since Click will make
214
+ # the value go through ConvertPath or ConvertDictOrStr
215
+ # - the actual value passed through prefixed with _CONVERT_PREFIX
216
+
217
+ debug.userconf_exec(
218
+ "Processing configs for %s -- incoming values: %s"
219
+ % (param_name, str(param_value))
220
+ )
221
+
222
+ do_return = self._value_values is None and self._path_values is None
223
+ # We only keep around non default values. We could simplify by checking just one
224
+ # value and if it is default it means all are but this doesn't seem much more effort
225
+ # and is clearer
226
+ if param_name == "config_value":
227
+ self._value_values = {
228
+ k.lower(): v
229
+ for k, v in param_value.items()
230
+ if v is not None and not v.startswith(_CONVERTED_DEFAULT)
231
+ }
232
+ else:
233
+ self._path_values = {
234
+ k.lower(): v
235
+ for k, v in param_value.items()
236
+ if v is not None and not v.startswith(_CONVERTED_DEFAULT)
237
+ }
238
+ if do_return:
239
+ # One of values["value"] or values["path"] is None -- we are in the first
240
+ # go around
241
+ debug.userconf_exec("Incomplete config options; waiting for more")
242
+ return None
243
+
244
+ # The second go around, we process all the values and merge them.
245
+
246
+ # If we are processing options that start with kv., we know we are in a subprocess
247
+ # and ignore other stuff. In particular, environment variables used to pass
248
+ # down configurations (like METAFLOW_FLOW_CONFIG) could still be present and
249
+ # would cause an issue -- we can ignore those as the kv. values should trump
250
+ # everything else.
251
+ # NOTE: These are all *non default* keys
252
+ all_keys = set(self._value_values).union(self._path_values)
253
+
254
+ if all_keys and click_obj:
255
+ click_obj.has_cl_config_options = True
256
+ # Make sure we have at least some non default keys (we need some if we have
257
+ # all kv)
258
+ has_all_kv = all_keys and all(
259
+ self._value_values.get(k, "").startswith(_CONVERT_PREFIX + "kv.")
260
+ for k in all_keys
261
+ )
262
+
263
+ to_return = {}
264
+
265
+ if not has_all_kv:
266
+ # Check that the user didn't provide *both* a path and a value. Again, these
267
+ # are only user-provided (not defaults)
268
+ common_keys = set(self._value_values or []).intersection(
269
+ [k for k, v in self._path_values.items()] or []
270
+ )
271
+ if common_keys:
272
+ exc = click.UsageError(
273
+ "Cannot provide both a value and a file for the same configuration. "
274
+ "Found such values for '%s'" % "', '".join(common_keys)
275
+ )
276
+ if click_obj:
277
+ click_obj.delayed_config_exception = exc
278
+ return None
279
+ raise exc
280
+
281
+ all_values = dict(self._path_values)
282
+ all_values.update(self._value_values)
283
+
284
+ debug.userconf_exec("All config values: %s" % str(all_values))
285
+
286
+ merged_configs = {}
287
+ # Now look at everything (including defaults)
288
+ for name, (val, is_path) in self._defaults.items():
289
+ n = name.lower()
290
+ if n in all_values:
291
+ # We have the value provided by the user -- use that.
292
+ merged_configs[n] = all_values[n]
293
+ else:
294
+ # No value provided by the user -- use the default
295
+ if isinstance(val, DeployTimeField):
296
+ # This supports a default value that is a deploy-time field (similar
297
+ # to Parameter).)
298
+ # We will form our own context and pass it down -- note that you cannot
299
+ # use configs in the default value of configs as this introduces a bit
300
+ # of circularity. Note also that quiet and datastore are *eager*
301
+ # options so are available here.
302
+ param_ctx = ParameterContext(
303
+ flow_name=flow_name,
304
+ user_name=get_username(),
305
+ parameter_name=n,
306
+ logger=(echo_dev_null if quiet else echo_always),
307
+ ds_type=datastore,
308
+ configs=None,
309
+ )
310
+ val = val.fun(param_ctx)
311
+ if is_path:
312
+ # This is a file path
313
+ merged_configs[n] = ConvertPath.convert_value(val, True)
314
+ else:
315
+ # This is a value
316
+ merged_configs[n] = ConvertDictOrStr.convert_value(val, True)
317
+ else:
318
+ debug.userconf_exec("Fast path due to pre-processed values")
319
+ merged_configs = self._value_values
320
+
321
+ if click_obj:
322
+ click_obj.has_config_options = True
323
+
324
+ debug.userconf_exec("Configs merged with defaults: %s" % str(merged_configs))
325
+
326
+ missing_configs = set()
327
+ no_file = []
328
+ no_default_file = []
329
+ msgs = []
330
+ for name, val in merged_configs.items():
331
+ if val is None:
332
+ missing_configs.add(name)
333
+ to_return[name] = None
334
+ flow_cls._flow_state.self_data[FlowStateItems.CONFIGS][name] = None
335
+ continue
336
+ if val.startswith(_CONVERTED_NO_FILE):
337
+ no_file.append(name)
338
+ continue
339
+ if val.startswith(_CONVERTED_DEFAULT_NO_FILE):
340
+ no_default_file.append(name)
341
+ continue
342
+
343
+ val = val[len(_CONVERT_PREFIX) :] # Remove the _CONVERT_PREFIX
344
+ if val.startswith(_DEFAULT_PREFIX): # Remove the _DEFAULT_PREFIX if needed
345
+ val = val[len(_DEFAULT_PREFIX) :]
346
+ if val.startswith("kv."):
347
+ # This means to load it from a file
348
+ try:
349
+ read_value = self.get_config(val[3:])
350
+ except KeyError as e:
351
+ exc = click.UsageError(
352
+ "Could not find configuration '%s' in INFO file" % val
353
+ )
354
+ if click_obj:
355
+ click_obj.delayed_config_exception = exc
356
+ return None
357
+ raise exc from e
358
+ flow_cls._flow_state.self_data[FlowStateItems.CONFIGS][
359
+ name
360
+ ] = read_value
361
+ to_return[name] = (
362
+ ConfigValue(read_value) if read_value is not None else None
363
+ )
364
+ else:
365
+ if self._parsers[name]:
366
+ read_value = self._call_parser(self._parsers[name], val)
367
+ else:
368
+ try:
369
+ read_value = json.loads(val)
370
+ except json.JSONDecodeError as e:
371
+ msgs.append(
372
+ "configuration value for '%s' is not valid JSON: %s"
373
+ % (name, e)
374
+ )
375
+ continue
376
+ # TODO: Support YAML
377
+ flow_cls._flow_state.self_data[FlowStateItems.CONFIGS][
378
+ name
379
+ ] = read_value
380
+ to_return[name] = (
381
+ ConfigValue(read_value) if read_value is not None else None
382
+ )
383
+
384
+ reqs = missing_configs.intersection(self._req_configs)
385
+ for missing in reqs:
386
+ msgs.append("missing configuration for '%s'" % missing)
387
+ for missing in no_file:
388
+ msgs.append(
389
+ "configuration file '%s' could not be read for '%s'"
390
+ % (merged_configs[missing][len(_CONVERTED_NO_FILE) :], missing)
391
+ )
392
+ for missing in no_default_file:
393
+ msgs.append(
394
+ "default configuration file '%s' could not be read for '%s'"
395
+ % (merged_configs[missing][len(_CONVERTED_DEFAULT_NO_FILE) :], missing)
396
+ )
397
+ if msgs:
398
+ exc = click.UsageError(
399
+ "Bad values passed for configuration options: %s" % ", ".join(msgs)
400
+ )
401
+ if click_obj:
402
+ click_obj.delayed_config_exception = exc
403
+ return None
404
+ raise exc
405
+
406
+ debug.userconf_exec("Finalized configs: %s" % str(to_return))
407
+ return to_return
408
+
409
+ def process_configs_click(self, ctx, param, value):
410
+ return self.process_configs(
411
+ ctx.obj.flow.name,
412
+ param.name,
413
+ dict(value),
414
+ ctx.params["quiet"],
415
+ ctx.params["datastore"],
416
+ click_obj=ctx.obj,
417
+ )
418
+
419
+ def __str__(self):
420
+ return repr(self)
421
+
422
+ def __repr__(self):
423
+ return "ConfigInput"
424
+
425
+ @staticmethod
426
+ def _call_parser(parser, val):
427
+ if isinstance(parser, str):
428
+ if len(parser) and parser[0] == ".":
429
+ parser = "metaflow" + parser
430
+ path, func = parser.rsplit(".", 1)
431
+ try:
432
+ func_module = importlib.import_module(path)
433
+ except ImportError as e:
434
+ raise ValueError("Cannot locate parser %s" % parser) from e
435
+ parser = getattr(func_module, func, None)
436
+ if parser is None or not callable(parser):
437
+ raise ValueError(
438
+ "Parser %s is either not part of %s or not a callable"
439
+ % (func, path)
440
+ )
441
+ return parser(val)
442
+
443
+
444
+ class LocalFileInput(click.Path):
445
+ # Small wrapper around click.Path to set the value from which to read configuration
446
+ # values. This is set immediately upon processing the --local-config-file
447
+ # option and will therefore then be available when processing any of the other
448
+ # --config options (which will call ConfigInput.process_configs)
449
+ name = "LocalFileInput"
450
+
451
+ def convert(self, value, param, ctx):
452
+ v = super().convert(value, param, ctx)
453
+ ConfigInput.set_config_file(value)
454
+ return v
455
+
456
+ def __str__(self):
457
+ return repr(self)
458
+
459
+ def __repr__(self):
460
+ return "LocalFileInput"
461
+
462
+
463
+ def config_options_with_config_input(cmd):
464
+ help_strs = []
465
+ required_names = []
466
+ defaults = {}
467
+ config_seen = set()
468
+ parsers = {}
469
+ flow_cls = getattr(current_flow, "flow_cls", None)
470
+ if flow_cls is None:
471
+ return cmd, None
472
+
473
+ parameters = [p for _, p in flow_cls._get_parameters() if p.IS_CONFIG_PARAMETER]
474
+ # List all the configuration options
475
+ for arg in parameters[::-1]:
476
+ kwargs = arg.option_kwargs(False)
477
+ if arg.name.lower() in config_seen:
478
+ msg = (
479
+ "Multiple configurations use the same name '%s'. Note that names are "
480
+ "case-insensitive. Please change the "
481
+ "names of some of your configurations" % arg.name
482
+ )
483
+ raise MetaflowException(msg)
484
+ config_seen.add(arg.name.lower())
485
+ if kwargs["required"]:
486
+ required_names.append(arg.name)
487
+
488
+ defaults[arg.name.lower()] = (
489
+ arg.kwargs.get("default", None),
490
+ arg._default_is_file,
491
+ )
492
+ help_strs.append(" - %s: %s" % (arg.name.lower(), kwargs.get("help", "")))
493
+ parsers[arg.name.lower()] = arg.parser
494
+
495
+ if not config_seen:
496
+ # No configurations -- don't add anything; we set it to False so that it
497
+ # can be checked whether or not we called this.
498
+ return cmd, False
499
+
500
+ help_str = (
501
+ "Configuration options for the flow. "
502
+ "Multiple configurations can be specified. Cannot be used with resume."
503
+ )
504
+ help_str = "\n\n".join([help_str] + help_strs)
505
+ config_input = ConfigInput(required_names, defaults, parsers)
506
+ cb_func = config_input.process_configs_click
507
+
508
+ cmd.params.insert(
509
+ 0,
510
+ click.Option(
511
+ ["--config-value", "config_value"],
512
+ nargs=2,
513
+ multiple=True,
514
+ type=MultipleTuple([click.Choice(config_seen), ConvertDictOrStr()]),
515
+ callback=cb_func,
516
+ help=help_str,
517
+ envvar="METAFLOW_FLOW_CONFIG_VALUE",
518
+ show_default=False,
519
+ default=[
520
+ (
521
+ k,
522
+ (
523
+ ConvertDictOrStr.mark_as_default(v[0])
524
+ if not callable(v[0]) and not v[1]
525
+ else None
526
+ ),
527
+ )
528
+ for k, v in defaults.items()
529
+ ],
530
+ required=False,
531
+ ),
532
+ )
533
+ cmd.params.insert(
534
+ 0,
535
+ click.Option(
536
+ ["--config", "config"],
537
+ nargs=2,
538
+ multiple=True,
539
+ type=MultipleTuple([click.Choice(config_seen), ConvertPath()]),
540
+ callback=cb_func,
541
+ help=help_str,
542
+ envvar="METAFLOW_FLOW_CONFIG",
543
+ show_default=False,
544
+ default=[
545
+ (
546
+ k,
547
+ (
548
+ ConvertPath.mark_as_default(v[0])
549
+ if not callable(v[0]) and v[1]
550
+ else None
551
+ ),
552
+ )
553
+ for k, v in defaults.items()
554
+ ],
555
+ required=False,
556
+ ),
557
+ )
558
+ return cmd, config_input
559
+
560
+
561
+ def config_options(cmd):
562
+ cmd, _ = config_options_with_config_input(cmd)
563
+ return cmd