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
@@ -1,9 +1,11 @@
1
1
  import re
2
+ import json
2
3
 
3
4
  from metaflow import current
4
5
  from metaflow.decorators import FlowDecorator
5
6
  from metaflow.exception import MetaflowException
6
7
  from metaflow.util import is_stringish
8
+ from metaflow.parameters import DeployTimeField, deploy_time_eval
7
9
 
8
10
  # TODO: Support dynamic parameter mapping through a context object that exposes
9
11
  # flow name and user name similar to parameter context
@@ -68,6 +70,128 @@ class TriggerDecorator(FlowDecorator):
68
70
  "options": {},
69
71
  }
70
72
 
73
+ def process_event(self, event):
74
+ """
75
+ Process a single event and return a dictionary if static trigger and a function
76
+ if deploy-time trigger.
77
+
78
+ Parameters
79
+ ----------
80
+ event : Union[str, Dict[str, Any], Callable]
81
+ Event to process
82
+
83
+ Returns
84
+ -------
85
+ Union[Dict[str, Union[str, Callable]], Callable]
86
+ Processed event
87
+
88
+ Raises
89
+ ------
90
+ MetaflowException
91
+ If the event is not in the correct format
92
+ """
93
+ if is_stringish(event):
94
+ return {"name": str(event)}
95
+ elif isinstance(event, dict):
96
+ if "name" not in event:
97
+ raise MetaflowException(
98
+ "The *event* attribute for *@trigger* is missing the *name* key."
99
+ )
100
+ if callable(event["name"]) and not isinstance(
101
+ event["name"], DeployTimeField
102
+ ):
103
+ event["name"] = DeployTimeField(
104
+ "event_name",
105
+ str,
106
+ None,
107
+ event["name"],
108
+ False,
109
+ print_representation=str(event["name"]),
110
+ )
111
+ event["parameters"] = self.process_parameters(
112
+ event.get("parameters", {}), event["name"]
113
+ )
114
+ return event
115
+ elif callable(event) and not isinstance(event, DeployTimeField):
116
+ return DeployTimeField(
117
+ "event",
118
+ [str, dict],
119
+ None,
120
+ event,
121
+ False,
122
+ print_representation=str(event),
123
+ )
124
+ else:
125
+ raise MetaflowException(
126
+ "Incorrect format for *event* attribute in *@trigger* decorator. "
127
+ "Supported formats are string and dictionary - \n"
128
+ "@trigger(event='foo') or @trigger(event={'name': 'foo', "
129
+ "'parameters': {'alpha': 'beta'}})"
130
+ )
131
+
132
+ def process_parameters(self, parameters, event_name):
133
+ """
134
+ Process the parameters for an event and return a dictionary of parameter mappings if
135
+ parameters was statically defined or a function if deploy-time trigger.
136
+
137
+ Parameters
138
+ ----------
139
+ Parameters : Union[Dict[str, str], List[Union[str, Tuple[str, str]]], Callable]
140
+ Parameters to process
141
+
142
+ event_name : Union[str, callable]
143
+ Name of the event
144
+
145
+ Returns
146
+ -------
147
+ Union[Dict[str, str], Callable]
148
+ Processed parameters
149
+
150
+ Raises
151
+ ------
152
+ MetaflowException
153
+ If the parameters are not in the correct format
154
+ """
155
+ new_param_values = {}
156
+ if isinstance(parameters, list):
157
+ for mapping in parameters:
158
+ if is_stringish(mapping):
159
+ # param_name
160
+ new_param_values[mapping] = mapping
161
+ elif isinstance(mapping, tuple) and len(mapping) == 2:
162
+ # (param_name, field_name)
163
+ param_name, field_name = mapping
164
+ if not is_stringish(param_name) or not is_stringish(field_name):
165
+ raise MetaflowException(
166
+ f"The *parameters* attribute for event {event_name} is invalid. "
167
+ "It should be a list/tuple of strings and lists/tuples of size 2."
168
+ )
169
+ new_param_values[param_name] = field_name
170
+ else:
171
+ raise MetaflowException(
172
+ "The *parameters* attribute for event is invalid. "
173
+ "It should be a list/tuple of strings and lists/tuples of size 2"
174
+ )
175
+ elif isinstance(parameters, dict):
176
+ for key, value in parameters.items():
177
+ if not is_stringish(key) or not is_stringish(value):
178
+ raise MetaflowException(
179
+ f"The *parameters* attribute for event {event_name} is invalid. "
180
+ "It should be a dictionary of string keys and string values."
181
+ )
182
+ new_param_values[key] = value
183
+ elif callable(parameters) and not isinstance(parameters, DeployTimeField):
184
+ # func
185
+ return DeployTimeField(
186
+ "parameters",
187
+ [list, dict, tuple],
188
+ None,
189
+ parameters,
190
+ False,
191
+ print_representation=str(parameters),
192
+ )
193
+ return new_param_values
194
+
71
195
  def flow_init(
72
196
  self,
73
197
  flow_name,
@@ -86,41 +210,9 @@ class TriggerDecorator(FlowDecorator):
86
210
  "attributes in *@trigger* decorator."
87
211
  )
