ob-metaflow 2.12.27.1__py2.py3-none-any.whl → 2.12.30.2__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 +2 -3
- metaflow/cli.py +27 -0
- 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 -7
- metaflow/metaflow_current.py +1 -1
- metaflow/parameters.py +3 -0
- metaflow/plugins/__init__.py +12 -8
- 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 -363
- 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/kubernetes/kubernetes_jobsets.py +43 -28
- 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 +134 -303
- 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/system/system_logger.py +1 -19
- metaflow/system/system_monitor.py +0 -24
- metaflow/task.py +5 -8
- metaflow/version.py +1 -1
- {ob_metaflow-2.12.27.1.dist-info → ob_metaflow-2.12.30.2.dist-info}/METADATA +2 -2
- {ob_metaflow-2.12.27.1.dist-info → ob_metaflow-2.12.30.2.dist-info}/RECORD +63 -60
- {ob_metaflow-2.12.27.1.dist-info → ob_metaflow-2.12.30.2.dist-info}/WHEEL +1 -1
- /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
- {ob_metaflow-2.12.27.1.dist-info → ob_metaflow-2.12.30.2.dist-info}/LICENSE +0 -0
- {ob_metaflow-2.12.27.1.dist-info → ob_metaflow-2.12.30.2.dist-info}/entry_points.txt +0 -0
- {ob_metaflow-2.12.27.1.dist-info → ob_metaflow-2.12.30.2.dist-info}/top_level.txt +0 -0
metaflow/runner/deployer.py
CHANGED
|
@@ -1,53 +1,50 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import sys
|
|
3
1
|
import json
|
|
4
2
|
import time
|
|
5
|
-
import importlib
|
|
6
|
-
import functools
|
|
7
|
-
import tempfile
|
|
8
3
|
|
|
9
|
-
from typing import
|
|
4
|
+
from typing import ClassVar, Dict, Optional, TYPE_CHECKING
|
|
10
5
|
|
|
11
6
|
from metaflow.exception import MetaflowNotFound
|
|
12
|
-
from metaflow.
|
|
13
|
-
from metaflow.runner.utils import handle_timeout
|
|
7
|
+
from metaflow.metaflow_config import DEFAULT_FROM_DEPLOYMENT_IMPL
|
|
14
8
|
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
import metaflow
|
|
11
|
+
import metaflow.runner.deployer_impl
|
|
15
12
|
|
|
16
|
-
def get_lower_level_group(
|
|
17
|
-
api, top_level_kwargs: Dict, _type: Optional[str], deployer_kwargs: Dict
|
|
18
|
-
):
|
|
19
|
-
"""
|
|
20
|
-
Retrieve a lower-level group from the API based on the type and provided arguments.
|
|
21
13
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
raise ValueError(
|
|
45
|
-
"DeployerImpl doesn't have a 'TYPE' to target. Please use a sub-class of DeployerImpl."
|
|
46
|
-
)
|
|
47
|
-
return getattr(api(**top_level_kwargs), _type)(**deployer_kwargs)
|
|
14
|
+
class DeployerMeta(type):
|
|
15
|
+
def __new__(mcs, name, bases, dct):
|
|
16
|
+
cls = super().__new__(mcs, name, bases, dct)
|
|
17
|
+
|
|
18
|
+
from metaflow.plugins import DEPLOYER_IMPL_PROVIDERS
|
|
19
|
+
|
|
20
|
+
def _injected_method(method_name, deployer_class):
|
|
21
|
+
def f(self, **deployer_kwargs):
|
|
22
|
+
return deployer_class(
|
|
23
|
+
deployer_kwargs=deployer_kwargs,
|
|
24
|
+
flow_file=self.flow_file,
|
|
25
|
+
show_output=self.show_output,
|
|
26
|
+
profile=self.profile,
|
|
27
|
+
env=self.env,
|
|
28
|
+
cwd=self.cwd,
|
|
29
|
+
file_read_timeout=self.file_read_timeout,
|
|
30
|
+
**self.top_level_kwargs,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
f.__doc__ = provider_class.__doc__ or ""
|
|
34
|
+
f.__name__ = method_name
|
|
35
|
+
return f
|
|
48
36
|
|
|
37
|
+
for provider_class in DEPLOYER_IMPL_PROVIDERS:
|
|
38
|
+
# TYPE is the name of the CLI groups i.e.
|
|
39
|
+
# `argo-workflows` instead of `argo_workflows`
|
|
40
|
+
# The injected method names replace '-' by '_' though.
|
|
41
|
+
method_name = provider_class.TYPE.replace("-", "_")
|
|
42
|
+
setattr(cls, method_name, _injected_method(method_name, provider_class))
|
|
43
|
+
|
|
44
|
+
return cls
|
|
49
45
|
|
|
50
|
-
|
|
46
|
+
|
|
47
|
+
class Deployer(metaclass=DeployerMeta):
|
|
51
48
|
"""
|
|
52
49
|
Use the `Deployer` class to configure and access one of the production
|
|
53
50
|
orchestrators supported by Metaflow.
|
|
@@ -81,7 +78,7 @@ class Deployer(object):
|
|
|
81
78
|
env: Optional[Dict] = None,
|
|
82
79
|
cwd: Optional[str] = None,
|
|
83
80
|
file_read_timeout: int = 3600,
|
|
84
|
-
**kwargs
|
|
81
|
+
**kwargs,
|
|
85
82
|
):
|
|
86
83
|
self.flow_file = flow_file
|
|
87
84
|
self.show_output = show_output
|
|
@@ -91,56 +88,16 @@ class Deployer(object):
|
|
|
91
88
|
self.file_read_timeout = file_read_timeout
|
|
92
89
|
self.top_level_kwargs = kwargs
|
|
93
90
|
|
|
94
|
-
from metaflow.plugins import DEPLOYER_IMPL_PROVIDERS
|
|
95
|
-
|
|
96
|
-
for provider_class in DEPLOYER_IMPL_PROVIDERS:
|
|
97
|
-
# TYPE is the name of the CLI groups i.e.
|
|
98
|
-
# `argo-workflows` instead of `argo_workflows`
|
|
99
|
-
# The injected method names replace '-' by '_' though.
|
|
100
|
-
method_name = provider_class.TYPE.replace("-", "_")
|
|
101
|
-
setattr(Deployer, method_name, self.__make_function(provider_class))
|
|
102
|
-
|
|
103
|
-
def __make_function(self, deployer_class):
|
|
104
|
-
"""
|
|
105
|
-
Create a function for the given deployer class.
|
|
106
|
-
|
|
107
|
-
Parameters
|
|
108
|
-
----------
|
|
109
|
-
deployer_class : Type[DeployerImpl]
|
|
110
|
-
Deployer implementation class.
|
|
111
|
-
|
|
112
|
-
Returns
|
|
113
|
-
-------
|
|
114
|
-
Callable
|
|
115
|
-
Function that initializes and returns an instance of the deployer class.
|
|
116
|
-
"""
|
|
117
|
-
|
|
118
|
-
def f(self, **deployer_kwargs):
|
|
119
|
-
return deployer_class(
|
|
120
|
-
deployer_kwargs=deployer_kwargs,
|
|
121
|
-
flow_file=self.flow_file,
|
|
122
|
-
show_output=self.show_output,
|
|
123
|
-
profile=self.profile,
|
|
124
|
-
env=self.env,
|
|
125
|
-
cwd=self.cwd,
|
|
126
|
-
file_read_timeout=self.file_read_timeout,
|
|
127
|
-
**self.top_level_kwargs
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
return f
|
|
131
|
-
|
|
132
91
|
|
|
133
92
|
class TriggeredRun(object):
|
|
134
93
|
"""
|
|
135
|
-
TriggeredRun class represents a run that has been triggered on a
|
|
136
|
-
|
|
137
|
-
Only when the `start` task starts running, the `run` object corresponding to the run
|
|
138
|
-
becomes available.
|
|
94
|
+
TriggeredRun class represents a run that has been triggered on a
|
|
95
|
+
production orchestrator.
|
|
139
96
|
"""
|
|
140
97
|
|
|
141
98
|
def __init__(
|
|
142
99
|
self,
|
|
143
|
-
deployer: "DeployerImpl",
|
|
100
|
+
deployer: "metaflow.runner.deployer_impl.DeployerImpl",
|
|
144
101
|
content: str,
|
|
145
102
|
):
|
|
146
103
|
self.deployer = deployer
|
|
@@ -149,31 +106,18 @@ class TriggeredRun(object):
|
|
|
149
106
|
self.pathspec = content_json.get("pathspec")
|
|
150
107
|
self.name = content_json.get("name")
|
|
151
108
|
|
|
152
|
-
def
|
|
153
|
-
"""
|
|
154
|
-
Enrich the TriggeredRun object with additional properties and methods.
|
|
155
|
-
|
|
156
|
-
Parameters
|
|
157
|
-
----------
|
|
158
|
-
env : dict
|
|
159
|
-
Environment dictionary containing properties and methods to add.
|
|
160
|
-
"""
|
|
161
|
-
for k, v in env.items():
|
|
162
|
-
if isinstance(v, property):
|
|
163
|
-
setattr(self.__class__, k, v)
|
|
164
|
-
elif callable(v):
|
|
165
|
-
setattr(self, k, functools.partial(v, self))
|
|
166
|
-
else:
|
|
167
|
-
setattr(self, k, v)
|
|
168
|
-
|
|
169
|
-
def wait_for_run(self, timeout=None):
|
|
109
|
+
def wait_for_run(self, timeout: Optional[int] = None):
|
|
170
110
|
"""
|
|
171
111
|
Wait for the `run` property to become available.
|
|
172
112
|
|
|
113
|
+
The `run` property becomes available only after the `start` task of the triggered
|
|
114
|
+
flow starts running.
|
|
115
|
+
|
|
173
116
|
Parameters
|
|
174
117
|
----------
|
|
175
|
-
timeout : int, optional
|
|
176
|
-
Maximum time to wait for the `run` to become available, in seconds. If
|
|
118
|
+
timeout : int, optional, default None
|
|
119
|
+
Maximum time to wait for the `run` to become available, in seconds. If
|
|
120
|
+
None, wait indefinitely.
|
|
177
121
|
|
|
178
122
|
Raises
|
|
179
123
|
------
|
|
@@ -194,7 +138,7 @@ class TriggeredRun(object):
|
|
|
194
138
|
time.sleep(check_interval)
|
|
195
139
|
|
|
196
140
|
@property
|
|
197
|
-
def run(self):
|
|
141
|
+
def run(self) -> Optional["metaflow.Run"]:
|
|
198
142
|
"""
|
|
199
143
|
Retrieve the `Run` object for the triggered run.
|
|
200
144
|
|
|
@@ -214,217 +158,104 @@ class TriggeredRun(object):
|
|
|
214
158
|
return None
|
|
215
159
|
|
|
216
160
|
|
|
217
|
-
class
|
|
218
|
-
def __init__(self, module_path, func_name):
|
|
219
|
-
self.module_path = module_path
|
|
220
|
-
self.func_name = func_name
|
|
221
|
-
self.func = None
|
|
222
|
-
|
|
223
|
-
def __call__(self, *args, **kwargs):
|
|
224
|
-
if self.func is None:
|
|
225
|
-
module = importlib.import_module(self.module_path)
|
|
226
|
-
self.func = getattr(module, self.func_name)
|
|
227
|
-
return self.func(*args, **kwargs)
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
class DeploymentMethodsMeta(type):
|
|
231
|
-
from metaflow.plugins import FROM_DEPLOYMENT_PROVIDERS
|
|
232
|
-
from metaflow.metaflow_config import FROM_DEPLOYMENT_IMPL
|
|
233
|
-
|
|
161
|
+
class DeployedFlowMeta(type):
|
|
234
162
|
def __new__(mcs, name, bases, dct):
|
|
235
163
|
cls = super().__new__(mcs, name, bases, dct)
|
|
164
|
+
if not bases:
|
|
165
|
+
# Inject methods only in DeployedFlow and not any of its
|
|
166
|
+
# subclasses
|
|
167
|
+
from metaflow.plugins import DEPLOYER_IMPL_PROVIDERS
|
|
168
|
+
|
|
169
|
+
allowed_providers = dict(
|
|
170
|
+
{
|
|
171
|
+
provider.TYPE.replace("-", "_"): provider
|
|
172
|
+
for provider in DEPLOYER_IMPL_PROVIDERS
|
|
173
|
+
}
|
|
174
|
+
)
|
|
236
175
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
176
|
+
def _default_injected_method():
|
|
177
|
+
def f(
|
|
178
|
+
cls,
|
|
179
|
+
identifier: str,
|
|
180
|
+
metadata: Optional[str] = None,
|
|
181
|
+
impl: str = DEFAULT_FROM_DEPLOYMENT_IMPL.replace("-", "_"),
|
|
182
|
+
) -> "DeployedFlow":
|
|
183
|
+
"""
|
|
184
|
+
Retrieves a `DeployedFlow` object from an identifier and optional
|
|
185
|
+
metadata. The `impl` parameter specifies the deployer implementation
|
|
186
|
+
to use (like `argo-workflows`).
|
|
187
|
+
|
|
188
|
+
Parameters
|
|
189
|
+
----------
|
|
190
|
+
identifier : str
|
|
191
|
+
Deployer specific identifier for the workflow to retrieve
|
|
192
|
+
metadata : str, optional, default None
|
|
193
|
+
Optional deployer specific metadata.
|
|
194
|
+
impl : str, optional, default given by METAFLOW_DEFAULT_FROM_DEPLOYMENT_IMPL
|
|
195
|
+
The default implementation to use if not specified
|
|
196
|
+
|
|
197
|
+
Returns
|
|
198
|
+
-------
|
|
199
|
+
DeployedFlow
|
|
200
|
+
A `DeployedFlow` object representing the deployed flow corresponding
|
|
201
|
+
to the identifier
|
|
202
|
+
"""
|
|
203
|
+
if impl in allowed_providers:
|
|
204
|
+
return (
|
|
205
|
+
allowed_providers[impl]
|
|
206
|
+
.deployed_flow_type()
|
|
207
|
+
.from_deployment(identifier, metadata)
|
|
208
|
+
)
|
|
209
|
+
else:
|
|
210
|
+
raise ValueError(
|
|
211
|
+
f"No deployer '{impl}' exists; valid deployers are: "
|
|
212
|
+
f"{list(allowed_providers.keys())}"
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
f.__name__ = "from_deployment"
|
|
216
|
+
return f
|
|
217
|
+
|
|
218
|
+
def _per_type_injected_method(method_name, impl):
|
|
219
|
+
def f(
|
|
220
|
+
cls,
|
|
221
|
+
identifier: str,
|
|
222
|
+
metadata: Optional[str] = None,
|
|
223
|
+
):
|
|
224
|
+
return (
|
|
225
|
+
allowed_providers[impl]
|
|
226
|
+
.deployed_flow_type()
|
|
227
|
+
.from_deployment(identifier, metadata)
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
f.__name__ = method_name
|
|
231
|
+
return f
|
|
232
|
+
|
|
233
|
+
setattr(cls, "from_deployment", classmethod(_default_injected_method()))
|
|
234
|
+
|
|
235
|
+
for impl in allowed_providers:
|
|
236
|
+
method_name = f"from_{impl}"
|
|
237
|
+
setattr(
|
|
238
|
+
cls,
|
|
239
|
+
method_name,
|
|
240
|
+
classmethod(_per_type_injected_method(method_name, impl)),
|
|
241
|
+
)
|
|
249
242
|
|
|
250
243
|
return cls
|
|
251
244
|
|
|
252
245
|
|
|
253
|
-
class DeployedFlow(metaclass=
|
|
246
|
+
class DeployedFlow(metaclass=DeployedFlowMeta):
|
|
254
247
|
"""
|
|
255
248
|
DeployedFlow class represents a flow that has been deployed.
|
|
256
249
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
deployer : DeployerImpl
|
|
260
|
-
Instance of the deployer implementation.
|
|
250
|
+
This class is not meant to be instantiated directly. Instead, it is returned from
|
|
251
|
+
methods of `Deployer`.
|
|
261
252
|
"""
|
|
262
253
|
|
|
263
|
-
|
|
254
|
+
# This should match the TYPE value in DeployerImpl for proper stub generation
|
|
255
|
+
TYPE: ClassVar[Optional[str]] = None
|
|
256
|
+
|
|
257
|
+
def __init__(self, deployer: "metaflow.runner.deployer_impl.DeployerImpl"):
|
|
264
258
|
self.deployer = deployer
|
|
265
259
|
self.name = self.deployer.name
|
|
266
260
|
self.flow_name = self.deployer.flow_name
|
|
267
261
|
self.metadata = self.deployer.metadata
|
|
268
|
-
|
|
269
|
-
def _enrich_object(self, env):
|
|
270
|
-
"""
|
|
271
|
-
Enrich the DeployedFlow object with additional properties and methods.
|
|
272
|
-
|
|
273
|
-
Parameters
|
|
274
|
-
----------
|
|
275
|
-
env : dict
|
|
276
|
-
Environment dictionary containing properties and methods to add.
|
|
277
|
-
"""
|
|
278
|
-
for k, v in env.items():
|
|
279
|
-
if isinstance(v, property):
|
|
280
|
-
setattr(self.__class__, k, v)
|
|
281
|
-
elif callable(v):
|
|
282
|
-
setattr(self, k, functools.partial(v, self))
|
|
283
|
-
else:
|
|
284
|
-
setattr(self, k, v)
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
class DeployerImpl(object):
|
|
288
|
-
"""
|
|
289
|
-
Base class for deployer implementations. Each implementation should define a TYPE
|
|
290
|
-
class variable that matches the name of the CLI group.
|
|
291
|
-
|
|
292
|
-
Parameters
|
|
293
|
-
----------
|
|
294
|
-
flow_file : str
|
|
295
|
-
Path to the flow file to deploy.
|
|
296
|
-
show_output : bool, default True
|
|
297
|
-
Show the 'stdout' and 'stderr' to the console by default.
|
|
298
|
-
profile : Optional[str], default None
|
|
299
|
-
Metaflow profile to use for the deployment. If not specified, the default
|
|
300
|
-
profile is used.
|
|
301
|
-
env : Optional[Dict], default None
|
|
302
|
-
Additional environment variables to set for the deployment.
|
|
303
|
-
cwd : Optional[str], default None
|
|
304
|
-
The directory to run the subprocess in; if not specified, the current
|
|
305
|
-
directory is used.
|
|
306
|
-
file_read_timeout : int, default 3600
|
|
307
|
-
The timeout until which we try to read the deployer attribute file.
|
|
308
|
-
**kwargs : Any
|
|
309
|
-
Additional arguments that you would pass to `python myflow.py` before
|
|
310
|
-
the deployment command.
|
|
311
|
-
"""
|
|
312
|
-
|
|
313
|
-
TYPE: ClassVar[Optional[str]] = None
|
|
314
|
-
|
|
315
|
-
def __init__(
|
|
316
|
-
self,
|
|
317
|
-
flow_file: str,
|
|
318
|
-
show_output: bool = True,
|
|
319
|
-
profile: Optional[str] = None,
|
|
320
|
-
env: Optional[Dict] = None,
|
|
321
|
-
cwd: Optional[str] = None,
|
|
322
|
-
file_read_timeout: int = 3600,
|
|
323
|
-
**kwargs
|
|
324
|
-
):
|
|
325
|
-
if self.TYPE is None:
|
|
326
|
-
raise ValueError(
|
|
327
|
-
"DeployerImpl doesn't have a 'TYPE' to target. Please use a sub-class of DeployerImpl."
|
|
328
|
-
)
|
|
329
|
-
|
|
330
|
-
if "metaflow.cli" in sys.modules:
|
|
331
|
-
importlib.reload(sys.modules["metaflow.cli"])
|
|
332
|
-
from metaflow.cli import start
|
|
333
|
-
from metaflow.runner.click_api import MetaflowAPI
|
|
334
|
-
|
|
335
|
-
self.flow_file = flow_file
|
|
336
|
-
self.show_output = show_output
|
|
337
|
-
self.profile = profile
|
|
338
|
-
self.env = env
|
|
339
|
-
self.cwd = cwd
|
|
340
|
-
self.file_read_timeout = file_read_timeout
|
|
341
|
-
|
|
342
|
-
self.env_vars = os.environ.copy()
|
|
343
|
-
self.env_vars.update(self.env or {})
|
|
344
|
-
if self.profile:
|
|
345
|
-
self.env_vars["METAFLOW_PROFILE"] = profile
|
|
346
|
-
|
|
347
|
-
self.spm = SubprocessManager()
|
|
348
|
-
self.top_level_kwargs = kwargs
|
|
349
|
-
self.api = MetaflowAPI.from_cli(self.flow_file, start)
|
|
350
|
-
|
|
351
|
-
def __enter__(self) -> "DeployerImpl":
|
|
352
|
-
return self
|
|
353
|
-
|
|
354
|
-
def create(self, **kwargs) -> DeployedFlow:
|
|
355
|
-
"""
|
|
356
|
-
Create a deployed flow using the deployer implementation.
|
|
357
|
-
|
|
358
|
-
Parameters
|
|
359
|
-
----------
|
|
360
|
-
**kwargs : Any
|
|
361
|
-
Additional arguments to pass to `create` corresponding to the
|
|
362
|
-
command line arguments of `create`
|
|
363
|
-
|
|
364
|
-
Returns
|
|
365
|
-
-------
|
|
366
|
-
DeployedFlow
|
|
367
|
-
DeployedFlow object representing the deployed flow.
|
|
368
|
-
|
|
369
|
-
Raises
|
|
370
|
-
------
|
|
371
|
-
Exception
|
|
372
|
-
If there is an error during deployment.
|
|
373
|
-
"""
|
|
374
|
-
with tempfile.TemporaryDirectory() as temp_dir:
|
|
375
|
-
tfp_runner_attribute = tempfile.NamedTemporaryFile(
|
|
376
|
-
dir=temp_dir, delete=False
|
|
377
|
-
)
|
|
378
|
-
# every subclass needs to have `self.deployer_kwargs`
|
|
379
|
-
command = get_lower_level_group(
|
|
380
|
-
self.api, self.top_level_kwargs, self.TYPE, self.deployer_kwargs
|
|
381
|
-
).create(deployer_attribute_file=tfp_runner_attribute.name, **kwargs)
|
|
382
|
-
|
|
383
|
-
pid = self.spm.run_command(
|
|
384
|
-
[sys.executable, *command],
|
|
385
|
-
env=self.env_vars,
|
|
386
|
-
cwd=self.cwd,
|
|
387
|
-
show_output=self.show_output,
|
|
388
|
-
)
|
|
389
|
-
|
|
390
|
-
command_obj = self.spm.get(pid)
|
|
391
|
-
content = handle_timeout(
|
|
392
|
-
tfp_runner_attribute, command_obj, self.file_read_timeout
|
|
393
|
-
)
|
|
394
|
-
content = json.loads(content)
|
|
395
|
-
self.name = content.get("name")
|
|
396
|
-
self.flow_name = content.get("flow_name")
|
|
397
|
-
self.metadata = content.get("metadata")
|
|
398
|
-
# Additional info is used to pass additional deployer specific information.
|
|
399
|
-
# It is used in non-OSS deployers (extensions).
|
|
400
|
-
self.additional_info = content.get("additional_info", {})
|
|
401
|
-
|
|
402
|
-
if command_obj.process.returncode == 0:
|
|
403
|
-
deployed_flow = DeployedFlow(deployer=self)
|
|
404
|
-
self._enrich_deployed_flow(deployed_flow)
|
|
405
|
-
return deployed_flow
|
|
406
|
-
|
|
407
|
-
raise Exception("Error deploying %s to %s" % (self.flow_file, self.TYPE))
|
|
408
|
-
|
|
409
|
-
def _enrich_deployed_flow(self, deployed_flow: DeployedFlow):
|
|
410
|
-
"""
|
|
411
|
-
Enrich the DeployedFlow object with additional properties and methods.
|
|
412
|
-
|
|
413
|
-
Parameters
|
|
414
|
-
----------
|
|
415
|
-
deployed_flow : DeployedFlow
|
|
416
|
-
The DeployedFlow object to enrich.
|
|
417
|
-
"""
|
|
418
|
-
raise NotImplementedError
|
|
419
|
-
|
|
420
|
-
def __exit__(self, exc_type, exc_value, traceback):
|
|
421
|
-
"""
|
|
422
|
-
Cleanup resources on exit.
|
|
423
|
-
"""
|
|
424
|
-
self.cleanup()
|
|
425
|
-
|
|
426
|
-
def cleanup(self):
|
|
427
|
-
"""
|
|
428
|
-
Cleanup resources.
|
|
429
|
-
"""
|
|
430
|
-
self.spm.cleanup()
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import tempfile
|
|
6
|
+
|
|
7
|
+
from typing import Any, ClassVar, Dict, Optional, TYPE_CHECKING, Type
|
|
8
|
+
|
|
9
|
+
from .subprocess_manager import SubprocessManager
|
|
10
|
+
from .utils import get_lower_level_group, handle_timeout
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
import metaflow.runner.deployer
|
|
14
|
+
|
|
15
|
+
# NOTE: This file is separate from the deployer.py file to prevent circular imports.
|
|
16
|
+
# This file is needed in any of the DeployerImpl implementations
|
|
17
|
+
# (like argo_workflows_deployer.py) which is in turn needed to create the Deployer
|
|
18
|
+
# class (ie: it uses ArgoWorkflowsDeployer to create the Deployer class).
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class DeployerImpl(object):
|
|
22
|
+
"""
|
|
23
|
+
Base class for deployer implementations. Each implementation should define a TYPE
|
|
24
|
+
class variable that matches the name of the CLI group.
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
flow_file : str
|
|
29
|
+
Path to the flow file to deploy.
|
|
30
|
+
show_output : bool, default True
|
|
31
|
+
Show the 'stdout' and 'stderr' to the console by default.
|
|
32
|
+
profile : Optional[str], default None
|
|
33
|
+
Metaflow profile to use for the deployment. If not specified, the default
|
|
34
|
+
profile is used.
|
|
35
|
+
env : Optional[Dict], default None
|
|
36
|
+
Additional environment variables to set for the deployment.
|
|
37
|
+
cwd : Optional[str], default None
|
|
38
|
+
The directory to run the subprocess in; if not specified, the current
|
|
39
|
+
directory is used.
|
|
40
|
+
file_read_timeout : int, default 3600
|
|
41
|
+
The timeout until which we try to read the deployer attribute file.
|
|
42
|
+
**kwargs : Any
|
|
43
|
+
Additional arguments that you would pass to `python myflow.py` before
|
|
44
|
+
the deployment command.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
TYPE: ClassVar[Optional[str]] = None
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
flow_file: str,
|
|
52
|
+
show_output: bool = True,
|
|
53
|
+
profile: Optional[str] = None,
|
|
54
|
+
env: Optional[Dict] = None,
|
|
55
|
+
cwd: Optional[str] = None,
|
|
56
|
+
file_read_timeout: int = 3600,
|
|
57
|
+
**kwargs
|
|
58
|
+
):
|
|
59
|
+
if self.TYPE is None:
|
|
60
|
+
raise ValueError(
|
|
61
|
+
"DeployerImpl doesn't have a 'TYPE' to target. Please use a sub-class "
|
|
62
|
+
"of DeployerImpl."
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if "metaflow.cli" in sys.modules:
|
|
66
|
+
importlib.reload(sys.modules["metaflow.cli"])
|
|
67
|
+
from metaflow.cli import start
|
|
68
|
+
from metaflow.runner.click_api import MetaflowAPI
|
|
69
|
+
|
|
70
|
+
self.flow_file = flow_file
|
|
71
|
+
self.show_output = show_output
|
|
72
|
+
self.profile = profile
|
|
73
|
+
self.env = env
|
|
74
|
+
self.cwd = cwd
|
|
75
|
+
self.file_read_timeout = file_read_timeout
|
|
76
|
+
|
|
77
|
+
self.env_vars = os.environ.copy()
|
|
78
|
+
self.env_vars.update(self.env or {})
|
|
79
|
+
if self.profile:
|
|
80
|
+
self.env_vars["METAFLOW_PROFILE"] = profile
|
|
81
|
+
|
|
82
|
+
self.spm = SubprocessManager()
|
|
83
|
+
self.top_level_kwargs = kwargs
|
|
84
|
+
self.api = MetaflowAPI.from_cli(self.flow_file, start)
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def deployer_kwargs(self) -> Dict[str, Any]:
|
|
88
|
+
raise NotImplementedError
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def deployed_flow_type() -> Type["metaflow.runner.deployer.DeployedFlow"]:
|
|
92
|
+
raise NotImplementedError
|
|
93
|
+
|
|
94
|
+
def __enter__(self) -> "DeployerImpl":
|
|
95
|
+
return self
|
|
96
|
+
|
|
97
|
+
def create(self, **kwargs) -> "metaflow.runner.deployer.DeployedFlow":
|
|
98
|
+
"""
|
|
99
|
+
Create a sub-class of a `DeployedFlow` depending on the deployer implementation.
|
|
100
|
+
|
|
101
|
+
Parameters
|
|
102
|
+
----------
|
|
103
|
+
**kwargs : Any
|
|
104
|
+
Additional arguments to pass to `create` corresponding to the
|
|
105
|
+
command line arguments of `create`
|
|
106
|
+
|
|
107
|
+
Returns
|
|
108
|
+
-------
|
|
109
|
+
DeployedFlow
|
|
110
|
+
DeployedFlow object representing the deployed flow.
|
|
111
|
+
|
|
112
|
+
Raises
|
|
113
|
+
------
|
|
114
|
+
Exception
|
|
115
|
+
If there is an error during deployment.
|
|
116
|
+
"""
|
|
117
|
+
# Sub-classes should implement this by simply calling _create and pass the
|
|
118
|
+
# proper class as the DeployedFlow to return.
|
|
119
|
+
raise NotImplementedError
|
|
120
|
+
|
|
121
|
+
def _create(
|
|
122
|
+
self, create_class: Type["metaflow.runner.deployer.DeployedFlow"], **kwargs
|
|
123
|
+
) -> "metaflow.runner.deployer.DeployedFlow":
|
|
124
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
125
|
+
tfp_runner_attribute = tempfile.NamedTemporaryFile(
|
|
126
|
+
dir=temp_dir, delete=False
|
|
127
|
+
)
|
|
128
|
+
# every subclass needs to have `self.deployer_kwargs`
|
|
129
|
+
command = get_lower_level_group(
|
|
130
|
+
self.api, self.top_level_kwargs, self.TYPE, self.deployer_kwargs
|
|
131
|
+
).create(deployer_attribute_file=tfp_runner_attribute.name, **kwargs)
|
|
132
|
+
|
|
133
|
+
pid = self.spm.run_command(
|
|
134
|
+
[sys.executable, *command],
|
|
135
|
+
env=self.env_vars,
|
|
136
|
+
cwd=self.cwd,
|
|
137
|
+
show_output=self.show_output,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
command_obj = self.spm.get(pid)
|
|
141
|
+
content = handle_timeout(
|
|
142
|
+
tfp_runner_attribute, command_obj, self.file_read_timeout
|
|
143
|
+
)
|
|
144
|
+
content = json.loads(content)
|
|
145
|
+
self.name = content.get("name")
|
|
146
|
+
self.flow_name = content.get("flow_name")
|
|
147
|
+
self.metadata = content.get("metadata")
|
|
148
|
+
# Additional info is used to pass additional deployer specific information.
|
|
149
|
+
# It is used in non-OSS deployers (extensions).
|
|
150
|
+
self.additional_info = content.get("additional_info", {})
|
|
151
|
+
|
|
152
|
+
if command_obj.process.returncode == 0:
|
|
153
|
+
return create_class(deployer=self)
|
|
154
|
+
|
|
155
|
+
raise RuntimeError("Error deploying %s to %s" % (self.flow_file, self.TYPE))
|
|
156
|
+
|
|
157
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
158
|
+
"""
|
|
159
|
+
Cleanup resources on exit.
|
|
160
|
+
"""
|
|
161
|
+
self.cleanup()
|
|
162
|
+
|
|
163
|
+
def cleanup(self):
|
|
164
|
+
"""
|
|
165
|
+
Cleanup resources.
|
|
166
|
+
"""
|
|
167
|
+
self.spm.cleanup()
|