Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Files for http://nikolak.com/pyqt-threading-tutorial/ PyQt Threading tutorial
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'threading_design.ui'
#
# Created: Thu Aug 6 13:47:18 2015
# by: PyQt4 UI code generator 4.10.4
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName(_fromUtf8("MainWindow"))
MainWindow.resize(526, 373)
self.centralwidget = QtGui.QWidget(MainWindow)
self.centralwidget.setObjectName(_fromUtf8("centralwidget"))
self.verticalLayout = QtGui.QVBoxLayout(self.centralwidget)
self.verticalLayout.setObjectName(_fromUtf8("verticalLayout"))
self.subreddits_input_layout = QtGui.QHBoxLayout()
self.subreddits_input_layout.setObjectName(_fromUtf8("subreddits_input_layout"))
self.label_subreddits = QtGui.QLabel(self.centralwidget)
self.label_subreddits.setObjectName(_fromUtf8("label_subreddits"))
self.subreddits_input_layout.addWidget(self.label_subreddits)
self.edit_subreddits = QtGui.QLineEdit(self.centralwidget)
self.edit_subreddits.setObjectName(_fromUtf8("edit_subreddits"))
self.subreddits_input_layout.addWidget(self.edit_subreddits)
self.verticalLayout.addLayout(self.subreddits_input_layout)
self.label_submissions_list = QtGui.QLabel(self.centralwidget)
self.label_submissions_list.setObjectName(_fromUtf8("label_submissions_list"))
self.verticalLayout.addWidget(self.label_submissions_list)
self.list_submissions = QtGui.QListWidget(self.centralwidget)
self.list_submissions.setBatchSize(1)
self.list_submissions.setObjectName(_fromUtf8("list_submissions"))
self.verticalLayout.addWidget(self.list_submissions)
self.progress_bar = QtGui.QProgressBar(self.centralwidget)
self.progress_bar.setProperty("value", 0)
self.progress_bar.setObjectName(_fromUtf8("progress_bar"))
self.verticalLayout.addWidget(self.progress_bar)
self.buttons_layout = QtGui.QHBoxLayout()
self.buttons_layout.setObjectName(_fromUtf8("buttons_layout"))
self.btn_stop = QtGui.QPushButton(self.centralwidget)
self.btn_stop.setEnabled(False)
self.btn_stop.setObjectName(_fromUtf8("btn_stop"))
self.buttons_layout.addWidget(self.btn_stop)
self.btn_start = QtGui.QPushButton(self.centralwidget)
self.btn_start.setObjectName(_fromUtf8("btn_start"))
self.buttons_layout.addWidget(self.btn_start)
self.verticalLayout.addLayout(self.buttons_layout)
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(_translate("MainWindow", "Threading Tutorial - nikolak.com", None))
self.label_subreddits.setText(_translate("MainWindow", "Subreddits:", None))
self.edit_subreddits.setPlaceholderText(_translate("MainWindow", "python,programming,linux,etc (comma separated)", None))
self.label_submissions_list.setText(_translate("MainWindow", "Submissions:", None))
self.btn_stop.setText(_translate("MainWindow", "Stop", None))
self.btn_start.setText(_translate("MainWindow", "Start", None))
from PyQt4 import QtGui
from PyQt4.QtCore import QThread, SIGNAL
import sys
import design
import urllib2
import json
import time
class getPostsThread(QThread):
def __init__(self, subreddits):
"""
Make a new thread instance with the specified
subreddits as the first argument. The subreddits argument
will be stored in an instance variable called subreddits
which then can be accessed by all other class instance functions
:param subreddits: A list of subreddit names
:type subreddits: list
"""
QThread.__init__(self)
self.subreddits = subreddits
def __del__(self):
self.wait()
def _get_top_post(self, subreddit):
"""
Return a pre-formatted string with top post title, author,
and subreddit name from the subreddit passed as the only required
argument.
:param subreddit: A valid subreddit name
:type subreddit: str
:return: A string with top post title, author,
and subreddit name from that subreddit.
:rtype: str
"""
url = "https://www.reddit.com/r/{}.json?limit=1".format(subreddit)
headers = {'User-Agent': 'nikolak@outlook.com tutorial code'}
request = urllib2.Request(url, headers=headers)
response = urllib2.urlopen(request)
data = json.load(response)
top_post = data['data']['children'][0]['data']
return "'{title}' by {author} in {subreddit}".format(**top_post)
def run(self):
"""
Go over every item in the self.subreddits list
(which was supplied during __init__)
and for every item assume it's a string with valid subreddit
name and fetch the top post using the _get_top_post method
from reddit. Store the result in a local variable named
top_post and then emit a SIGNAL add_post(QString) where
QString is equal to the top_post variable that was set by the
_get_top_post function.
"""
for subreddit in self.subreddits:
top_post = self._get_top_post(subreddit)
self.emit(SIGNAL('add_post(QString)'), top_post)
self.sleep(2)
class ThreadingTutorial(QtGui.QMainWindow, design.Ui_MainWindow):
"""
How the basic structure of PyQt GUI code looks and behaves like is
explained in this tutorial
http://nikolak.com/pyqt-qt-designer-getting-started/
"""
def __init__(self):
super(self.__class__, self).__init__()
self.setupUi(self)
self.btn_start.clicked.connect(self.start_getting_top_posts)
def start_getting_top_posts(self):
# Get the subreddits user entered into an QLineEdit field
# this will be equal to '' if there is no text entered
subreddit_list = str(self.edit_subreddits.text()).split(',')
if subreddit_list == ['']: # since ''.split(',') == [''] we use that to check
# whether there is anything there to fetch from
# and if not show a message and abort
QtGui.QMessageBox.critical(self, "No subreddits",
"You didn't enter any subreddits.",
QtGui.QMessageBox.Ok)
return
# Set the maximum value of progress bar, can be any int and it will
# be automatically converted to x/100% values
# e.g. max_value = 3, current_value = 1, the progress bar will show 33%
self.progress_bar.setMaximum(len(subreddit_list))
# Setting the value on every run to 0
self.progress_bar.setValue(0)
# We have a list of subreddits which we use to create a new getPostsThread
# instance and we pass that list to the thread
self.get_thread = getPostsThread(subreddit_list)
# Next we need to connect the events from that thread to functions we want
# to be run when those signals get fired
# Adding post will be handeled in the add_post method and the signal that
# the thread will emit is SIGNAL("add_post(QString)")
# the rest is same as we can use to connect any signal
self.connect(self.get_thread, SIGNAL("add_post(QString)"), self.add_post)
# This is pretty self explanatory
# regardless of whether the thread finishes or the user terminates it
# we want to show the notification to the user that adding is done
# and regardless of whether it was terminated or finished by itself
# the finished signal will go off. So we don't need to catch the
# terminated one specifically, but we could if we wanted.
self.connect(self.get_thread, SIGNAL("finished()"), self.done)
# We have all the events we need connected we can start the thread
self.get_thread.start()
# At this point we want to allow user to stop/terminate the thread
# so we enable that button
self.btn_stop.setEnabled(True)
# And we connect the click of that button to the built in
# terminate method that all QThread instances have
self.btn_stop.clicked.connect(self.get_thread.terminate)
# We don't want to enable user to start another thread while this one is
# running so we disable the start button.
self.btn_start.setEnabled(False)
def add_post(self, post_text):
"""
Add the text that's given to this function to the
list_submissions QListWidget we have in our GUI and
increase the current value of progress bar by 1
:param post_text: text of the item to add to the list
:type post_text: str
"""
self.list_submissions.addItem(post_text)
self.progress_bar.setValue(self.progress_bar.value()+1)
def done(self):
"""
Show the message that fetching posts is done.
Disable Stop button, enable the Start one and reset progress bar to 0
"""
self.btn_stop.setEnabled(False)
self.btn_start.setEnabled(True)
self.progress_bar.setValue(0)
QtGui.QMessageBox.information(self, "Done!", "Done fetching posts!")
def main():
app = QtGui.QApplication(sys.argv)
form = ThreadingTutorial()
form.show()
app.exec_()
if __name__ == '__main__':
main()
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>526</width>
<height>373</height>
</rect>
</property>
<property name="windowTitle">
<string>Threading Tutorial - nikolak.com</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="subreddits_input_layout">
<item>
<widget class="QLabel" name="label_subreddits">
<property name="text">
<string>Subreddits:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="edit_subreddits">
<property name="placeholderText">
<string>python,programming,linux,etc (comma separated)</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="label_submissions_list">
<property name="text">
<string>Submissions:</string>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="list_submissions">
<property name="batchSize">
<number>1</number>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="progress_bar">
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="buttons_layout">
<item>
<widget class="QPushButton" name="btn_stop">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Stop</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_start">
<property name="text">
<string>Start</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>

Hi Nikola,

I tried to translate your code from Python 2 to Python 3, but came to a standstill due to the request and json parsing (line 44 to line 52 of trial.py).

I was also hoping to use PyQt5 instead.

Could you advise me?

[start]
from PyQt4 import QtGui
from PyQt4.QtCore import QThread, SIGNAL
import sys
import design
import urllib
from urllib.request import *
import urllib.request
import json
import time

class getPostsThread(QThread):
def init(self, subreddits):
"""
Make a new thread instance with the specified
subreddits as the first argument. The subreddits argument
will be stored in an instance variable called subreddits
which then can be accessed by all other class instance functions

    :param subreddits: A list of subreddit names
    :type subreddits: list
    """
    QThread.__init__(self)
    self.subreddits = subreddits

def __del__(self):
    self.wait()

def _get_top_post(self, subreddit):
    """
    Return a pre-formatted string with top post title, author,
    and subreddit name from the subreddit passed as the only required
    argument.

    :param subreddit: A valid subreddit name
    :type subreddit: str
    :return: A string with top post title, author, 
                and subreddit name from that subreddit.
    :rtype: str
    """
    url = "https://www.reddit.com/r/{}.json?limit=1".format(subreddit)
    headers = {'User-Agent': 'nikolak@outlook.com tutorial code'}
    request = Request(url, headers=headers)
    
    response = urlopen(request)
    #print (response)
    
    #with urlopen(request) as response:
    #    response = response.read()
    #print (response)			
    #data = json.loads(json.dumps(list(response)))
    data = json.loads(json.dumps(response).decode('utf-8'))
    # print (data)
    top_post = data['data']['children'][0]['data']
    return "'{title}' by {author} in {subreddit}".format(**top_post)

