This expands on Uwe C. Schroeder's recipe titled "Using wxPython with Twisted Python" to show one way to implement a modal progress bar using Twisted to make an XML-RPC call.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 | # In the public domain
# Author: Andrew Dalke <dalke@dalkescientific.com
#
# This is an example of a wxPython-based modal progress bar
# which uses Twisted to do XML-RPC requests from the state name
# demo server. It's easily extensible to other tasks.
from __future__ import generators
from wxPython.wx import *
from twisted.internet import reactor
from twisted.web.xmlrpc import Proxy
# returned from the main modal
COMPLETED, CANCELED, ERROR = range(3)
# returned from the (sub)modal when there's an XML-RPC error
RETRY, SKIP, STOP = range(20, 23)
# The progress modal does N "tasks". Each task has a
# "start" method, which returns a deferred. The deferred
# is chained to call "good" with the result or "bad"
# with the failure. The "bad" method may return one of
# RETRY, SKIP, or STOP to tell the modal how to recover.
class Task:
def start(self):
"""returns a defered"""
raise NotImplementedError
def good(self, result):
pass
def bad(self, fail):
pass
# To show examples of error modes
TEST_ERRORS = 1
# A Task to get the state name corresponding to the given number.
class StateTask(Task):
def __init__(self, i):
self.i = i
def start(self):
proxy = Proxy("http://beatty.userland.com/RPC2")
if TEST_ERRORS:
i = self.i
if i == 3:
proxy = Proxy("http://illegal-host_name/")
elif i == 6:
proxy = Proxy("http://beatty.userland.com/")
elif i == 8:
proxy = Proxy("http://beatty.userland.com/testing_xmlrpc_error_case")
return proxy.callRemote('examples.getStateName', self.i)
def good(self, result):
print "state", self.i, "is", result
def bad(self, fail):
# pop up a submodal
status = wxMessageBox("Cannot get name for state %d. Try again?\n"
"\n"
"The problem is: %s" % (self.i, fail.getErrorMessage()),
"Connection problem",
wxCANCEL | wxYES_NO | wxICON_QUESTION)
if status == wxYES:
return RETRY
elif status == wxNO:
return SKIP
elif status == wxCANCEL:
return STOP
else:
raise AssertionError(status)
# The progress dialog must be passed a "task list" object which
# implements len() (needed to know how many steps to show) and
# does forward iteration.
class StateTaskList:
def __init__(self, min=0, max=50):
self.min = min
self.max = max
def __iter__(self):
for i in range(self.min, self.max):
yield StateTask(i)
def __len__(self):
return self.max - self.min
class Progress(wxDialog):
def __init__(self, parent, ID, title, tasks,
pos=wxDefaultPosition, size=wxDefaultSize,
style=wxDEFAULT_DIALOG_STYLE):
wxDialog.__init__(self, parent, ID, title, pos, size, style)
n = len(tasks)
self.task_iter = iter(tasks)
sizer = wxBoxSizer(wxVERTICAL)
self.gauge = wxGauge(self, -1, 100, size = (300, -1))
sizer.Add(self.gauge, 0, wxALIGN_CENTER|wxALL, 5)
box = wxBoxSizer(wxHORIZONTAL)
spacer = wxStaticText(self, -1, "")
box.Add(spacer, 1, wxALIGN_CENTRE|wxALL|wxGROW)
btn = wxButton(self, wxID_CANCEL, " Cancel ")
box.Add(btn, 0, wxALIGN_CENTRE|wxALL, 5)
spacer = wxStaticText(self, -1, "")
box.Add(spacer, 1, wxALIGN_CENTRE|wxALL|wxGROW)
sizer.AddSizer(box, 0, wxALIGN_CENTER_VERTICAL|wxALL|wxGROW, 5)
EVT_BUTTON(self, wxID_CANCEL, self.OnCancel)
self.SetSizer(sizer)
self.SetAutoLayout(true)
sizer.Fit(self)
self.i = 0
self.Start(n)
self._canceled = 0
self.Feed()
def OnCancel(self, event):
self._canceled = 1
self.EndModal(CANCELED)
def Feed(self):
# Get the next task and start it up
try:
task = self.task_iter.next()
except StopIteration:
self.End()
return
self.StartTask(task)
def StartTask(self, task):
defered = task.start()
def do_good(result):
self.Good(task, result)
def do_bad(fail):
self.Bad(task, fail)
defered.addCallbacks(do_good, do_bad)
def Good(self, task, result):
if self._canceled:
return
task.good(result)
self.Update(1)
self.Feed()
def Bad(self, task, fail):
if self._canceled:
return
try_again = task.bad(fail)
if try_again == RETRY:
self.StartTask(task)
elif try_again == SKIP:
self.Update(1)
self.Feed()
elif try_again == STOP:
self._canceled = 1
self.EndModal(ERROR)
else:
raise AssertionError(try_again)
def Start(self, count):
self.gauge.SetRange(count)
self.gauge.SetValue(0)
def Update(self, incr):
# Increment the counter.
self.i += incr
self.gauge.SetValue(self.i)
def End(self):
self.gauge.SetValue(self.gauge.GetRange())
self.EndModal(COMPLETED)
# Thanks to Uwe C. Schroeder and his "Using wxPython with Twisted
# Python" recipe at aspn.ActiveState.com
class MyApp(wxApp):
def OnInit(self):
# Twisted Reactor code
reactor.startRunning()
EVT_TIMER(self, 999999, self.OnTimer)
self.timer = wxTimer(self, 999999)
self.timer.Start(150, False)
return true
def OnTimer(self, event):
reactor.runUntilCurrent()
reactor.doIteration(0)
def __del__(self):
self.timer.Stop()
reactor.stop()
wxApp.__del__(self)
def main():
app = MyApp(0)
win = Progress(None, -1, "Processing ...", StateTaskList(1, 10))
status = win.ShowModal()
if status == COMPLETED:
print "All done"
elif status == CANCELED:
print "Okay, I stopped."
elif status == ERROR:
print "What happened?"
else:
raise AssertionError(status)
if __name__ == "__main__":
main()
|
I wanted a simple GUI client for making a set of remote procedure calls using XML-RPC. Python has the standard library 'xmlrpclib' for doing this, but it blocks in the request. Since the call may take several minutes to complete, I want to give the user the chance to cancel the operation, which means no blocking.
There are several ways to do this, as described in detail on the wxPython wiki at http://wiki.wxpython.org/index.cgi/LongRunningTasks wxYield and idle events wouldn't help, since my code blocks on the socket. Threads might work, except that the libraries i'm using elsewhere in the project might not be thread safe.
I instead chose to give Twisted a try. Twisted has problems working with wxPython because both projects want to manage the main event loop. Luckily, Uwe C. Schroeder posted a recipe which makes them work together. This recipe expands on that to show how the different pieces can all fit together.
for the record... Twisted now comes with built in wxsupport. Use:
and
where app is your wxApp subclass. I don't know if there is any difference between this and your code, but wxsupport has always worked fine for me.
Event loop incompatibilities on Windows. The problem Uwe's recipe addresses (and this one expands on) is that, on MS Windows, Modal dialogs (and therefore also menus) use a whole new event loop, which Twisted doesn't know about. Therefore, Twisted events stall waiting for modal dialogs to close.
Uwe's recipe effectively turns wxsupport inside-out, and pumps Twisted events from inside the wx event loop instead of vice-versa. This is often good enough. Disadvantage: Twisted may not be as responsive; this would probably not effect the performance of many kinds of network clients, but it would certainly hurt you if you tried to use this recipe with a server attached to a wx GUI.
Another solution, as discussed, is to put the whole wxPython app inside another thread; yet another is to avoid menus and modals in your wx Application. There is no "perfect" solution yet.
The only real solution to this problem is a better win32eventreactor inside Twisted, to work with both kinds of events. (It doesn't exist yet, as of this writing.)
error running this sample. I get an unhandled Twisted exception running this sample code on Windows:
same problem when using wxsupport. I tried bot the sample and simply running an application using wxsupport, but seem to be getting the same exception. I am using Twisted 1.0.7rc1 with wxPython 2.41 for Python 2.3 on Windows 2000.
Here is the snippet of offending code (class names changed):
And here is the traceback:
I wonder if anyone can shed some light on:
1) What is the problem?
2) What platforms / versions of wxPython/Python wxsupport is expected to work on? The problem with menus and modal dialogs is actually not an issue for us(we'll do without), but the exception obviously is :-)
Twisted expects to handle signal handlers cleanly. Signal handlers are such things as ctrl-C, ctrl-Break, etc. They are handled on the main thread.
Eliminate the trapping of signal handlers by not using reactor.run(), but instead use reactor.run(installSignalHandlers=0).
You may want to consider putting reactor on its own thread and leaving wxPython on the main thread.
Twisted 1.1.1 has wxreactor. This recipe is no longer necessary. Itamar Shtull-Trauring added twisted.internet.wxreactor, which is a full-fledged reactor and runs each of Twisted and wx in timeslices. Modal dialogs (and menus, which are secretly modal dialogs) will now work on MSWindows with no additional code.
Update your wx projects to use wxreactor and rejoice! :-)
New Solution. For me, wxreactor doesn't work on linux and wxsupport doesn't work on windows XP. And neither work with modal dialogs.
I've posted another solution which I think is quite neat...
It's long because there's a demo chat app in there... http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/286201
twisted.internet.wxreactor works on Windows, Linux, and OS X now.