metaflow 2.12.27__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.
Files changed (63) hide show
  1. metaflow/__init__.py +2 -3
  2. metaflow/cli.py +50 -13
  3. metaflow/client/core.py +2 -2
  4. metaflow/clone_util.py +1 -1
  5. metaflow/cmd/develop/stub_generator.py +623 -233
  6. metaflow/datastore/task_datastore.py +1 -1
  7. metaflow/extension_support/plugins.py +1 -0
  8. metaflow/flowspec.py +2 -2
  9. metaflow/includefile.py +8 -14
  10. metaflow/metaflow_config.py +4 -0
  11. metaflow/metaflow_current.py +1 -1
  12. metaflow/parameters.py +3 -0
  13. metaflow/plugins/__init__.py +12 -3
  14. metaflow/plugins/airflow/airflow_cli.py +5 -0
  15. metaflow/plugins/airflow/airflow_decorator.py +1 -1
  16. metaflow/plugins/argo/argo_workflows_decorator.py +1 -1
  17. metaflow/plugins/argo/argo_workflows_deployer.py +77 -263
  18. metaflow/plugins/argo/argo_workflows_deployer_objects.py +381 -0
  19. metaflow/plugins/aws/batch/batch_cli.py +1 -1
  20. metaflow/plugins/aws/batch/batch_decorator.py +2 -2
  21. metaflow/plugins/aws/step_functions/step_functions_cli.py +7 -0
  22. metaflow/plugins/aws/step_functions/step_functions_decorator.py +1 -1
  23. metaflow/plugins/aws/step_functions/step_functions_deployer.py +65 -224
  24. metaflow/plugins/aws/step_functions/step_functions_deployer_objects.py +236 -0
  25. metaflow/plugins/azure/includefile_support.py +2 -0
  26. metaflow/plugins/cards/card_cli.py +3 -2
  27. metaflow/plugins/cards/card_modules/components.py +9 -9
  28. metaflow/plugins/cards/card_server.py +39 -14
  29. metaflow/plugins/datatools/local.py +2 -0
  30. metaflow/plugins/datatools/s3/s3.py +2 -0
  31. metaflow/plugins/env_escape/__init__.py +3 -3
  32. metaflow/plugins/gcp/includefile_support.py +3 -0
  33. metaflow/plugins/kubernetes/kubernetes_cli.py +1 -1
  34. metaflow/plugins/kubernetes/kubernetes_decorator.py +5 -4
  35. metaflow/plugins/kubernetes/kubernetes_jobsets.py +1 -4
  36. metaflow/plugins/{metadata → metadata_providers}/local.py +2 -2
  37. metaflow/plugins/{metadata → metadata_providers}/service.py +2 -2
  38. metaflow/plugins/parallel_decorator.py +1 -1
  39. metaflow/plugins/pypi/conda_decorator.py +1 -1
  40. metaflow/plugins/test_unbounded_foreach_decorator.py +1 -1
  41. metaflow/runner/click_api.py +4 -0
  42. metaflow/runner/deployer.py +139 -269
  43. metaflow/runner/deployer_impl.py +167 -0
  44. metaflow/runner/metaflow_runner.py +10 -9
  45. metaflow/runner/nbdeploy.py +12 -13
  46. metaflow/runner/nbrun.py +3 -3
  47. metaflow/runner/utils.py +55 -8
  48. metaflow/runtime.py +1 -1
  49. metaflow/system/system_logger.py +1 -19
  50. metaflow/system/system_monitor.py +0 -24
  51. metaflow/task.py +5 -8
  52. metaflow/version.py +1 -1
  53. {metaflow-2.12.27.dist-info → metaflow-2.12.29.dist-info}/METADATA +2 -2
  54. {metaflow-2.12.27.dist-info → metaflow-2.12.29.dist-info}/RECORD +63 -60
  55. {metaflow-2.12.27.dist-info → metaflow-2.12.29.dist-info}/WHEEL +1 -1
  56. /metaflow/{metadata → metadata_provider}/__init__.py +0 -0
  57. /metaflow/{metadata → metadata_provider}/heartbeat.py +0 -0
  58. /metaflow/{metadata → metadata_provider}/metadata.py +0 -0
  59. /metaflow/{metadata → metadata_provider}/util.py +0 -0
  60. /metaflow/plugins/{metadata → metadata_providers}/__init__.py +0 -0
  61. {metaflow-2.12.27.dist-info → metaflow-2.12.29.dist-info}/LICENSE +0 -0
  62. {metaflow-2.12.27.dist-info → metaflow-2.12.29.dist-info}/entry_points.txt +0 -0
  63. {metaflow-2.12.27.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.metadata.util import sync_local_metadata_from_datastore
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.metadata import MetaDatum
14
- from metaflow.metadata.util import sync_local_metadata_to_datastore
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:
@@ -3,7 +3,7 @@ import os
3
3
  import time
4
4
 
5
5
  from metaflow.decorators import StepDecorator
6
- from metaflow.metadata import MetaDatum
6
+ from metaflow.metadata_provider import MetaDatum
7
7
 
8
8
  from .dynamo_db_client import DynamoDbClient
9
9