wxPython threading downloads
Question: I need to download a list of urls in a wxPython application, but I'm really
new to threading, could anyone show me a working example of how to download
links and put the output in a wx control.


Answer: It would have been nice to see what you had done to at least try to accomplish
this. Anyway, there are a couple of good resources for learning how to do
threads in wxPython. Here they are:

  * wiki.wxpython.org/LongRunningTasks
  * <http://www.blog.pythonlibrary.org/2010/05/22/wxpython-and-threads/>

I wrote up a tutorial on downloading files using urllib/urllib2 and requests
here:

  * <http://www.blog.pythonlibrary.org/2012/06/07/python-101-how-to-download-a-file/>

If you combine these three articles together, you can come up with your
answer. However, I went ahead and wrote up a super simple downloader:

    
    
    import requests
    import os
    import wx
    import wx.lib.scrolledpanel as scrolled
    
    from threading import Thread
    from wx.lib.pubsub import pub
    
    ########################################################################
    class DownloadThread(Thread):
        """Downloading thread"""
    
        #----------------------------------------------------------------------
        def __init__(self, gnum, url, fsize):
            """Constructor"""
            Thread.__init__(self)
            self.fsize = fsize
            self.gnum = gnum
            self.url = url
            self.start()
    
        #----------------------------------------------------------------------
        def run(self):
            """
            Run the worker thread
            """
            local_fname = os.path.basename(self.url)
            count = 1
            while True:
                if os.path.exists(local_fname):
                    tmp, ext = os.path.splitext(local_fname)
                    cnt = "(%s)" % count
                    local_fname = tmp + cnt + ext
                    count += 1
                else:
                    break
            req = requests.get(self.url, stream=True)
            total_size = 0
            print local_fname
            with open(local_fname, "wb") as fh:
                for byte in req.iter_content(chunk_size=1024):
                    if byte:
                        fh.write(byte)
                        fh.flush()
                    total_size += 1024
                    if total_size < self.fsize:
                        wx.CallAfter(pub.sendMessage, 
                                     "update_%s" % self.gnum,
                                     msg=total_size)
            print "DONE!"
            wx.CallAfter(pub.sendMessage,
                         "update_%s" % self.gnum,
                         msg=self.fsize)
    
    
    ########################################################################
    class MyGauge(wx.Gauge):
        """"""
    
        #----------------------------------------------------------------------
        def __init__(self, parent, range, num):
            """Constructor"""
            wx.Gauge.__init__(self, parent, range=range)
    
            pub.subscribe(self.updateProgress, "update_%s" % num)
    
        #----------------------------------------------------------------------
        def updateProgress(self, msg):
            """"""
            self.SetValue(msg)
    
    ########################################################################
    class MyPanel(scrolled.ScrolledPanel):
        """"""
    
        #----------------------------------------------------------------------
        def __init__(self, parent):
            """Constructor"""
            scrolled.ScrolledPanel.__init__(self, parent)
    
            self.data = []
            self.download_number = 1
    
            # create the sizers
            self.main_sizer = wx.BoxSizer(wx.VERTICAL)
            dl_sizer = wx.BoxSizer(wx.HORIZONTAL)
    
            # create the widgets
            lbl = wx.StaticText(self, label="Download URL:")
            self.dl_txt = wx.TextCtrl(self)
            btn = wx.Button(self, label="Download")
            btn.Bind(wx.EVT_BUTTON, self.onDownload)
    
            # layout the widgets
            dl_sizer.Add(lbl, 0, wx.ALL|wx.CENTER, 5)
            dl_sizer.Add(self.dl_txt, 1, wx.EXPAND|wx.ALL, 5)
            dl_sizer.Add(btn, 0, wx.ALL, 5)
            self.main_sizer.Add(dl_sizer, 0, wx.EXPAND)
    
            self.SetSizer(self.main_sizer)
            self.SetAutoLayout(1)
            self.SetupScrolling()
    
        #----------------------------------------------------------------------
        def onDownload(self, event):
            """
            Update display with downloading gauges
            """
            url = self.dl_txt.GetValue()
            try:
                header = requests.head(url)
                fsize = int(header.headers["content-length"]) / 1024
    
                sizer = wx.BoxSizer(wx.HORIZONTAL)
                fname = os.path.basename(url)
                lbl = wx.StaticText(self, label="Downloading %s" % fname)
                gauge = MyGauge(self, fsize, self.download_number)
    
                sizer.Add(lbl, 0, wx.ALL|wx.CENTER, 5)
                sizer.Add(gauge, 0, wx.ALL|wx.EXPAND, 5)
                self.main_sizer.Add(sizer, 0, wx.EXPAND)
    
                self.Layout()
    
                # start thread
                DownloadThread(self.download_number, url, fsize)
    
                self.download_number += 1
            except Exception, e:
                print "Error: ", e
    
    ########################################################################
    class DownloaderFrame(wx.Frame):
        """"""
    
        #----------------------------------------------------------------------
        def __init__(self):
            """Constructor"""
            wx.Frame.__init__(self, None, title="Downloader", size=(800, 400))
            panel = MyPanel(self)
            self.Show()
    
    #----------------------------------------------------------------------
    if __name__ == "__main__":
        app = wx.App(False)
        frame = DownloaderFrame()
        app.MainLoop()
    

Note: You will need [requests](http://docs.python-requests.org/en/latest/) to
use this script.

