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,717 @@
1
+ import os
2
+ import sys
3
+
4
+ _py_ver = sys.version_info[:2]
5
+
6
+ if _py_ver >= (3, 8):
7
+ from metaflow._vendor.typeguard import TypeCheckError, check_type
8
+ elif _py_ver >= (3, 7):
9
+ from metaflow._vendor.v3_7.typeguard import TypeCheckError, check_type
10
+ else:
11
+ raise RuntimeError(
12
+ """
13
+ The Metaflow Programmatic API is not supported for versions of Python less than 3.7
14
+ """
15
+ )
16
+
17
+ import datetime
18
+ import functools
19
+ import importlib
20
+ import inspect
21
+ import itertools
22
+ import uuid
23
+ import json
24
+ from collections import OrderedDict
25
+ from typing import Any, Callable, Dict, List, Optional, Type
26
+ from typing import OrderedDict as TOrderedDict
27
+ from typing import Tuple as TTuple
28
+ from typing import Union
29
+
30
+ from metaflow import FlowSpec, Parameter
31
+ from metaflow._vendor import click
32
+ from metaflow._vendor.click.types import (
33
+ BoolParamType,
34
+ Choice,
35
+ DateTime,
36
+ File,
37
+ FloatParamType,
38
+ IntParamType,
39
+ Path,
40
+ StringParamType,
41
+ Tuple,
42
+ UUIDParameterType,
43
+ )
44
+ from metaflow.decorators import add_decorator_options
45
+ from metaflow.exception import MetaflowException
46
+ from metaflow.flowspec import FlowStateItems
47
+ from metaflow.includefile import FilePathClass
48
+ from metaflow.metaflow_config import CLICK_API_PROCESS_CONFIG
49
+ from metaflow.parameters import JSONTypeClass, flow_context
50
+ from metaflow.user_configs.config_options import (
51
+ ConfigValue,
52
+ ConvertDictOrStr,
53
+ ConvertPath,
54
+ LocalFileInput,
55
+ MultipleTuple,
56
+ config_options_with_config_input,
57
+ )
58
+ from metaflow.user_decorators.user_flow_decorator import FlowMutator
59
+
60
+ # Define a recursive type alias for JSON
61
+ JSON = Union[Dict[str, "JSON"], List["JSON"], str, int, float, bool, None]
62
+
63
+ click_to_python_types = {
64
+ StringParamType: str,
65
+ IntParamType: int,
66
+ FloatParamType: float,
67
+ BoolParamType: bool,
68
+ UUIDParameterType: uuid.UUID,
69
+ Path: str,
70
+ DateTime: datetime.datetime,
71
+ Tuple: tuple,
72
+ Choice: str,
73
+ File: str,
74
+ JSONTypeClass: JSON,
75
+ FilePathClass: str,
76
+ LocalFileInput: str,
77
+ MultipleTuple: TTuple[str, Union[JSON, ConfigValue]],
78
+ }
79
+
80
+
81
+ def _method_sanity_check(
82
+ possible_arg_params: TOrderedDict[str, click.Argument],
83
+ possible_opt_params: TOrderedDict[str, click.Option],
84
+ annotations: TOrderedDict[str, Any],
85
+ defaults: TOrderedDict[str, Any],
86
+ **kwargs
87
+ ) -> Dict[str, Any]:
88
+ method_params = {"args": {}, "options": {}, "defaults": defaults}
89
+
90
+ possible_params = OrderedDict()
91
+ possible_params.update(possible_arg_params)
92
+ possible_params.update(possible_opt_params)
93
+
94
+ # supplied kwargs
95
+ for supplied_k, supplied_v in kwargs.items():
96
+ if supplied_k not in possible_params:
97
+ raise ValueError(
98
+ "Unknown argument: '%s', possible args are: %s"
99
+ % (supplied_k, ", ".join(possible_params.keys()))
100
+ )
101
+
102
+ try:
103
+ check_type(supplied_v, annotations[supplied_k])
104
+ except TypeCheckError:
105
+ raise TypeError(
106
+ "Invalid type for '%s' (%s), expected: '%s', default is '%s' but found '%s'"
107
+ % (
108
+ supplied_k,
109
+ type(supplied_k),
110
+ annotations[supplied_k],
111
+ defaults[supplied_k],
112
+ str(supplied_v),
113
+ )
114
+ )
115
+
116
+ # Clean up values to make them into what click expects
117
+ if annotations[supplied_k] == JSON:
118
+ # JSON should be a string (json dumps)
119
+ supplied_v = json.dumps(supplied_v)
120
+ elif supplied_k == "config_value":
121
+ # Special handling of config value because we need to go look in the tuple
122
+ new_list = []
123
+ for cfg_name, cfg_value in supplied_v:
124
+ if isinstance(cfg_value, ConfigValue):
125
+ # ConfigValue should be JSONified and converted to a string
126
+ new_list.append((cfg_name, json.dumps(cfg_value.to_dict())))
127
+ elif isinstance(cfg_value, dict):
128
+ # ConfigValue passed as a dictionary
129
+ new_list.append((cfg_name, json.dumps(cfg_value)))
130
+ else:
131
+ raise TypeError(
132
+ "Invalid type for a config-value, expected a ConfigValue or "
133
+ "dict but got '%s'" % type(cfg_value)
134
+ )
135
+ supplied_v = new_list
136
+
137
+ if supplied_k in possible_arg_params:
138
+ cli_name = possible_arg_params[supplied_k].opts[0].strip("-")
139
+ method_params["args"][cli_name] = supplied_v
140
+ elif supplied_k in possible_opt_params:
141
+ if possible_opt_params[supplied_k].is_bool_flag:
142
+ # it is a boolean flag..
143
+ if supplied_v == True:
144
+ cli_name = possible_opt_params[supplied_k].opts[0].strip("-")
145
+ elif supplied_v == False:
146
+ if possible_opt_params[supplied_k].secondary_opts:
147
+ cli_name = (
148
+ possible_opt_params[supplied_k].secondary_opts[0].strip("-")
149
+ )
150
+ else:
151
+ continue
152
+ supplied_v = "flag"
153
+ else:
154
+ cli_name = possible_opt_params[supplied_k].opts[0].strip("-")
155
+ method_params["options"][cli_name] = supplied_v
156
+
157
+ # possible kwargs
158
+ for _, possible_v in possible_params.items():
159
+ cli_name = possible_v.opts[0].strip("-")
160
+ if (
161
+ (cli_name not in method_params["args"])
162
+ and (cli_name not in method_params["options"])
163
+ ) and possible_v.required:
164
+ raise ValueError("Missing argument: %s is required." % cli_name)
165
+
166
+ return method_params
167
+
168
+
169
+ def _cleanup_flow_parameters(cmd_obj: Union[click.Command, click.Group]):
170
+ if hasattr(cmd_obj, "original_params"):
171
+ cmd_obj.params = list(cmd_obj.original_params)
172
+
173
+ if isinstance(cmd_obj, click.Group):
174
+ for sub_cmd_name in cmd_obj.list_commands(None):
175
+ sub_cmd = cmd_obj.get_command(None, sub_cmd_name)
176
+ if sub_cmd:
177
+ _cleanup_flow_parameters(sub_cmd)
178
+
179
+
180
+ def _lazy_load_command(
181
+ cli_collection: click.Group,
182
+ flow_parameters: Union[str, List[Parameter]],
183
+ _self,
184
+ name: str,
185
+ ):
186
+ # Context is not used in get_command so we can pass None. Since we pin click,
187
+ # this won't change from under us.
188
+
189
+ if isinstance(flow_parameters, str):
190
+ # Resolve flow_parameters -- for start, this is a function which we
191
+ # need to call to figure out the actual parameters (may be changed by configs)
192
+ flow_parameters = getattr(_self, flow_parameters)()
193
+ cmd_obj = cli_collection.get_command(None, name)
194
+ if cmd_obj:
195
+ _cleanup_flow_parameters(cmd_obj)
196
+ if isinstance(cmd_obj, click.Group):
197
+ # TODO: possibly check for fake groups with cmd_obj.name in ["cli", "main"]
198
+ result = functools.partial(extract_group(cmd_obj, flow_parameters), _self)
199
+ elif isinstance(cmd_obj, click.Command):
200
+ result = functools.partial(extract_command(cmd_obj, flow_parameters), _self)
201
+ else:
202
+ raise RuntimeError(
203
+ "Cannot handle %s of type %s" % (cmd_obj.name, type(cmd_obj))
204
+ )
205
+ setattr(_self, name, result)
206
+ return result
207
+ else:
208
+ raise AttributeError()
209
+
210
+
211
+ def get_annotation(param: click.Parameter) -> TTuple[Type, bool]:
212
+ py_type = click_to_python_types[type(param.type)]
213
+ if param.nargs == -1:
214
+ # This is the equivalent of *args effectively
215
+ # so the type annotation should be the type of the
216
+ # elements in the list
217
+ return py_type, True
218
+ if not param.required:
219
+ if param.multiple or param.nargs > 1:
220
+ return Optional[Union[List[py_type], TTuple[py_type]]], False
221
+ else:
222
+ return Optional[py_type], False
223
+ else:
224
+ if param.multiple or param.nargs > 1:
225
+ return Union[List[py_type], TTuple[py_type]], False
226
+ else:
227
+ return py_type, False
228
+
229
+
230
+ def get_inspect_param_obj(p: Union[click.Argument, click.Option], kind: str):
231
+ annotation, is_vararg = get_annotation(p)
232
+ return (
233
+ inspect.Parameter(
234
+ name="args" if is_vararg else p.name,
235
+ kind=inspect.Parameter.VAR_POSITIONAL if is_vararg else kind,
236
+ default=inspect.Parameter.empty if is_vararg else p.default,
237
+ annotation=annotation,
238
+ ),
239
+ (
240
+ Optional[Union[TTuple[annotation], List[annotation]]]
241
+ if is_vararg
242
+ else annotation
243
+ ),
244
+ )
245
+
246
+
247
+ # Cache to store already loaded modules
248
+ loaded_modules = {}
249
+
250
+
251
+ def extract_flow_class_from_file(flow_file: str) -> FlowSpec:
252
+ if not os.path.exists(flow_file):
253
+ raise FileNotFoundError("Flow file not present at '%s'" % flow_file)
254
+
255
+ flow_dir = os.path.dirname(os.path.abspath(flow_file))
256
+ path_was_added = False
257
+
258
+ # Only add to path if it's not already there
259
+ if flow_dir not in sys.path:
260
+ sys.path.insert(0, flow_dir)
261
+ path_was_added = True
262
+
263
+ try:
264
+ # Get module name from the file path
265
+ module_name = os.path.splitext(os.path.basename(flow_file))[0]
266
+
267
+ # Check if the module has already been loaded
268
+ if flow_file in loaded_modules:
269
+ module = loaded_modules[flow_file]
270
+ else:
271
+ # Load the module if it's not already loaded
272
+ spec = importlib.util.spec_from_file_location(module_name, flow_file)
273
+ module = importlib.util.module_from_spec(spec)
274
+ spec.loader.exec_module(module)
275
+ # Cache the loaded module
276
+ loaded_modules[flow_file] = module
277
+
278
+ classes = inspect.getmembers(
279
+ module, lambda x: inspect.isclass(x) or isinstance(x, FlowMutator)
280
+ )
281
+ flow_cls = None
282
+
283
+ for _, kls in classes:
284
+ if isinstance(kls, FlowMutator):
285
+ kls = kls._flow_cls
286
+ if (
287
+ kls is not FlowSpec
288
+ and kls.__module__ == module_name
289
+ and issubclass(kls, FlowSpec)
290
+ ):
291
+ if flow_cls is not None:
292
+ raise MetaflowException(
293
+ "Multiple FlowSpec classes found in %s" % flow_file
294
+ )
295
+ flow_cls = kls
296
+
297
+ if flow_cls is None:
298
+ raise MetaflowException("No FlowSpec class found in %s" % flow_file)
299
+ return flow_cls
300
+ finally:
301
+ # Only remove from path if we added it
302
+ if path_was_added:
303
+ try:
304
+ sys.path.remove(flow_dir)
305
+ except ValueError:
306
+ # User's code might have removed it already
307
+ pass
308
+
309
+
310
+ class MetaflowAPI(object):
311
+ def __init__(self, parent=None, flow_cls=None, config_input=None, **kwargs):
312
+ self._parent = parent
313
+ self._chain = [{self._API_NAME: kwargs}]
314
+ self._flow_cls = flow_cls
315
+ self._config_input = config_input
316
+ self._cached_computed_parameters = None
317
+
318
+ @property
319
+ def parent(self):
320
+ if self._parent:
321
+ return self._parent
322
+ return None
323
+
324
+ @property
325
+ def chain(self):
326
+ return self._chain
327
+
328
+ @property
329
+ def name(self):
330
+ return self._API_NAME
331
+
332
+ @classmethod
333
+ def from_cli(cls, flow_file: str, cli_collection: Callable) -> Callable:
334
+ flow_cls = extract_flow_class_from_file(flow_file)
335
+
336
+ with flow_context(flow_cls) as _:
337
+ cli_collection, config_input = config_options_with_config_input(
338
+ cli_collection
339
+ )
340
+ cli_collection = add_decorator_options(cli_collection)
341
+
342
+ def getattr_wrapper(_self, name):
343
+ # Functools.partial do not automatically bind self (no __get__)
344
+ with flow_context(flow_cls) as _:
345
+ # We also wrap this in the proper flow context because since commands
346
+ # are loaded lazily, we need the proper flow context to compute things
347
+ # like parameters. If we do not do this, the outer flow's context will
348
+ # be used.
349
+ return _self._internal_getattr(_self, name)
350
+
351
+ class_dict = {
352
+ "__module__": "metaflow",
353
+ "_API_NAME": flow_file,
354
+ "_internal_getattr": staticmethod(
355
+ functools.partial(
356
+ _lazy_load_command, cli_collection, "_compute_flow_parameters"
357
+ )
358
+ ),
359
+ "__getattr__": getattr_wrapper,
360
+ }
361
+
362
+ to_return = type(flow_file, (MetaflowAPI,), class_dict)
363
+ to_return.__name__ = flow_file
364
+
365
+ (
366
+ params_sigs,
367
+ possible_arg_params,
368
+ possible_opt_params,
369
+ annotations,
370
+ defaults,
371
+ ) = extract_all_params(cli_collection)
372
+
373
+ def _method(_self, *args, **kwargs):
374
+ method_params = _method_sanity_check(
375
+ possible_arg_params,
376
+ possible_opt_params,
377
+ annotations,
378
+ defaults,
379
+ **kwargs,
380
+ )
381
+ return to_return(
382
+ parent=None,
383
+ flow_cls=flow_cls,
384
+ config_input=config_input,
385
+ **method_params,
386
+ )
387
+
388
+ m = _method
389
+ m.__name__ = cli_collection.name
390
+ m.__doc__ = getattr(cli_collection, "help", None)
391
+ m.__signature__ = inspect.signature(_method).replace(
392
+ parameters=params_sigs.values()
393
+ )
394
+ m.__annotations__ = annotations
395
+ m.__defaults__ = tuple(defaults.values())
396
+
397
+ return m
398
+
399
+ def execute(self) -> List[str]:
400
+ parents = []
401
+ current = self
402
+ while current.parent:
403
+ parents.append(current.parent)
404
+ current = current.parent
405
+
406
+ parents.reverse()
407
+
408
+ final_chain = list(itertools.chain.from_iterable([p.chain for p in parents]))
409
+ final_chain.extend(self.chain)
410
+
411
+ components = []
412
+ for each_cmd in final_chain:
413
+ for cmd, params in each_cmd.items():
414
+ components.append(cmd)
415
+ args = params.pop("args", {})
416
+ options = params.pop("options", {})
417
+
418
+ for _, v in args.items():
419
+ if v is None:
420
+ continue
421
+ if isinstance(v, (list, tuple)):
422
+ for i in v:
423
+ components.append(i)
424
+ else:
425
+ components.append(v)
426
+ for k, v in options.items():
427
+ if v is None:
428
+ continue
429
+ if isinstance(v, list):
430
+ for i in v:
431
+ if isinstance(i, tuple):
432
+ components.append("--%s" % k)
433
+ components.extend(map(str, i))
434
+ else:
435
+ components.append("--%s" % k)
436
+ components.append(str(i))
437
+ elif v is None:
438
+ continue # Skip None values -- they are defaults and converting
439
+ # them to string will not be what the user wants
440
+ else:
441
+ components.append("--%s" % k)
442
+ if v != "flag":
443
+ components.append(str(v))
444
+
445
+ return components
446
+
447
+ def _compute_flow_parameters(self):
448
+ if (
449
+ self._flow_cls is None
450
+ or self._config_input is None
451
+ or self._parent is not None
452
+ ):
453
+ raise RuntimeError(
454
+ "Computing flow-level parameters for a non start API. "
455
+ "Please report to the Metaflow team."
456
+ )
457
+
458
+ if self._cached_computed_parameters is not None:
459
+ return self._cached_computed_parameters
460
+ self._cached_computed_parameters = []
461
+
462
+ config_options = None
463
+ if CLICK_API_PROCESS_CONFIG:
464
+ with flow_context(self._flow_cls) as _:
465
+ # We are going to resolve the configs first and then get the parameters.
466
+ # Note that configs may update/add parameters so the order is important
467
+ # Since part of the processing of configs happens by click, we need to
468
+ # "fake" it.
469
+
470
+ # Extract any config options as well as datastore and quiet options
471
+ method_params = self._chain[0][self._API_NAME]
472
+ opts = method_params["options"]
473
+ defaults = method_params["defaults"]
474
+
475
+ ds = opts.get("datastore", defaults["datastore"])
476
+ quiet = opts.get("quiet", defaults["quiet"])
477
+
478
+ # Order to find config or config_value:
479
+ # 1. Passed directly to the Click API
480
+ # 2. If not found, check if passed through an environment variable
481
+ # 3. If not found, use the default value
482
+ is_default = False
483
+ config_file = opts.get("config")
484
+ if config_file is None:
485
+ # Check if it was set through an environment variable -- we
486
+ # don't have click process them here so we need to "fake" it.
487
+ env_config_file = os.environ.get("METAFLOW_FLOW_CONFIG")
488
+ if env_config_file:
489
+ # Convert dict items to list of tuples
490
+ config_file = list(json.loads(env_config_file).items())
491
+ is_default = False
492
+ else:
493
+ is_default = True
494
+ config_file = defaults.get("config")
495
+
496
+ if config_file:
497
+ config_file = dict(
498
+ map(
499
+ lambda x: (
500
+ x[0],
501
+ ConvertPath.convert_value(x[1], is_default),
502
+ ),
503
+ config_file,
504
+ )
505
+ )
506
+
507
+ is_default = False
508
+ config_value = opts.get("config-value")
509
+ if config_value is None:
510
+ env_config_value = os.environ.get("METAFLOW_FLOW_CONFIG_VALUE")
511
+ if env_config_value:
512
+ # Parse environment variable using MultipleTuple logic
513
+ loaded = json.loads(env_config_value)
514
+ # Convert dict items to list of tuples with JSON-serialized values
515
+ config_value = [
516
+ (k, json.dumps(v) if not isinstance(v, str) else v)
517
+ for k, v in loaded.items()
518
+ ]
519
+ is_default = False
520
+ else:
521
+ is_default = True
522
+ config_value = defaults.get("config_value")
523
+
524
+ if config_value:
525
+ config_value = dict(
526
+ map(
527
+ lambda x: (
528
+ x[0],
529
+ ConvertDictOrStr.convert_value(x[1], is_default),
530
+ ),
531
+ config_value,
532
+ )
533
+ )
534
+
535
+ if (config_file is None) ^ (config_value is None):
536
+ # If we have one, we should have the other
537
+ raise MetaflowException(
538
+ "Options were not properly set -- this is an internal error."
539
+ )
540
+
541
+ if config_file:
542
+ # Process both configurations; the second one will return all the merged
543
+ # configuration options properly processed.
544
+ self._config_input.process_configs(
545
+ self._flow_cls.__name__, "config", config_file, quiet, ds
546
+ )
547
+ config_options = self._config_input.process_configs(
548
+ self._flow_cls.__name__, "config_value", config_value, quiet, ds
549
+ )
550
+
551
+ # At this point, we are like in start() in cli.py -- we obtained the
552
+ # properly processed config_options which we can now use to process
553
+ # the config decorators (including StepMutator/FlowMutator)
554
+ # Note that if CLICK_API_PROCESS_CONFIG is False, we still do this because
555
+ # it will init all parameters (config_options will be None)
556
+ # We ignore any errors if we don't check the configs in the click API.
557
+
558
+ # Init all values in the flow mutators and then process them
559
+ for decorator in self._flow_cls._flow_state[FlowStateItems.FLOW_MUTATORS]:
560
+ decorator.external_init()
561
+
562
+ new_cls = self._flow_cls._process_config_decorators(
563
+ config_options, process_configs=CLICK_API_PROCESS_CONFIG
564
+ )
565
+ if new_cls:
566
+ self._flow_cls = new_cls
567
+
568
+ for _, param in self._flow_cls._get_parameters():
569
+ if param.IS_CONFIG_PARAMETER:
570
+ continue
571
+ self._cached_computed_parameters.append(param)
572
+ return self._cached_computed_parameters
573
+
574
+
575
+ def extract_all_params(cmd_obj: Union[click.Command, click.Group]):
576
+ arg_params_sigs = OrderedDict()
577
+ opt_params_sigs = OrderedDict()
578
+ params_sigs = OrderedDict()
579
+
580
+ arg_parameters = OrderedDict()
581
+ opt_parameters = OrderedDict()
582
+ annotations = OrderedDict()
583
+ defaults = OrderedDict()
584
+
585
+ for each_param in cmd_obj.params:
586
+ if isinstance(each_param, click.Argument):
587
+ (
588
+ arg_params_sigs[each_param.name],
589
+ annotations[each_param.name],
590
+ ) = get_inspect_param_obj(each_param, inspect.Parameter.POSITIONAL_ONLY)
591
+ arg_parameters[each_param.name] = each_param
592
+ elif isinstance(each_param, click.Option):
593
+ (
594
+ opt_params_sigs[each_param.name],
595
+ annotations[each_param.name],
596
+ ) = get_inspect_param_obj(each_param, inspect.Parameter.KEYWORD_ONLY)
597
+ opt_parameters[each_param.name] = each_param
598
+
599
+ defaults[each_param.name] = each_param.default
600
+
601
+ # first, fill in positional arguments
602
+ for name, each_arg_param in arg_params_sigs.items():
603
+ params_sigs[name] = each_arg_param
604
+ # then, fill in keyword arguments
605
+ for name, each_opt_param in opt_params_sigs.items():
606
+ params_sigs[name] = each_opt_param
607
+
608
+ return params_sigs, arg_parameters, opt_parameters, annotations, defaults
609
+
610
+
611
+ def extract_group(cmd_obj: click.Group, flow_parameters: List[Parameter]) -> Callable:
612
+ class_dict = {"__module__": "metaflow", "_API_NAME": cmd_obj.name}
613
+ for _, sub_cmd_obj in cmd_obj.commands.items():
614
+ if isinstance(sub_cmd_obj, click.Group):
615
+ # recursion
616
+ class_dict[sub_cmd_obj.name] = extract_group(sub_cmd_obj, flow_parameters)
617
+ elif isinstance(sub_cmd_obj, click.Command):
618
+ class_dict[sub_cmd_obj.name] = extract_command(sub_cmd_obj, flow_parameters)
619
+ else:
620
+ raise RuntimeError(
621
+ "Cannot handle %s of type %s" % (sub_cmd_obj.name, type(sub_cmd_obj))
622
+ )
623
+
624
+ resulting_class = type(cmd_obj.name, (MetaflowAPI,), class_dict)
625
+ resulting_class.__name__ = cmd_obj.name
626
+
627
+ (
628
+ params_sigs,
629
+ possible_arg_params,
630
+ possible_opt_params,
631
+ annotations,
632
+ defaults,
633
+ ) = extract_all_params(cmd_obj)
634
+
635
+ def _method(_self, *args, **kwargs):
636
+ method_params = _method_sanity_check(
637
+ possible_arg_params, possible_opt_params, annotations, defaults, **kwargs
638
+ )
639
+ return resulting_class(parent=_self, flow_cls=None, **method_params)
640
+
641
+ m = _method
642
+ m.__name__ = cmd_obj.name
643
+ m.__doc__ = getattr(cmd_obj, "help", None)
644
+ m.__signature__ = inspect.signature(_method).replace(
645
+ parameters=params_sigs.values()
646
+ )
647
+ m.__annotations__ = annotations
648
+ m.__defaults__ = tuple(defaults.values())
649
+
650
+ return m
651
+
652
+
653
+ def extract_command(
654
+ cmd_obj: click.Command, flow_parameters: List[Parameter]
655
+ ) -> Callable:
656
+ if getattr(cmd_obj, "has_flow_params", False):
657
+ for p in flow_parameters[::-1]:
658
+ cmd_obj.params.insert(0, click.Option(("--" + p.name,), **p.kwargs))
659
+
660
+ (
661
+ params_sigs,
662
+ possible_arg_params,
663
+ possible_opt_params,
664
+ annotations,
665
+ defaults,
666
+ ) = extract_all_params(cmd_obj)
667
+
668
+ def _method(_self, *args, **kwargs):
669
+ method_params = _method_sanity_check(
670
+ possible_arg_params, possible_opt_params, annotations, defaults, **kwargs
671
+ )
672
+ _self._chain.append({cmd_obj.name: method_params})
673
+ return _self.execute()
674
+
675
+ m = _method
676
+ m.__name__ = cmd_obj.name
677
+ m.__doc__ = getattr(cmd_obj, "help", None)
678
+ m.__signature__ = inspect.signature(_method).replace(
679
+ parameters=params_sigs.values()
680
+ )
681
+ m.__annotations__ = annotations
682
+ m.__defaults__ = tuple(defaults.values())
683
+
684
+ return m
685
+
686
+
687
+ if __name__ == "__main__":
688
+ from metaflow.cli import start
689
+
690
+ api = MetaflowAPI.from_cli("../try.py", start)
691
+
692
+ command = api(metadata="local").run(
693
+ tags=["abc", "def"],
694
+ decospecs=["kubernetes"],
695
+ max_workers=5,
696
+ alpha=3,
697
+ myfile="path/to/file",
698
+ )
699
+ print(" ".join(command))
700
+
701
+ command = (
702
+ api(metadata="local")
703
+ .kubernetes()
704
+ .step(
705
+ step_name="process",
706
+ code_package_metadata="some_version",
707
+ code_package_sha="some_sha",
708
+ code_package_url="some_url",
709
+ )
710
+ )
711
+ print(" ".join(command))
712
+
713
+ command = api().tag().add(tags=["abc", "def"])
714
+ print(" ".join(command))
715
+
716
+ command = getattr(api(decospecs=["retry"]), "argo-workflows")().create()
717
+ print(" ".join(command))