Skip to content

Instantly share code, notes, and snippets.

@henryroe
Created August 25, 2014 22:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save henryroe/d3d565619617cd7d7990 to your computer and use it in GitHub Desktop.
Save henryroe/d3d565619617cd7d7990 to your computer and use it in GitHub Desktop.
Demo of launching a traitsui GUI in a separate thread and communicating with it via stdin and stdout
import time
import os
import sys
import pickle
import numpy as np
import subprocess
from threading import Thread
from traits.api import HasTraits, String, Instance, Bool, on_trait_change, Long
from traitsui.api import View, Item, Handler
import psutil
import pdb # NOQA
"""
A quick and dirty test/example of running a subprocess and communicating with it via pickled objects on stdin/stdout
"""
class ListenProperties(HasTraits):
info_string = String()
listen_thread_not_done = Bool(True)
masterPID = Long(-1)
class Error(Exception):
pass
class ListenThread(Thread):
def _send(self, x):
if isinstance(x, str):
x = (x,)
pkl = pickle.dumps(x).replace("\n", "\\()")
sys.stdout.write(pkl + '\n')
sys.stdout.flush()
def run(self):
self.listen_properties.info_string = 'Listen started\n' + self.listen_properties.info_string
while self.listen_properties.listen_thread_not_done:
in_str = sys.stdin.readline()
try:
x = pickle.loads(in_str.replace("\\()", "\n"))
except EOFError: # means we are done here...
return
if not isinstance(x, tuple):
raise Error("ListenThread only accepts tuples")
if x[0] == 'SHUTDOWN':
self.listen_properties.info_string = "shutdown is upon us...\n" + self.listen_properties.info_string
self.listen_properties.listen_thread_not_done = False
elif x[0] == 'masterPID':
self.listen_properties.masterPID = x[1]
elif x[0] == 'question':
n = np.int(np.random.rand() * 100) + 1
self.listen_properties.info_string = ("listen was asked a question and answered {}\n".format(n) +
self.listen_properties.info_string)
self._send(('answer', n))
else:
self.listen_properties.info_string = ("listen saw (type), object:\n\t{}\n\t{}".format(type(x), x) + "\n" +
self.listen_properties.info_string)
self.listen_properties.info_string = 'listen is now quitting...\n' + self.listen_properties.info_string
class WatchMasterPIDThread(Thread):
def run(self):
while psutil.pid_exists(self.listen_properties.masterPID) or self.listen_properties.masterPID == -1:
time.sleep(2)
sys.stderr.write("\n\n----\nlooks like python session that owned this gui is gone, so dispose the gui\n----\n")
self.listen_properties.listen_thread_not_done = False
class MWHandler(Handler):
def object_kill_switch_changed(self, info):
if not info.initialized:
return
info.ui.dispose()
def init(self, info):
info.object._start_listen()
class MainWindow(HasTraits):
listen_properties = Instance(ListenProperties, ())
kill_switch = Bool(False)
listen_thread = Instance(ListenThread)
watch_pid_thread = Instance(WatchMasterPIDThread)
title = String("Demo of talking to a subprocess")
def _start_listen(self):
self.listen_thread = ListenThread()
self.listen_thread.listen_properties = self.listen_properties
self.listen_thread.daemon = True
self.listen_thread.start()
self.watch_pid_thread = WatchMasterPIDThread()
self.watch_pid_thread.listen_properties = self.listen_properties
self.watch_pid_thread.daemon = True
self.watch_pid_thread.start()
def default_traits_view(self):
return View(Item('object.listen_properties.info_string', show_label=False, springy=True, style='readonly'),
style="custom", resizable=True, width=500, height=500, handler=MWHandler(),
title=self.title)
@on_trait_change('listen_properties.listen_thread_not_done')
def on_thread_quit(self, new):
self.kill_switch = True
class talker():
def __init__(self):
cmd = 'python -c "from talk2subprocess_traitsui import MainWindow ; MainWindow().configure_traits()"'
# if shell=True, make darn sure are controlling what input we allow....
self.subproc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, shell=True)
self._send_pid()
def _send(self, x):
if isinstance(x, str):
x = (x,)
pkl = pickle.dumps(x).replace("\n", "\\()")
self.subproc.stdin.write(pkl + '\n')
self.subproc.stdin.flush()
def _receive(self):
in_str = self.subproc.stdout.readline()
x = pickle.loads(in_str.replace("\\()", "\n"))
return x
def _send_pid(self):
self._send(('masterPID', os.getpid()))
def send_yes(self):
self._send("yes")
def send_no(self):
self._send("no")
def send_question(self):
self._send("question")
x = self._receive()
if x[0] == 'answer':
print "the answer is {}".format(x[1])
else:
print "unrecognized return: {}".format(x)
return x[1]
def shutdown(self):
self._send("SHUTDOWN")
if __name__ == '__main__':
t = talker()
time.sleep(2)
t.send_no()
time.sleep(2)
t.send_yes()
time.sleep(2)
answers = []
for i in range(10):
answers.append(t.send_question())
print "The answers are {}".format(answers)
t.shutdown()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment