B
Bryan Olson
Here's a module to show stderr output from console-less Python
apps, and stay out of the way otherwise. I plan to make a ASPN
recipe of it, but I thought I'd run it by this group first.
To use it, import the module. That's it. Upon import it will
assign sys.stderr.
In the normal case, your code is perfect so nothing ever gets
written to stderr, and the module won't do much of anything.
Upon the first write to stderr, if any, the module will launch a
new process, and that process will show the stderr output in a
window. The window will live until dismissed; I hate, hate, hate
those vanishing-consoles-with-critical-information.
The code shows some arguably-cool tricks. To fit everthing in
one file, the module runs the Python interpreter on itself; it
uses the "if __name__ == '__main__'" idiom to behave radically
differently upon import versus direct execution. It uses TkInter
for the window, but that's in a new process; it does not import
TkInter into your application.
To try it out, save it to a file -- I call it "errorwindow.py" -
- and import it into some subsequently-incorrect code. For
example:
import errorwindow
a = 3 + 1 + nonesuchdefined
should cause a window to appear, showing the traceback of a
Python NameError.
--
--Bryan
----------------------------------------------------------------
"""
Import this module into graphical Python apps to provide a
sys.stderr. No functions to call, just import it. It uses
only facilities in the Python standard distribution.
If nothing is ever written to stderr, then the module just
sits there and stays out of your face. Upon write to stderr,
it launches a new process, piping it error stream. The new
process throws up a window showing the error messages.
"""
import sys
import os
import thread
if __name__ == '__main__':
from Tkinter import *
import Queue
queue = Queue.Queue(99)
def read_stdin(app):
while 1:
data = os.read(sys.stdin.fileno(), 2048)
queue.put(data)
if not data:
break
class Application(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.master.title("Error Stream from run of %s" % sys.argv[-1])
self.pack(fill=BOTH, expand=YES)
self.logwidget = Text(self)
self.logwidget.pack(side=TOP, fill=BOTH, expand=YES)
# Disallow key entry, but allow copy with <Control-c>
self.logwidget.bind('<Key>', lambda x: 'break')
self.logwidget.bind('<Control-c>', lambda x: None)
self.after(200, self.start_thread, ())
def start_thread(self, _):
thread.start_new_thread(read_stdin, (self,))
self.after(200, self.check_q, ())
def check_q(self, _):
go = True
while go:
try:
data = queue.get_nowait()
if not data:
self.logwidget.configure(foreground ='#0000AA')
data = "\n==== File Closed ====\n"
go = False
self.logwidget.insert(END, data)
self.logwidget.see(END)
except Queue.Empty:
self.after(200, self.check_q, ())
go = False
app = Application()
app.mainloop()
else:
class ErrorPipe(object):
def __init__(self):
self.lock = thread.allocate_lock()
self.empty = True
def on_first_write(self):
command = "%s %s %s" % (sys.executable, __file__, sys.argv[0])
self.pipe = os.popen(command, 'w')
def _write(self, data):
self.pipe.write(data)
os.fsync(self.pipe)
def write(self, data):
self.lock.acquire()
try:
if self.empty:
self.on_first_write()
self.empty = False
self._write(data)
finally:
self.lock.release()
sys.stderr = ErrorPipe()
apps, and stay out of the way otherwise. I plan to make a ASPN
recipe of it, but I thought I'd run it by this group first.
To use it, import the module. That's it. Upon import it will
assign sys.stderr.
In the normal case, your code is perfect so nothing ever gets
written to stderr, and the module won't do much of anything.
Upon the first write to stderr, if any, the module will launch a
new process, and that process will show the stderr output in a
window. The window will live until dismissed; I hate, hate, hate
those vanishing-consoles-with-critical-information.
The code shows some arguably-cool tricks. To fit everthing in
one file, the module runs the Python interpreter on itself; it
uses the "if __name__ == '__main__'" idiom to behave radically
differently upon import versus direct execution. It uses TkInter
for the window, but that's in a new process; it does not import
TkInter into your application.
To try it out, save it to a file -- I call it "errorwindow.py" -
- and import it into some subsequently-incorrect code. For
example:
import errorwindow
a = 3 + 1 + nonesuchdefined
should cause a window to appear, showing the traceback of a
Python NameError.
--
--Bryan
----------------------------------------------------------------
"""
Import this module into graphical Python apps to provide a
sys.stderr. No functions to call, just import it. It uses
only facilities in the Python standard distribution.
If nothing is ever written to stderr, then the module just
sits there and stays out of your face. Upon write to stderr,
it launches a new process, piping it error stream. The new
process throws up a window showing the error messages.
"""
import sys
import os
import thread
if __name__ == '__main__':
from Tkinter import *
import Queue
queue = Queue.Queue(99)
def read_stdin(app):
while 1:
data = os.read(sys.stdin.fileno(), 2048)
queue.put(data)
if not data:
break
class Application(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.master.title("Error Stream from run of %s" % sys.argv[-1])
self.pack(fill=BOTH, expand=YES)
self.logwidget = Text(self)
self.logwidget.pack(side=TOP, fill=BOTH, expand=YES)
# Disallow key entry, but allow copy with <Control-c>
self.logwidget.bind('<Key>', lambda x: 'break')
self.logwidget.bind('<Control-c>', lambda x: None)
self.after(200, self.start_thread, ())
def start_thread(self, _):
thread.start_new_thread(read_stdin, (self,))
self.after(200, self.check_q, ())
def check_q(self, _):
go = True
while go:
try:
data = queue.get_nowait()
if not data:
self.logwidget.configure(foreground ='#0000AA')
data = "\n==== File Closed ====\n"
go = False
self.logwidget.insert(END, data)
self.logwidget.see(END)
except Queue.Empty:
self.after(200, self.check_q, ())
go = False
app = Application()
app.mainloop()
else:
class ErrorPipe(object):
def __init__(self):
self.lock = thread.allocate_lock()
self.empty = True
def on_first_write(self):
command = "%s %s %s" % (sys.executable, __file__, sys.argv[0])
self.pipe = os.popen(command, 'w')
def _write(self, data):
self.pipe.write(data)
os.fsync(self.pipe)
def write(self, data):
self.lock.acquire()
try:
if self.empty:
self.on_first_write()
self.empty = False
self._write(data)
finally:
self.lock.release()
sys.stderr = ErrorPipe()