blog.humaneguitarist.org

getting real-time values from imported modules with a Python GUI

[Thu, 07 Feb 2013 20:00:10 +0000]
Situation: Over a year ago I wrote a Python script called PubMed2XL to allow one to convert XML citations from Pubmed.gov to a Microsoft spreadsheet. I know some people are using it here and there, so I wanted to make it better. The main problem with the old script is it's just sloppy (I knew even less back then than I do now!). It's also a script that wraps everything into one: non-GUI and GUI. If you don't pass command line options, then it launches the GUI, etc. Anyway, that makes the code hard to read for me since it intermixes data parsing with command line option stuff and GUI stuff. So, for the next version I'm working on, I started with the premise to write it as a Python library so that it can be imported and one can use the function to make a spreadsheet inside another Python script a la: import pubmed2xl<br/> pubmed2xl.makeSheet("pubmed.xml", "pubmed.xls") #pass input and output (Excel file to be written) It's also setup to make it easy to use command line options a la: python pubmed2xl.py pubmed.xml pubmed.xls The function and command line options also support showing the progress of completion while the spreadsheet is being made. This can be called as such: pubmed2xl.makeSheet("pubmed.xml", "pubmed.xls", showProgress=True) or python pubmed2xl.py pubmed.xml pubmed.xsl --verbose The problem for me, then, was how to show the progress inside a GUI application. Essentially, I needed the value of a the progress counter "variable" that was created inside a loop and updated each time the loop occurred - i.e. updating the progress counter. But I couldn't figure out how to retrieve the value of the progress counter variable in real time as the loop occurred. And I need it in real time so my GUI could show the progress update to the user - in real time! I spent way too much time following leads that got me nowhere. I tried threads, running the python script as a sub-process, etc. but I could never access the variable "progressValue" that equates to the percentage of task completion as citations are getting processed into a spreadsheet. So, somehow I found my way to realizing that if my original script had a class and my second script added a method to the class then I could get the value of "progressValue" in real time. Anyway, I've got two scripts below. The "first.py" script emulates a progress calculator by simply counting to 100. The script also has a class, "callback" and a global dictionary "_CALLBACK_DICT" into which I can place key/value pairs for whatever variables I want to retrieve during the loop. The function "canYouSeeMe()" inside "first.py" also tries to execute the method "_CALLBACK.callback()" during the loop. In other words, if the method's there, run it, otherwise just ignore it. The second script "second.py" is a little TKinter GUI app. It imports the first module and the instantiated class("_CALLBACK"). It also has a function called "getCallback()" that does what I want: i.e. retrieve the progress count in real time and show it in the GUI in real time. I then I equate "getCallback()" to the "_CALLBACK.callback()" method. So now, when I run the "second.py" script, the loop in "first.py" can give me the data I want to show in "second.py" in real time. Make sense? I hope so because it seems to be working OK. Here's a screenshot from running "second.py" and below are the scripts themselves. I'd love any feedback on better ways of doing this, by the way. IMAGE: "Tkinter callback example"[http://blog.humaneguitarist.org/uploads/callback_python.png] first.py ##### "first.py" class callback(): pass _CALLBACK = callback() _CALLBACK_DICT = {} rangers = range(0, 101, 10) def canYouSeeMe(): for ranger in rangers: _CALLBACK_DICT["this_ranger"] = str(ranger) try: _CALLBACK.callback() except: pass second.py ##### "second.py" #import first module import first from first import _CALLBACK #import Tkinter from Tkinter import * #create function and add as method to class "_CALLBACK" def getCallback(): importedValue = first._CALLBACK_DICT["this_ranger"] t.insert(END, importedValue + "%\n") if importedValue == "100": t.insert(END, "\nDone.") t.see(END) t.update_idletasks() _CALLBACK.callback = getCallback #adding method to class #create GUI buttons class buttons(): def __init__(self, root): #make frame/button frame = Frame(root) frame.pack() buttonText = "go" buttonAction = self.go self.makeButton = Button(frame, text=buttonText, command=buttonAction) self.makeButton.pack() #run go() def go(self): first.canYouSeeMe() #create GUI root = Tk() buttons = buttons(root) t = Text(root, background="black", foreground="blue") t.pack() geo = ("150x250") root.geometry(geo) root.mainloop()