planex
planex copied to clipboard
utils.run should have an option to echo live stdout and stderr
Currently utils.run(cmd) swallows stdout and stderr and by default only prints them if cmd fails.
This will be much easier with python 3.4+ (even better, 3.5).
A solution only requiring smsll-ish dependencies is using trollius, a backport of asyncio present in epel.
The command would look something like:
import trollius
from trollius import From, Return
@trollius.coroutine
def _read_stream(stream, cbk):
"""
Read from stream line by line until EOF and run the callback cb
on the lines.
"""
while True:
line = yield From(stream.readline())
if line:
cbk(line)
else:
break
@trollius.coroutine
def _stream_subprocess(cmd, stdout_cb, stderr_cb, env):
"""
Capture cmd's stdout, stderr (line by line) executing the stdout_cb and
stderr_cb callbacks as they arrive.
"""
process = yield From(trollius.create_subprocess_exec(
*cmd,
stdout=trollius.subprocess.PIPE, stderr=trollius.subprocess.PIPE,
env=env))
yield From(trollius.wait([
_read_stream(process.stdout, stdout_cb),
_read_stream(process.stderr, stderr_cb)
]))
yield From(process.communicate())
raise Return(process.returncode)
def execute(cmd, stdout_cb, stderr_cb, env):
"""
Run cmd and process stdout and stderr concurrently line by line.
"""
loop = trollius.get_event_loop()
retc = loop.run_until_complete(
_stream_subprocess(
cmd,
stdout_cb,
stderr_cb,
env
))
return retc
def run_with_live_stdout(cmd, env=None):
"""
Run cmd in a subprocess, piping out the stdout.
"""
if env is None:
env = os.environ.copy()
stderr = []
print("+ %s" % " ".join(cmd))
ret = execute(cmd, lambda o: print(o, end=""), stderr.append, env)
if ret != 0:
print(" ".join(stderr))
raise Exception("Error while running\n\t%s" % " ".join(cmd))