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,264 @@
1
+ import sys
2
+ import json
3
+ from typing import ClassVar, Optional, List
4
+
5
+ from metaflow.plugins.aws.step_functions.step_functions import StepFunctions
6
+ from metaflow.runner.deployer import DeployedFlow, TriggeredRun
7
+
8
+ from metaflow.runner.utils import get_lower_level_group, handle_timeout, temporary_fifo
9
+
10
+
11
+ class StepFunctionsTriggeredRun(TriggeredRun):
12
+ """
13
+ A class representing a triggered AWS Step Functions state machine execution.
14
+ """
15
+
16
+ def terminate(self, **kwargs) -> bool:
17
+ """
18
+ Terminate the running state machine execution.
19
+
20
+ Parameters
21
+ ----------
22
+ authorize : str, optional, default None
23
+ Authorize the termination with a production token.
24
+
25
+ Returns
26
+ -------
27
+ bool
28
+ True if the command was successful, False otherwise.
29
+ """
30
+ _, run_id = self.pathspec.split("/")
31
+
32
+ # every subclass needs to have `self.deployer_kwargs`
33
+ command = get_lower_level_group(
34
+ self.deployer.api,
35
+ self.deployer.top_level_kwargs,
36
+ self.deployer.TYPE,
37
+ self.deployer.deployer_kwargs,
38
+ ).terminate(run_id=run_id, **kwargs)
39
+
40
+ pid = self.deployer.spm.run_command(
41
+ [sys.executable, *command],
42
+ env=self.deployer.env_vars,
43
+ cwd=self.deployer.cwd,
44
+ show_output=self.deployer.show_output,
45
+ )
46
+
47
+ command_obj = self.deployer.spm.get(pid)
48
+ command_obj.sync_wait()
49
+ return command_obj.process.returncode == 0
50
+
51
+
52
+ class StepFunctionsDeployedFlow(DeployedFlow):
53
+ """
54
+ A class representing a deployed AWS Step Functions state machine.
55
+ """
56
+
57
+ TYPE: ClassVar[Optional[str]] = "step-functions"
58
+
59
+ @classmethod
60
+ def list_deployed_flows(cls, flow_name: Optional[str] = None):
61
+ """
62
+ This method is not currently implemented for Step Functions.
63
+
64
+ Raises
65
+ ------
66
+ NotImplementedError
67
+ This method is not implemented for Step Functions.
68
+ """
69
+ raise NotImplementedError(
70
+ "list_deployed_flows is not implemented for StepFunctions"
71
+ )
72
+
73
+ @classmethod
74
+ def from_deployment(cls, identifier: str, metadata: Optional[str] = None):
75
+ """
76
+ This method is not currently implemented for Step Functions.
77
+
78
+ Raises
79
+ ------
80
+ NotImplementedError
81
+ This method is not implemented for Step Functions.
82
+ """
83
+ raise NotImplementedError(
84
+ "from_deployment is not implemented for StepFunctions"
85
+ )
86
+
87
+ @classmethod
88
+ def get_triggered_run(
89
+ cls, identifier: str, run_id: str, metadata: Optional[str] = None
90
+ ):
91
+ """
92
+ This method is not currently implemented for Step Functions.
93
+
94
+ Raises
95
+ ------
96
+ NotImplementedError
97
+ This method is not implemented for Step Functions.
98
+ """
99
+ raise NotImplementedError(
100
+ "get_triggered_run is not implemented for StepFunctions"
101
+ )
102
+
103
+ @property
104
+ def production_token(self: DeployedFlow) -> Optional[str]:
105
+ """
106
+ Get the production token for the deployed flow.
107
+
108
+ Returns
109
+ -------
110
+ str, optional
111
+ The production token, None if it cannot be retrieved.
112
+ """
113
+ try:
114
+ _, production_token = StepFunctions.get_existing_deployment(
115
+ self.deployer.name
116
+ )
117
+ return production_token
118
+ except TypeError:
119
+ return None
120
+
121
+ def list_runs(
122
+ self, states: Optional[List[str]] = None
123
+ ) -> List[StepFunctionsTriggeredRun]:
124
+ """
125
+ List runs of the deployed flow.
126
+
127
+ Parameters
128
+ ----------
129
+ states : List[str], optional, default None
130
+ A list of states to filter the runs by. Allowed values are:
131
+ RUNNING, SUCCEEDED, FAILED, TIMED_OUT, ABORTED.
132
+ If not provided, all states will be considered.
133
+
134
+ Returns
135
+ -------
136
+ List[StepFunctionsTriggeredRun]
137
+ A list of TriggeredRun objects representing the runs of the deployed flow.
138
+
139
+ Raises
140
+ ------
141
+ ValueError
142
+ If any of the provided states are invalid or if there are duplicate states.
143
+ """
144
+ VALID_STATES = {"RUNNING", "SUCCEEDED", "FAILED", "TIMED_OUT", "ABORTED"}
145
+
146
+ if states is None:
147
+ states = []
148
+
149
+ unique_states = set(states)
150
+ if not unique_states.issubset(VALID_STATES):
151
+ invalid_states = unique_states - VALID_STATES
152
+ raise ValueError(
153
+ f"Invalid states found: {invalid_states}. Valid states are: {VALID_STATES}"
154
+ )
155
+
156
+ if len(states) != len(unique_states):
157
+ raise ValueError("Duplicate states are not allowed")
158
+
159
+ triggered_runs = []
160
+ executions = StepFunctions.list(self.deployer.name, states)
161
+
162
+ for e in executions:
163
+ run_id = "sfn-%s" % e["name"]
164
+ tr = StepFunctionsTriggeredRun(
165
+ deployer=self.deployer,
166
+ content=json.dumps(
167
+ {
168
+ "metadata": self.deployer.metadata,
169
+ "pathspec": "/".join((self.deployer.flow_name, run_id)),
170
+ "name": run_id,
171
+ }
172
+ ),
173
+ )
174
+ triggered_runs.append(tr)
175
+
176
+ return triggered_runs
177
+
178
+ def delete(self, **kwargs) -> bool:
179
+ """
180
+ Delete the deployed state machine.
181
+
182
+ Parameters
183
+ ----------
184
+ authorize : str, optional, default None
185
+ Authorize the deletion with a production token.
186
+
187
+ Returns
188
+ -------
189
+ bool
190
+ True if the command was successful, False otherwise.
191
+ """
192
+ command = get_lower_level_group(
193
+ self.deployer.api,
194
+ self.deployer.top_level_kwargs,
195
+ self.deployer.TYPE,
196
+ self.deployer.deployer_kwargs,
197
+ ).delete(**kwargs)
198
+
199
+ pid = self.deployer.spm.run_command(
200
+ [sys.executable, *command],
201
+ env=self.deployer.env_vars,
202
+ cwd=self.deployer.cwd,
203
+ show_output=self.deployer.show_output,
204
+ )
205
+
206
+ command_obj = self.deployer.spm.get(pid)
207
+ command_obj.sync_wait()
208
+ return command_obj.process.returncode == 0
209
+
210
+ def trigger(self, **kwargs) -> StepFunctionsTriggeredRun:
211
+ """
212
+ Trigger a new run for the deployed flow.
213
+
214
+ Parameters
215
+ ----------
216
+ **kwargs : Any
217
+ Additional arguments to pass to the trigger command,
218
+ `Parameters` in particular
219
+
220
+ Returns
221
+ -------
222
+ StepFunctionsTriggeredRun
223
+ The triggered run instance.
224
+
225
+ Raises
226
+ ------
227
+ Exception
228
+ If there is an error during the trigger process.
229
+ """
230
+ with temporary_fifo() as (attribute_file_path, attribute_file_fd):
231
+ # every subclass needs to have `self.deployer_kwargs`
232
+ command = get_lower_level_group(
233
+ self.deployer.api,
234
+ self.deployer.top_level_kwargs,
235
+ self.deployer.TYPE,
236
+ self.deployer.deployer_kwargs,
237
+ ).trigger(deployer_attribute_file=attribute_file_path, **kwargs)
238
+
239
+ pid = self.deployer.spm.run_command(
240
+ [sys.executable, *command],
241
+ env=self.deployer.env_vars,
242
+ cwd=self.deployer.cwd,
243
+ show_output=self.deployer.show_output,
244
+ )
245
+
246
+ command_obj = self.deployer.spm.get(pid)
247
+ content = handle_timeout(
248
+ attribute_file_fd, command_obj, self.deployer.file_read_timeout
249
+ )
250
+
251
+ command_obj.sync_wait()
252
+ if command_obj.process.returncode == 0:
253
+ return StepFunctionsTriggeredRun(
254
+ deployer=self.deployer, content=content
255
+ )
256
+
257
+ raise Exception(
258
+ "Error triggering %s on %s for %s"
259
+ % (
260
+ self.deployer.name,
261
+ self.deployer.TYPE,
262
+ self.deployer.flow_file,
263
+ )
264
+ )
@@ -10,4 +10,4 @@ class MetaflowAzureResourceError(MetaflowException):
10
10
 
11
11
 
12
12
  class MetaflowAzurePackageError(MetaflowException):
13
- headline = "Missing required packages 'azure-identity' and 'azure-storage-blob'"
13
+ headline = "Missing required packages 'azure-identity' and 'azure-storage-blob' and 'azure-keyvault-secrets'"
@@ -0,0 +1,240 @@
1
+ from metaflow.plugins.secrets import SecretsProvider
2
+ import re
3
+ import base64
4
+ import codecs
5
+ from urllib.parse import urlparse
6
+ from metaflow.exception import MetaflowException
7
+ import sys
8
+ from metaflow.metaflow_config import AZURE_KEY_VAULT_PREFIX
9
+ from metaflow.plugins.azure.azure_credential import (
10
+ create_cacheable_azure_credential,
11
+ )
12
+
13
+
14
+ class MetaflowAzureKeyVaultBadVault(MetaflowException):
15
+ """Raised when the secretid is fully qualified but does not have the right key vault domain"""
16
+
17
+
18
+ class MetaflowAzureKeyVaultBadSecretType(MetaflowException):
19
+ """Raised when the secret type is anything except secrets"""
20
+
21
+
22
+ class MetaflowAzureKeyVaultBadSecretPath(MetaflowException):
23
+ """Raised when the secret path does not match to expected length"""
24
+
25
+
26
+ class MetaflowAzureKeyVaultBadSecretName(MetaflowException):
27
+ """Raised when the secret name does not match expected pattern"""
28
+
29
+
30
+ class MetaflowAzureKeyVaultBadSecretVersion(MetaflowException):
31
+ """Raised when the secret version does not match expected pattern"""
32
+
33
+
34
+ class MetaflowAzureKeyVaultBadSecret(MetaflowException):
35
+ """Raised when the secret does not match supported patterns in Metaflow"""
36
+
37
+
38
+ class AzureKeyVaultSecretsProvider(SecretsProvider):
39
+ TYPE = "az-key-vault"
40
+ key_vault_domains = [
41
+ ".vault.azure.net",
42
+ ".vault.azure.cn",
43
+ ".vault.usgovcloudapi.net",
44
+ ".vault.microsoftazure.de",
45
+ ]
46
+ supported_vault_object_types = ["secrets"]
47
+
48
+ # https://learn.microsoft.com/en-us/azure/key-vault/general/about-keys-secrets-certificates has details on vault name structure
49
+ # Vault name and Managed HSM pool name must be a 3-24 character string, containing only 0-9, a-z, A-Z, and not consecutive -.
50
+ def _is_valid_vault_name(self, vault_name):
51
+ vault_name_pattern = r"^(?!.*--)[a-zA-Z0-9-]{3,24}$"
52
+ return re.match(vault_name_pattern, vault_name) is not None
53
+
54
+ # The type of the object can be, "keys", "secrets", or "certificates".
55
+ # Currently only secrets will be supported
56
+ def _is_valid_object_type(self, secret_type):
57
+ for type in self.supported_vault_object_types:
58
+ if secret_type == type:
59
+ return True
60
+ return False
61
+
62
+ # The secret name must be a 1-127 character string, starting with a letter and containing only 0-9, a-z, A-Z, and -.
63
+ def _is_valid_secret_name(self, secret_name):
64
+ secret_name_pattern = r"^[a-zA-Z][a-zA-Z0-9-]{0,126}$"
65
+ return re.match(secret_name_pattern, secret_name) is not None
66
+
67
+ # An object-version is a system-generated, 32 character string identifier that is optionally used to address a unique version of an object.
68
+ def _is_valid_object_version(self, secret_version):
69
+ object_version_pattern = r"^[a-zA-Z0-9]{32}$"
70
+ return re.match(object_version_pattern, secret_version) is not None
71
+
72
+ # This function will check if the secret_id is fully qualified url. It will return True iff the secret_id is of the form:
73
+ # https://myvault.vault.azure.net/secrets/mysecret/ec96f02080254f109c51a1f14cdb1931 OR
74
+ # https://myvault.vault.azure.net/secrets/mysecret/
75
+ # validating the above as per recommendations in https://devblogs.microsoft.com/azure-sdk/guidance-for-applications-using-the-key-vault-libraries/
76
+ def _is_secret_id_fully_qualified_url(self, secret_id):
77
+ # if the secret_id is None/empty/does not start with https then return false
78
+ if secret_id is None or secret_id == "" or not secret_id.startswith("https://"):
79
+ return False
80
+ try:
81
+ parsed_vault_url = urlparse(secret_id)
82
+ except ValueError:
83
+ print("invalid vault url", file=sys.stderr)
84
+ return False
85
+ hostname = parsed_vault_url.netloc
86
+
87
+ k_v_domain_found = False
88
+ actual_k_v_domain = ""
89
+ for k_v_domain in self.key_vault_domains:
90
+ if k_v_domain in hostname:
91
+ k_v_domain_found = True
92
+ actual_k_v_domain = k_v_domain
93
+ break
94
+ if not k_v_domain_found:
95
+ # the secret_id started with https:// however the key_vault_domains
96
+ # were not present in the secret_id which means
97
+ raise MetaflowAzureKeyVaultBadVault("bad key vault domain %s" % secret_id)
98
+
99
+ # given the secret_id seems to have a valid key vault domain
100
+ # lets verify that the vault name corresponds to its regex.
101
+ vault_name = hostname[: -len(actual_k_v_domain)]
102
+ # verify the vault name pattern
103
+ if not self._is_valid_vault_name(vault_name):
104
+ raise MetaflowAzureKeyVaultBadVault("bad key vault name %s" % vault_name)
105
+
106
+ path_parts = parsed_vault_url.path.strip("/").split("/")
107
+ total_path_parts = len(path_parts)
108
+ if total_path_parts < 2 or total_path_parts > 3:
109
+ raise MetaflowAzureKeyVaultBadSecretPath(
110
+ "bad secret uri path %s" % path_parts
111
+ )
112
+
113
+ object_type = path_parts[0]
114
+ if not self._is_valid_object_type(object_type):
115
+ raise MetaflowAzureKeyVaultBadSecretType("bad secret type %s" % object_type)
116
+
117
+ secret_name = path_parts[1]
118
+ if not self._is_valid_secret_name(secret_name=secret_name):
119
+ raise MetaflowAzureKeyVaultBadSecretName("bad secret name %s" % secret_name)
120
+
121
+ if total_path_parts == 3:
122
+ if not self._is_valid_object_version(path_parts[2]):
123
+ raise MetaflowAzureKeyVaultBadSecretVersion(
124
+ "bad secret version %s" % path_parts[2]
125
+ )
126
+
127
+ return True
128
+
129
+ # This function will validate the correctness of the partial secret id.
130
+ # It will attempt to construct the fully qualified secret URL internally and
131
+ # call the _is_secret_id_fully_qualified_url to check validity
132
+ def _is_partial_secret_valid(self, secret_id):
133
+ secret_parts = secret_id.strip("/").split("/")
134
+ total_secret_parts = len(secret_parts)
135
+ if total_secret_parts < 1 or total_secret_parts > 2:
136
+ return False
137
+
138
+ # since the secret_id is supposedly a partial id, the AZURE_KEY_VAULT_PREFIX
139
+ # must be set.
140
+ if not AZURE_KEY_VAULT_PREFIX:
141
+ raise ValueError(
142
+ "cannot use simple secret id without setting METAFLOW_AZURE_KEY_VAULT_PREFIX. %s"
143
+ % AZURE_KEY_VAULT_PREFIX
144
+ )
145
+ domain = AZURE_KEY_VAULT_PREFIX.rstrip("/")
146
+ full_secret = "%s/secrets/%s" % (domain, secret_id)
147
+ if not self._is_secret_id_fully_qualified_url(full_secret):
148
+ return False
149
+
150
+ return True
151
+
152
+ def _sanitize_key_as_env_var(self, key):
153
+ """
154
+ Sanitize a key as an environment variable name.
155
+ This is purely a convenience trade-off to cover common cases well, vs. introducing
156
+ ambiguities (e.g. did the final '_' come from '.', or '-' or is original?).
157
+
158
+ 1/27/2023(jackie):
159
+
160
+ We start with few rules and should *sparingly* add more over time.
161
+ Also, it's TBD whether all possible providers will share the same sanitization logic.
162
+ Therefore we will keep this function private for now
163
+ """
164
+ return key.replace("-", "_").replace(".", "_").replace("/", "_")
165
+
166
+ def get_secret_as_dict(self, secret_id, options={}, role=None):
167
+ # https://learn.microsoft.com/en-us/azure/app-service/app-service-key-vault-references?tabs=azure-cli has a lot of details on
168
+ # the patterns used in key vault
169
+ # Vault names and Managed HSM pool names are selected by the user and are globally unique.
170
+ # Vault name and Managed HSM pool name must be a 3-24 character string, containing only 0-9, a-z, A-Z, and not consecutive -.
171
+ # object-type The type of the object. As of 05/08/24 only "secrets", are supported
172
+ # object-name An object-name is a user provided name for and must be unique within a key vault. The name must be a 1-127 character string, starting with a letter and containing only 0-9, a-z, A-Z, and -.
173
+ # object-version An object-version is a system-generated, 32 character string identifier that is optionally used to address a unique version of an object.
174
+
175
+ # We allow these forms of secret_id:
176
+ #
177
+ # 1. Full path like https://<key-vault-name><.vault-domain>/secrets/<secret-name>/<secret-version>. This is what you
178
+ # see in Azure portal and is easy to copy paste.
179
+ #
180
+ # 2. Full path but without the version like https://<key-vault-name><.vault-domain>/secrets/<secret-name>
181
+ #
182
+ # 3. Simple string like mysecret. This corresponds to the SecretName.
183
+ #
184
+ # 4. Simple string with <secret-name>/<secret-version> suffix like mysecret/123
185
+
186
+ # The latter two forms require METAFLOW_AZURE_KEY_VAULT_PREFIX to be set.
187
+
188
+ # if the secret_id is None/empty/does not start with https then return false
189
+ if secret_id is None or secret_id == "":
190
+ raise MetaflowAzureKeyVaultBadSecret("empty secret id is not supported")
191
+
192
+ # check if the passed in secret is a short-form ( #3/#4 in the above comment)
193
+ if not secret_id.startswith("https://"):
194
+ # check if the secret_id is of form `secret_name` OR `secret_name/secret_version`
195
+ if not self._is_partial_secret_valid(secret_id=secret_id):
196
+ raise MetaflowAzureKeyVaultBadSecret(
197
+ "unsupported partial secret %s" % secret_id
198
+ )
199
+
200
+ domain = AZURE_KEY_VAULT_PREFIX.rstrip("/")
201
+ full_secret = "%s/secrets/%s" % (domain, secret_id)
202
+
203
+ # if the secret id is passed as a URL - then check if the url is fully qualified
204
+ if secret_id.startswith("https://"):
205
+ if not self._is_secret_id_fully_qualified_url(secret_id=secret_id):
206
+ raise MetaflowException("unsupported secret %s" % secret_id)
207
+ full_secret = secret_id
208
+
209
+ # at this point I know that the secret URL is good so we can start creating the Secret Client
210
+ az_credentials = create_cacheable_azure_credential()
211
+ res = urlparse(full_secret)
212
+ az_vault_url = "%s://%s" % (
213
+ res.scheme,
214
+ res.netloc,
215
+ ) # https://myvault.vault.azure.net
216
+ secret_data = res.path.strip("/").split("/")[1:]
217
+ secret_name = secret_data[0]
218
+ secret_version = None
219
+ if len(secret_data) > 1:
220
+ secret_version = secret_data[1]
221
+
222
+ from azure.keyvault.secrets import SecretClient
223
+
224
+ client = SecretClient(vault_url=az_vault_url, credential=az_credentials)
225
+
226
+ key_vault_secret_val = client.get_secret(
227
+ name=secret_name, version=secret_version
228
+ )
229
+
230
+ result = {}
231
+
232
+ if options.get("env_var_name") is not None:
233
+ env_var_name = options["env_var_name"]
234
+ sanitized_key = self._sanitize_key_as_env_var(env_var_name)
235
+ else:
236
+ sanitized_key = self._sanitize_key_as_env_var(key_vault_secret_val.name)
237
+
238
+ response_payload = key_vault_secret_val.value
239
+ result[sanitized_key] = response_payload
240
+ return result
@@ -70,7 +70,7 @@ class AzureTail(object):
70
70
  if data is None:
71
71
  return None
72
72
  if data:
73
- buf = BytesIO(data)
73
+ buf = BytesIO(self._tail + data)
74
74
  self._pos += len(data)
75
75
  self._tail = b""
76
76
  return buf
@@ -8,6 +8,8 @@ from metaflow.exception import MetaflowException, MetaflowInternalError
8
8
 
9
9
 
10
10
  class Azure(object):
11
+ TYPE = "azure"
12
+
11
13
  @classmethod
12
14
  def get_root_from_config(cls, echo, create_on_absent=True):
13
15
  from metaflow.metaflow_config import DATATOOLS_AZUREROOT