from __future__ import print_function """ Utility class for spawning and controlling CLI processes. See: http://stackoverflow.com/questions/375427/non-blocking-read-on-a-subprocess-pipe-in-python """ import sys, time from subprocess import PIPE, Popen from threading import Thread try: from Queue import Queue, Empty except ImportError: from queue import Queue, Empty # python 3.x ON_POSIX = "posix" in sys.builtin_module_names class Process(object): """ Process encapsulates a subprocess with queued stderr/stdout pipes. Wraps most methods from subprocess.Popen. For non-blocking reads, use proc.stdout.get_nowait() or proc.stdout.get(timeout=0.1). """ def __init__(self, exec_args, cwd=None): self.proc = Popen( exec_args, stdout=PIPE, stdin=PIPE, stderr=PIPE, bufsize=1, close_fds=ON_POSIX, universal_newlines=True, cwd=cwd, ) self.stdin = self.proc.stdin # self.stdin = PipePrinter(self.proc.stdin) self.stdout = PipeQueue(self.proc.stdout) self.stderr = PipeQueue(self.proc.stderr) for method in ["poll", "wait", "send_signal", "kill", "terminate"]: setattr(self, method, getattr(self.proc, method)) class PipePrinter(object): """ For debugging writes to a pipe. """ def __init__(self, pipe): self._pipe = pipe def __getattr__(self, attr): return getattr(self._pipe, attr) def write(self, strn): print("WRITE:" + repr(strn)) return self._pipe.write(strn) class PipeQueue(Queue): """ Queue that starts a second process to monitor a PIPE for new data. This is needed to allow non-blocking pipe reads. """ def __init__(self, pipe): Queue.__init__(self) self.thread = Thread(target=self.enqueue_output, args=(pipe, self)) self.thread.daemon = True # thread dies with the program self.thread.start() @staticmethod def enqueue_output(out, queue): for line in iter(out.readline, b""): queue.put(line) # print "READ: " + repr(line) out.close() def read(self): """ Read all available lines from the queue, concatenated into a single string. """ out = "" while True: try: out += self.get_nowait() except Empty: break return out def readline(self, timeout=None): """ Read a single line from the queue. """ # we break this up into multiple short reads to allow keyboard # interrupts start = time.time() ret = "" while True: if timeout is not None: remaining = start + timeout - time.time() if remaining <= 0: return "" else: remaining = 1 try: return self.get(timeout=min(0.1, remaining)) except Empty: pass