metaflow 2.12.28__py2.py3-none-any.whl → 2.12.29__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.
- metaflow/__init__.py +2 -3
- metaflow/cli.py +23 -13
- metaflow/client/core.py +2 -2
- metaflow/clone_util.py +1 -1
- metaflow/cmd/develop/stub_generator.py +623 -233
- metaflow/datastore/task_datastore.py +1 -1
- metaflow/extension_support/plugins.py +1 -0
- metaflow/flowspec.py +2 -2
- metaflow/includefile.py +8 -14
- metaflow/metaflow_config.py +4 -0
- metaflow/metaflow_current.py +1 -1
- metaflow/parameters.py +3 -0
- metaflow/plugins/__init__.py +12 -3
- metaflow/plugins/airflow/airflow_cli.py +5 -0
- metaflow/plugins/airflow/airflow_decorator.py +1 -1
- metaflow/plugins/argo/argo_workflows_decorator.py +1 -1
- metaflow/plugins/argo/argo_workflows_deployer.py +77 -263
- metaflow/plugins/argo/argo_workflows_deployer_objects.py +381 -0
- metaflow/plugins/aws/batch/batch_cli.py +1 -1
- metaflow/plugins/aws/batch/batch_decorator.py +2 -2
- metaflow/plugins/aws/step_functions/step_functions_cli.py +7 -0
- metaflow/plugins/aws/step_functions/step_functions_decorator.py +1 -1
- metaflow/plugins/aws/step_functions/step_functions_deployer.py +65 -224
- metaflow/plugins/aws/step_functions/step_functions_deployer_objects.py +236 -0
- metaflow/plugins/azure/includefile_support.py +2 -0
- metaflow/plugins/cards/card_cli.py +3 -2
- metaflow/plugins/cards/card_modules/components.py +9 -9
- metaflow/plugins/cards/card_server.py +39 -14
- metaflow/plugins/datatools/local.py +2 -0
- metaflow/plugins/datatools/s3/s3.py +2 -0
- metaflow/plugins/env_escape/__init__.py +3 -3
- metaflow/plugins/gcp/includefile_support.py +3 -0
- metaflow/plugins/kubernetes/kubernetes_cli.py +1 -1
- metaflow/plugins/kubernetes/kubernetes_decorator.py +5 -4
- metaflow/plugins/{metadata → metadata_providers}/local.py +2 -2
- metaflow/plugins/{metadata → metadata_providers}/service.py +2 -2
- metaflow/plugins/parallel_decorator.py +1 -1
- metaflow/plugins/pypi/conda_decorator.py +1 -1
- metaflow/plugins/test_unbounded_foreach_decorator.py +1 -1
- metaflow/runner/click_api.py +4 -0
- metaflow/runner/deployer.py +139 -269
- metaflow/runner/deployer_impl.py +167 -0
- metaflow/runner/metaflow_runner.py +10 -9
- metaflow/runner/nbdeploy.py +12 -13
- metaflow/runner/nbrun.py +3 -3
- metaflow/runner/utils.py +55 -8
- metaflow/runtime.py +1 -1
- metaflow/task.py +1 -1
- metaflow/version.py +1 -1
- {metaflow-2.12.28.dist-info → metaflow-2.12.29.dist-info}/METADATA +2 -2
- {metaflow-2.12.28.dist-info → metaflow-2.12.29.dist-info}/RECORD +60 -57
- /metaflow/{metadata → metadata_provider}/__init__.py +0 -0
- /metaflow/{metadata → metadata_provider}/heartbeat.py +0 -0
- /metaflow/{metadata → metadata_provider}/metadata.py +0 -0
- /metaflow/{metadata → metadata_provider}/util.py +0 -0
- /metaflow/plugins/{metadata → metadata_providers}/__init__.py +0 -0
- {metaflow-2.12.28.dist-info → metaflow-2.12.29.dist-info}/LICENSE +0 -0
- {metaflow-2.12.28.dist-info → metaflow-2.12.29.dist-info}/WHEEL +0 -0
- {metaflow-2.12.28.dist-info → metaflow-2.12.29.dist-info}/entry_points.txt +0 -0
- {metaflow-2.12.28.dist-info → metaflow-2.12.29.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,381 @@
|
|
1
|
+
import sys
|
2
|
+
import json
|
3
|
+
import tempfile
|
4
|
+
from typing import ClassVar, Optional
|
5
|
+
|
6
|
+
from metaflow.client.core import get_metadata
|
7
|
+
from metaflow.exception import MetaflowException
|
8
|
+
from metaflow.plugins.argo.argo_client import ArgoClient
|
9
|
+
from metaflow.metaflow_config import KUBERNETES_NAMESPACE
|
10
|
+
from metaflow.plugins.argo.argo_workflows import ArgoWorkflows
|
11
|
+
from metaflow.runner.deployer import Deployer, DeployedFlow, TriggeredRun
|
12
|
+
|
13
|
+
from metaflow.runner.utils import get_lower_level_group, handle_timeout
|
14
|
+
|
15
|
+
|
16
|
+
def generate_fake_flow_file_contents(
|
17
|
+
flow_name: str, param_info: dict, project_name: Optional[str] = None
|
18
|
+
):
|
19
|
+
params_code = ""
|
20
|
+
for _, param_details in param_info.items():
|
21
|
+
param_name = param_details["name"]
|
22
|
+
param_type = param_details["type"]
|
23
|
+
param_help = param_details["description"]
|
24
|
+
param_required = param_details["is_required"]
|
25
|
+
|
26
|
+
if param_type == "JSON":
|
27
|
+
params_code += (
|
28
|
+
f" {param_name} = Parameter('{param_name}', "
|
29
|
+
f"type=JSONType, help='{param_help}', required={param_required})\n"
|
30
|
+
)
|
31
|
+
elif param_type == "FilePath":
|
32
|
+
is_text = param_details.get("is_text", True)
|
33
|
+
encoding = param_details.get("encoding", "utf-8")
|
34
|
+
params_code += (
|
35
|
+
f" {param_name} = IncludeFile('{param_name}', "
|
36
|
+
f"is_text={is_text}, encoding='{encoding}', help='{param_help}', "
|
37
|
+
f"required={param_required})\n"
|
38
|
+
)
|
39
|
+
else:
|
40
|
+
params_code += (
|
41
|
+
f" {param_name} = Parameter('{param_name}', "
|
42
|
+
f"type={param_type}, help='{param_help}', required={param_required})\n"
|
43
|
+
)
|
44
|
+
|
45
|
+
project_decorator = f"@project(name='{project_name}')\n" if project_name else ""
|
46
|
+
|
47
|
+
contents = f"""\
|
48
|
+
from metaflow import FlowSpec, Parameter, IncludeFile, JSONType, step, project
|
49
|
+
{project_decorator}class {flow_name}(FlowSpec):
|
50
|
+
{params_code}
|
51
|
+
@step
|
52
|
+
def start(self):
|
53
|
+
self.next(self.end)
|
54
|
+
@step
|
55
|
+
def end(self):
|
56
|
+
pass
|
57
|
+
if __name__ == '__main__':
|
58
|
+
{flow_name}()
|
59
|
+
"""
|
60
|
+
return contents
|
61
|
+
|
62
|
+
|
63
|
+
class ArgoWorkflowsTriggeredRun(TriggeredRun):
|
64
|
+
"""
|
65
|
+
A class representing a triggered Argo Workflow execution.
|
66
|
+
"""
|
67
|
+
|
68
|
+
def suspend(self, **kwargs) -> bool:
|
69
|
+
"""
|
70
|
+
Suspend the running workflow.
|
71
|
+
|
72
|
+
Parameters
|
73
|
+
----------
|
74
|
+
authorize : str, optional, default None
|
75
|
+
Authorize the suspension with a production token.
|
76
|
+
|
77
|
+
Returns
|
78
|
+
-------
|
79
|
+
bool
|
80
|
+
True if the command was successful, False otherwise.
|
81
|
+
"""
|
82
|
+
_, run_id = self.pathspec.split("/")
|
83
|
+
|
84
|
+
# every subclass needs to have `self.deployer_kwargs`
|
85
|
+
command = get_lower_level_group(
|
86
|
+
self.deployer.api,
|
87
|
+
self.deployer.top_level_kwargs,
|
88
|
+
self.deployer.TYPE,
|
89
|
+
self.deployer.deployer_kwargs,
|
90
|
+
).suspend(run_id=run_id, **kwargs)
|
91
|
+
|
92
|
+
pid = self.deployer.spm.run_command(
|
93
|
+
[sys.executable, *command],
|
94
|
+
env=self.deployer.env_vars,
|
95
|
+
cwd=self.deployer.cwd,
|
96
|
+
show_output=self.deployer.show_output,
|
97
|
+
)
|
98
|
+
|
99
|
+
command_obj = self.deployer.spm.get(pid)
|
100
|
+
return command_obj.process.returncode == 0
|
101
|
+
|
102
|
+
def unsuspend(self, **kwargs) -> bool:
|
103
|
+
"""
|
104
|
+
Unsuspend the suspended workflow.
|
105
|
+
|
106
|
+
Parameters
|
107
|
+
----------
|
108
|
+
authorize : str, optional, default None
|
109
|
+
Authorize the unsuspend with a production token.
|
110
|
+
|
111
|
+
Returns
|
112
|
+
-------
|
113
|
+
bool
|
114
|
+
True if the command was successful, False otherwise.
|
115
|
+
"""
|
116
|
+
_, run_id = self.pathspec.split("/")
|
117
|
+
|
118
|
+
# every subclass needs to have `self.deployer_kwargs`
|
119
|
+
command = get_lower_level_group(
|
120
|
+
self.deployer.api,
|
121
|
+
self.deployer.top_level_kwargs,
|
122
|
+
self.deployer.TYPE,
|
123
|
+
self.deployer.deployer_kwargs,
|
124
|
+
).unsuspend(run_id=run_id, **kwargs)
|
125
|
+
|
126
|
+
pid = self.deployer.spm.run_command(
|
127
|
+
[sys.executable, *command],
|
128
|
+
env=self.deployer.env_vars,
|
129
|
+
cwd=self.deployer.cwd,
|
130
|
+
show_output=self.deployer.show_output,
|
131
|
+
)
|
132
|
+
|
133
|
+
command_obj = self.deployer.spm.get(pid)
|
134
|
+
return command_obj.process.returncode == 0
|
135
|
+
|
136
|
+
def terminate(self, **kwargs) -> bool:
|
137
|
+
"""
|
138
|
+
Terminate the running workflow.
|
139
|
+
|
140
|
+
Parameters
|
141
|
+
----------
|
142
|
+
authorize : str, optional, default None
|
143
|
+
Authorize the termination with a production token.
|
144
|
+
|
145
|
+
Returns
|
146
|
+
-------
|
147
|
+
bool
|
148
|
+
True if the command was successful, False otherwise.
|
149
|
+
"""
|
150
|
+
_, run_id = self.pathspec.split("/")
|
151
|
+
|
152
|
+
# every subclass needs to have `self.deployer_kwargs`
|
153
|
+
command = get_lower_level_group(
|
154
|
+
self.deployer.api,
|
155
|
+
self.deployer.top_level_kwargs,
|
156
|
+
self.deployer.TYPE,
|
157
|
+
self.deployer.deployer_kwargs,
|
158
|
+
).terminate(run_id=run_id, **kwargs)
|
159
|
+
|
160
|
+
pid = self.deployer.spm.run_command(
|
161
|
+
[sys.executable, *command],
|
162
|
+
env=self.deployer.env_vars,
|
163
|
+
cwd=self.deployer.cwd,
|
164
|
+
show_output=self.deployer.show_output,
|
165
|
+
)
|
166
|
+
|
167
|
+
command_obj = self.deployer.spm.get(pid)
|
168
|
+
return command_obj.process.returncode == 0
|
169
|
+
|
170
|
+
@property
|
171
|
+
def status(self) -> Optional[str]:
|
172
|
+
"""
|
173
|
+
Get the status of the triggered run.
|
174
|
+
|
175
|
+
Returns
|
176
|
+
-------
|
177
|
+
str, optional
|
178
|
+
The status of the workflow considering the run object, or None if
|
179
|
+
the status could not be retrieved.
|
180
|
+
"""
|
181
|
+
from metaflow.plugins.argo.argo_workflows_cli import (
|
182
|
+
get_status_considering_run_object,
|
183
|
+
)
|
184
|
+
|
185
|
+
flow_name, run_id = self.pathspec.split("/")
|
186
|
+
name = run_id[5:]
|
187
|
+
status = ArgoWorkflows.get_workflow_status(flow_name, name)
|
188
|
+
if status is not None:
|
189
|
+
return get_status_considering_run_object(status, self.run)
|
190
|
+
return None
|
191
|
+
|
192
|
+
|
193
|
+
class ArgoWorkflowsDeployedFlow(DeployedFlow):
|
194
|
+
"""
|
195
|
+
A class representing a deployed Argo Workflow template.
|
196
|
+
"""
|
197
|
+
|
198
|
+
TYPE: ClassVar[Optional[str]] = "argo-workflows"
|
199
|
+
|
200
|
+
@classmethod
|
201
|
+
def from_deployment(cls, identifier: str, metadata: Optional[str] = None):
|
202
|
+
"""
|
203
|
+
Retrieves a `ArgoWorkflowsDeployedFlow` object from an identifier and optional
|
204
|
+
metadata.
|
205
|
+
|
206
|
+
Parameters
|
207
|
+
----------
|
208
|
+
identifier : str
|
209
|
+
Deployer specific identifier for the workflow to retrieve
|
210
|
+
metadata : str, optional, default None
|
211
|
+
Optional deployer specific metadata.
|
212
|
+
|
213
|
+
Returns
|
214
|
+
-------
|
215
|
+
ArgoWorkflowsDeployedFlow
|
216
|
+
A `ArgoWorkflowsDeployedFlow` object representing the
|
217
|
+
deployed flow on argo workflows.
|
218
|
+
"""
|
219
|
+
client = ArgoClient(namespace=KUBERNETES_NAMESPACE)
|
220
|
+
workflow_template = client.get_workflow_template(identifier)
|
221
|
+
|
222
|
+
if workflow_template is None:
|
223
|
+
raise MetaflowException("No deployed flow found for: %s" % identifier)
|
224
|
+
|
225
|
+
metadata_annotations = workflow_template.get("metadata", {}).get(
|
226
|
+
"annotations", {}
|
227
|
+
)
|
228
|
+
|
229
|
+
flow_name = metadata_annotations.get("metaflow/flow_name", "")
|
230
|
+
username = metadata_annotations.get("metaflow/owner", "")
|
231
|
+
parameters = json.loads(metadata_annotations.get("metaflow/parameters", {}))
|
232
|
+
|
233
|
+
# these two only exist if @project decorator is used..
|
234
|
+
branch_name = metadata_annotations.get("metaflow/branch_name", None)
|
235
|
+
project_name = metadata_annotations.get("metaflow/project_name", None)
|
236
|
+
|
237
|
+
project_kwargs = {}
|
238
|
+
if branch_name is not None:
|
239
|
+
if branch_name.startswith("prod."):
|
240
|
+
project_kwargs["production"] = True
|
241
|
+
project_kwargs["branch"] = branch_name[len("prod.") :]
|
242
|
+
elif branch_name.startswith("test."):
|
243
|
+
project_kwargs["branch"] = branch_name[len("test.") :]
|
244
|
+
elif branch_name == "prod":
|
245
|
+
project_kwargs["production"] = True
|
246
|
+
|
247
|
+
fake_flow_file_contents = generate_fake_flow_file_contents(
|
248
|
+
flow_name=flow_name, param_info=parameters, project_name=project_name
|
249
|
+
)
|
250
|
+
|
251
|
+
with tempfile.NamedTemporaryFile(suffix=".py", delete=False) as fake_flow_file:
|
252
|
+
with open(fake_flow_file.name, "w") as fp:
|
253
|
+
fp.write(fake_flow_file_contents)
|
254
|
+
|
255
|
+
if branch_name is not None:
|
256
|
+
d = Deployer(
|
257
|
+
fake_flow_file.name,
|
258
|
+
env={"METAFLOW_USER": username},
|
259
|
+
**project_kwargs,
|
260
|
+
).argo_workflows()
|
261
|
+
else:
|
262
|
+
d = Deployer(
|
263
|
+
fake_flow_file.name, env={"METAFLOW_USER": username}
|
264
|
+
).argo_workflows(name=identifier)
|
265
|
+
|
266
|
+
d.name = identifier
|
267
|
+
d.flow_name = flow_name
|
268
|
+
if metadata is None:
|
269
|
+
d.metadata = get_metadata()
|
270
|
+
else:
|
271
|
+
d.metadata = metadata
|
272
|
+
|
273
|
+
return cls(deployer=d)
|
274
|
+
|
275
|
+
@property
|
276
|
+
def production_token(self) -> Optional[str]:
|
277
|
+
"""
|
278
|
+
Get the production token for the deployed flow.
|
279
|
+
|
280
|
+
Returns
|
281
|
+
-------
|
282
|
+
str, optional
|
283
|
+
The production token, None if it cannot be retrieved.
|
284
|
+
"""
|
285
|
+
try:
|
286
|
+
_, production_token = ArgoWorkflows.get_existing_deployment(
|
287
|
+
self.deployer.name
|
288
|
+
)
|
289
|
+
return production_token
|
290
|
+
except TypeError:
|
291
|
+
return None
|
292
|
+
|
293
|
+
def delete(self, **kwargs) -> bool:
|
294
|
+
"""
|
295
|
+
Delete the deployed workflow template.
|
296
|
+
|
297
|
+
Parameters
|
298
|
+
----------
|
299
|
+
authorize : str, optional, default None
|
300
|
+
Authorize the deletion with a production token.
|
301
|
+
|
302
|
+
Returns
|
303
|
+
-------
|
304
|
+
bool
|
305
|
+
True if the command was successful, False otherwise.
|
306
|
+
"""
|
307
|
+
command = get_lower_level_group(
|
308
|
+
self.deployer.api,
|
309
|
+
self.deployer.top_level_kwargs,
|
310
|
+
self.deployer.TYPE,
|
311
|
+
self.deployer.deployer_kwargs,
|
312
|
+
).delete(**kwargs)
|
313
|
+
|
314
|
+
pid = self.deployer.spm.run_command(
|
315
|
+
[sys.executable, *command],
|
316
|
+
env=self.deployer.env_vars,
|
317
|
+
cwd=self.deployer.cwd,
|
318
|
+
show_output=self.deployer.show_output,
|
319
|
+
)
|
320
|
+
|
321
|
+
command_obj = self.deployer.spm.get(pid)
|
322
|
+
return command_obj.process.returncode == 0
|
323
|
+
|
324
|
+
def trigger(self, **kwargs) -> ArgoWorkflowsTriggeredRun:
|
325
|
+
"""
|
326
|
+
Trigger a new run for the deployed flow.
|
327
|
+
|
328
|
+
Parameters
|
329
|
+
----------
|
330
|
+
**kwargs : Any
|
331
|
+
Additional arguments to pass to the trigger command,
|
332
|
+
`Parameters` in particular.
|
333
|
+
|
334
|
+
Returns
|
335
|
+
-------
|
336
|
+
ArgoWorkflowsTriggeredRun
|
337
|
+
The triggered run instance.
|
338
|
+
|
339
|
+
Raises
|
340
|
+
------
|
341
|
+
Exception
|
342
|
+
If there is an error during the trigger process.
|
343
|
+
"""
|
344
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
345
|
+
tfp_runner_attribute = tempfile.NamedTemporaryFile(
|
346
|
+
dir=temp_dir, delete=False
|
347
|
+
)
|
348
|
+
|
349
|
+
# every subclass needs to have `self.deployer_kwargs`
|
350
|
+
command = get_lower_level_group(
|
351
|
+
self.deployer.api,
|
352
|
+
self.deployer.top_level_kwargs,
|
353
|
+
self.deployer.TYPE,
|
354
|
+
self.deployer.deployer_kwargs,
|
355
|
+
).trigger(deployer_attribute_file=tfp_runner_attribute.name, **kwargs)
|
356
|
+
|
357
|
+
pid = self.deployer.spm.run_command(
|
358
|
+
[sys.executable, *command],
|
359
|
+
env=self.deployer.env_vars,
|
360
|
+
cwd=self.deployer.cwd,
|
361
|
+
show_output=self.deployer.show_output,
|
362
|
+
)
|
363
|
+
|
364
|
+
command_obj = self.deployer.spm.get(pid)
|
365
|
+
content = handle_timeout(
|
366
|
+
tfp_runner_attribute, command_obj, self.deployer.file_read_timeout
|
367
|
+
)
|
368
|
+
|
369
|
+
if command_obj.process.returncode == 0:
|
370
|
+
return ArgoWorkflowsTriggeredRun(
|
371
|
+
deployer=self.deployer, content=content
|
372
|
+
)
|
373
|
+
|
374
|
+
raise Exception(
|
375
|
+
"Error triggering %s on %s for %s"
|
376
|
+
% (
|
377
|
+
self.deployer.name,
|
378
|
+
self.deployer.TYPE,
|
379
|
+
self.deployer.flow_file,
|
380
|
+
)
|
381
|
+
)
|
@@ -7,7 +7,7 @@ import traceback
|
|
7
7
|
from metaflow import util
|
8
8
|
from metaflow import R
|
9
9
|
from metaflow.exception import CommandException, METAFLOW_EXIT_DISALLOW_RETRY
|
10
|
-
from metaflow.
|
10
|
+
from metaflow.metadata_provider.util import sync_local_metadata_from_datastore
|
11
11
|
from metaflow.metaflow_config import DATASTORE_LOCAL_DIR
|
12
12
|
from metaflow.mflog import TASK_LOG_SOURCE
|
13
13
|
from metaflow.unbounded_foreach import UBF_CONTROL, UBF_TASK
|
@@ -10,8 +10,8 @@ from metaflow import R, current
|
|
10
10
|
from metaflow.decorators import StepDecorator
|
11
11
|
from metaflow.plugins.resources_decorator import ResourcesDecorator
|
12
12
|
from metaflow.plugins.timeout_decorator import get_run_time_limit_for_task
|
13
|
-
from metaflow.
|
14
|
-
from metaflow.
|
13
|
+
from metaflow.metadata_provider import MetaDatum
|
14
|
+
from metaflow.metadata_provider.util import sync_local_metadata_to_datastore
|
15
15
|
from metaflow.metaflow_config import (
|
16
16
|
ECS_S3_ACCESS_IAM_ROLE,
|
17
17
|
BATCH_JOB_QUEUE,
|
@@ -154,6 +154,13 @@ def create(
|
|
154
154
|
use_distributed_map=False,
|
155
155
|
deployer_attribute_file=None,
|
156
156
|
):
|
157
|
+
for node in obj.graph:
|
158
|
+
if any([d.name == "slurm" for d in node.decorators]):
|
159
|
+
raise MetaflowException(
|
160
|
+
"Step *%s* is marked for execution on Slurm with AWS Step Functions which isn't currently supported."
|
161
|
+
% node.name
|
162
|
+
)
|
163
|
+
|
157
164
|
validate_tags(tags)
|
158
165
|
|
159
166
|
if deployer_attribute_file:
|