ob-metaflow 2.12.30.2__py2.py3-none-any.whl → 2.13.6.1__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.
Potentially problematic release.
This version of ob-metaflow might be problematic. Click here for more details.
- metaflow/__init__.py +3 -0
- metaflow/cards.py +1 -0
- metaflow/cli.py +185 -717
- metaflow/cli_args.py +17 -0
- metaflow/cli_components/__init__.py +0 -0
- metaflow/cli_components/dump_cmd.py +96 -0
- metaflow/cli_components/init_cmd.py +51 -0
- metaflow/cli_components/run_cmds.py +362 -0
- metaflow/cli_components/step_cmd.py +176 -0
- metaflow/cli_components/utils.py +140 -0
- metaflow/cmd/develop/stub_generator.py +9 -2
- metaflow/datastore/flow_datastore.py +2 -2
- metaflow/decorators.py +63 -2
- metaflow/exception.py +8 -2
- metaflow/extension_support/plugins.py +42 -27
- metaflow/flowspec.py +176 -23
- metaflow/graph.py +28 -27
- metaflow/includefile.py +50 -22
- metaflow/lint.py +35 -20
- metaflow/metadata_provider/heartbeat.py +23 -8
- metaflow/metaflow_config.py +10 -1
- metaflow/multicore_utils.py +31 -14
- metaflow/package.py +17 -3
- metaflow/parameters.py +97 -25
- metaflow/plugins/__init__.py +22 -0
- metaflow/plugins/airflow/airflow.py +18 -17
- metaflow/plugins/airflow/airflow_cli.py +1 -0
- metaflow/plugins/argo/argo_client.py +0 -2
- metaflow/plugins/argo/argo_workflows.py +195 -132
- metaflow/plugins/argo/argo_workflows_cli.py +1 -1
- metaflow/plugins/argo/argo_workflows_decorator.py +2 -4
- metaflow/plugins/argo/argo_workflows_deployer_objects.py +51 -9
- metaflow/plugins/argo/jobset_input_paths.py +0 -1
- metaflow/plugins/aws/aws_utils.py +6 -1
- metaflow/plugins/aws/batch/batch_client.py +1 -3
- metaflow/plugins/aws/batch/batch_decorator.py +13 -13
- metaflow/plugins/aws/secrets_manager/aws_secrets_manager_secrets_provider.py +13 -10
- metaflow/plugins/aws/step_functions/dynamo_db_client.py +0 -3
- metaflow/plugins/aws/step_functions/production_token.py +1 -1
- metaflow/plugins/aws/step_functions/step_functions.py +33 -1
- metaflow/plugins/aws/step_functions/step_functions_cli.py +1 -1
- metaflow/plugins/aws/step_functions/step_functions_decorator.py +0 -1
- metaflow/plugins/aws/step_functions/step_functions_deployer_objects.py +7 -9
- metaflow/plugins/cards/card_cli.py +7 -2
- metaflow/plugins/cards/card_creator.py +1 -0
- metaflow/plugins/cards/card_decorator.py +79 -8
- metaflow/plugins/cards/card_modules/basic.py +56 -5
- metaflow/plugins/cards/card_modules/card.py +16 -1
- metaflow/plugins/cards/card_modules/components.py +64 -16
- metaflow/plugins/cards/card_modules/main.js +27 -25
- metaflow/plugins/cards/card_modules/test_cards.py +4 -4
- metaflow/plugins/cards/component_serializer.py +1 -1
- metaflow/plugins/datatools/s3/s3.py +12 -4
- metaflow/plugins/datatools/s3/s3op.py +3 -3
- metaflow/plugins/events_decorator.py +338 -186
- metaflow/plugins/kubernetes/kube_utils.py +84 -1
- metaflow/plugins/kubernetes/kubernetes.py +40 -92
- metaflow/plugins/kubernetes/kubernetes_cli.py +32 -7
- metaflow/plugins/kubernetes/kubernetes_decorator.py +76 -4
- metaflow/plugins/kubernetes/kubernetes_job.py +23 -20
- metaflow/plugins/kubernetes/kubernetes_jobsets.py +41 -20
- metaflow/plugins/kubernetes/spot_metadata_cli.py +69 -0
- metaflow/plugins/kubernetes/spot_monitor_sidecar.py +109 -0
- metaflow/plugins/parallel_decorator.py +4 -1
- metaflow/plugins/project_decorator.py +33 -5
- metaflow/plugins/pypi/bootstrap.py +249 -81
- metaflow/plugins/pypi/conda_decorator.py +20 -10
- metaflow/plugins/pypi/conda_environment.py +83 -27
- metaflow/plugins/pypi/micromamba.py +82 -37
- metaflow/plugins/pypi/pip.py +9 -6
- metaflow/plugins/pypi/pypi_decorator.py +11 -9
- metaflow/plugins/pypi/utils.py +4 -2
- metaflow/plugins/timeout_decorator.py +2 -2
- metaflow/runner/click_api.py +240 -50
- metaflow/runner/deployer.py +1 -1
- metaflow/runner/deployer_impl.py +12 -11
- metaflow/runner/metaflow_runner.py +68 -34
- metaflow/runner/nbdeploy.py +2 -0
- metaflow/runner/nbrun.py +1 -1
- metaflow/runner/subprocess_manager.py +61 -10
- metaflow/runner/utils.py +208 -44
- metaflow/runtime.py +216 -112
- metaflow/sidecar/sidecar_worker.py +1 -1
- metaflow/tracing/tracing_modules.py +4 -1
- metaflow/user_configs/__init__.py +0 -0
- metaflow/user_configs/config_decorators.py +563 -0
- metaflow/user_configs/config_options.py +548 -0
- metaflow/user_configs/config_parameters.py +436 -0
- metaflow/util.py +22 -0
- metaflow/version.py +1 -1
- {ob_metaflow-2.12.30.2.dist-info → ob_metaflow-2.13.6.1.dist-info}/METADATA +12 -3
- {ob_metaflow-2.12.30.2.dist-info → ob_metaflow-2.13.6.1.dist-info}/RECORD +96 -84
- {ob_metaflow-2.12.30.2.dist-info → ob_metaflow-2.13.6.1.dist-info}/WHEEL +1 -1
- {ob_metaflow-2.12.30.2.dist-info → ob_metaflow-2.13.6.1.dist-info}/LICENSE +0 -0
- {ob_metaflow-2.12.30.2.dist-info → ob_metaflow-2.13.6.1.dist-info}/entry_points.txt +0 -0
- {ob_metaflow-2.12.30.2.dist-info → ob_metaflow-2.13.6.1.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,75 @@ class TriggerDecorator(FlowDecorator):
|
|
|
68
70
|
"options": {},
|
|
69
71
|
}
|
|
70
72
|
|
|
73
|
+
def process_event_name(self, event):
|
|
74
|
+
if is_stringish(event):
|
|
75
|
+
return {"name": str(event)}
|
|
76
|
+
elif isinstance(event, dict):
|
|
77
|
+
if "name" not in event:
|
|
78
|
+
raise MetaflowException(
|
|
79
|
+
"The *event* attribute for *@trigger* is missing the *name* key."
|
|
80
|
+
)
|
|
81
|
+
if callable(event["name"]) and not isinstance(
|
|
82
|
+
event["name"], DeployTimeField
|
|
83
|
+
):
|
|
84
|
+
event["name"] = DeployTimeField(
|
|
85
|
+
"event_name", str, None, event["name"], False
|
|
86
|
+
)
|
|
87
|
+
event["parameters"] = self.process_parameters(event.get("parameters", {}))
|
|
88
|
+
return event
|
|
89
|
+
elif callable(event) and not isinstance(event, DeployTimeField):
|
|
90
|
+
return DeployTimeField("event", [str, dict], None, event, False)
|
|
91
|
+
else:
|
|
92
|
+
raise MetaflowException(
|
|
93
|
+
"Incorrect format for *event* attribute in *@trigger* decorator. "
|
|
94
|
+
"Supported formats are string and dictionary - \n"
|
|
95
|
+
"@trigger(event='foo') or @trigger(event={'name': 'foo', "
|
|
96
|
+
"'parameters': {'alpha': 'beta'}})"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
def process_parameters(self, parameters):
|
|
100
|
+
new_param_values = {}
|
|
101
|
+
if isinstance(parameters, (list, tuple)):
|
|
102
|
+
for mapping in parameters:
|
|
103
|
+
if is_stringish(mapping):
|
|
104
|
+
new_param_values[mapping] = mapping
|
|
105
|
+
elif callable(mapping) and not isinstance(mapping, DeployTimeField):
|
|
106
|
+
mapping = DeployTimeField(
|
|
107
|
+
"parameter_val", str, None, mapping, False
|
|
108
|
+
)
|
|
109
|
+
new_param_values[mapping] = mapping
|
|
110
|
+
elif isinstance(mapping, (list, tuple)) and len(mapping) == 2:
|
|
111
|
+
if callable(mapping[0]) and not isinstance(
|
|
112
|
+
mapping[0], DeployTimeField
|
|
113
|
+
):
|
|
114
|
+
mapping[0] = DeployTimeField(
|
|
115
|
+
"parameter_val", str, None, mapping[0], False
|
|
116
|
+
)
|
|
117
|
+
if callable(mapping[1]) and not isinstance(
|
|
118
|
+
mapping[1], DeployTimeField
|
|
119
|
+
):
|
|
120
|
+
mapping[1] = DeployTimeField(
|
|
121
|
+
"parameter_val", str, None, mapping[1], False
|
|
122
|
+
)
|
|
123
|
+
new_param_values[mapping[0]] = mapping[1]
|
|
124
|
+
else:
|
|
125
|
+
raise MetaflowException(
|
|
126
|
+
"The *parameters* attribute for event is invalid. "
|
|
127
|
+
"It should be a list/tuple of strings and lists/tuples of size 2"
|
|
128
|
+
)
|
|
129
|
+
elif callable(parameters) and not isinstance(parameters, DeployTimeField):
|
|
130
|
+
return DeployTimeField(
|
|
131
|
+
"parameters", [list, dict, tuple], None, parameters, False
|
|
132
|
+
)
|
|
133
|
+
elif isinstance(parameters, dict):
|
|
134
|
+
for key, value in parameters.items():
|
|
135
|
+
if callable(key) and not isinstance(key, DeployTimeField):
|
|
136
|
+
key = DeployTimeField("flow_parameter", str, None, key, False)
|
|
137
|
+
if callable(value) and not isinstance(value, DeployTimeField):
|
|
138
|
+
value = DeployTimeField("signal_parameter", str, None, value, False)
|
|
139
|
+
new_param_values[key] = value
|
|
140
|
+
return new_param_values
|
|
141
|
+
|
|
71
142
|
def flow_init(
|
|
72
143
|
self,
|
|
73
144
|
flow_name,
|
|
@@ -86,41 +157,9 @@ class TriggerDecorator(FlowDecorator):
|
|
|
86
157
|
"attributes in *@trigger* decorator."
|
|
87
158
|
)
|
|
88
159
|
elif self.attributes["event"]:
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
)
|
|
160
|
+
event = self.attributes["event"]
|
|
161
|
+
processed_event = self.process_event_name(event)
|
|
162
|
+
self.triggers.append(processed_event)
|
|
124
163
|
elif self.attributes["events"]:
|
|
125
164
|
# events attribute supports the following formats -
|
|
126
165
|
# 1. events=[{'name': 'table.prod_db.members',
|
|
@@ -128,43 +167,17 @@ class TriggerDecorator(FlowDecorator):
|
|
|
128
167
|
# {'name': 'table.prod_db.metadata',
|
|
129
168
|
# 'parameters': {'beta': 'grade'}}]
|
|
130
169
|
if isinstance(self.attributes["events"], list):
|
|
170
|
+
# process every event in events
|
|
131
171
|
for event in self.attributes["events"]:
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
)
|
|
172
|
+
processed_event = self.process_event_name(event)
|
|
173
|
+
self.triggers.append(processed_event)
|
|
174
|
+
elif callable(self.attributes["events"]) and not isinstance(
|
|
175
|
+
self.attributes["events"], DeployTimeField
|
|
176
|
+
):
|
|
177
|
+
trig = DeployTimeField(
|
|
178
|
+
"events", list, None, self.attributes["events"], False
|
|
179
|
+
)
|
|
180
|
+
self.triggers.append(trig)
|
|
168
181
|
else:
|
|
169
182
|
raise MetaflowException(
|
|
170
183
|
"Incorrect format for *events* attribute in *@trigger* decorator. "
|
|
@@ -178,7 +191,12 @@ class TriggerDecorator(FlowDecorator):
|
|
|
178
191
|
raise MetaflowException("No event(s) specified in *@trigger* decorator.")
|
|
179
192
|
|
|
180
193
|
# same event shouldn't occur more than once
|
|
181
|
-
names = [
|
|
194
|
+
names = [
|
|
195
|
+
x["name"]
|
|
196
|
+
for x in self.triggers
|
|
197
|
+
if not isinstance(x, DeployTimeField)
|
|
198
|
+
and not isinstance(x["name"], DeployTimeField)
|
|
199
|
+
]
|
|
182
200
|
if len(names) != len(set(names)):
|
|
183
201
|
raise MetaflowException(
|
|
184
202
|
"Duplicate event names defined in *@trigger* decorator."
|
|
@@ -188,6 +206,104 @@ class TriggerDecorator(FlowDecorator):
|
|
|
188
206
|
|
|
189
207
|
# TODO: Handle scenario for local testing using --trigger.
|
|
190
208
|
|
|
209
|
+
def format_deploytime_value(self):
|
|
210
|
+
new_triggers = []
|
|
211
|
+
for trigger in self.triggers:
|
|
212
|
+
# Case where trigger is a function that returns a list of events
|
|
213
|
+
# Need to do this bc we need to iterate over list later
|
|
214
|
+
if isinstance(trigger, DeployTimeField):
|
|
215
|
+
evaluated_trigger = deploy_time_eval(trigger)
|
|
216
|
+
if isinstance(evaluated_trigger, dict):
|
|
217
|
+
trigger = evaluated_trigger
|
|
218
|
+
elif isinstance(evaluated_trigger, str):
|
|
219
|
+
trigger = {"name": evaluated_trigger}
|
|
220
|
+
if isinstance(evaluated_trigger, list):
|
|
221
|
+
for trig in evaluated_trigger:
|
|
222
|
+
if is_stringish(trig):
|
|
223
|
+
new_triggers.append({"name": trig})
|
|
224
|
+
else: # dict or another deploytimefield
|
|
225
|
+
new_triggers.append(trig)
|
|
226
|
+
else:
|
|
227
|
+
new_triggers.append(trigger)
|
|
228
|
+
else:
|
|
229
|
+
new_triggers.append(trigger)
|
|
230
|
+
|
|
231
|
+
self.triggers = new_triggers
|
|
232
|
+
for trigger in self.triggers:
|
|
233
|
+
old_trigger = trigger
|
|
234
|
+
trigger_params = trigger.get("parameters", {})
|
|
235
|
+
# Case where param is a function (can return list or dict)
|
|
236
|
+
if isinstance(trigger_params, DeployTimeField):
|
|
237
|
+
trigger_params = deploy_time_eval(trigger_params)
|
|
238
|
+
# If params is a list of strings, convert to dict with same key and value
|
|
239
|
+
if isinstance(trigger_params, (list, tuple)):
|
|
240
|
+
new_trigger_params = {}
|
|
241
|
+
for mapping in trigger_params:
|
|
242
|
+
if is_stringish(mapping) or callable(mapping):
|
|
243
|
+
new_trigger_params[mapping] = mapping
|
|
244
|
+
elif callable(mapping) and not isinstance(mapping, DeployTimeField):
|
|
245
|
+
mapping = DeployTimeField(
|
|
246
|
+
"parameter_val", str, None, mapping, False
|
|
247
|
+
)
|
|
248
|
+
new_trigger_params[mapping] = mapping
|
|
249
|
+
elif isinstance(mapping, (list, tuple)) and len(mapping) == 2:
|
|
250
|
+
if callable(mapping[0]) and not isinstance(
|
|
251
|
+
mapping[0], DeployTimeField
|
|
252
|
+
):
|
|
253
|
+
mapping[0] = DeployTimeField(
|
|
254
|
+
"parameter_val",
|
|
255
|
+
str,
|
|
256
|
+
None,
|
|
257
|
+
mapping[1],
|
|
258
|
+
False,
|
|
259
|
+
)
|
|
260
|
+
if callable(mapping[1]) and not isinstance(
|
|
261
|
+
mapping[1], DeployTimeField
|
|
262
|
+
):
|
|
263
|
+
mapping[1] = DeployTimeField(
|
|
264
|
+
"parameter_val",
|
|
265
|
+
str,
|
|
266
|
+
None,
|
|
267
|
+
mapping[1],
|
|
268
|
+
False,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
new_trigger_params[mapping[0]] = mapping[1]
|
|
272
|
+
else:
|
|
273
|
+
raise MetaflowException(
|
|
274
|
+
"The *parameters* attribute for event '%s' is invalid. "
|
|
275
|
+
"It should be a list/tuple of strings and lists/tuples "
|
|
276
|
+
"of size 2" % self.attributes["event"]["name"]
|
|
277
|
+
)
|
|
278
|
+
trigger_params = new_trigger_params
|
|
279
|
+
trigger["parameters"] = trigger_params
|
|
280
|
+
|
|
281
|
+
trigger_name = trigger.get("name")
|
|
282
|
+
# Case where just the name is a function (always a str)
|
|
283
|
+
if isinstance(trigger_name, DeployTimeField):
|
|
284
|
+
trigger_name = deploy_time_eval(trigger_name)
|
|
285
|
+
trigger["name"] = trigger_name
|
|
286
|
+
|
|
287
|
+
# Third layer
|
|
288
|
+
# {name:, parameters:[func, ..., ...]}
|
|
289
|
+
# {name:, parameters:{func : func2}}
|
|
290
|
+
for trigger in self.triggers:
|
|
291
|
+
old_trigger = trigger
|
|
292
|
+
trigger_params = trigger.get("parameters", {})
|
|
293
|
+
new_trigger_params = {}
|
|
294
|
+
for key, value in trigger_params.items():
|
|
295
|
+
if isinstance(value, DeployTimeField) and key is value:
|
|
296
|
+
evaluated_param = deploy_time_eval(value)
|
|
297
|
+
new_trigger_params[evaluated_param] = evaluated_param
|
|
298
|
+
elif isinstance(value, DeployTimeField):
|
|
299
|
+
new_trigger_params[key] = deploy_time_eval(value)
|
|
300
|
+
elif isinstance(key, DeployTimeField):
|
|
301
|
+
new_trigger_params[deploy_time_eval(key)] = value
|
|
302
|
+
else:
|
|
303
|
+
new_trigger_params[key] = value
|
|
304
|
+
trigger["parameters"] = new_trigger_params
|
|
305
|
+
self.triggers[self.triggers.index(old_trigger)] = trigger
|
|
306
|
+
|
|
191
307
|
|
|
192
308
|
class TriggerOnFinishDecorator(FlowDecorator):
|
|
193
309
|
"""
|
|
@@ -246,11 +362,7 @@ class TriggerOnFinishDecorator(FlowDecorator):
|
|
|
246
362
|
"""
|
|
247
363
|
|
|
248
364
|
name = "trigger_on_finish"
|
|
249
|
-
|
|
250
|
-
"flow": None, # flow_name or project_flow_name
|
|
251
|
-
"flows": [], # flow_names or project_flow_names
|
|
252
|
-
"options": {},
|
|
253
|
-
}
|
|
365
|
+
|
|
254
366
|
options = {
|
|
255
367
|
"trigger": dict(
|
|
256
368
|
multiple=True,
|
|
@@ -258,6 +370,14 @@ class TriggerOnFinishDecorator(FlowDecorator):
|
|
|
258
370
|
help="Specify run pathspec for testing @trigger_on_finish locally.",
|
|
259
371
|
),
|
|
260
372
|
}
|
|
373
|
+
defaults = {
|
|
374
|
+
"flow": None, # flow_name or project_flow_name
|
|
375
|
+
"flows": [], # flow_names or project_flow_names
|
|
376
|
+
"options": {},
|
|
377
|
+
# Re-enable if you want to support TL options directly in the decorator like
|
|
378
|
+
# for @project decorator
|
|
379
|
+
# **{k: v["default"] for k, v in options.items()},
|
|
380
|
+
}
|
|
261
381
|
|
|
262
382
|
def flow_init(
|
|
263
383
|
self,
|
|
@@ -278,97 +398,23 @@ class TriggerOnFinishDecorator(FlowDecorator):
|
|
|
278
398
|
)
|
|
279
399
|
elif self.attributes["flow"]:
|
|
280
400
|
# flow supports the format @trigger_on_finish(flow='FooFlow')
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
)
|
|
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)
|
|
401
|
+
flow = self.attributes["flow"]
|
|
402
|
+
if callable(flow) and not isinstance(
|
|
403
|
+
self.attributes["flow"], DeployTimeField
|
|
404
|
+
):
|
|
405
|
+
trig = DeployTimeField("fq_name", [str, dict], None, flow, False)
|
|
406
|
+
self.triggers.append(trig)
|
|
315
407
|
else:
|
|
316
|
-
|
|
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
|
-
)
|
|
408
|
+
self.triggers.extend(self._parse_static_triggers([flow]))
|
|
322
409
|
elif self.attributes["flows"]:
|
|
323
410
|
# flows attribute supports the following formats -
|
|
324
411
|
# 1. flows=['FooFlow', 'BarFlow']
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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
|
-
)
|
|
412
|
+
flows = self.attributes["flows"]
|
|
413
|
+
if callable(flows) and not isinstance(flows, DeployTimeField):
|
|
414
|
+
trig = DeployTimeField("flows", list, None, flows, False)
|
|
415
|
+
self.triggers.append(trig)
|
|
416
|
+
elif isinstance(flows, list):
|
|
417
|
+
self.triggers.extend(self._parse_static_triggers(flows))
|
|
372
418
|
else:
|
|
373
419
|
raise MetaflowException(
|
|
374
420
|
"Incorrect type for *flows* attribute in *@trigger_on_finish* "
|
|
@@ -383,37 +429,50 @@ class TriggerOnFinishDecorator(FlowDecorator):
|
|
|
383
429
|
|
|
384
430
|
# Make triggers @project aware
|
|
385
431
|
for trigger in self.triggers:
|
|
386
|
-
if trigger
|
|
387
|
-
|
|
388
|
-
|
|
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
|
-
)
|
|
432
|
+
if isinstance(trigger, DeployTimeField):
|
|
433
|
+
continue
|
|
434
|
+
self._parse_fq_name(trigger)
|
|
406
435
|
|
|
407
436
|
self.options = self.attributes["options"]
|
|
408
437
|
|
|
409
438
|
# Handle scenario for local testing using --trigger.
|
|
439
|
+
|
|
440
|
+
# Re-enable this code if you want to support passing trigger directly in the
|
|
441
|
+
# decorator in a way similar to how production and branch are passed in the
|
|
442
|
+
# project decorator.
|
|
443
|
+
|
|
444
|
+
# # This is overkill since default is None for all options but adding this code
|
|
445
|
+
# # to make it safe if other non None-default options are added in the future.
|
|
446
|
+
# for op in options:
|
|
447
|
+
# if (
|
|
448
|
+
# op in self._user_defined_attributes
|
|
449
|
+
# and options[op] != self.defaults[op]
|
|
450
|
+
# and self.attributes[op] != options[op]
|
|
451
|
+
# ):
|
|
452
|
+
# # Exception if:
|
|
453
|
+
# # - the user provides a value in the attributes field
|
|
454
|
+
# # - AND the user provided a value in the command line (non default)
|
|
455
|
+
# # - AND the values are different
|
|
456
|
+
# # Note that this won't raise an error if the user provided the default
|
|
457
|
+
# # value in the command line and provided one in attribute but although
|
|
458
|
+
# # slightly inconsistent, it is not incorrect.
|
|
459
|
+
# raise MetaflowException(
|
|
460
|
+
# "You cannot pass %s as both a command-line argument and an attribute "
|
|
461
|
+
# "of the @trigger_on_finish decorator." % op
|
|
462
|
+
# )
|
|
463
|
+
|
|
464
|
+
# if "trigger" in self._user_defined_attributes:
|
|
465
|
+
# trigger_option = self.attributes["trigger"]
|
|
466
|
+
# else:
|
|
467
|
+
trigger_option = options["trigger"]
|
|
468
|
+
|
|
410
469
|
self._option_values = options
|
|
411
|
-
if
|
|
470
|
+
if trigger_option:
|
|
412
471
|
from metaflow import Run
|
|
413
472
|
from metaflow.events import Trigger
|
|
414
473
|
|
|
415
474
|
run_objs = []
|
|
416
|
-
for run_pathspec in
|
|
475
|
+
for run_pathspec in trigger_option:
|
|
417
476
|
if len(run_pathspec.split("/")) != 2:
|
|
418
477
|
raise MetaflowException(
|
|
419
478
|
"Incorrect format for run pathspec for *--trigger*. "
|
|
@@ -427,5 +486,98 @@ class TriggerOnFinishDecorator(FlowDecorator):
|
|
|
427
486
|
run_objs.append(run_obj)
|
|
428
487
|
current._update_env({"trigger": Trigger.from_runs(run_objs)})
|
|
429
488
|
|
|
489
|
+
@staticmethod
|
|
490
|
+
def _parse_static_triggers(flows):
|
|
491
|
+
results = []
|
|
492
|
+
for flow in flows:
|
|
493
|
+
if is_stringish(flow):
|
|
494
|
+
results.append(
|
|
495
|
+
{
|
|
496
|
+
"fq_name": flow,
|
|
497
|
+
}
|
|
498
|
+
)
|
|
499
|
+
elif isinstance(flow, dict):
|
|
500
|
+
if "name" not in flow:
|
|
501
|
+
if len(flows) > 1:
|
|
502
|
+
raise MetaflowException(
|
|
503
|
+
"One or more flows in the *flows* attribute for "
|
|
504
|
+
"*@trigger_on_finish* is missing the "
|
|
505
|
+
"*name* key."
|
|
506
|
+
)
|
|
507
|
+
raise MetaflowException(
|
|
508
|
+
"The *flow* attribute for *@trigger_on_finish* is missing the "
|
|
509
|
+
"*name* key."
|
|
510
|
+
)
|
|
511
|
+
flow_name = flow["name"]
|
|
512
|
+
|
|
513
|
+
if not is_stringish(flow_name) or "." in flow_name:
|
|
514
|
+
raise MetaflowException(
|
|
515
|
+
f"The *name* attribute of the *flow* {flow_name} is not a valid string"
|
|
516
|
+
)
|
|
517
|
+
result = {"fq_name": flow_name}
|
|
518
|
+
if "project" in flow:
|
|
519
|
+
if is_stringish(flow["project"]):
|
|
520
|
+
result["project"] = flow["project"]
|
|
521
|
+
else:
|
|
522
|
+
raise MetaflowException(
|
|
523
|
+
f"The *project* attribute of the *flow* {flow_name} is not a string"
|
|
524
|
+
)
|
|
525
|
+
if "project_branch" in flow:
|
|
526
|
+
if is_stringish(flow["project_branch"]):
|
|
527
|
+
result["branch"] = flow["project_branch"]
|
|
528
|
+
else:
|
|
529
|
+
raise MetaflowException(
|
|
530
|
+
f"The *project_branch* attribute of the *flow* {flow_name} is not a string"
|
|
531
|
+
)
|
|
532
|
+
results.append(result)
|
|
533
|
+
else:
|
|
534
|
+
if len(flows) > 1:
|
|
535
|
+
raise MetaflowException(
|
|
536
|
+
"One or more flows in the *flows* attribute for "
|
|
537
|
+
"*@trigger_on_finish* decorator have an incorrect type. "
|
|
538
|
+
"Supported type is string or Dict[str, str]- \n"
|
|
539
|
+
"@trigger_on_finish(flows=['FooFlow', 'BarFlow']"
|
|
540
|
+
)
|
|
541
|
+
raise MetaflowException(
|
|
542
|
+
"Incorrect type for *flow* attribute in *@trigger_on_finish* "
|
|
543
|
+
" decorator. Supported type is string or Dict[str, str] - \n"
|
|
544
|
+
"@trigger_on_finish(flow='FooFlow') or "
|
|
545
|
+
"@trigger_on_finish(flow={'name':'FooFlow', 'project_branch': 'branch'})"
|
|
546
|
+
)
|
|
547
|
+
return results
|
|
548
|
+
|
|
549
|
+
def _parse_fq_name(self, trigger):
|
|
550
|
+
if trigger["fq_name"].count(".") == 0:
|
|
551
|
+
# fully qualified name is just the flow name
|
|
552
|
+
trigger["flow"] = trigger["fq_name"]
|
|
553
|
+
elif trigger["fq_name"].count(".") >= 2:
|
|
554
|
+
# fully qualified name is of the format - project.branch.flow_name
|
|
555
|
+
trigger["project"], tail = trigger["fq_name"].split(".", maxsplit=1)
|
|
556
|
+
trigger["branch"], trigger["flow"] = tail.rsplit(".", maxsplit=1)
|
|
557
|
+
else:
|
|
558
|
+
raise MetaflowException(
|
|
559
|
+
"Incorrect format for *flow* in *@trigger_on_finish* "
|
|
560
|
+
"decorator. Specify either just the *flow_name* or a fully "
|
|
561
|
+
"qualified name like *project_name.branch_name.flow_name*."
|
|
562
|
+
)
|
|
563
|
+
if not re.match(r"^[A-Za-z0-9_]+$", trigger["flow"]):
|
|
564
|
+
raise MetaflowException(
|
|
565
|
+
"Invalid flow name *%s* in *@trigger_on_finish* "
|
|
566
|
+
"decorator. Only alphanumeric characters and "
|
|
567
|
+
"underscores(_) are allowed." % trigger["flow"]
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
def format_deploytime_value(self):
|
|
571
|
+
if len(self.triggers) == 1 and isinstance(self.triggers[0], DeployTimeField):
|
|
572
|
+
deploy_value = deploy_time_eval(self.triggers[0])
|
|
573
|
+
if isinstance(deploy_value, list):
|
|
574
|
+
self.triggers = deploy_value
|
|
575
|
+
else:
|
|
576
|
+
self.triggers = [deploy_value]
|
|
577
|
+
triggers = self._parse_static_triggers(self.triggers)
|
|
578
|
+
for trigger in triggers:
|
|
579
|
+
self._parse_fq_name(trigger)
|
|
580
|
+
self.triggers = triggers
|
|
581
|
+
|
|
430
582
|
def get_top_level_options(self):
|
|
431
583
|
return list(self._option_values.items())
|