1
1
import os
2
2
import sys
3
3
import time
4
- import asyncio
5
4
import tempfile
6
5
from typing import Dict , Iterator , Optional , Tuple
7
6
from metaflow import Run
@@ -86,6 +85,20 @@ async def wait(
86
85
await self .command_obj .wait (timeout , stream )
87
86
return self
88
87
88
+ @property
89
+ def returncode (self ) -> Optional [int ]:
90
+ """
91
+ Gets the returncode of the underlying subprocess that is responsible
92
+ for executing the run.
93
+
94
+ Returns
95
+ -------
96
+ Optional[int]
97
+ The returncode for the subprocess that executes the run.
98
+ (None if process is still running)
99
+ """
100
+ return self .command_obj .process .returncode
101
+
89
102
@property
90
103
def status (self ) -> str :
91
104
"""
@@ -214,32 +227,49 @@ def __init__(
214
227
from metaflow .runner .click_api import MetaflowAPI
215
228
216
229
self .flow_file = flow_file
217
- self .env_vars = os .environ .copy ().update (env or {})
230
+ self .env_vars = os .environ .copy ()
231
+ self .env_vars .update (env or {})
218
232
if profile :
219
233
self .env_vars ["METAFLOW_PROFILE" ] = profile
220
234
self .spm = SubprocessManager ()
235
+ self .top_level_kwargs = kwargs
221
236
self .api = MetaflowAPI .from_cli (self .flow_file , start )
222
- self .runner = self .api (** kwargs ).run
223
237
224
238
def __enter__ (self ) -> "Runner" :
225
239
return self
226
240
227
241
async def __aenter__ (self ) -> "Runner" :
228
242
return self
229
243
230
- def __exit__ (self , exc_type , exc_value , traceback ):
231
- self .spm .cleanup ()
232
-
233
- async def __aexit__ (self , exc_type , exc_value , traceback ):
234
- self .spm .cleanup ()
235
-
236
- def run (self , ** kwargs ) -> ExecutingRun :
244
+ def __get_executing_run (self , tfp_pathspec , command_obj ):
245
+ try :
246
+ pathspec = read_from_file_when_ready (tfp_pathspec .name , timeout = 10 )
247
+ run_object = Run (pathspec , _namespace_check = False )
248
+ return ExecutingRun (self , command_obj , run_object )
249
+ except TimeoutError as e :
250
+ stdout_log = open (command_obj .log_files ["stdout" ]).read ()
251
+ stderr_log = open (command_obj .log_files ["stderr" ]).read ()
252
+ command = " " .join (command_obj .command )
253
+ error_message = "Error executing: '%s':\n " % command
254
+ if stdout_log .strip ():
255
+ error_message += "\n Stdout:\n %s\n " % stdout_log
256
+ if stderr_log .strip ():
257
+ error_message += "\n Stderr:\n %s\n " % stderr_log
258
+ raise RuntimeError (error_message ) from e
259
+
260
+ def run (self , show_output : bool = False , ** kwargs ) -> ExecutingRun :
237
261
"""
238
262
Synchronous execution of the run. This method will *block* until
239
263
the run has completed execution.
240
264
241
265
Parameters
242
266
----------
267
+ show_output : bool, default False
268
+ Suppress the 'stdout' and 'stderr' to the console by default.
269
+ They can be accessed later by reading the files present in the
270
+ ExecutingRun object (referenced as 'result' below) returned:
271
+ - result.stdout
272
+ - result.stderr
243
273
**kwargs : Any
244
274
Additional arguments that you would pass to `python ./myflow.py` after
245
275
the `run` command.
@@ -249,15 +279,53 @@ def run(self, **kwargs) -> ExecutingRun:
249
279
ExecutingRun
250
280
ExecutingRun object for this run.
251
281
"""
252
- loop = asyncio .new_event_loop ()
253
- asyncio .set_event_loop (loop )
282
+ with tempfile .TemporaryDirectory () as temp_dir :
283
+ tfp_pathspec = tempfile .NamedTemporaryFile (dir = temp_dir , delete = False )
284
+ command = self .api (** self .top_level_kwargs ).run (
285
+ pathspec_file = tfp_pathspec .name , ** kwargs
286
+ )
254
287
255
- try :
256
- result = loop .run_until_complete (self .async_run (** kwargs ))
257
- result = loop .run_until_complete (result .wait ())
258
- return result
259
- finally :
260
- loop .close ()
288
+ pid = self .spm .run_command (
289
+ [sys .executable , * command ], env = self .env_vars , show_output = show_output
290
+ )
291
+ command_obj = self .spm .get (pid )
292
+
293
+ return self .__get_executing_run (tfp_pathspec , command_obj )
294
+
295
+ def resume (self , show_output : bool = False , ** kwargs ):
296
+ """
297
+ Synchronous resume execution of the run.
298
+ This method will *block* until the resumed run has completed execution.
299
+
300
+ Parameters
301
+ ----------
302
+ show_output : bool, default False
303
+ Suppress the 'stdout' and 'stderr' to the console by default.
304
+ They can be accessed later by reading the files present in the
305
+ ExecutingRun object (referenced as 'result' below) returned:
306
+ - result.stdout
307
+ - result.stderr
308
+ **kwargs : Any
309
+ Additional arguments that you would pass to `python ./myflow.py` after
310
+ the `resume` command.
311
+
312
+ Returns
313
+ -------
314
+ ExecutingRun
315
+ ExecutingRun object for this resumed run.
316
+ """
317
+ with tempfile .TemporaryDirectory () as temp_dir :
318
+ tfp_pathspec = tempfile .NamedTemporaryFile (dir = temp_dir , delete = False )
319
+ command = self .api (** self .top_level_kwargs ).resume (
320
+ pathspec_file = tfp_pathspec .name , ** kwargs
321
+ )
322
+
323
+ pid = self .spm .run_command (
324
+ [sys .executable , * command ], env = self .env_vars , show_output = show_output
325
+ )
326
+ command_obj = self .spm .get (pid )
327
+
328
+ return self .__get_executing_run (tfp_pathspec , command_obj )
261
329
262
330
async def async_run (self , ** kwargs ) -> ExecutingRun :
263
331
"""
@@ -277,33 +345,48 @@ async def async_run(self, **kwargs) -> ExecutingRun:
277
345
"""
278
346
with tempfile .TemporaryDirectory () as temp_dir :
279
347
tfp_pathspec = tempfile .NamedTemporaryFile (dir = temp_dir , delete = False )
348
+ command = self .api (** self .top_level_kwargs ).run (
349
+ pathspec_file = tfp_pathspec .name , ** kwargs
350
+ )
280
351
281
- command = self .runner (pathspec_file = tfp_pathspec .name , ** kwargs )
282
-
283
- pid = await self .spm .run_command (
352
+ pid = await self .spm .async_run_command (
284
353
[sys .executable , * command ], env = self .env_vars
285
354
)
286
355
command_obj = self .spm .get (pid )
287
356
288
- try :
289
- pathspec = read_from_file_when_ready (tfp_pathspec .name , timeout = 5 )
290
- run_object = Run (pathspec , _namespace_check = False )
291
- return ExecutingRun (self , command_obj , run_object )
292
- except TimeoutError as e :
293
- stdout_log = open (
294
- command_obj .log_files ["stdout" ], encoding = "utf-8"
295
- ).read ()
296
- stderr_log = open (
297
- command_obj .log_files ["stderr" ], encoding = "utf-8"
298
- ).read ()
299
- command = " " .join (command_obj .command )
357
+ return self .__get_executing_run (tfp_pathspec , command_obj )
358
+
359
+ async def async_resume (self , ** kwargs ):
360
+ """
361
+ Asynchronous resume execution of the run.
362
+ This method will return as soon as the resume has launched.
363
+
364
+ Parameters
365
+ ----------
366
+ **kwargs : Any
367
+ Additional arguments that you would pass to `python ./myflow.py` after
368
+ the `resume` command.
369
+
370
+ Returns
371
+ -------
372
+ ExecutingRun
373
+ ExecutingRun object for this resumed run.
374
+ """
375
+ with tempfile .TemporaryDirectory () as temp_dir :
376
+ tfp_pathspec = tempfile .NamedTemporaryFile (dir = temp_dir , delete = False )
377
+ command = self .api (** self .top_level_kwargs ).resume (
378
+ pathspec_file = tfp_pathspec .name , ** kwargs
379
+ )
300
380
301
- error_message = "Error executing: '%s':\n " % command
381
+ pid = await self .spm .async_run_command (
382
+ [sys .executable , * command ], env = self .env_vars
383
+ )
384
+ command_obj = self .spm .get (pid )
302
385
303
- if stdout_log .strip ():
304
- error_message += "\n Stdout:\n %s\n " % stdout_log
386
+ return self .__get_executing_run (tfp_pathspec , command_obj )
305
387
306
- if stderr_log . strip ( ):
307
- error_message += " \n Stderr: \n %s \n " % stderr_log
388
+ def __exit__ ( self , exc_type , exc_value , traceback ):
389
+ self . spm . cleanup ()
308
390
309
- raise RuntimeError (error_message ) from e
391
+ async def __aexit__ (self , exc_type , exc_value , traceback ):
392
+ self .spm .cleanup ()
0 commit comments