私はpythonスクリプトを流体力学コードのドライバとして使用しています。シミュレーションを実行するときは、subprocess.Popen
を使用してコードを実行し、stdoutとstderrからの出力をsubprocess.PIPE
に収集します。その後、出力情報を印刷(およびログファイルに保存)して、すべてのエラー問題は、コードがどのように進歩しているのか私にはわかりません。コマンドラインから直接実行した場合は、その反復の時点、時点、次のタイムステップなどについて出力されます。
ロギングとエラーチェックのために)出力を保存し、さらにライブストリーミングの出力を作成する方法はありますか?
私のコードの関連セクション:
ret_val = subprocess.Popen( run_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, Shell=True )
output, errors = ret_val.communicate()
log_file.write(output)
print output
if( ret_val.returncode ):
print "RUN failed\n\n%s\n\n" % (errors)
success = False
if( errors ): log_file.write("\n\n%s\n\n" % errors)
もともと私はrun_command
からtee
までをコピーしてコピーが直接ログファイルに行き、ストリームがまだ直接端末に出力されるようにしていました - しかしそのようにしてエラーを(私の知識に)保存することはできません。
編集する
一時的な解決策:
ret_val = subprocess.Popen( run_command, stdout=log_file, stderr=subprocess.PIPE, Shell=True )
while not ret_val.poll():
log_file.flush()
次に、別の端末でtail -f log.txt
(s.t. log_file = 'log.txt'
)を実行します。
これを行うには、read
またはreadline
関数からイテレータを作成する方法があります。
import subprocess
import sys
with open('test.log', 'w') as f: # replace 'w' with 'wb' for Python 3
process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
for c in iter(lambda: process.stdout.read(1), ''): # replace '' with b'' for Python 3
sys.stdout.write(c)
f.write(c)
または
import subprocess
import sys
with open('test.log', 'w') as f: # replace 'w' with 'wb' for Python 3
process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
for line in iter(process.stdout.readline, ''): # replace '' with b'' for Python 3
sys.stdout.write(line)
f.write(line)
reader
およびwriter
ファイルを作成することもできます。 writer
をPopen
に渡し、reader
から読み取ります。
import io
import time
import subprocess
import sys
filename = 'test.log'
with io.open(filename, 'wb') as writer, io.open(filename, 'rb', 1) as reader:
process = subprocess.Popen(command, stdout=writer)
while process.poll() is None:
sys.stdout.write(reader.read())
time.sleep(0.5)
# Read the remaining
sys.stdout.write(reader.read())
このようにして、標準出力と同様にtest.log
にデータを書き込むことができます。
ファイルアプローチの唯一の利点は、コードがブロックされないことです。そのため、その間に必要なことは何でも実行でき、reader
から必要なときにいつでもブロックすることなく読み取ることができます。 PIPE
を使用すると、read
およびreadline
関数は、それぞれ1文字がパイプに書き込まれるか1行がパイプに書き込まれるまでブロックされます。
subprocess.PIPE
が1つしかない場合は簡単ですが、それ以外の場合は困難です。subprocess.Popen
がどのように機能するかについて少し説明する時があるかもしれません。
(警告:これはPython 2.x用ですが、3.xは似ていますが、Windows版についてはかなりあいまいです。POSIXのものははるかに良く理解できます。)
Popen
関数は、ゼロから3つのI/Oストリームをやや同時に処理する必要があります。これらは、通常どおりstdin
、stdout
、およびstderr
と表示されます。
あなたが提供することができます:
None
は、ストリームをリダイレクトしたくないことを示します。代わりに通常通りこれらを継承します。少なくともPOSIXシステムでは、これはPythonのsys.stdout
を使うことを意味するのではなく、単にPythonのactual stdoutを使います。最後にデモを見てください。int
値これは(少なくともPOSIXでは)「生の」ファイル記述子です。 (補足:PIPE
とSTDOUT
は、実際は内部的にはint
sですが、 "不可能な"記述子である-1と-2です。)fileno
メソッドを持つ任意のオブジェクト。 Popen
は、そのストリームの記述子をstream.fileno()
を使用して検索し、次にint
の値と同じように処理を進めます。subprocess.PIPE
は、Pythonがパイプを作成する必要があることを示します。subprocess.STDOUT
(stderr
のみ):stdout
と同じ記述子を使うようにPythonに指示します。これは、None
に(non -stdout
)値を指定した場合にのみ意味があります。それでも、stdout=subprocess.PIPE
を設定した場合は必要になります。 (それ以外の場合は、stdout
に指定したものと同じ引数、たとえばPopen(..., stdout=stream, stderr=stream)
を指定することができます。)何もリダイレクトしない場合(3つすべてをデフォルトのNone
の値のままにするか、明示的にNone
を指定する)、Pipe
は非常に簡単です。サブプロセスをスピンオフして実行させるだけです。あるいは、PIPE
以外の_、またはint
、またはストリームのfileno()
にリダイレクトした場合でも、OSがすべての作業を実行するので簡単です。 Pythonはサブプロセスをスピンオフして、その標準入力、標準出力、標準エラー出力を提供されたファイル記述子に接続するだけです。
1つのストリームだけをリダイレクトしても、Pipe
にはまだかなり簡単なものがあります。一度に1つのストリームを選んで見ましょう。
stdin
を指定したいが、stdout
とstderr
をリダイレクトしないようにするか、ファイル記述子に移動します。親プロセスとして、あなたのPythonプログラムは単純にwrite()
を使ってデータをパイプに送ります。あなたはこれを自分で行うことができます、例えば:
proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
proc.stdin.write('here, have some data\n') # etc
または、stdinデータをproc.communicate()
に渡すこともできます。そして、それは上記のstdin.write
を行います。戻ってくる出力はないのでcommunicate()
には他に1つの本当の仕事しかありません:それはまたあなたのためにパイプを閉じます。 (proc.communicate()
を呼び出さないのであれば、パイプを閉じるためにproc.stdin.close()
を呼び出さなければならないので、サブプロセスはそれ以上データがないことを認識しています。)
stdout
をキャプチャしたいが、stdin
とstderr
はそのままにしておくとします。繰り返しますが、これは簡単です。出力がなくなるまでproc.stdout.read()
(または同等のもの)を呼び出すだけです。 proc.stdout()
は通常のPythonのI/Oストリームなので、次のように通常のコンストラクトをすべて使用できます。
for line in proc.stdout:
また、proc.communicate()
を使用することもできます。これは単にread()
を実行するだけです。
stderr
だけをキャプチャしたい場合は、stdout
と同じように機能します。
物事が難しくなる前にもう1つトリックがあります。 stdout
をキャプチャし、さらにstderr
をキャプチャしたいがstdoutと同じパイプ上で
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
この場合、subprocess
"cheats"!まあ、それはそれをしなければならない、それでそれは本当に詐欺的ではありません:それはその親(Python)プロセスにフィードバックする(単一の)パイプ記述子に向けられたそのstdoutとstderrの両方でサブプロセスを開始します。親側にも、出力を読み取るための単一のパイプディスクリプタしかありません。 "stderr"の出力はすべてproc.stdout
に表示されます。proc.communicate()
を呼び出すと、stderrの結果(Tupleの2番目の値)は文字列ではなくNone
になります。
問題はすべて、少なくとも2本のパイプを使いたいときに起こります。実際、subprocess
コード自体にこのビットがあります。
def communicate(self, input=None):
...
# Optimization: If we are only using one pipe, or no pipe at
# all, using select() or threads is unnecessary.
if [self.stdin, self.stdout, self.stderr].count(None) >= 2:
しかし、残念ながら、ここでは少なくとも2つ、場合によっては3つの異なるパイプを作成しました。したがって、count(None)
は1または0のいずれかを返します。
Windowsでは、これはthreading.Thread
を使用してself.stdout
およびself.stderr
の結果を累積し、親スレッドにself.stdin
入力データを配信させます(そしてパイプを閉じます)。
POSIXでは、利用可能であればpoll
を、そうでなければselect
を使って出力を蓄積し、標準入力を渡します。これはすべて(単一の)親プロセス/スレッドで実行されます。
デッドロックを回避するために、スレッドまたはポーリング/選択がここで必要です。たとえば、3つのストリームすべてを3つの別々のパイプにリダイレクトしたとします。さらに、書き込み処理が中断されるまでの間にパイプに格納できるデータ量にはわずかな制限があり、読み取り処理がもう一方の端からパイプを「片付ける」のを待つとします。説明のために、この小さな制限を1バイトに設定しましょう。 (これは実際には物事がどのように機能するかを示していますが、制限が1バイトよりはるかに大きいことを除いて)。
'go\n'
からproc.stdin
のように、親(Python)プロセスが数バイトを書き込もうとすると、最初のバイトが入り、次に2番目のバイトがPythonプロセスを中断し、サブプロセスが最初のバイトを読み取るのを待ってパイプを空にします。
一方、サブプロセスが「こんにちは、パニックにならないでください」という親しみやすいものを印刷することにしたとします。挨拶。 H
はその標準出力パイプに入りますが、e
はそれを中断させ、親がそのH
を読み取って標準出力パイプを空にするのを待ちます。
これで私たちは動けなくなりました。Pythonプロセスは "go"と言って終了するのを待って眠っています、そしてサブプロセスも "Hello!Don't Panic!"と言って終了するのを待って眠っています。
subprocess.Popen
コードはthreading-or-select/pollに関するこの問題を回避します。バイトがパイプを通過できるとき、それらは行きます。それができない場合、スレッド(プロセス全体ではない)だけがスリープする必要があります。あるいは、select/pollの場合、Pythonプロセスは同時に "can write"または "data available"を待って、プロセスのstdinに書き込みます。スペースがあるときだけ、そしてデータが準備ができているときだけそのstdoutやstderrを読みます。 proc.communicate()
コード(実際には毛深いケースが扱われる場合は_communicate
)は、すべての標準入力データ(もしあれば)が送信され、すべての標準出力または標準エラーデータ、あるいはその両方が累積されると戻ります。
2つの異なるパイプでstdout
とstderr
の両方を(stdin
リダイレクトに関係なく)読み取る場合は、デッドロックも回避する必要があります。ここでのデッドロックのシナリオは異なります - stderr
からデータを取得している間にサブプロセスがstdout
に長い時間書き込みを行った場合、またはその逆の場合ですが、それでも問題はありません。
リダイレクトされていないPython subprocess
esがsys.stdout
ではなく基礎となる標準出力に書き込むことを実証することを約束しました。だから、ここでいくつかのコードです:
from cStringIO import StringIO
import os
import subprocess
import sys
def show1():
print 'start show1'
save = sys.stdout
sys.stdout = StringIO()
print 'sys.stdout being buffered'
proc = subprocess.Popen(['echo', 'hello'])
proc.wait()
in_stdout = sys.stdout.getvalue()
sys.stdout = save
print 'in buffer:', in_stdout
def show2():
print 'start show2'
save = sys.stdout
sys.stdout = open(os.devnull, 'w')
print 'after redirect sys.stdout'
proc = subprocess.Popen(['echo', 'hello'])
proc.wait()
sys.stdout = save
show1()
show2()
実行時:
$ python out.py
start show1
hello
in buffer: sys.stdout being buffered
start show2
hello
StringIO
オブジェクトにはfileno
がないため、stdout=sys.stdout
を追加した場合、最初のルーチンは失敗します。 stdout=sys.stdout
はsys.stdout
にリダイレクトされているので、os.devnull
を追加した場合、2番目はhello
を省略します。
(Pythonのfile-descriptor-1をリダイレクトすると、サブプロセスwillがそのリダイレクトに従います。open(os.devnull, 'w')
呼び出しは、fileno()
が2より大きいストリームを生成します。)
Readline()でiterコンストラクトを使用する代わりに、標準出力の読み取りにデフォルトのファイル反復子を使用することもできます。
import subprocess
import sys
process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
for line in process.stdout:
sys.stdout.write(line)
もしあなたがサードパーティのライブラリを使うことができるなら、あなたは sarge
のようなものを使うことができるかもしれません(開示:私はそのメンテナです)。このライブラリはサブプロセスからの出力ストリームへのノンブロッキングアクセスを可能にします - それはsubprocess
モジュールの上に層にされます。
良いが「ヘビー級」の解決策はTwistedを使うことです - 下を見てください。
あなたがこれらの線に沿って何か標準出力だけで生きることをいとわないのであればうまくいくはずです:
import subprocess
import sys
popenobj = subprocess.Popen(["ls", "-Rl"], stdout=subprocess.PIPE)
while not popenobj.poll():
stdoutdata = popenobj.stdout.readline()
if stdoutdata:
sys.stdout.write(stdoutdata)
else:
break
print "Return code", popenobj.returncode
(read()を使用すると有用ではない「ファイル」全体を読み取ろうとしますが、ここで実際に使用できるのは、現在パイプ内にあるすべてのデータを読み取るものです)
次のように、スレッドを使ってこれに取り組むこともできます。
import subprocess
import sys
import threading
popenobj = subprocess.Popen("ls", stdout=subprocess.PIPE, Shell=True)
def stdoutprocess(o):
while True:
stdoutdata = o.stdout.readline()
if stdoutdata:
sys.stdout.write(stdoutdata)
else:
break
t = threading.Thread(target=stdoutprocess, args=(popenobj,))
t.start()
popenobj.wait()
t.join()
print "Return code", popenobj.returncode
これで、2つのスレッドを持つことで標準エラー出力を追加する可能性があります。
ただし、サブプロセスドキュメントではこれらのファイルを直接使用することは推奨されず、communicate()
主に上記の問題ではないと思われるデッドロックが関係します)の使用を推奨します。仕事まで(また参照してください: http://www.python.org/dev/peps/pep-3145/ )そして我々は他の何かを見る必要があります。
より複雑な解決策は、次に示すように Twisted を使用することです。 https://twistedmatrix.com/documents/11.1.0/core/howto/process.html
これを Twisted で実行する方法は、reactor.spawnprocess()
を使用してプロセスを作成し、次に出力を非同期的に処理するProcessProtocol
を提供することです。 TwistedのサンプルPythonコードはこちらです。 https://twistedmatrix.com/documents/11.1.0/core/howto/listings/process/process.py
私が試した上記の解決策はすべて、stderrとstdoutの出力の分離(複数のパイプ)に失敗するか、OSのパイプバッファがいっぱいになると永久にブロックされます。サブプロセスのpoll()マニュアル私が見つけた唯一の信頼できる方法はselectを通してでした、しかしこれはposixだけの解決策です:
import subprocess
import sys
import os
import select
# returns command exit status, stdout text, stderr text
# rtoutput: show realtime output while running
def run_script(cmd,rtoutput=0):
p = subprocess.Popen(cmd, Shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
poller = select.poll()
poller.register(p.stdout, select.POLLIN)
poller.register(p.stderr, select.POLLIN)
coutput=''
cerror=''
fdhup={}
fdhup[p.stdout.fileno()]=0
fdhup[p.stderr.fileno()]=0
while sum(fdhup.values()) < len(fdhup):
try:
r = poller.poll(1)
except select.error, err:
if err.args[0] != EINTR:
raise
r=[]
for fd, flags in r:
if flags & (select.POLLIN | select.POLLPRI):
c = os.read(fd, 1024)
if rtoutput:
sys.stdout.write(c)
sys.stdout.flush()
if fd == p.stderr.fileno():
cerror+=c
else:
coutput+=c
else:
fdhup[fd]=1
return p.poll(), coutput.strip(), cerror.strip()
ラインバッファ出力がうまくいくように見えますが、その場合は次のようなものが適しています。 (警告:テストされていません。)これはサブプロセスの標準出力をリアルタイムで表示するだけです。リアルタイムでstderrとstdoutの両方を使いたい場合は、select
を使ってもっと複雑なことをする必要があります。
proc = subprocess.Popen(run_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, Shell=True)
while proc.poll() is None:
line = proc.stdout.readline()
print line
log_file.write(line + '\n')
# Might still be data on stdout at this point. Grab any
# remainder.
for line in proc.stdout.read().split('\n'):
print line
log_file.write(line + '\n')
# Do whatever you want with proc.stderr here...
解決策1:stdout
AND stderr
をリアルタイムで同時に記録
stdout
AND stderr
の両方を同時にrealtimeに記録する簡単なソリューション。
import subprocess as sp
from concurrent.futures import ThreadPoolExecutor
def log_popen_pipe(p, pipe_name):
while p.poll() is None:
line = getattr(p, pipe_name).readline()
log_file.write(line)
with sp.Popen(my_cmd, stdout=sp.PIPE, stderr=sp.PIPE, text=True) as p:
with ThreadPoolExecutor(2) as pool:
r1 = pool.submit(log_popen_pipe, p, 'stdout')
r2 = pool.submit(log_popen_pipe, p, 'stderr')
r1.result()
r2.result()
解決策2:stdout
とstderr
を1行ずつ、同時にリアルタイムで返すイテレータを作成します
ここでは、リアルタイムで同時に両方のパイプ(stdout
/stderr
)を反復処理できる関数read_popen_pipes()
を作成します。
import subprocess as sp
from queue import Queue, Empty
from concurrent.futures import ThreadPoolExecutor
def enqueue_output(file, queue):
for line in iter(file.readline, ''):
queue.put(line)
file.close()
def read_popen_pipes(p):
with ThreadPoolExecutor(2) as pool:
q_stdout, q_stderr = Queue(), Queue()
pool.submit(enqueue_output, p.stdout, q_stdout)
pool.submit(enqueue_output, p.stderr, q_stderr)
while True:
if p.poll() is not None and q_stdout.empty() and q_stderr.empty():
break
out_line = err_line = ''
try:
out_line = q_stdout.get_nowait()
err_line = q_stderr.get_nowait()
except Empty:
pass
yield (out_line, err_line)
with sp.Popen(my_cmd, stdout=sp.PIPE, stderr=sp.PIPE, text=True) as p:
for out_line, err_line in read_popen_pipes(p):
print(out_line, end='')
print(err_line, end='')
return p.poll()
上記すべてに基づいて、私はわずかに修正されたバージョン(python3)を提案します。
None
を返さない場合は、読み取りデータの処理を複製する必要はありません。コード:
import subprocess
proc = subprocess.Popen(cmd, Shell=True, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, universal_newlines=True)
while True:
rd = proc.stdout.readline()
print(rd, end='') # and whatever you want to do...
if not rd: # EOF
returncode = proc.poll()
if returncode is not None:
break
time.sleep(0.1) # cmd closed stdout, but not exited yet
# You may want to check on ReturnCode here
なぜstdout
を直接sys.stdout
に設定しないのですか?また、ログにも出力する必要がある場合は、単純にfのwriteメソッドをオーバーライドできます。
import sys
import subprocess
class SuperFile(open.__class__):
def write(self, data):
sys.stdout.write(data)
super(SuperFile, self).write(data)
f = SuperFile("log.txt","w+")
process = subprocess.Popen(command, stdout=f, stderr=f)
これらすべての答えに加えて、1つの簡単な方法も次のようになります。
process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
while process.stdout.readable():
line = process.stdout.readline()
if not line:
break
print(line.strip())
読み取り可能な限り読み取り可能なストリームをループ処理し、空の結果が得られた場合は停止します。
ここで重要なのは、出力がある限りreadline()
は(最後に\n
を含む)行を返すことです。それが本当に最後であれば空です。
これが誰かに役立つことを願っています。
Pythonicソリューションのどれも私のために働きませんでした。 proc.stdout.read()
やそれに類するものが永遠にブロックされる可能性があることがわかりました。
したがって、私は次のようにtee
を使用します。
subprocess.run('./my_long_running_binary 2>&1 | tee -a my_log_file.txt && exit ${PIPESTATUS}', Shell=True, check=True, executable='/bin/bash')
あなたが既にShell=True
を使っているならば、この解決法は便利です。
${PIPESTATUS}
はコマンドチェーン全体の成功ステータスをキャプチャします(Bashでのみ利用可能)。 && exit ${PIPESTATUS}
を省略した場合、tee
が失敗することはないので、これは常にゼロを返します。
"パイプバッファ"が一杯になるまで待ち時間が長すぎるのではなく、unbuffer
が各行を直ちに端末に表示するのに必要かもしれません。しかし、アンバッファはassert(SIG Abort)の終了ステータスを飲み込みます...
2>&1
もstderrorをファイルに記録します。
これが私のプロジェクトのひとつで使っているクラスです。サブプロセスの出力をログにリダイレクトします。最初は単純にwrite-methodを上書きしようとしましたが、サブプロセスが決してそれを呼び出すことはないのでそれは動作しません(リダイレクトはファイルディスクリプタレベルで起こります)。サブプロセスモジュールで行われているのと同じように、私は自分のパイプを使っています。これには、すべてのロギング/印刷ロジックをアダプターにカプセル化するという利点があり、ロガーのインスタンスをPopen
に渡すことができます。subprocess.Popen("/path/to/binary", stderr = LogAdapter("foo"))
class LogAdapter(threading.Thread):
def __init__(self, logname, level = logging.INFO):
super().__init__()
self.log = logging.getLogger(logname)
self.readpipe, self.writepipe = os.pipe()
logFunctions = {
logging.DEBUG: self.log.debug,
logging.INFO: self.log.info,
logging.WARN: self.log.warn,
logging.ERROR: self.log.warn,
}
try:
self.logFunction = logFunctions[level]
except KeyError:
self.logFunction = self.log.info
def fileno(self):
#when fileno is called this indicates the subprocess is about to fork => start thread
self.start()
return self.writepipe
def finished(self):
"""If the write-filedescriptor is not closed this thread will
prevent the whole program from exiting. You can use this method
to clean up after the subprocess has terminated."""
os.close(self.writepipe)
def run(self):
inputFile = os.fdopen(self.readpipe)
while True:
line = inputFile.readline()
if len(line) == 0:
#no new data was added
break
self.logFunction(line.strip())
ロギングを必要とせず、単にprint()
を使いたいのであれば、明らかにコードの大部分を削除してクラスを短くすることができます。 __enter__
および__exit__
メソッドでそれを拡張し、__exit__
内でfinished
を呼び出すことで、コンテキストとして簡単に使用できるようにすることもできます。
これまでの回答と似ていますが、Python 3を使用しているウィンドウでは、次の解決法が役に立ちました。( getting-realtime-output-using-python ) :
def print_and_log(command, logFile):
with open(logFile, 'wb') as f:
command = subprocess.Popen(command, stdout=subprocess.PIPE, Shell=True)
while True:
output = command.stdout.readline()
if not output and command.poll() is not None:
f.close()
break
if output:
f.write(output)
print(str(output.strip(), 'utf-8'), flush=True)
return command.poll()
subprocess.communicate
メソッドは少し紛らわしいと思います。実際にはsubprocess.Popen
で指定したstdoutとstderrを埋めます。
ただし、subprocess.PIPE
のstdoutおよびstderrパラメータに指定できるsubprocess.Popen
から読み取ると、最終的にはOSのパイプバッファがいっぱいになり、アプリケーションがデッドロックされます(特に複数のプロセスがある場合)。 subprocess
を使用する必要がある/ threads。
私が提案した解決策は、stdoutとstderrをファイルと一緒に提供し、デッドロックのPIPE
からではなくファイルの内容を読むことです。これらのファイルはtempfile.NamedTemporaryFile()
にすることができます - それらがsubprocess.communicate
によって書き込まれている間に読み込みのためにアクセスすることもできます。
以下は使用例です
try:
with ProcessRunner(('python', 'task.py'), env=os.environ.copy(), seconds_to_wait=0.01) as process_runner:
for out in process_runner:
print(out)
catch ProcessError as e:
print(e.error_message)
raise
そして、これは使用する準備ができているというソースコードです。
あなたがpython 2を使っているのであれば、まずpypiのsubprocess32パッケージの最新版をインストールしてください。
import os
import sys
import threading
import time
import tempfile
import logging
if os.name == 'posix' and sys.version_info[0] < 3:
# Support python 2
import subprocess32 as subprocess
else:
# Get latest and greatest from python 3
import subprocess
logger = logging.getLogger(__name__)
class ProcessError(Exception):
"""Base exception for errors related to running the process"""
class ProcessTimeout(ProcessError):
"""Error that will be raised when the process execution will exceed a timeout"""
class ProcessRunner(object):
def __init__(self, args, env=None, timeout=None, bufsize=-1, seconds_to_wait=0.25, **kwargs):
"""
Constructor facade to subprocess.Popen that receives parameters which are more specifically required for the
Process Runner. This is a class that should be used as a context manager - and that provides an iterator
for reading captured output from subprocess.communicate in near realtime.
Example usage:
try:
with ProcessRunner(('python', task_file_path), env=os.environ.copy(), seconds_to_wait=0.01) as process_runner:
for out in process_runner:
print(out)
catch ProcessError as e:
print(e.error_message)
raise
:param args: same as subprocess.Popen
:param env: same as subprocess.Popen
:param timeout: same as subprocess.communicate
:param bufsize: same as subprocess.Popen
:param seconds_to_wait: time to wait between each readline from the temporary file
:param kwargs: same as subprocess.Popen
"""
self._seconds_to_wait = seconds_to_wait
self._process_has_timed_out = False
self._timeout = timeout
self._process_done = False
self._std_file_handle = tempfile.NamedTemporaryFile()
self._process = subprocess.Popen(args, env=env, bufsize=bufsize,
stdout=self._std_file_handle, stderr=self._std_file_handle, **kwargs)
self._thread = threading.Thread(target=self._run_process)
self._thread.daemon = True
def __enter__(self):
self._thread.start()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self._thread.join()
self._std_file_handle.close()
def __iter__(self):
# read all output from stdout file that subprocess.communicate fills
with open(self._std_file_handle.name, 'r') as stdout:
# while process is alive, keep reading data
while not self._process_done:
out = stdout.readline()
out_without_trailing_whitespaces = out.rstrip()
if out_without_trailing_whitespaces:
# yield stdout data without trailing \n
yield out_without_trailing_whitespaces
else:
# if there is nothing to read, then please wait a tiny little bit
time.sleep(self._seconds_to_wait)
# this is a hack: terraform seems to write to buffer after process has finished
out = stdout.read()
if out:
yield out
if self._process_has_timed_out:
raise ProcessTimeout('Process has timed out')
if self._process.returncode != 0:
raise ProcessError('Process has failed')
def _run_process(self):
try:
# Start gathering information (stdout and stderr) from the opened process
self._process.communicate(timeout=self._timeout)
# Graceful termination of the opened process
self._process.terminate()
except subprocess.TimeoutExpired:
self._process_has_timed_out = True
# Force termination of the opened process
self._process.kill()
self._process_done = True
@property
def return_code(self):
return self._process.returncode