Under Python 3.4 on Windows, I need to stream data written to stdout/stderr by a child process, i.e. receive its output as it occurs, using the asyncio framework introduced
Since the event loop may see and notify the process exit before reading the remaining data for stdout/stderr, we need to check PIPE close events in addition to the process exit event.
This is a correction for aknuds1 answer:
class SubprocessProtocol(asyncio.SubprocessProtocol):
def __init__(self):
self._exited = False
self._closed_stdout = False
self._closed_stderr = False
@property
def finished(self):
return self._exited and self._closed_stdout and self._closed_stderr
def signal_exit(self):
if not self.finished:
return
loop.stop()
def pipe_data_received(self, fd, data):
if fd == 1:
name = 'stdout'
elif fd == 2:
name = 'stderr'
text = data.decode(locale.getpreferredencoding(False))
print('Received from {}: {}'.format(name, text.strip()))
def pipe_connection_lost(self, fd, exc):
if fd == 1:
self._closed_stdout = True
elif fd == 2:
self._closed_stderr = True
self.signal_exit()
def process_exited(self):
self._exited = True
self.signal_exit()
I guess to use high-level api:
proc = yield from asyncio.create_subprocess_exec(
'python', '-c', 'print(\'Hello async world!\')')
stdout, stderr = yield from proc.communicate()
retcode = proc.returncode
Also you can do more:
yield from proc.stdin.write(b'data')
yield from proc.stdin.drain()
stdout = yield from proc.stdout.read()
stderr = yield from proc.stderr.read()
retcode = yield from proc.wait()
and so on.
But, please, keep in mind that waiting for, say, stdout
when child process prints nothing can hang you coroutine.
The solution I've come up with so far uses SubprocessProtocol to receive output from the child process, and the associated transport to get the process' exit code. I don't know if this is optimal though. I've based my approach on an answer to a similar question by J.F. Sebastian.
import asyncio
import contextlib
import os
import locale
class SubprocessProtocol(asyncio.SubprocessProtocol):
def pipe_data_received(self, fd, data):
if fd == 1:
name = 'stdout'
elif fd == 2:
name = 'stderr'
text = data.decode(locale.getpreferredencoding(False))
print('Received from {}: {}'.format(name, text.strip()))
def process_exited(self):
loop.stop()
if os.name == 'nt':
# On Windows, the ProactorEventLoop is necessary to listen on pipes
loop = asyncio.ProactorEventLoop()
asyncio.set_event_loop(loop)
else:
loop = asyncio.get_event_loop()
with contextlib.closing(loop):
# This will only connect to the process
transport = loop.run_until_complete(loop.subprocess_exec(
SubprocessProtocol, 'python', '-c', 'print(\'Hello async world!\')'))[0]
# Wait until process has finished
loop.run_forever()
print('Program exited with: {}'.format(transport.get_returncode()))