metaflow 2.12.8__py2.py3-none-any.whl → 2.12.9__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 -0
- metaflow/cli.py +12 -4
- metaflow/extension_support/plugins.py +1 -0
- metaflow/flowspec.py +8 -1
- metaflow/lint.py +13 -0
- metaflow/metaflow_current.py +0 -8
- metaflow/plugins/__init__.py +12 -0
- metaflow/plugins/argo/argo_workflows.py +462 -42
- metaflow/plugins/argo/argo_workflows_cli.py +60 -3
- metaflow/plugins/argo/argo_workflows_decorator.py +38 -7
- metaflow/plugins/argo/argo_workflows_deployer.py +290 -0
- metaflow/plugins/argo/jobset_input_paths.py +16 -0
- metaflow/plugins/aws/batch/batch_decorator.py +16 -13
- metaflow/plugins/aws/step_functions/step_functions_cli.py +45 -3
- metaflow/plugins/aws/step_functions/step_functions_deployer.py +251 -0
- metaflow/plugins/cards/card_cli.py +1 -1
- metaflow/plugins/kubernetes/kubernetes.py +279 -52
- metaflow/plugins/kubernetes/kubernetes_cli.py +26 -8
- metaflow/plugins/kubernetes/kubernetes_client.py +0 -1
- metaflow/plugins/kubernetes/kubernetes_decorator.py +56 -44
- metaflow/plugins/kubernetes/kubernetes_job.py +6 -6
- metaflow/plugins/kubernetes/kubernetes_jobsets.py +510 -272
- metaflow/plugins/parallel_decorator.py +108 -8
- metaflow/plugins/secrets/secrets_decorator.py +12 -3
- metaflow/plugins/test_unbounded_foreach_decorator.py +39 -4
- metaflow/runner/deployer.py +386 -0
- metaflow/runner/metaflow_runner.py +1 -20
- metaflow/runner/nbdeploy.py +130 -0
- metaflow/runner/nbrun.py +4 -28
- metaflow/runner/utils.py +49 -0
- metaflow/runtime.py +246 -134
- metaflow/version.py +1 -1
- {metaflow-2.12.8.dist-info → metaflow-2.12.9.dist-info}/METADATA +2 -2
- {metaflow-2.12.8.dist-info → metaflow-2.12.9.dist-info}/RECORD +38 -32
- {metaflow-2.12.8.dist-info → metaflow-2.12.9.dist-info}/WHEEL +1 -1
- {metaflow-2.12.8.dist-info → metaflow-2.12.9.dist-info}/LICENSE +0 -0
- {metaflow-2.12.8.dist-info → metaflow-2.12.9.dist-info}/entry_points.txt +0 -0
- {metaflow-2.12.8.dist-info → metaflow-2.12.9.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,130 @@
|
|
1
|
+
import os
|
2
|
+
import tempfile
|
3
|
+
from typing import Dict, Optional
|
4
|
+
|
5
|
+
from metaflow import Deployer
|
6
|
+
from metaflow.runner.utils import get_current_cell, format_flowfile
|
7
|
+
|
8
|
+
DEFAULT_DIR = tempfile.gettempdir()
|
9
|
+
|
10
|
+
|
11
|
+
class NBDeployerInitializationError(Exception):
|
12
|
+
"""Custom exception for errors during NBDeployer initialization."""
|
13
|
+
|
14
|
+
pass
|
15
|
+
|
16
|
+
|
17
|
+
class NBDeployer(object):
|
18
|
+
"""
|
19
|
+
A wrapper over `Deployer` for deploying flows defined in a Jupyter
|
20
|
+
notebook cell.
|
21
|
+
|
22
|
+
Instantiate this class on the last line of a notebook cell where
|
23
|
+
a `flow` is defined. In contrast to `Deployer`, this class is not
|
24
|
+
meant to be used in a context manager.
|
25
|
+
|
26
|
+
```python
|
27
|
+
deployer = NBDeployer(FlowName)
|
28
|
+
ar = deployer.argo_workflows(name="madhur")
|
29
|
+
ar_obj = ar.create()
|
30
|
+
result = ar_obj.trigger(alpha=300)
|
31
|
+
print(result.status)
|
32
|
+
print(result.run)
|
33
|
+
result.terminate()
|
34
|
+
```
|
35
|
+
|
36
|
+
Parameters
|
37
|
+
----------
|
38
|
+
flow : FlowSpec
|
39
|
+
Flow defined in the same cell
|
40
|
+
show_output : bool, default True
|
41
|
+
Show the 'stdout' and 'stderr' to the console by default,
|
42
|
+
profile : Optional[str], default None
|
43
|
+
Metaflow profile to use to deploy this run. If not specified, the default
|
44
|
+
profile is used (or the one already set using `METAFLOW_PROFILE`)
|
45
|
+
env : Optional[Dict[str, str]], default None
|
46
|
+
Additional environment variables to set. This overrides the
|
47
|
+
environment set for this process.
|
48
|
+
base_dir : Optional[str], default None
|
49
|
+
The directory to run the subprocess in; if not specified, a temporary
|
50
|
+
directory is used.
|
51
|
+
**kwargs : Any
|
52
|
+
Additional arguments that you would pass to `python myflow.py` i.e. options
|
53
|
+
listed in `python myflow.py --help`
|
54
|
+
|
55
|
+
"""
|
56
|
+
|
57
|
+
def __init__(
|
58
|
+
self,
|
59
|
+
flow,
|
60
|
+
show_output: bool = True,
|
61
|
+
profile: Optional[str] = None,
|
62
|
+
env: Optional[Dict] = None,
|
63
|
+
base_dir: str = DEFAULT_DIR,
|
64
|
+
**kwargs,
|
65
|
+
):
|
66
|
+
try:
|
67
|
+
from IPython import get_ipython
|
68
|
+
|
69
|
+
ipython = get_ipython()
|
70
|
+
except ModuleNotFoundError:
|
71
|
+
raise NBDeployerInitializationError(
|
72
|
+
"'NBDeployer' requires an interactive Python environment (such as Jupyter)"
|
73
|
+
)
|
74
|
+
|
75
|
+
self.cell = get_current_cell(ipython)
|
76
|
+
self.flow = flow
|
77
|
+
self.show_output = show_output
|
78
|
+
self.profile = profile
|
79
|
+
self.env = env
|
80
|
+
self.cwd = base_dir
|
81
|
+
self.top_level_kwargs = kwargs
|
82
|
+
|
83
|
+
self.env_vars = os.environ.copy()
|
84
|
+
self.env_vars.update(env or {})
|
85
|
+
# clears the Jupyter parent process ID environment variable
|
86
|
+
# prevents server from interfering with Metaflow
|
87
|
+
self.env_vars.update({"JPY_PARENT_PID": ""})
|
88
|
+
|
89
|
+
if self.profile:
|
90
|
+
self.env_vars["METAFLOW_PROFILE"] = self.profile
|
91
|
+
|
92
|
+
if not self.cell:
|
93
|
+
raise ValueError("Couldn't find a cell.")
|
94
|
+
|
95
|
+
self.tmp_flow_file = tempfile.NamedTemporaryFile(
|
96
|
+
prefix=self.flow.__name__,
|
97
|
+
suffix=".py",
|
98
|
+
mode="w",
|
99
|
+
dir=self.cwd,
|
100
|
+
delete=False,
|
101
|
+
)
|
102
|
+
|
103
|
+
self.tmp_flow_file.write(format_flowfile(self.cell))
|
104
|
+
self.tmp_flow_file.flush()
|
105
|
+
self.tmp_flow_file.close()
|
106
|
+
|
107
|
+
self.flow_file = self.tmp_flow_file.name
|
108
|
+
|
109
|
+
self.deployer = Deployer(
|
110
|
+
flow_file=self.flow_file,
|
111
|
+
show_output=self.show_output,
|
112
|
+
profile=self.profile,
|
113
|
+
env=self.env_vars,
|
114
|
+
cwd=self.cwd,
|
115
|
+
**kwargs,
|
116
|
+
)
|
117
|
+
|
118
|
+
from metaflow.plugins import DEPLOYER_IMPL_PROVIDERS
|
119
|
+
|
120
|
+
for provider_class in DEPLOYER_IMPL_PROVIDERS:
|
121
|
+
method_name = provider_class.TYPE.replace("-", "_")
|
122
|
+
setattr(
|
123
|
+
NBDeployer, method_name, self.deployer.__make_function(provider_class)
|
124
|
+
)
|
125
|
+
|
126
|
+
def cleanup(self):
|
127
|
+
"""
|
128
|
+
Delete any temporary files created during execution.
|
129
|
+
"""
|
130
|
+
os.remove(self.flow_file)
|
metaflow/runner/nbrun.py
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
import ast
|
2
1
|
import os
|
3
2
|
import tempfile
|
4
3
|
from typing import Dict, Optional
|
5
4
|
|
6
5
|
from metaflow import Runner
|
6
|
+
from metaflow.runner.utils import get_current_cell, format_flowfile
|
7
7
|
|
8
8
|
DEFAULT_DIR = tempfile.gettempdir()
|
9
9
|
|
@@ -14,32 +14,6 @@ class NBRunnerInitializationError(Exception):
|
|
14
14
|
pass
|
15
15
|
|
16
16
|
|
17
|
-
def get_current_cell(ipython):
|
18
|
-
if ipython:
|
19
|
-
return ipython.history_manager.input_hist_raw[-1]
|
20
|
-
return None
|
21
|
-
|
22
|
-
|
23
|
-
def format_flowfile(cell):
|
24
|
-
"""
|
25
|
-
Formats the given cell content to create a valid Python script that can be executed as a Metaflow flow.
|
26
|
-
"""
|
27
|
-
flowspec = [
|
28
|
-
x
|
29
|
-
for x in ast.parse(cell).body
|
30
|
-
if isinstance(x, ast.ClassDef) and any(b.id == "FlowSpec" for b in x.bases)
|
31
|
-
]
|
32
|
-
|
33
|
-
if not flowspec:
|
34
|
-
raise ModuleNotFoundError(
|
35
|
-
"The cell doesn't contain any class that inherits from 'FlowSpec'"
|
36
|
-
)
|
37
|
-
|
38
|
-
lines = cell.splitlines()[: flowspec[0].end_lineno]
|
39
|
-
lines += ["if __name__ == '__main__':", f" {flowspec[0].name}()"]
|
40
|
-
return "\n".join(lines)
|
41
|
-
|
42
|
-
|
43
17
|
class NBRunner(object):
|
44
18
|
"""
|
45
19
|
A wrapper over `Runner` for executing flows defined in a Jupyter
|
@@ -47,7 +21,7 @@ class NBRunner(object):
|
|
47
21
|
|
48
22
|
Instantiate this class on the last line of a notebook cell where
|
49
23
|
a `flow` is defined. In contrast to `Runner`, this class is not
|
50
|
-
meant to be used a context manager. Instead, use a blocking helper
|
24
|
+
meant to be used in a context manager. Instead, use a blocking helper
|
51
25
|
function like `nbrun` (which calls `cleanup()` internally) or call
|
52
26
|
`cleanup()` explictly when using non-blocking APIs.
|
53
27
|
|
@@ -101,6 +75,8 @@ class NBRunner(object):
|
|
101
75
|
|
102
76
|
self.env_vars = os.environ.copy()
|
103
77
|
self.env_vars.update(env or {})
|
78
|
+
# clears the Jupyter parent process ID environment variable
|
79
|
+
# prevents server from interfering with Metaflow
|
104
80
|
self.env_vars.update({"JPY_PARENT_PID": ""})
|
105
81
|
if profile:
|
106
82
|
self.env_vars["METAFLOW_PROFILE"] = profile
|
metaflow/runner/utils.py
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
import os
|
2
|
+
import ast
|
3
|
+
import time
|
4
|
+
from typing import Dict
|
5
|
+
|
6
|
+
|
7
|
+
def get_current_cell(ipython):
|
8
|
+
if ipython:
|
9
|
+
return ipython.history_manager.input_hist_raw[-1]
|
10
|
+
return None
|
11
|
+
|
12
|
+
|
13
|
+
def format_flowfile(cell):
|
14
|
+
"""
|
15
|
+
Formats the given cell content to create a valid Python script that can be executed as a Metaflow flow.
|
16
|
+
"""
|
17
|
+
flowspec = [
|
18
|
+
x
|
19
|
+
for x in ast.parse(cell).body
|
20
|
+
if isinstance(x, ast.ClassDef) and any(b.id == "FlowSpec" for b in x.bases)
|
21
|
+
]
|
22
|
+
|
23
|
+
if not flowspec:
|
24
|
+
raise ModuleNotFoundError(
|
25
|
+
"The cell doesn't contain any class that inherits from 'FlowSpec'"
|
26
|
+
)
|
27
|
+
|
28
|
+
lines = cell.splitlines()[: flowspec[0].end_lineno]
|
29
|
+
lines += ["if __name__ == '__main__':", f" {flowspec[0].name}()"]
|
30
|
+
return "\n".join(lines)
|
31
|
+
|
32
|
+
|
33
|
+
def clear_and_set_os_environ(env: Dict):
|
34
|
+
os.environ.clear()
|
35
|
+
os.environ.update(env)
|
36
|
+
|
37
|
+
|
38
|
+
def read_from_file_when_ready(file_path: str, timeout: float = 5):
|
39
|
+
start_time = time.time()
|
40
|
+
with open(file_path, "r", encoding="utf-8") as file_pointer:
|
41
|
+
content = file_pointer.read()
|
42
|
+
while not content:
|
43
|
+
if time.time() - start_time > timeout:
|
44
|
+
raise TimeoutError(
|
45
|
+
"Timeout while waiting for file content from '%s'" % file_path
|
46
|
+
)
|
47
|
+
time.sleep(0.1)
|
48
|
+
content = file_pointer.read()
|
49
|
+
return content
|