88
212
  elif self.attributes["event"]:
89
- # event attribute supports the following formats -
90
- # 1. event='table.prod_db.members'
91
- # 2. event={'name': 'table.prod_db.members',
92
- # 'parameters': {'alpha': 'member_weight'}}
93
- if is_stringish(self.attributes["event"]):
94
- self.triggers.append({"name": str(self.attributes["event"])})
95
- elif isinstance(self.attributes["event"], dict):
96
- if "name" not in self.attributes["event"]:
97
- raise MetaflowException(
98
- "The *event* attribute for *@trigger* is missing the "
99
- "*name* key."
100
- )
101
- param_value = self.attributes["event"].get("parameters", {})
102
- if isinstance(param_value, (list, tuple)):
103
- new_param_value = {}
104
- for mapping in param_value:
105
- if is_stringish(mapping):
106
- new_param_value[mapping] = mapping
107
- elif isinstance(mapping, (list, tuple)) and len(mapping) == 2:
108
- new_param_value[mapping[0]] = mapping[1]
109
- else:
110
- raise MetaflowException(
111
- "The *parameters* attribute for event '%s' is invalid. "
112
- "It should be a list/tuple of strings and lists/tuples "
113
- "of size 2" % self.attributes["event"]["name"]
114
- )
115
- self.attributes["event"]["parameters"] = new_param_value
116
- self.triggers.append(self.attributes["event"])
117
- else:
118
- raise MetaflowException(
119
- "Incorrect format for *event* attribute in *@trigger* decorator. "
120
- "Supported formats are string and dictionary - \n"
121
- "@trigger(event='foo') or @trigger(event={'name': 'foo', "
122
- "'parameters': {'alpha': 'beta'}})"
123
- )
213
+ event = self.attributes["event"]
214
+ processed_event = self.process_event(event)
215
+ self.triggers.append(processed_event)
124
216
  elif self.attributes["events"]:
125
217
  # events attribute supports the following formats -
126
218
  # 1. events=[{'name': 'table.prod_db.members',
@@ -128,43 +220,22 @@ class TriggerDecorator(FlowDecorator):
128
220
  # {'name': 'table.prod_db.metadata',
129
221
  # 'parameters': {'beta': 'grade'}}]
130
222
  if isinstance(self.attributes["events"], list):
223
+ # process every event in events
131
224
  for event in self.attributes["events"]:
