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.

Files changed (63) hide show
  1. metaflow/__init__.py +2 -3
  2. metaflow/cli.py +27 -0
  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 -7
  11. metaflow/metaflow_current.py +1 -1
  12. metaflow/parameters.py +3 -0
  13. metaflow/plugins/__init__.py +12 -8
  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 -363
  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 +43 -28
  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 +134 -303
  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. {ob_metaflow-2.12.27.1.dist-info → ob_metaflow-2.12.30.2.dist-info}/METADATA +2 -2
  54. {ob_metaflow-2.12.27.1.dist-info → ob_metaflow-2.12.30.2.dist-info}/RECORD +63 -60
  55. {ob_metaflow-2.12.27.1.dist-info → ob_metaflow-2.12.30.2.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. {ob_metaflow-2.12.27.1.dist-info → ob_metaflow-2.12.30.2.dist-info}/LICENSE +0 -0
  62. {ob_metaflow-2.12.27.1.dist-info → ob_metaflow-2.12.30.2.dist-info}/entry_points.txt +0 -0
  63. {ob_metaflow-2.12.27.1.dist-info → ob_metaflow-2.12.30.2.dist-info}/top_level.txt +0 -0
@@ -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 Optional, Dict, ClassVar
4
+ from typing import ClassVar, Dict, Optional, TYPE_CHECKING
10
5
 
11
6
  from metaflow.exception import MetaflowNotFound
12
- from metaflow.runner.subprocess_manager import SubprocessManager
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
- Parameters
23
- ----------
24
- api : MetaflowAPI
25
- Metaflow API instance.
26
- top_level_kwargs : Dict
27
- Top-level keyword arguments to pass to the API.
28
- _type : str
29
- Type of the deployer implementation to target.
30
- deployer_kwargs : Dict
31
- Keyword arguments specific to the deployer.
32
-
33
- Returns
34
- -------
35
- Any
36
- The lower-level group object retrieved from the API.
37
-
38
- Raises
39
- ------
40
- ValueError
41
- If the `_type` is None.
42
- """
43
- if _type is None:
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
- class Deployer(object):
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 production orchestrator.
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 _enrich_object(self, env):
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 None, wait indefinitely.
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 LazyDeploymentMethod:
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
- def from_deployment(identifier, metadata=None, impl=None):
238
- if impl is None:
239
- impl = mcs.FROM_DEPLOYMENT_IMPL
240
-
241
- if impl not in mcs.FROM_DEPLOYMENT_PROVIDERS:
242
- raise ValueError("This method is not available for: %s" % impl)
243
-
244
- module_path = mcs.FROM_DEPLOYMENT_PROVIDERS[impl]
245
- lazy_method = LazyDeploymentMethod(module_path, "from_deployment")
246
- return lazy_method(identifier=identifier, metadata=metadata)
247
-
248
- setattr(cls, "from_deployment", staticmethod(from_deployment))
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=DeploymentMethodsMeta):
246
+ class DeployedFlow(metaclass=DeployedFlowMeta):
254
247
  """
255
248
  DeployedFlow class represents a flow that has been deployed.
256
249
 
257
- Parameters
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
- def __init__(self, deployer: "DeployerImpl"):
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()