ob-metaflow 2.15.18.1__py2.py3-none-any.whl → 2.16.0.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 +7 -1
- metaflow/_vendor/imghdr/__init__.py +180 -0
- metaflow/cli.py +16 -1
- metaflow/cli_components/init_cmd.py +1 -0
- metaflow/cli_components/run_cmds.py +6 -2
- metaflow/client/core.py +22 -30
- metaflow/cmd/develop/stub_generator.py +19 -2
- metaflow/datastore/task_datastore.py +0 -1
- metaflow/debug.py +5 -0
- metaflow/decorators.py +230 -70
- metaflow/extension_support/__init__.py +15 -8
- metaflow/extension_support/_empty_file.py +2 -2
- metaflow/flowspec.py +80 -53
- metaflow/graph.py +24 -2
- metaflow/meta_files.py +13 -0
- metaflow/metadata_provider/metadata.py +7 -1
- metaflow/metaflow_config.py +5 -0
- metaflow/metaflow_environment.py +82 -25
- metaflow/metaflow_version.py +1 -1
- metaflow/package/__init__.py +664 -0
- metaflow/packaging_sys/__init__.py +870 -0
- metaflow/packaging_sys/backend.py +113 -0
- metaflow/packaging_sys/distribution_support.py +153 -0
- metaflow/packaging_sys/tar_backend.py +86 -0
- metaflow/packaging_sys/utils.py +91 -0
- metaflow/packaging_sys/v1.py +476 -0
- metaflow/plugins/__init__.py +3 -0
- metaflow/plugins/airflow/airflow.py +11 -1
- metaflow/plugins/airflow/airflow_cli.py +15 -4
- metaflow/plugins/argo/argo_workflows.py +346 -301
- metaflow/plugins/argo/argo_workflows_cli.py +16 -4
- metaflow/plugins/argo/exit_hooks.py +209 -0
- metaflow/plugins/aws/aws_utils.py +1 -1
- metaflow/plugins/aws/batch/batch.py +22 -3
- metaflow/plugins/aws/batch/batch_cli.py +3 -0
- metaflow/plugins/aws/batch/batch_decorator.py +13 -5
- metaflow/plugins/aws/step_functions/step_functions.py +10 -1
- metaflow/plugins/aws/step_functions/step_functions_cli.py +15 -4
- metaflow/plugins/cards/card_cli.py +20 -1
- metaflow/plugins/cards/card_creator.py +24 -1
- metaflow/plugins/cards/card_decorator.py +57 -6
- metaflow/plugins/cards/card_modules/convert_to_native_type.py +5 -2
- metaflow/plugins/cards/card_modules/test_cards.py +16 -0
- metaflow/plugins/cards/metadata.py +22 -0
- metaflow/plugins/exit_hook/__init__.py +0 -0
- metaflow/plugins/exit_hook/exit_hook_decorator.py +46 -0
- metaflow/plugins/exit_hook/exit_hook_script.py +52 -0
- metaflow/plugins/kubernetes/kubernetes.py +8 -1
- metaflow/plugins/kubernetes/kubernetes_cli.py +3 -0
- metaflow/plugins/kubernetes/kubernetes_decorator.py +13 -5
- metaflow/plugins/package_cli.py +25 -23
- metaflow/plugins/parallel_decorator.py +4 -2
- metaflow/plugins/pypi/bootstrap.py +8 -2
- metaflow/plugins/pypi/conda_decorator.py +39 -82
- metaflow/plugins/pypi/conda_environment.py +6 -2
- metaflow/plugins/pypi/pypi_decorator.py +4 -4
- metaflow/plugins/secrets/__init__.py +3 -0
- metaflow/plugins/secrets/secrets_decorator.py +9 -173
- metaflow/plugins/secrets/secrets_func.py +49 -0
- metaflow/plugins/secrets/secrets_spec.py +101 -0
- metaflow/plugins/secrets/utils.py +74 -0
- metaflow/plugins/test_unbounded_foreach_decorator.py +2 -2
- metaflow/plugins/timeout_decorator.py +0 -1
- metaflow/plugins/uv/bootstrap.py +11 -0
- metaflow/plugins/uv/uv_environment.py +4 -2
- metaflow/pylint_wrapper.py +5 -1
- metaflow/runner/click_api.py +5 -4
- metaflow/runner/metaflow_runner.py +16 -1
- metaflow/runner/subprocess_manager.py +14 -2
- metaflow/runtime.py +82 -11
- metaflow/task.py +91 -7
- metaflow/user_configs/config_options.py +13 -8
- metaflow/user_configs/config_parameters.py +0 -4
- metaflow/user_decorators/__init__.py +0 -0
- metaflow/user_decorators/common.py +144 -0
- metaflow/user_decorators/mutable_flow.py +499 -0
- metaflow/user_decorators/mutable_step.py +424 -0
- metaflow/user_decorators/user_flow_decorator.py +263 -0
- metaflow/user_decorators/user_step_decorator.py +712 -0
- metaflow/util.py +4 -1
- metaflow/version.py +1 -1
- {ob_metaflow-2.15.18.1.data → ob_metaflow-2.16.0.1.data}/data/share/metaflow/devtools/Tiltfile +27 -2
- {ob_metaflow-2.15.18.1.dist-info → ob_metaflow-2.16.0.1.dist-info}/METADATA +2 -2
- {ob_metaflow-2.15.18.1.dist-info → ob_metaflow-2.16.0.1.dist-info}/RECORD +90 -70
- metaflow/info_file.py +0 -25
- metaflow/package.py +0 -203
- metaflow/user_configs/config_decorators.py +0 -568
- {ob_metaflow-2.15.18.1.data → ob_metaflow-2.16.0.1.data}/data/share/metaflow/devtools/Makefile +0 -0
- {ob_metaflow-2.15.18.1.data → ob_metaflow-2.16.0.1.data}/data/share/metaflow/devtools/pick_services.sh +0 -0
- {ob_metaflow-2.15.18.1.dist-info → ob_metaflow-2.16.0.1.dist-info}/WHEEL +0 -0
- {ob_metaflow-2.15.18.1.dist-info → ob_metaflow-2.16.0.1.dist-info}/entry_points.txt +0 -0
- {ob_metaflow-2.15.18.1.dist-info → ob_metaflow-2.16.0.1.dist-info}/licenses/LICENSE +0 -0
- {ob_metaflow-2.15.18.1.dist-info → ob_metaflow-2.16.0.1.dist-info}/top_level.txt +0 -0
|
@@ -15,6 +15,7 @@ from metaflow.exception import (
|
|
|
15
15
|
)
|
|
16
16
|
from metaflow.metaflow_config import (
|
|
17
17
|
ARGO_WORKFLOWS_UI_URL,
|
|
18
|
+
FEAT_ALWAYS_UPLOAD_CODE_PACKAGE,
|
|
18
19
|
KUBERNETES_NAMESPACE,
|
|
19
20
|
SERVICE_VERSION_CHECK,
|
|
20
21
|
UI_URL,
|
|
@@ -518,16 +519,27 @@ def make_flow(
|
|
|
518
519
|
# Save the code package in the flow datastore so that both user code and
|
|
519
520
|
# metaflow package can be retrieved during workflow execution.
|
|
520
521
|
obj.package = MetaflowPackage(
|
|
521
|
-
obj.flow,
|
|
522
|
+
obj.flow,
|
|
523
|
+
obj.environment,
|
|
524
|
+
obj.echo,
|
|
525
|
+
suffixes=obj.package_suffixes,
|
|
526
|
+
flow_datastore=obj.flow_datastore if FEAT_ALWAYS_UPLOAD_CODE_PACKAGE else None,
|
|
522
527
|
)
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
528
|
+
|
|
529
|
+
# This blocks until the package is created
|
|
530
|
+
if FEAT_ALWAYS_UPLOAD_CODE_PACKAGE:
|
|
531
|
+
package_url = obj.package.package_url()
|
|
532
|
+
package_sha = obj.package.package_sha()
|
|
533
|
+
else:
|
|
534
|
+
package_url, package_sha = obj.flow_datastore.save_data(
|
|
535
|
+
[obj.package.blob], len_hint=1
|
|
536
|
+
)[0]
|
|
526
537
|
|
|
527
538
|
return ArgoWorkflows(
|
|
528
539
|
name,
|
|
529
540
|
obj.graph,
|
|
530
541
|
obj.flow,
|
|
542
|
+
obj.package.package_metadata,
|
|
531
543
|
package_sha,
|
|
532
544
|
package_url,
|
|
533
545
|
token,
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
import json
|
|
3
|
+
from typing import Dict, List
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class JsonSerializable(object):
|
|
7
|
+
def to_json(self):
|
|
8
|
+
return self.payload
|
|
9
|
+
|
|
10
|
+
def __str__(self):
|
|
11
|
+
return json.dumps(self.payload, indent=4)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class _LifecycleHook(JsonSerializable):
|
|
15
|
+
# https://argoproj.github.io/argo-workflows/fields/#lifecyclehook
|
|
16
|
+
|
|
17
|
+
def __init__(self, name):
|
|
18
|
+
tree = lambda: defaultdict(tree)
|
|
19
|
+
self.name = name
|
|
20
|
+
self.payload = tree()
|
|
21
|
+
|
|
22
|
+
def expression(self, expression):
|
|
23
|
+
self.payload["expression"] = str(expression)
|
|
24
|
+
return self
|
|
25
|
+
|
|
26
|
+
def template(self, template):
|
|
27
|
+
self.payload["template"] = template
|
|
28
|
+
return self
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class _Template(JsonSerializable):
|
|
32
|
+
# https://argoproj.github.io/argo-workflows/fields/#template
|
|
33
|
+
|
|
34
|
+
def __init__(self, name):
|
|
35
|
+
tree = lambda: defaultdict(tree)
|
|
36
|
+
self.name = name
|
|
37
|
+
self.payload = tree()
|
|
38
|
+
self.payload["name"] = name
|
|
39
|
+
|
|
40
|
+
def http(self, http):
|
|
41
|
+
self.payload["http"] = http.to_json()
|
|
42
|
+
return self
|
|
43
|
+
|
|
44
|
+
def script(self, script):
|
|
45
|
+
self.payload["script"] = script.to_json()
|
|
46
|
+
return self
|
|
47
|
+
|
|
48
|
+
def container(self, container):
|
|
49
|
+
self.payload["container"] = container
|
|
50
|
+
return self
|
|
51
|
+
|
|
52
|
+
def service_account_name(self, service_account_name):
|
|
53
|
+
self.payload["serviceAccountName"] = service_account_name
|
|
54
|
+
return self
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class Hook(object):
|
|
58
|
+
"""
|
|
59
|
+
Abstraction for Argo Workflows exit hooks.
|
|
60
|
+
A hook consists of a Template, and one or more LifecycleHooks that trigger the template
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
template: "_Template"
|
|
64
|
+
lifecycle_hooks: List["_LifecycleHook"]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class _HttpSpec(JsonSerializable):
|
|
68
|
+
# https://argoproj.github.io/argo-workflows/fields/#http
|
|
69
|
+
|
|
70
|
+
def __init__(self, method):
|
|
71
|
+
tree = lambda: defaultdict(tree)
|
|
72
|
+
self.payload = tree()
|
|
73
|
+
self.payload["method"] = method
|
|
74
|
+
self.payload["headers"] = []
|
|
75
|
+
|
|
76
|
+
def header(self, header, value):
|
|
77
|
+
self.payload["headers"].append({"name": header, "value": value})
|
|
78
|
+
return self
|
|
79
|
+
|
|
80
|
+
def body(self, body):
|
|
81
|
+
self.payload["body"] = str(body)
|
|
82
|
+
return self
|
|
83
|
+
|
|
84
|
+
def url(self, url):
|
|
85
|
+
self.payload["url"] = url
|
|
86
|
+
return self
|
|
87
|
+
|
|
88
|
+
def success_condition(self, success_condition):
|
|
89
|
+
self.payload["successCondition"] = success_condition
|
|
90
|
+
return self
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# HTTP hook
|
|
94
|
+
class HttpExitHook(Hook):
|
|
95
|
+
def __init__(
|
|
96
|
+
self,
|
|
97
|
+
name,
|
|
98
|
+
url,
|
|
99
|
+
method="GET",
|
|
100
|
+
headers=None,
|
|
101
|
+
body=None,
|
|
102
|
+
on_success=False,
|
|
103
|
+
on_error=False,
|
|
104
|
+
):
|
|
105
|
+
self.template = _Template(name)
|
|
106
|
+
http = _HttpSpec(method).url(url)
|
|
107
|
+
if headers is not None:
|
|
108
|
+
for header, value in headers.items():
|
|
109
|
+
http.header(header, value)
|
|
110
|
+
|
|
111
|
+
if body is not None:
|
|
112
|
+
http.body(json.dumps(body))
|
|
113
|
+
|
|
114
|
+
self.template.http(http)
|
|
115
|
+
|
|
116
|
+
self.lifecycle_hooks = []
|
|
117
|
+
|
|
118
|
+
if on_success and on_error:
|
|
119
|
+
raise Exception("Set only one of the on_success/on_error at a time.")
|
|
120
|
+
|
|
121
|
+
if on_success:
|
|
122
|
+
self.lifecycle_hooks.append(
|
|
123
|
+
_LifecycleHook(name)
|
|
124
|
+
.expression("workflow.status == 'Succeeded'")
|
|
125
|
+
.template(self.template.name)
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
if on_error:
|
|
129
|
+
self.lifecycle_hooks.append(
|
|
130
|
+
_LifecycleHook(name)
|
|
131
|
+
.expression("workflow.status == 'Error' || workflow.status == 'Failed'")
|
|
132
|
+
.template(self.template.name)
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
if not on_success and not on_error:
|
|
136
|
+
# add an expressionless lifecycle hook
|
|
137
|
+
self.lifecycle_hooks.append(_LifecycleHook(name).template(name))
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class ExitHookHack(Hook):
|
|
141
|
+
# Warning: terrible hack to workaround a bug in Argo Workflow where the
|
|
142
|
+
# templates listed above do not execute unless there is an
|
|
143
|
+
# explicit exit hook. as and when this bug is patched, we should
|
|
144
|
+
# remove this effectively no-op template.
|
|
145
|
+
# Note: We use the Http template because changing this to an actual no-op container had the side-effect of
|
|
146
|
+
# leaving LifecycleHooks in a pending state even when they have finished execution.
|
|
147
|
+
def __init__(
|
|
148
|
+
self,
|
|
149
|
+
url,
|
|
150
|
+
headers=None,
|
|
151
|
+
body=None,
|
|
152
|
+
):
|
|
153
|
+
self.template = _Template("exit-hook-hack")
|
|
154
|
+
http = _HttpSpec("GET").url(url)
|
|
155
|
+
if headers is not None:
|
|
156
|
+
for header, value in headers.items():
|
|
157
|
+
http.header(header, value)
|
|
158
|
+
|
|
159
|
+
if body is not None:
|
|
160
|
+
http.body(json.dumps(body))
|
|
161
|
+
|
|
162
|
+
http.success_condition("true == true")
|
|
163
|
+
|
|
164
|
+
self.template.http(http)
|
|
165
|
+
|
|
166
|
+
self.lifecycle_hooks = []
|
|
167
|
+
|
|
168
|
+
# add an expressionless lifecycle hook
|
|
169
|
+
self.lifecycle_hooks.append(_LifecycleHook("exit").template("exit-hook-hack"))
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class ContainerHook(Hook):
|
|
173
|
+
def __init__(
|
|
174
|
+
self,
|
|
175
|
+
name: str,
|
|
176
|
+
container: Dict,
|
|
177
|
+
service_account_name: str = None,
|
|
178
|
+
on_success: bool = False,
|
|
179
|
+
on_error: bool = False,
|
|
180
|
+
):
|
|
181
|
+
self.template = _Template(name)
|
|
182
|
+
|
|
183
|
+
if service_account_name is not None:
|
|
184
|
+
self.template.service_account_name(service_account_name)
|
|
185
|
+
|
|
186
|
+
self.template.container(container)
|
|
187
|
+
|
|
188
|
+
self.lifecycle_hooks = []
|
|
189
|
+
|
|
190
|
+
if on_success and on_error:
|
|
191
|
+
raise Exception("Set only one of the on_success/on_error at a time.")
|
|
192
|
+
|
|
193
|
+
if on_success:
|
|
194
|
+
self.lifecycle_hooks.append(
|
|
195
|
+
_LifecycleHook(name)
|
|
196
|
+
.expression("workflow.status == 'Succeeded'")
|
|
197
|
+
.template(self.template.name)
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
if on_error:
|
|
201
|
+
self.lifecycle_hooks.append(
|
|
202
|
+
_LifecycleHook(name)
|
|
203
|
+
.expression("workflow.status == 'Error' || workflow.status == 'Failed'")
|
|
204
|
+
.template(self.template.name)
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
if not on_success and not on_error:
|
|
208
|
+
# add an expressionless lifecycle hook
|
|
209
|
+
self.lifecycle_hooks.append(_LifecycleHook(name).template(name))
|
|
@@ -49,7 +49,7 @@ def get_ec2_instance_metadata():
|
|
|
49
49
|
# Try to get an IMDSv2 token.
|
|
50
50
|
token = requests.put(
|
|
51
51
|
url="http://169.254.169.254/latest/api/token",
|
|
52
|
-
headers={"X-aws-ec2-metadata-token-ttl-seconds": 100},
|
|
52
|
+
headers={"X-aws-ec2-metadata-token-ttl-seconds": "100"},
|
|
53
53
|
timeout=timeout,
|
|
54
54
|
).text
|
|
55
55
|
except:
|
|
@@ -59,14 +59,24 @@ class Batch(object):
|
|
|
59
59
|
self._client = BatchClient()
|
|
60
60
|
atexit.register(lambda: self.job.kill() if hasattr(self, "job") else None)
|
|
61
61
|
|
|
62
|
-
def _command(
|
|
62
|
+
def _command(
|
|
63
|
+
self,
|
|
64
|
+
environment,
|
|
65
|
+
code_package_metadata,
|
|
66
|
+
code_package_url,
|
|
67
|
+
step_name,
|
|
68
|
+
step_cmds,
|
|
69
|
+
task_spec,
|
|
70
|
+
):
|
|
63
71
|
mflog_expr = export_mflog_env_vars(
|
|
64
72
|
datastore_type="s3",
|
|
65
73
|
stdout_path=STDOUT_PATH,
|
|
66
74
|
stderr_path=STDERR_PATH,
|
|
67
75
|
**task_spec
|
|
68
76
|
)
|
|
69
|
-
init_cmds = environment.get_package_commands(
|
|
77
|
+
init_cmds = environment.get_package_commands(
|
|
78
|
+
code_package_url, "s3", code_package_metadata
|
|
79
|
+
)
|
|
70
80
|
init_expr = " && ".join(init_cmds)
|
|
71
81
|
step_expr = bash_capture_logs(
|
|
72
82
|
" && ".join(environment.bootstrap_commands(step_name, "s3") + step_cmds)
|
|
@@ -167,6 +177,7 @@ class Batch(object):
|
|
|
167
177
|
step_name,
|
|
168
178
|
step_cli,
|
|
169
179
|
task_spec,
|
|
180
|
+
code_package_metadata,
|
|
170
181
|
code_package_sha,
|
|
171
182
|
code_package_url,
|
|
172
183
|
code_package_ds,
|
|
@@ -210,7 +221,12 @@ class Batch(object):
|
|
|
210
221
|
.job_queue(queue)
|
|
211
222
|
.command(
|
|
212
223
|
self._command(
|
|
213
|
-
self.environment,
|
|
224
|
+
self.environment,
|
|
225
|
+
code_package_metadata,
|
|
226
|
+
code_package_url,
|
|
227
|
+
step_name,
|
|
228
|
+
[step_cli],
|
|
229
|
+
task_spec,
|
|
214
230
|
)
|
|
215
231
|
)
|
|
216
232
|
.image(image)
|
|
@@ -249,6 +265,7 @@ class Batch(object):
|
|
|
249
265
|
)
|
|
250
266
|
.task_id(attrs.get("metaflow.task_id"))
|
|
251
267
|
.environment_variable("AWS_DEFAULT_REGION", self._client.region())
|
|
268
|
+
.environment_variable("METAFLOW_CODE_METADATA", code_package_metadata)
|
|
252
269
|
.environment_variable("METAFLOW_CODE_SHA", code_package_sha)
|
|
253
270
|
.environment_variable("METAFLOW_CODE_URL", code_package_url)
|
|
254
271
|
.environment_variable("METAFLOW_CODE_DS", code_package_ds)
|
|
@@ -334,6 +351,7 @@ class Batch(object):
|
|
|
334
351
|
step_name,
|
|
335
352
|
step_cli,
|
|
336
353
|
task_spec,
|
|
354
|
+
code_package_metadata,
|
|
337
355
|
code_package_sha,
|
|
338
356
|
code_package_url,
|
|
339
357
|
code_package_ds,
|
|
@@ -374,6 +392,7 @@ class Batch(object):
|
|
|
374
392
|
step_name,
|
|
375
393
|
step_cli,
|
|
376
394
|
task_spec,
|
|
395
|
+
code_package_metadata,
|
|
377
396
|
code_package_sha,
|
|
378
397
|
code_package_url,
|
|
379
398
|
code_package_ds,
|
|
@@ -100,6 +100,7 @@ def kill(ctx, run_id, user, my_runs):
|
|
|
100
100
|
"Metaflow."
|
|
101
101
|
)
|
|
102
102
|
@click.argument("step-name")
|
|
103
|
+
@click.argument("code-package-metadata")
|
|
103
104
|
@click.argument("code-package-sha")
|
|
104
105
|
@click.argument("code-package-url")
|
|
105
106
|
@click.option("--executable", help="Executable requirement for AWS Batch.")
|
|
@@ -185,6 +186,7 @@ def kill(ctx, run_id, user, my_runs):
|
|
|
185
186
|
def step(
|
|
186
187
|
ctx,
|
|
187
188
|
step_name,
|
|
189
|
+
code_package_metadata,
|
|
188
190
|
code_package_sha,
|
|
189
191
|
code_package_url,
|
|
190
192
|
executable=None,
|
|
@@ -317,6 +319,7 @@ def step(
|
|
|
317
319
|
step_name,
|
|
318
320
|
step_cli,
|
|
319
321
|
task_spec,
|
|
322
|
+
code_package_metadata,
|
|
320
323
|
code_package_sha,
|
|
321
324
|
code_package_url,
|
|
322
325
|
ctx.obj.flow_datastore.TYPE,
|
|
@@ -14,6 +14,7 @@ from metaflow.metaflow_config import (
|
|
|
14
14
|
DATASTORE_LOCAL_DIR,
|
|
15
15
|
ECS_FARGATE_EXECUTION_ROLE,
|
|
16
16
|
ECS_S3_ACCESS_IAM_ROLE,
|
|
17
|
+
FEAT_ALWAYS_UPLOAD_CODE_PACKAGE,
|
|
17
18
|
)
|
|
18
19
|
from metaflow.plugins.timeout_decorator import get_run_time_limit_for_task
|
|
19
20
|
from metaflow.sidecar import Sidecar
|
|
@@ -126,6 +127,7 @@ class BatchDecorator(StepDecorator):
|
|
|
126
127
|
"gpu": "0",
|
|
127
128
|
"memory": "4096",
|
|
128
129
|
}
|
|
130
|
+
package_metadata = None
|
|
129
131
|
package_url = None
|
|
130
132
|
package_sha = None
|
|
131
133
|
run_time_limit = None
|
|
@@ -135,8 +137,6 @@ class BatchDecorator(StepDecorator):
|
|
|
135
137
|
target_platform = "linux-64"
|
|
136
138
|
|
|
137
139
|
def init(self):
|
|
138
|
-
super(BatchDecorator, self).init()
|
|
139
|
-
|
|
140
140
|
# If no docker image is explicitly specified, impute a default image.
|
|
141
141
|
if not self.attributes["image"]:
|
|
142
142
|
# If metaflow-config specifies a docker image, just use that.
|
|
@@ -228,6 +228,7 @@ class BatchDecorator(StepDecorator):
|
|
|
228
228
|
# to execute on AWS Batch anymore. We can execute possible fallback
|
|
229
229
|
# code locally.
|
|
230
230
|
cli_args.commands = ["batch", "step"]
|
|
231
|
+
cli_args.command_args.append(self.package_metadata)
|
|
231
232
|
cli_args.command_args.append(self.package_sha)
|
|
232
233
|
cli_args.command_args.append(self.package_url)
|
|
233
234
|
cli_args.command_options.update(self.attributes)
|
|
@@ -403,9 +404,16 @@ class BatchDecorator(StepDecorator):
|
|
|
403
404
|
@classmethod
|
|
404
405
|
def _save_package_once(cls, flow_datastore, package):
|
|
405
406
|
if cls.package_url is None:
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
407
|
+
if not FEAT_ALWAYS_UPLOAD_CODE_PACKAGE:
|
|
408
|
+
cls.package_url, cls.package_sha = flow_datastore.save_data(
|
|
409
|
+
[package.blob], len_hint=1
|
|
410
|
+
)[0]
|
|
411
|
+
cls.package_metadata = package.package_metadata
|
|
412
|
+
else:
|
|
413
|
+
# Blocks until the package is uploaded
|
|
414
|
+
cls.package_url = package.package_url()
|
|
415
|
+
cls.package_sha = package.package_sha()
|
|
416
|
+
cls.package_metadata = package.package_metadata
|
|
409
417
|
|
|
410
418
|
|
|
411
419
|
def _setup_multinode_environment():
|
|
@@ -40,6 +40,7 @@ class StepFunctions(object):
|
|
|
40
40
|
name,
|
|
41
41
|
graph,
|
|
42
42
|
flow,
|
|
43
|
+
code_package_metadata,
|
|
43
44
|
code_package_sha,
|
|
44
45
|
code_package_url,
|
|
45
46
|
production_token,
|
|
@@ -59,6 +60,7 @@ class StepFunctions(object):
|
|
|
59
60
|
self.name = name
|
|
60
61
|
self.graph = graph
|
|
61
62
|
self.flow = flow
|
|
63
|
+
self.code_package_metadata = code_package_metadata
|
|
62
64
|
self.code_package_sha = code_package_sha
|
|
63
65
|
self.code_package_url = code_package_url
|
|
64
66
|
self.production_token = production_token
|
|
@@ -301,6 +303,12 @@ class StepFunctions(object):
|
|
|
301
303
|
"to AWS Step Functions is not supported currently."
|
|
302
304
|
)
|
|
303
305
|
|
|
306
|
+
if self.flow._flow_decorators.get("exit_hook"):
|
|
307
|
+
raise StepFunctionsException(
|
|
308
|
+
"Deploying flows with the @exit_hook decorator "
|
|
309
|
+
"to AWS Step Functions is not currently supported."
|
|
310
|
+
)
|
|
311
|
+
|
|
304
312
|
# Visit every node of the flow and recursively build the state machine.
|
|
305
313
|
def _visit(node, workflow, exit_node=None):
|
|
306
314
|
if node.parallel_foreach:
|
|
@@ -847,6 +855,7 @@ class StepFunctions(object):
|
|
|
847
855
|
node, input_paths, self.code_package_url, user_code_retries
|
|
848
856
|
),
|
|
849
857
|
task_spec=task_spec,
|
|
858
|
+
code_package_metadata=self.code_package_metadata,
|
|
850
859
|
code_package_sha=self.code_package_sha,
|
|
851
860
|
code_package_url=self.code_package_url,
|
|
852
861
|
code_package_ds=self.flow_datastore.TYPE,
|
|
@@ -907,7 +916,7 @@ class StepFunctions(object):
|
|
|
907
916
|
"with": [
|
|
908
917
|
decorator.make_decorator_spec()
|
|
909
918
|
for decorator in node.decorators
|
|
910
|
-
if not decorator.statically_defined
|
|
919
|
+
if not decorator.statically_defined and decorator.inserted_by is None
|
|
911
920
|
]
|
|
912
921
|
}
|
|
913
922
|
# FlowDecorators can define their own top-level options. They are
|
|
@@ -7,6 +7,7 @@ from metaflow import JSONType, current, decorators, parameters
|
|
|
7
7
|
from metaflow._vendor import click
|
|
8
8
|
from metaflow.exception import MetaflowException, MetaflowInternalError
|
|
9
9
|
from metaflow.metaflow_config import (
|
|
10
|
+
FEAT_ALWAYS_UPLOAD_CODE_PACKAGE,
|
|
10
11
|
SERVICE_VERSION_CHECK,
|
|
11
12
|
SFN_STATE_MACHINE_PREFIX,
|
|
12
13
|
UI_URL,
|
|
@@ -331,16 +332,26 @@ def make_flow(
|
|
|
331
332
|
)
|
|
332
333
|
|
|
333
334
|
obj.package = MetaflowPackage(
|
|
334
|
-
obj.flow,
|
|
335
|
+
obj.flow,
|
|
336
|
+
obj.environment,
|
|
337
|
+
obj.echo,
|
|
338
|
+
suffixes=obj.package_suffixes,
|
|
339
|
+
flow_datastore=obj.flow_datastore if FEAT_ALWAYS_UPLOAD_CODE_PACKAGE else None,
|
|
335
340
|
)
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
341
|
+
# This blocks until the package is created
|
|
342
|
+
if FEAT_ALWAYS_UPLOAD_CODE_PACKAGE:
|
|
343
|
+
package_url = obj.package.package_url()
|
|
344
|
+
package_sha = obj.package.package_sha()
|
|
345
|
+
else:
|
|
346
|
+
package_url, package_sha = obj.flow_datastore.save_data(
|
|
347
|
+
[obj.package.blob], len_hint=1
|
|
348
|
+
)[0]
|
|
339
349
|
|
|
340
350
|
return StepFunctions(
|
|
341
351
|
name,
|
|
342
352
|
obj.graph,
|
|
343
353
|
obj.flow,
|
|
354
|
+
obj.package.package_metadata,
|
|
344
355
|
package_sha,
|
|
345
356
|
package_url,
|
|
346
357
|
token,
|
|
@@ -30,7 +30,7 @@ from .exception import (
|
|
|
30
30
|
)
|
|
31
31
|
import traceback
|
|
32
32
|
from collections import namedtuple
|
|
33
|
-
|
|
33
|
+
from .metadata import _save_metadata
|
|
34
34
|
from .card_resolver import resolve_paths_from_task, resumed_info
|
|
35
35
|
|
|
36
36
|
id_func = id
|
|
@@ -613,6 +613,14 @@ def update_card(mf_card, mode, task, data, timeout_value=None):
|
|
|
613
613
|
hidden=True,
|
|
614
614
|
help="Delete data-file and component-file after reading. (internal)",
|
|
615
615
|
)
|
|
616
|
+
@click.option(
|
|
617
|
+
"--save-metadata",
|
|
618
|
+
default=None,
|
|
619
|
+
show_default=True,
|
|
620
|
+
type=JSONTypeClass(),
|
|
621
|
+
hidden=True,
|
|
622
|
+
help="JSON string containing metadata to be saved. (internal)",
|
|
623
|
+
)
|
|
616
624
|
@click.pass_context
|
|
617
625
|
def create(
|
|
618
626
|
ctx,
|
|
@@ -627,6 +635,7 @@ def create(
|
|
|
627
635
|
card_uuid=None,
|
|
628
636
|
delete_input_files=None,
|
|
629
637
|
id=None,
|
|
638
|
+
save_metadata=None,
|
|
630
639
|
):
|
|
631
640
|
card_id = id
|
|
632
641
|
rendered_info = None # Variable holding all the information which will be rendered
|
|
@@ -824,6 +833,16 @@ def create(
|
|
|
824
833
|
% (card_info.type, card_info.hash[:NUM_SHORT_HASH_CHARS]),
|
|
825
834
|
fg="green",
|
|
826
835
|
)
|
|
836
|
+
if save_metadata:
|
|
837
|
+
_save_metadata(
|
|
838
|
+
ctx.obj.metadata,
|
|
839
|
+
task.parent.parent.id,
|
|
840
|
+
task.parent.id,
|
|
841
|
+
task.id,
|
|
842
|
+
task.current_attempt,
|
|
843
|
+
card_uuid,
|
|
844
|
+
save_metadata,
|
|
845
|
+
)
|
|
827
846
|
|
|
828
847
|
|
|
829
848
|
@card.command()
|
|
@@ -5,6 +5,8 @@ import json
|
|
|
5
5
|
import sys
|
|
6
6
|
import os
|
|
7
7
|
from metaflow import current
|
|
8
|
+
from typing import Callable, Tuple, Dict
|
|
9
|
+
|
|
8
10
|
|
|
9
11
|
ASYNC_TIMEOUT = 30
|
|
10
12
|
|
|
@@ -44,8 +46,18 @@ class CardProcessManager:
|
|
|
44
46
|
|
|
45
47
|
|
|
46
48
|
class CardCreator:
|
|
47
|
-
def __init__(
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
top_level_options,
|
|
52
|
+
should_save_metadata_lambda: Callable[[str], Tuple[bool, Dict]],
|
|
53
|
+
):
|
|
54
|
+
# should_save_metadata_lambda is a lambda that provides a flag to indicate if
|
|
55
|
+
# card metadata should be written to the metadata store.
|
|
56
|
+
# It gets called only once when the card is created inside the subprocess.
|
|
57
|
+
# The intent is that this is a stateful lambda that will ensure that we only end
|
|
58
|
+
# up writing to the metadata store once.
|
|
48
59
|
self._top_level_options = top_level_options
|
|
60
|
+
self._should_save_metadata = should_save_metadata_lambda
|
|
49
61
|
|
|
50
62
|
def create(
|
|
51
63
|
self,
|
|
@@ -62,6 +74,8 @@ class CardCreator:
|
|
|
62
74
|
# Setting `final` will affect the Reload token set during the card refresh
|
|
63
75
|
# data creation along with synchronous execution of subprocess.
|
|
64
76
|
# Setting `sync` will only cause synchronous execution of subprocess.
|
|
77
|
+
save_metadata = False
|
|
78
|
+
metadata_dict = {}
|
|
65
79
|
if mode != "render" and not runtime_card:
|
|
66
80
|
# silently ignore runtime updates for cards that don't support them
|
|
67
81
|
return
|
|
@@ -71,6 +85,8 @@ class CardCreator:
|
|
|
71
85
|
component_strings = []
|
|
72
86
|
else:
|
|
73
87
|
component_strings = current.card._serialize_components(card_uuid)
|
|
88
|
+
# Since the mode is a render, we can check if we need to write to the metadata store.
|
|
89
|
+
save_metadata, metadata_dict = self._should_save_metadata(card_uuid)
|
|
74
90
|
data = current.card._get_latest_data(card_uuid, final=final, mode=mode)
|
|
75
91
|
runspec = "/".join([current.run_id, current.step_name, current.task_id])
|
|
76
92
|
self._run_cards_subprocess(
|
|
@@ -85,6 +101,8 @@ class CardCreator:
|
|
|
85
101
|
data,
|
|
86
102
|
final=final,
|
|
87
103
|
sync=sync,
|
|
104
|
+
save_metadata=save_metadata,
|
|
105
|
+
metadata_dict=metadata_dict,
|
|
88
106
|
)
|
|
89
107
|
|
|
90
108
|
def _run_cards_subprocess(
|
|
@@ -100,6 +118,8 @@ class CardCreator:
|
|
|
100
118
|
data=None,
|
|
101
119
|
final=False,
|
|
102
120
|
sync=False,
|
|
121
|
+
save_metadata=False,
|
|
122
|
+
metadata_dict=None,
|
|
103
123
|
):
|
|
104
124
|
components_file = data_file = None
|
|
105
125
|
wait = final or sync
|
|
@@ -156,6 +176,9 @@ class CardCreator:
|
|
|
156
176
|
if data_file is not None:
|
|
157
177
|
cmd += ["--data-file", data_file.name]
|
|
158
178
|
|
|
179
|
+
if save_metadata:
|
|
180
|
+
cmd += ["--save-metadata", json.dumps(metadata_dict)]
|
|
181
|
+
|
|
159
182
|
response, fail = self._run_command(
|
|
160
183
|
cmd,
|
|
161
184
|
card_uuid,
|