Created
August 25, 2014 22:40
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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