def run(self):
    """
    Go over every item in the self.subreddits list 
    (which was supplied during __init__)
    and for every item assume it's a string with valid subreddit
    name and fetch the top post using the _get_top_post method
    from reddit. Store the result in a local variable named
    top_post and then emit a SIGNAL add_post(QString) where
    QString is equal to the top_post variable that was set by the
    _get_top_post function.

    """
    for subreddit in self.subreddits:
        top_post = self._get_top_post(subreddit)
        self.emit(SIGNAL('add_post(QString)'), top_post)
        self.sleep(2)

class ThreadingTutorial(QtGui.QMainWindow, design.Ui_MainWindow):
"""
How the basic structure of PyQt GUI code looks and behaves like is
explained in this tutorial
http://nikolak.com/pyqt-qt-designer-getting-started/
"""

def __init__(self):
    super(self.__class__, self).__init__()
    self.setupUi(self)
    self.btn_start.clicked.connect(self.start_getting_top_posts)

def start_getting_top_posts(self):
    # Get the subreddits user entered into an QLineEdit field
    # this will be equal to '' if there is no text entered
    subreddit_list = str(self.edit_subreddits.text()).split(',')
    if subreddit_list == ['']:  # since ''.split(',') == [''] we use that to check
                                # whether there is anything there to fetch from
                                # and if not show a message and abort
        QtGui.QMessageBox.critical(self, "No subreddits",
                                   "You didn't enter any subreddits.",
                                   QtGui.QMessageBox.Ok)
        return
    # Set the maximum value of progress bar, can be any int and it will
    # be automatically converted to x/100% values
    # e.g. max_value = 3, current_value = 1, the progress bar will show 33%
    self.progress_bar.setMaximum(len(subreddit_list))
    # Setting the value on every run to 0
    self.progress_bar.setValue(0)

    # We have a list of subreddits which we use to create a new getPostsThread
    # instance and we pass that list to the thread
    self.get_thread = getPostsThread(subreddit_list)

    # Next we need to connect the events from that thread to functions we want
    # to be run when those signals get fired

    # Adding post will be handeled in the add_post method and the signal that
    # the thread will emit is  SIGNAL("add_post(QString)")
    # the rest is same as we can use to connect any signal
    self.connect(self.get_thread, SIGNAL("add_post(QString)"), self.add_post)

    # This is pretty self explanatory
    # regardless of whether the thread finishes or the user terminates it
    # we want to show the notification to the user that adding is done
    # and regardless of whether it was terminated or finished by itself
    # the finished signal will go off. So we don't need to catch the
    # terminated one specifically, but we could if we wanted.
    self.connect(self.get_thread, SIGNAL("finished()"), self.done)

    # We have all the events we need connected we can start the thread
    self.get_thread.start()
    # At this point we want to allow user to stop/terminate the thread
    # so we enable that button
    self.btn_stop.setEnabled(True)
    # And we connect the click of that button to the built in
    # terminate method that all QThread instances have
    self.btn_stop.clicked.connect(self.get_thread.terminate)
    # We don't want to enable user to start another thread while this one is
    # running so we disable the start button.
    self.btn_start.setEnabled(False)

def add_post(self, post_text):
    """
    Add the text that's given to this function to the
    list_submissions QListWidget we have in our GUI and
    increase the current value of progress bar by 1

    :param post_text: text of the item to add to the list
    :type post_text: str
    """
    self.list_submissions.addItem(post_text)
    self.progress_bar.setValue(self.progress_bar.value()+1)

def done(self):
    """
    Show the message that fetching posts is done.
    Disable Stop button, enable the Start one and reset progress bar to 0
    """
    self.btn_stop.setEnabled(False)
    self.btn_start.setEnabled(True)
    self.progress_bar.setValue(0)
    QtGui.QMessageBox.information(self, "Done!", "Done fetching posts!")

def main():
app = QtGui.QApplication(sys.argv)
form = ThreadingTutorial()
form.show()
app.exec_()

if name == 'main':
main()
[end]

Hello Nikola,

Thanks for this tutorial! Two years after its publication it has helped me a lot designing my application. In this project I came across with this article by Maya Posch:

https://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/

She suggests a different approach than subclassing QThread and changing the run() method. There are very good points on why subclassing is the wrong approach. I have adapted what I learned from you to what I learned from her and though will share here the article so we can all write better code.

Best,
Darien

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment