Prior info: I’m on a Mac.
Q: How can I get terminal-like text output from the program execution, if I compile it with py2app for redistribution?
My case is a program that copies a lot of big files and takes a while to process so I would like to at least have an output notification everytime each file is copied.
This is easy if I run it on the command line, I can just print a new line.
But when I make a self-sufficient package, it simply opens on the bottom dock, with no window, and closes upon completion.
A simple text window would be fine.
Thanks in advance.
If you want to create a simple text window, you need to pick a GUI framework to do that with. For something this simple, there’s no reason not to use
Tkinter(which comes with any Python) orPyObjC(which is pre-installed with Apple’s Python 2.7), unless you happen to be more familiar withwx,gobject,Qt, etc.At any rate, however you do it, you’ll need to write a function that takes a message and appends it to the text window (maybe creating it lazily, if necessary), and call that function wherever you would normally
print. You may also want to write and install alogginghandler that does the same thing, so you can justlog.infostuff. (You could instead create a file-like object that does this and redirectstdoutand/orstderr, but unless you have no control over theprinting code, that’s going to be a lot more work.)The only real problem here is that a GUI needs an event loop, and you probably just wrote your code as a sequential script.
One way around that is to turn your whole current script into a background thread. If you’re using a GUI library that allows you to access the widgets from background threads, everything is easy; your
printfuncjust doestextwidget.append(msg). If not, it may at least have acall_on_main_threadtype function, so yourprintfuncdoescall_on_main_thread(textwidget.append, msg). If worst comes to worst (and I believe with Tkinter, it does), you have to create an explicit queue to push messages through, and write a queue handler in the event loop. This recipe should give you an idea. Replace the body ofworkerThreadwith your code, and end it withself.endApplication(). (There are probably better examples out there; this was just what I found first in a quick search.)The other way around that is to have your code cooperatively operate with the event loop. Some libraries, like
wx, have functions likeSafeYieldthat make things work if you just call it after every chunk of processing. Others don’t have that, but have a way to explicitly drive the event loop from your code. Others have neither—but every event loop framework has to have a way to schedule new events, so you can break your code up into a sequence of functions that each finish quickly and then do something likeroot.after_idle(nextfunc).However… are you sure you need to do this?
First, any app, including one created by
py2app, will send its stdout to the terminal if you run it withFoo.app/Contents/MacOS/Foo. And you can even set things up so thatopen Foo.appworks that way, if you want. Obviously this doesn’t help for people who just double-click the app in Finder (because then there is no terminal), but sometimes it’s sufficient to just have to output available when people need it and know how to follow instructions.And you can take this farther: Create a
Foo.commandfile that just does something like$(dirname $0)/Foo.app/Contents/MacOS/Foo, and when you double-click that file, it launches Terminal.app and runs your script.Or you can get even simpler: Just use
loggingto syslog the output, and if you want to see when each file is done, just watch the log messages go by inConsole.app.Finally, do you even need
py2appin the first place? If you don’t have any external dependencies, just rename you script toFoo.command, and double-clicking it will run it in Terminal.app. If you do have external dependencies, you might still be able to get away with bundling it all together as a folder with a.commandin it instead of as a.app.Obviously none of these ideas are exactly a professional or newbie-friendly way to build an interface, so if that matters, you will have to create a GUI.