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,598 @@
1
+ import inspect
2
+ import json
3
+ import collections.abc
4
+ import copy
5
+ import os
6
+ import re
7
+
8
+ from typing import (
9
+ Any,
10
+ Callable,
11
+ Dict,
12
+ Iterable,
13
+ Iterator,
14
+ List,
15
+ Optional,
16
+ Tuple,
17
+ TYPE_CHECKING,
18
+ Union,
19
+ )
20
+
21
+
22
+ from ..exception import MetaflowException
23
+
24
+ from ..parameters import (
25
+ Parameter,
26
+ ParameterContext,
27
+ current_flow,
28
+ )
29
+
30
+ if TYPE_CHECKING:
31
+ from metaflow import FlowSpec
32
+
33
+ # _tracefunc_depth = 0
34
+
35
+
36
+ # def tracefunc(func):
37
+ # """Decorates a function to show its trace."""
38
+
39
+ # @functools.wraps(func)
40
+ # def tracefunc_closure(*args, **kwargs):
41
+ # global _tracefunc_depth
42
+ # """The closure."""
43
+ # print(f"{_tracefunc_depth}: {func.__name__}(args={args}, kwargs={kwargs})")
44
+ # _tracefunc_depth += 1
45
+ # result = func(*args, **kwargs)
46
+ # _tracefunc_depth -= 1
47
+ # print(f"{_tracefunc_depth} => {result}")
48
+ # return result
49
+
50
+ # return tracefunc_closure
51
+
52
+ ID_PATTERN = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*$")
53
+
54
+ UNPACK_KEY = "_unpacked_delayed_"
55
+
56
+
57
+ def dump_config_values(flow: "FlowSpec"):
58
+ from ..flowspec import FlowStateItems # Prevent circular import
59
+
60
+ configs = flow._flow_state[FlowStateItems.CONFIGS]
61
+ if configs:
62
+ return {"user_configs": configs}
63
+ return {}
64
+
65
+
66
+ class ConfigValue(collections.abc.Mapping, dict):
67
+ """
68
+ ConfigValue is a thin wrapper around an arbitrarily nested dictionary-like
69
+ configuration object. It allows you to access elements of this nested structure
70
+ using either a "." notation or a [] notation. As an example, if your configuration
71
+ object is:
72
+ {"foo": {"bar": 42}}
73
+ you can access the value 42 using either config["foo"]["bar"] or config.foo.bar.
74
+
75
+ All "keys"" need to be valid Python identifiers
76
+ """
77
+
78
+ # Thin wrapper to allow configuration values to be accessed using a "." notation
79
+ # as well as a [] notation.
80
+
81
+ # We inherit from dict to allow the isinstanceof check to work easily and also
82
+ # to provide a simple json dumps functionality.
83
+
84
+ def __init__(self, data: Union["ConfigValue", Dict[str, Any]]):
85
+ self._data = {k: self._construct(v) for k, v in data.items()}
86
+
87
+ # Enable json dumps
88
+ dict.__init__(self, self._data)
89
+
90
+ @classmethod
91
+ def fromkeys(cls, iterable: Iterable, value: Any = None) -> "ConfigValue":
92
+ """
93
+ Creates a new ConfigValue object from the given iterable and value.
94
+
95
+ Parameters
96
+ ----------
97
+ iterable : Iterable
98
+ Iterable to create the ConfigValue from.
99
+ value : Any, optional
100
+ Value to set for each key in the iterable.
101
+
102
+ Returns
103
+ -------
104
+ ConfigValue
105
+ A new ConfigValue object.
106
+ """
107
+ return cls(dict.fromkeys(iterable, value))
108
+
109
+ def to_dict(self) -> Dict[Any, Any]:
110
+ """
111
+ Returns a dictionary representation of this configuration object.
112
+
113
+ Returns
114
+ -------
115
+ Dict[Any, Any]
116
+ Dictionary equivalent of this configuration object.
117
+ """
118
+ return self._to_dict(self._data)
119
+
120
+ def copy(self) -> "ConfigValue":
121
+ return self.__copy__()
122
+
123
+ def clear(self) -> None:
124
+ # Prevent configuration modification
125
+ raise TypeError("ConfigValue is immutable")
126
+
127
+ def update(self, *args, **kwargs) -> None:
128
+ # Prevent configuration modification
129
+ raise TypeError("ConfigValue is immutable")
130
+
131
+ def setdefault(self, key: Any, default: Any = None) -> Any:
132
+ # Prevent configuration modification
133
+ raise TypeError("ConfigValue is immutable")
134
+
135
+ def pop(self, key: Any, default: Any = None) -> Any:
136
+ # Prevent configuration modification
137
+ raise TypeError("ConfigValue is immutable")
138
+
139
+ def popitem(self) -> Tuple[Any, Any]:
140
+ # Prevent configuration modification
141
+ raise TypeError("ConfigValue is immutable")
142
+
143
+ def __getattr__(self, key: str) -> Any:
144
+ """
145
+ Access an element of this configuration
146
+
147
+ Parameters
148
+ ----------
149
+ key : str
150
+ Element to access
151
+
152
+ Returns
153
+ -------
154
+ Any
155
+ Element of the configuration
156
+ """
157
+ if key == "_data":
158
+ # Called during unpickling. Special case to not run into infinite loop
159
+ # below.
160
+ raise AttributeError(key)
161
+
162
+ if key in self._data:
163
+ return self[key]
164
+ raise AttributeError(key)
165
+
166
+ def __setattr__(self, name: str, value: Any) -> None:
167
+ # Prevent configuration modification
168
+ if name == "_data":
169
+ return super().__setattr__(name, value)
170
+ raise TypeError("ConfigValue is immutable")
171
+
172
+ def __getitem__(self, key: Any) -> Any:
173
+ """
174
+ Access an element of this configuration
175
+
176
+ Parameters
177
+ ----------
178
+ key : Any
179
+ Element to access
180
+
181
+ Returns
182
+ -------
183
+ Any
184
+ Element of the configuration
185
+ """
186
+ return self._data[key]
187
+
188
+ def __setitem__(self, key: Any, value: Any) -> None:
189
+ # Prevent configuration modification
190
+ raise TypeError("ConfigValue is immutable")
191
+
192
+ def __delattr__(self, key) -> None:
193
+ # Prevent configuration modification
194
+ raise TypeError("ConfigValue is immutable")
195
+
196
+ def __delitem__(self, key: Any) -> None:
197
+ # Prevent configuration modification
198
+ raise TypeError("ConfigValue is immutable")
199
+
200
+ def __len__(self) -> int:
201
+ return len(self._data)
202
+
203
+ def __iter__(self) -> Iterator:
204
+ return iter(self._data)
205
+
206
+ def __eq__(self, other: Any) -> bool:
207
+ if isinstance(other, ConfigValue):
208
+ return self._data == other._data
209
+ if isinstance(other, dict):
210
+ return self._data == other
211
+ return False
212
+
213
+ def __ne__(self, other: Any) -> bool:
214
+ return not self.__eq__(other)
215
+
216
+ def __copy__(self) -> "ConfigValue":
217
+ cls = self.__class__
218
+ result = cls.__new__(cls)
219
+ result.__dict__.update({k: copy.copy(v) for k, v in self.__dict__.items()})
220
+ return result
221
+
222
+ def __repr__(self) -> str:
223
+ return repr(self._data)
224
+
225
+ def __str__(self) -> str:
226
+ return str(self._data)
227
+
228
+ def __dir__(self) -> Iterable[str]:
229
+ return dir(type(self)) + [k for k in self._data.keys() if ID_PATTERN.match(k)]
230
+
231
+ def __contains__(self, key: Any) -> bool:
232
+ try:
233
+ self[key]
234
+ except KeyError:
235
+ return False
236
+ return True
237
+
238
+ def keys(self):
239
+ """
240
+ Returns the keys of this configuration object.
241
+
242
+ Returns
243
+ -------
244
+ Any
245
+ Keys of this configuration object.
246
+ """
247
+ return self._data.keys()
248
+
249
+ @classmethod
250
+ def _construct(cls, obj: Any) -> Any:
251
+ # Internal method to construct a ConfigValue so that all mappings internally
252
+ # are also converted to ConfigValue
253
+ if isinstance(obj, ConfigValue):
254
+ v = obj
255
+ elif isinstance(obj, collections.abc.Mapping):
256
+ v = ConfigValue({k: cls._construct(v) for k, v in obj.items()})
257
+ elif isinstance(obj, (list, tuple, set)):
258
+ v = type(obj)([cls._construct(x) for x in obj])
259
+ else:
260
+ v = obj
261
+ return v
262
+
263
+ @classmethod
264
+ def _to_dict(cls, obj: Any) -> Any:
265
+ # Internal method to convert all nested mappings to dicts
266
+ if isinstance(obj, collections.abc.Mapping):
267
+ v = {k: cls._to_dict(v) for k, v in obj.items()}
268
+ elif isinstance(obj, (list, tuple, set)):
269
+ v = type(obj)([cls._to_dict(x) for x in obj])
270
+ else:
271
+ v = obj
272
+ return v
273
+
274
+ def __reduce__(self):
275
+ return (self.__class__, (self.to_dict(),))
276
+
277
+
278
+ class DelayEvaluator(collections.abc.Mapping):
279
+ """
280
+ Small wrapper that allows the evaluation of a Config() value in a delayed manner.
281
+ This is used when we want to use config.* values in decorators for example.
282
+
283
+ It also allows the following "delayed" access on an obj that is a DelayEvaluation
284
+ - obj.x.y.z (ie: accessing members of DelayEvaluator; accesses will be delayed until
285
+ the DelayEvaluator is evaluated)
286
+ - **obj (ie: unpacking the DelayEvaluator as a dictionary). Note that this requires
287
+ special handling in whatever this is being unpacked into, specifically the handling
288
+ of _unpacked_delayed_*
289
+ """
290
+
291
+ def __init__(self, ex: str, saved_globals: Optional[Dict[str, Any]] = None):
292
+ self._config_expr = ex
293
+ self._globals = saved_globals
294
+ if ID_PATTERN.match(self._config_expr):
295
+ # This is a variable only so allow things like config_expr("config").var
296
+ self._is_var_only = True
297
+ self._access = []
298
+ else:
299
+ self._is_var_only = False
300
+ self._access = None
301
+ self._cached_expr = None
302
+
303
+ def __copy__(self):
304
+ c = DelayEvaluator(self._config_expr)
305
+ c._access = self._access.copy() if self._access is not None else None
306
+ # Globals are not copied -- always kept as a reference
307
+ return c
308
+
309
+ def __deepcopy__(self, memo):
310
+ c = DelayEvaluator(self._config_expr)
311
+ c._access = (
312
+ copy.deepcopy(self._access, memo) if self._access is not None else None
313
+ )
314
+ # Globals are not copied -- always kept as a reference
315
+ return c
316
+
317
+ def __iter__(self):
318
+ yield "%s%d" % (UNPACK_KEY, id(self))
319
+
320
+ def __getitem__(self, key):
321
+ if isinstance(key, str) and key == "%s%d" % (UNPACK_KEY, id(self)):
322
+ return self
323
+ if self._access is None:
324
+ raise KeyError(key)
325
+
326
+ # Make a copy so that we can support something like
327
+ # foo = delay_evaluator["blah"]
328
+ # bar = delay_evaluator["baz"]
329
+ # and don't end up with a access list that contains both "blah" and "baz"
330
+ c = self.__copy__()
331
+ c._access.append(key)
332
+ c._cached_expr = None
333
+ return c
334
+
335
+ def __len__(self):
336
+ return 1
337
+
338
+ def __getattr__(self, name):
339
+ if self._access is None:
340
+ raise AttributeError(name)
341
+ c = self.__copy__()
342
+ c._access.append(name)
343
+ c._cached_expr = None
344
+ return c
345
+
346
+ def __call__(self, ctx=None, deploy_time=False):
347
+ from ..flowspec import FlowStateItems # Prevent circular import
348
+
349
+ # Two additional arguments are only used by DeployTimeField which will call
350
+ # this function with those two additional arguments. They are ignored.
351
+ flow_cls = getattr(current_flow, "flow_cls", None)
352
+ if flow_cls is None:
353
+ # We are not executing inside a flow (ie: not the CLI)
354
+ raise MetaflowException(
355
+ "Config object can only be used directly in the FlowSpec defining them "
356
+ "(or their flow decorators)."
357
+ )
358
+ if self._cached_expr is not None:
359
+ to_eval_expr = self._cached_expr
360
+ elif self._access is not None:
361
+ # Build the final expression by adding all the fields in access as . fields
362
+ access_list = [self._config_expr]
363
+ for a in self._access:
364
+ if isinstance(a, str):
365
+ access_list.append(a)
366
+ elif isinstance(a, DelayEvaluator):
367
+ # Supports things like config[other_config.selector].var
368
+ access_list.append(a())
369
+ else:
370
+ raise MetaflowException(
371
+ "Field '%s' of type '%s' is not supported" % (str(a), type(a))
372
+ )
373
+ to_eval_expr = self._cached_expr = ".".join(access_list)
374
+ else:
375
+ to_eval_expr = self._cached_expr = self._config_expr
376
+ # Evaluate the expression setting the config values as local variables
377
+ try:
378
+ return eval(
379
+ to_eval_expr,
380
+ self._globals or globals(),
381
+ {
382
+ k: ConfigValue(v) if v is not None else None
383
+ for k, v in flow_cls._flow_state[FlowStateItems.CONFIGS].items()
384
+ },
385
+ )
386
+ except NameError as e:
387
+ raise MetaflowException(
388
+ "Config expression '%s' could not be evaluated: %s"
389
+ % (to_eval_expr, str(e))
390
+ ) from e
391
+
392
+
393
+ def config_expr(expr: str) -> DelayEvaluator:
394
+ """
395
+ Function to allow you to use an expression involving a config parameter in
396
+ places where it may not be directory accessible or if you want a more complicated
397
+ expression than just a single variable.
398
+
399
+ You can use it as follows:
400
+ - When the config is not directly accessible:
401
+
402
+ @project(name=config_expr("config").project.name)
403
+ class MyFlow(FlowSpec):
404
+ config = Config("config")
405
+ ...
406
+ - When you want a more complex expression:
407
+ class MyFlow(FlowSpec):
408
+ config = Config("config")
409
+
410
+ @environment(vars={"foo": config_expr("config.bar.baz.lower()")})
411
+ @step
412
+ def start(self):
413
+ ...
414
+
415
+ Parameters
416
+ ----------
417
+ expr : str
418
+ Expression using the config values.
419
+ """
420
+ # Get globals where the expression is defined so that the user can use
421
+ # something like `config_expr("my_func()")` in the expression.
422
+ parent_globals = inspect.currentframe().f_back.f_globals
423
+ return DelayEvaluator(expr, saved_globals=parent_globals)
424
+
425
+
426
+ class Config(Parameter, collections.abc.Mapping):
427
+ """
428
+ Includes a configuration for this flow.
429
+
430
+ `Config` is a special type of `Parameter` but differs in a few key areas:
431
+ - it is immutable and determined at deploy time (or prior to running if not deploying
432
+ to a scheduler)
433
+ - as such, it can be used anywhere in your code including in Metaflow decorators
434
+
435
+ The value of the configuration is determines as follows:
436
+ - use the user-provided file path or value. It is an error to provide both
437
+ - if none are present:
438
+ - if a default file path (default) is provided, attempt to read this file
439
+ - if the file is present, use that value. Note that the file will be used
440
+ even if it has an invalid syntax
441
+ - if the file is not present, and a default value is present, use that
442
+ - if still None and is required, this is an error.
443
+
444
+ Parameters
445
+ ----------
446
+ name : str
447
+ User-visible configuration name.
448
+ default : Union[str, Callable[[ParameterContext], str], optional, default None
449
+ Default path from where to read this configuration. A function implies that the
450
+ value will be computed using that function.
451
+ You can only specify default or default_value, not both.
452
+ default_value : Union[str, Dict[str, Any], Callable[[ParameterContext, Union[str, Dict[str, Any]]], Any], optional, default None
453
+ Default value for the parameter. A function
454
+ implies that the value will be computed using that function.
455
+ You can only specify default or default_value, not both.
456
+ help : str, optional, default None
457
+ Help text to show in `run --help`.
458
+ required : bool, optional, default None
459
+ Require that the user specifies a value for the configuration. Note that if
460
+ a default or default_value is provided, the required flag is ignored.
461
+ A value of None is equivalent to False.
462
+ parser : Union[str, Callable[[str], Dict[Any, Any]]], optional, default None
463
+ If a callable, it is a function that can parse the configuration string
464
+ into an arbitrarily nested dictionary. If a string, the string should refer to
465
+ a function (like "my_parser_package.my_parser.my_parser_function") which should
466
+ be able to parse the configuration string into an arbitrarily nested dictionary.
467
+ If the name starts with a ".", it is assumed to be relative to "metaflow".
468
+ show_default : bool, default True
469
+ If True, show the default value in the help text.
470
+ """
471
+
472
+ IS_CONFIG_PARAMETER = True
473
+
474
+ def __init__(
475
+ self,
476
+ name: str,
477
+ default: Optional[Union[str, Callable[[ParameterContext], str]]] = None,
478
+ default_value: Optional[
479
+ Union[
480
+ str,
481
+ Dict[str, Any],
482
+ Callable[[ParameterContext], Union[str, Dict[str, Any]]],
483
+ ]
484
+ ] = None,
485
+ help: Optional[str] = None,
486
+ required: Optional[bool] = None,
487
+ parser: Optional[Union[str, Callable[[str], Dict[Any, Any]]]] = None,
488
+ **kwargs: Dict[str, str]
489
+ ):
490
+ if default is not None and default_value is not None:
491
+ raise MetaflowException(
492
+ "For config '%s', you can only specify default or default_value, not both"
493
+ % name
494
+ )
495
+ self._default_is_file = default is not None
496
+ kwargs["default"] = default if default is not None else default_value
497
+ super(Config, self).__init__(
498
+ name, required=required, help=help, type=str, **kwargs
499
+ )
500
+ super(Config, self).init()
501
+
502
+ if isinstance(kwargs.get("default", None), str):
503
+ kwargs["default"] = json.dumps(kwargs["default"])
504
+ self.parser = parser
505
+ self._computed_value = None
506
+
507
+ self._delayed_evaluator = None
508
+
509
+ def load_parameter(self, v):
510
+ return ConfigValue(v) if v is not None else None
511
+
512
+ def _store_value(self, v: Any) -> None:
513
+ self._computed_value = v
514
+
515
+ def _init_delayed_evaluator(self) -> None:
516
+ if self._delayed_evaluator is None:
517
+ self._delayed_evaluator = DelayEvaluator(self.name.lower())
518
+
519
+ # Support <config>.<var> syntax
520
+ def __getattr__(self, name):
521
+ # Need to return a new DelayEvaluator everytime because the evaluator will
522
+ # contain the "path" (ie: .name) and can be further accessed.
523
+ return getattr(DelayEvaluator(self.name.lower()), name)
524
+
525
+ # Next three methods are to implement mapping to support **<config> syntax. We
526
+ # need to be careful, however, to also support a regular `config["key"]` syntax
527
+ # which calls into `__getitem__` and therefore behaves like __getattr__ above.
528
+ def __iter__(self):
529
+ self._init_delayed_evaluator()
530
+ yield from self._delayed_evaluator
531
+
532
+ def __len__(self):
533
+ self._init_delayed_evaluator()
534
+ return len(self._delayed_evaluator)
535
+
536
+ def __getitem__(self, key):
537
+ self._init_delayed_evaluator()
538
+ if isinstance(key, str) and key.startswith(UNPACK_KEY):
539
+ return self._delayed_evaluator[key]
540
+ return DelayEvaluator(self.name.lower())[key]
541
+
542
+
543
+ def resolve_delayed_evaluator(
544
+ v: Any, ignore_errors: bool = False, to_dict: bool = False
545
+ ) -> Any:
546
+ # NOTE: We don't ignore errors in downstream calls because we want to have either
547
+ # all or nothing for the top-level call by the user.
548
+ try:
549
+ if isinstance(v, DelayEvaluator):
550
+ to_return = v()
551
+ if to_dict and isinstance(to_return, ConfigValue):
552
+ to_return = to_return.to_dict()
553
+ return to_return
554
+ if isinstance(v, dict):
555
+ return {
556
+ resolve_delayed_evaluator(
557
+ k, to_dict=to_dict
558
+ ): resolve_delayed_evaluator(v, to_dict=to_dict)
559
+ for k, v in v.items()
560
+ }
561
+ if isinstance(v, list):
562
+ return [resolve_delayed_evaluator(x, to_dict=to_dict) for x in v]
563
+ if isinstance(v, tuple):
564
+ return tuple(resolve_delayed_evaluator(x, to_dict=to_dict) for x in v)
565
+ if isinstance(v, set):
566
+ return {resolve_delayed_evaluator(x, to_dict=to_dict) for x in v}
567
+ return v
568
+ except Exception as e:
569
+ if ignore_errors:
570
+ # Assumption is that default value of None is always allowed.
571
+ # This code path is *only* used when evaluating Parameters AND they
572
+ # use configs in their attributes AND the runner/deployer is being used
573
+ # AND CLICK_API_PROCESS_CONFIG is False. In those cases, all attributes in
574
+ # Parameter can be set to None except for required and show_default
575
+ # and even in those cases, a wrong value will have very limited consequence.
576
+ return None
577
+ raise e
578
+
579
+
580
+ def unpack_delayed_evaluator(
581
+ to_unpack: Dict[str, Any], ignore_errors: bool = False
582
+ ) -> Tuple[Dict[str, Any], List[str]]:
583
+ result = {}
584
+ new_keys = []
585
+ for k, v in to_unpack.items():
586
+ if not isinstance(k, str) or not k.startswith(UNPACK_KEY):
587
+ result[k] = v
588
+ else:
589
+ # k.startswith(UNPACK_KEY)
590
+ try:
591
+ new_vals = resolve_delayed_evaluator(v, to_dict=True)
592
+ new_keys.extend(new_vals.keys())
593
+ result.update(new_vals)
594
+ except Exception as e:
595
+ if ignore_errors:
596
+ continue
597
+ raise e
598
+ return result, new_keys
File without changes