132
- if is_stringish(event):
133
- self.triggers.append({"name": str(event)})
134
- elif isinstance(event, dict):
135
- if "name" not in event:
136
- raise MetaflowException(
137
- "One or more events in *events* attribute for "
138
- "*@trigger* are missing the *name* key."
139
- )
140
- param_value = event.get("parameters", {})
141
- if isinstance(param_value, (list, tuple)):
142
- new_param_value = {}
143
- for mapping in param_value:
144
- if is_stringish(mapping):
145
- new_param_value[mapping] = mapping
146
- elif (
147
- isinstance(mapping, (list, tuple))
148
- and len(mapping) == 2
149
- ):
150
- new_param_value[mapping[0]] = mapping[1]
151
- else:
152
- raise MetaflowException(
153
- "The *parameters* attribute for event '%s' is "
154
- "invalid. It should be a list/tuple of strings "
155
- "and lists/tuples of size 2" % event["name"]
156
- )
157
- event["parameters"] = new_param_value
158
- self.triggers.append(event)
159
- else:
160
- raise MetaflowException(
161
- "One or more events in *events* attribute in *@trigger* "
162
- "decorator have an incorrect format. Supported format "
163
- "is dictionary - \n"
164
- "@trigger(events=[{'name': 'foo', 'parameters': {'alpha': "
165
- "'beta'}}, {'name': 'bar', 'parameters': "
166
- "{'gamma': 'kappa'}}])"
167
- )
225
+ processed_event = self.process_event(event)
226
+ self.triggers.append(processed_event)
227
+ elif callable(self.attributes["events"]) and not isinstance(
228
+ self.attributes["events"], DeployTimeField
229
+ ):
230
+ trig = DeployTimeField(
231
+ "events",
232
+ list,
233
+ None,
234
+ self.attributes["events"],
235
+ False,
236
+ print_representation=str(self.attributes["events"]),
237
+ )
238
+ self.triggers.append(trig)
168
239
  else:
169
240
  raise MetaflowException(
170
241
  "Incorrect format for *events* attribute in *@trigger* decorator. "
@@ -178,7 +249,12 @@ class TriggerDecorator(FlowDecorator):
178
249
  raise MetaflowException("No event(s) specified in *@trigger* decorator.")
179
250
 
180
251
  # same event shouldn't occur more than once
181
- names = [x["name"] for x in self.triggers]
252
+ names = [
253
+ x["name"]
254
+ for x in self.triggers
255
+ if not isinstance(x, DeployTimeField)
256
+ and not isinstance(x["name"], DeployTimeField)
257
+ ]
182
258
  if len(names) != len(set(names)):
183
259
  raise MetaflowException(
184
260
  "Duplicate event names defined in *@trigger* decorator."
@@ -188,6 +264,43 @@ class TriggerDecorator(FlowDecorator):
188
264
 
189
265
  # TODO: Handle scenario for local testing using --trigger.
190
266
 
267
+ def format_deploytime_value(self):
268
+ new_triggers = []
269
+
270
+ # First pass to evaluate DeployTimeFields
271
+ for trigger in self.triggers:
272
+ # Case where trigger is a function that returns a list of events
273
+ # Need to do this bc we need to iterate over list later
274
+ if isinstance(trigger, DeployTimeField):
275
+ evaluated_trigger = deploy_time_eval(trigger)
276
+ if isinstance(evaluated_trigger, list):
277
+ for event in evaluated_trigger:
278
+ new_triggers.append(self.process_event(event))
279
+ else:
280
+ new_triggers.append(self.process_event(evaluated_trigger))
281
+ else:
282
+ new_triggers.append(trigger)
283
+
284
+ # Second pass to evaluate names
285
+ for trigger in new_triggers:
286
+ name = trigger.get("name")
287
+ if isinstance(name, DeployTimeField):
288
+ trigger["name"] = deploy_time_eval(name)
289
+ if not is_stringish(trigger["name"]):
290
+ raise MetaflowException(
291
+ f"The *name* attribute for event {trigger} is not a valid string"
292
+ )
293
+
294
+ # third pass to evaluate parameters
295
+ for trigger in new_triggers:
296
+ parameters = trigger.get("parameters", {})
297
+ if isinstance(parameters, DeployTimeField):
298
+ parameters_eval = deploy_time_eval(parameters)
299
+ parameters = self.process_parameters(parameters_eval, trigger["name"])
300
+ trigger["parameters"] = parameters
301
+
302
+ self.triggers = new_triggers
303
+
191
304
 
192
305
  class TriggerOnFinishDecorator(FlowDecorator):
193
306
  """
@@ -246,11 +359,7 @@ class TriggerOnFinishDecorator(FlowDecorator):
246
359
  """
247
360
 
248
361
  name = "trigger_on_finish"
249
- defaults = {
250
- "flow": None, # flow_name or project_flow_name
251
- "flows": [], # flow_names or project_flow_names
252
- "options": {},
253
- }
362
+
254
363
  options = {
255
364
  "trigger": dict(
256
365
  multiple=True,
@@ -258,6 +367,14 @@ class TriggerOnFinishDecorator(FlowDecorator):
258
367
  help="Specify run pathspec for testing @trigger_on_finish locally.",
259
368
  ),
260
369
  }
370
+ defaults = {
371
+ "flow": None, # flow_name or project_flow_name
372
+ "flows": [], # flow_names or project_flow_names
373
+ "options": {},
374
+ # Re-enable if you want to support TL options directly in the decorator like
375
+ # for @project decorator
376
+ # **{k: v["default"] for k, v in options.items()},
377
+ }
261
378
 
262
379
  def flow_init(
263
380
  self,
@@ -278,97 +395,32 @@ class TriggerOnFinishDecorator(FlowDecorator):
278
395
  )
279
396
  elif self.attributes["flow"]:
280
397
  # flow supports the format @trigger_on_finish(flow='FooFlow')
281
- if is_stringish(self.attributes["flow"]):
282
- self.triggers.append(
283
- {
284
- "fq_name": self.attributes["flow"],
285
- }
398
+ flow = self.attributes["flow"]
399
+ if callable(flow) and not isinstance(
400
+ self.attributes["flow"], DeployTimeField
401
+ ):
402
+ trig = DeployTimeField(
403
+ "fq_name",
404
+ [str, dict],
405
+ None,
406
+ flow,
407
+ False,
408
+ print_representation=str(flow),
286
409
  )
287
- elif isinstance(self.attributes["flow"], dict):
288
- if "name" not in self.attributes["flow"]:
289
- raise MetaflowException(
290
- "The *flow* attribute for *@trigger_on_finish* is missing the "
291
- "*name* key."
292
- )
293
- flow_name = self.attributes["flow"]["name"]
294
-
295
- if not is_stringish(flow_name) or "." in flow_name:
296
- raise MetaflowException(
297
- "The *name* attribute of the *flow* is not a valid string"
298
- )
299
- result = {"fq_name": flow_name}
300
- if "project" in self.attributes["flow"]:
301
- if is_stringish(self.attributes["flow"]["project"]):
302
- result["project"] = self.attributes["flow"]["project"]
303
- else:
304
- raise MetaflowException(
305
- "The *project* attribute of the *flow* is not a string"
306
- )
307
- if "project_branch" in self.attributes["flow"]:
308
- if is_stringish(self.attributes["flow"]["project_branch"]):
309
- result["branch"] = self.attributes["flow"]["project_branch"]
310
- else:
311
- raise MetaflowException(
312
- "The *project_branch* attribute of the *flow* is not a string"
313
- )
314
- self.triggers.append(result)
410
+ self.triggers.append(trig)
315
411
  else:
316
- raise MetaflowException(
317
- "Incorrect type for *flow* attribute in *@trigger_on_finish* "
318
- " decorator. Supported type is string or Dict[str, str] - \n"
319
- "@trigger_on_finish(flow='FooFlow') or "
320
- "@trigger_on_finish(flow={'name':'FooFlow', 'project_branch': 'branch'})"
321
- )
412
+ self.triggers.extend(self._parse_static_triggers([flow]))
322
413
  elif self.attributes["flows"]:
323
414
  # flows attribute supports the following formats -
324
415
  # 1. flows=['FooFlow', 'BarFlow']
325
- if isinstance(self.attributes["flows"], list):
326
- for flow in self.attributes["flows"]:
327
- if is_stringish(flow):
328
- self.triggers.append(
329
- {
330
- "fq_name": flow,
331
- }
332
- )
333
- elif isinstance(flow, dict):
334
- if "name" not in flow:
335
- raise MetaflowException(
336
- "One or more flows in the *flows* attribute for "
337
- "*@trigger_on_finish* is missing the "
338
- "*name* key."
339
- )
340
- flow_name = flow["name"]
341
-
342
- if not is_stringish(flow_name) or "." in flow_name:
343
- raise MetaflowException(
344
- "The *name* attribute '%s' is not a valid string"
345
- % str(flow_name)
346
- )
347
- result = {"fq_name": flow_name}
348
- if "project" in flow:
349
- if is_stringish(flow["project"]):
350
- result["project"] = flow["project"]
351
- else:
352
- raise MetaflowException(
353
- "The *project* attribute of the *flow* '%s' is not "
354
- "a string" % flow_name
355
- )
356
- if "project_branch" in flow:
357
- if is_stringish(flow["project_branch"]):
358
- result["branch"] = flow["project_branch"]
359
- else:
360
- raise MetaflowException(
361
- "The *project_branch* attribute of the *flow* %s "
362
- "is not a string" % flow_name
363
- )
364
- self.triggers.append(result)
365
- else:
366
- raise MetaflowException(
367
- "One or more flows in *flows* attribute in "
368
- "*@trigger_on_finish* decorator have an incorrect type. "
369
- "Supported type is string or Dict[str, str]- \n"
370
- "@trigger_on_finish(flows=['FooFlow', 'BarFlow']"
371
- )
416
+ flows = self.attributes["flows"]
417
+ if callable(flows) and not isinstance(flows, DeployTimeField):
418
+ trig = DeployTimeField(
419
+ "flows", list, None, flows, False, print_representation=str(flows)
420
+ )
421
+ self.triggers.append(trig)
422
+ elif isinstance(flows, list):
423
+ self.triggers.extend(self._parse_static_triggers(flows))
372
424
  else:
373
425
  raise MetaflowException(
374
426
  "Incorrect type for *flows* attribute in *@trigger_on_finish* "
@@ -383,37 +435,50 @@ class TriggerOnFinishDecorator(FlowDecorator):
383
435
 
384
436
  # Make triggers @project aware
385
437
  for trigger in self.triggers:
386
- if trigger["fq_name"].count(".") == 0:
387
- # fully qualified name is just the flow name
388
- trigger["flow"] = trigger["fq_name"]
389
- elif trigger["fq_name"].count(".") >= 2:
390
- # fully qualified name is of the format - project.branch.flow_name
391
- trigger["project"], tail = trigger["fq_name"].split(".", maxsplit=1)
392
- trigger["branch"], trigger["flow"] = tail.rsplit(".", maxsplit=1)
393
- else:
394
- raise MetaflowException(
395
- "Incorrect format for *flow* in *@trigger_on_finish* "
396
- "decorator. Specify either just the *flow_name* or a fully "
397
- "qualified name like *project_name.branch_name.flow_name*."
398
- )
399
- # TODO: Also sanity check project and branch names
400
- if not re.match(r"^[A-Za-z0-9_]+$", trigger["flow"]):
401
- raise MetaflowException(
402
- "Invalid flow name *%s* in *@trigger_on_finish* "
403
- "decorator. Only alphanumeric characters and "
404
- "underscores(_) are allowed." % trigger["flow"]
405
- )
438
+ if isinstance(trigger, DeployTimeField):
439
+ continue
440
+ self._parse_fq_name(trigger)
406
441
 
407
442
  self.options = self.attributes["options"]
408
443
 
409
444
  # Handle scenario for local testing using --trigger.
445
+
446
+ # Re-enable this code if you want to support passing trigger directly in the
447
+ # decorator in a way similar to how production and branch are passed in the
448
+ # project decorator.
449
+
450
+ # # This is overkill since default is None for all options but adding this code
451
+ # # to make it safe if other non None-default options are added in the future.
452
+ # for op in options:
453
+ # if (
454
+ # op in self._user_defined_attributes
455
+ # and options[op] != self.defaults[op]
456
+ # and self.attributes[op] != options[op]
457
+ # ):
458
+ # # Exception if:
459
+ # # - the user provides a value in the attributes field
460
+ # # - AND the user provided a value in the command line (non default)
461
+ # # - AND the values are different
462
+ # # Note that this won't raise an error if the user provided the default
463
+ # # value in the command line and provided one in attribute but although
464
+ # # slightly inconsistent, it is not incorrect.
465
+ # raise MetaflowException(
466
+ # "You cannot pass %s as both a command-line argument and an attribute "
467
+ # "of the @trigger_on_finish decorator." % op
468
+ # )
469
+
470
+ # if "trigger" in self._user_defined_attributes:
471
+ # trigger_option = self.attributes["trigger"]
472
+ # else:
473
+ trigger_option = options["trigger"]
474
+
410
475
  self._option_values = options
411
- if options["trigger"]:
476
+ if trigger_option:
412
477
  from metaflow import Run
413
478
  from metaflow.events import Trigger
414
479
 
415
480
  run_objs = []
416
- for run_pathspec in options["trigger"]:
481
+ for run_pathspec in trigger_option:
417
482
  if len(run_pathspec.split("/")) != 2:
418
483
  raise MetaflowException(
419
484
  "Incorrect format for run pathspec for *--trigger*. "
@@ -427,5 +492,98 @@ class TriggerOnFinishDecorator(FlowDecorator):
427
492
  run_objs.append(run_obj)
428
493
  current._update_env({"trigger": Trigger.from_runs(run_objs)})
429
494
 
495
+ @staticmethod
496
+ def _parse_static_triggers(flows):
497
+ results = []
498
+ for flow in flows:
499
+ if is_stringish(flow):
500
+ results.append(
501
+ {
502
+ "fq_name": flow,
503
+ }
504
+ )
505
+ elif isinstance(flow, dict):
506
+ if "name" not in flow:
507
+ if len(flows) > 1:
508
+ raise MetaflowException(
509
+ "One or more flows in the *flows* attribute for "
510
+ "*@trigger_on_finish* is missing the "
511
+ "*name* key."
512
+ )
513
+ raise MetaflowException(
514
+ "The *flow* attribute for *@trigger_on_finish* is missing the "
515
+ "*name* key."
516
+ )
517
+ flow_name = flow["name"]
518
+
519
+ if not is_stringish(flow_name) or "." in flow_name:
520
+ raise MetaflowException(
521
+ f"The *name* attribute of the *flow* {flow_name} is not a valid string"
522
+ )
523
+ result = {"fq_name": flow_name}
524
+ if "project" in flow:
525
+ if is_stringish(flow["project"]):
526
+ result["project"] = flow["project"]
527
+ else:
528
+ raise MetaflowException(
529
+ f"The *project* attribute of the *flow* {flow_name} is not a string"
530
+ )
531
+ if "project_branch" in flow:
532
+ if is_stringish(flow["project_branch"]):
533
+ result["branch"] = flow["project_branch"]
534
+ else:
535
+ raise MetaflowException(
536
+ f"The *project_branch* attribute of the *flow* {flow_name} is not a string"
537
+ )
538
+ results.append(result)
539
+ else:
540
+ if len(flows) > 1:
541
+ raise MetaflowException(
542
+ "One or more flows in the *flows* attribute for "
543
+ "*@trigger_on_finish* decorator have an incorrect type. "
544
+ "Supported type is string or Dict[str, str]- \n"
545
+ "@trigger_on_finish(flows=['FooFlow', 'BarFlow']"
546
+ )
547
+ raise MetaflowException(
548
+ "Incorrect type for *flow* attribute in *@trigger_on_finish* "
549
+ " decorator. Supported type is string or Dict[str, str] - \n"
550
+ "@trigger_on_finish(flow='FooFlow') or "
551
+ "@trigger_on_finish(flow={'name':'FooFlow', 'project_branch': 'branch'})"
552
+ )
553
+ return results
554
+
555
+ def _parse_fq_name(self, trigger):
556
+ if trigger["fq_name"].count(".") == 0:
557
+ # fully qualified name is just the flow name
558
+ trigger["flow"] = trigger["fq_name"]
559
+ elif trigger["fq_name"].count(".") >= 2:
560
+ # fully qualified name is of the format - project.branch.flow_name
561
+ trigger["project"], tail = trigger["fq_name"].split(".", maxsplit=1)
562
+ trigger["branch"], trigger["flow"] = tail.rsplit(".", maxsplit=1)
563
+ else:
564
+ raise MetaflowException(
565
+ "Incorrect format for *flow* in *@trigger_on_finish* "
566
+ "decorator. Specify either just the *flow_name* or a fully "
567
+ "qualified name like *project_name.branch_name.flow_name*."
568
+ )
569
+ if not re.match(r"^[A-Za-z0-9_]+$", trigger["flow"]):
570
+ raise MetaflowException(
571
+ "Invalid flow name *%s* in *@trigger_on_finish* "
572
+ "decorator. Only alphanumeric characters and "
573
+ "underscores(_) are allowed." % trigger["flow"]
574
+ )
575
+
576
+ def format_deploytime_value(self):
577
+ if len(self.triggers) == 1 and isinstance(self.triggers[0], DeployTimeField):
578
+ deploy_value = deploy_time_eval(self.triggers[0])
579
+ if isinstance(deploy_value, list):
580
+ self.triggers = deploy_value
581
+ else:
582
+ self.triggers = [deploy_value]
583
+ triggers = self._parse_static_triggers(self.triggers)
584
+ for trigger in triggers:
585
+ self._parse_fq_name(trigger)
586
+ self.triggers = triggers
587
+
430
588
  def get_top_level_options(self):
431
589
  return list(self._option_values.items())
File without changes
@@ -0,0 +1,46 @@
1
+ from metaflow.decorators import FlowDecorator
2
+ from metaflow.exception import MetaflowException
3
+
4
+
5
+ class ExitHookDecorator(FlowDecorator):
6
+ name = "exit_hook"
7
+ allow_multiple = True
8
+
9
+ defaults = {
10
+ "on_success": [],
11
+ "on_error": [],
12
+ "options": {},
13
+ }
14
+
15
+ def flow_init(
16
+ self, flow, graph, environment, flow_datastore, metadata, logger, echo, options
17
+ ):
18
+ on_success = self.attributes["on_success"]
19
+ on_error = self.attributes["on_error"]
20
+
21
+ if not on_success and not on_error:
22
+ raise MetaflowException(
23
+ "Choose at least one of the options on_success/on_error"
24
+ )
25
+
26
+ self.success_hooks = []
27
+ self.error_hooks = []
28
+ for success_fn in on_success:
29
+ if isinstance(success_fn, str):
30
+ self.success_hooks.append(success_fn)
31
+ elif callable(success_fn):
32
+ self.success_hooks.append(success_fn.__name__)
33
+ else:
34
+ raise ValueError(
35
+ "Exit hooks inside 'on_success' must be a function or a string referring to the function"
36
+ )
37
+
38
+ for error_fn in on_error:
39
+ if isinstance(error_fn, str):
40
+ self.error_hooks.append(error_fn)
41
+ elif callable(error_fn):
42
+ self.error_hooks.append(error_fn.__name__)
43
+ else:
44
+ raise ValueError(
45
+ "Exit hooks inside 'on_error' must be a function or a string referring to the function"
46
+ )