PyQt is the Python binding for Qt library. To write Qt5
code, we use PyQt5 module. Like many others, my first introduction to GUI
application development was using PyQt. Back in foss.in 2005 a talk from
Sirtaj introduced me to PyQt, and later fall in love with
it.
I tried to help in a GUI application after 8 years (I think), a lot of things
have changed in between. But, Qt/PyQt still seems to be super helpful when it
comes to ease of development. Qt has one of the best
documentation out there for any Open Source project.
Many students start developing GUI tools by replacing one of the command line
tool they use. Generally the idea is very simple, take some input in the GUI,
and then process it (using a subprocess call) on a button click, and then show
the output. The subprocess call happens over a simple method, means the whole
GUI gets stuck till the function call finishes. We can fix this issue by using
a QThread. In the below example, we will
just write a frontend for git clone
command and then will do the same using
QThread.
Setting up project directory
I have used qt creator to create a
simple MainWindow form and saved it as mainwindow.ui in the project
directory. Then, used pipenv to create a virtualenv and also installed the
pyqt5 module. Next, used the pyuic5 command to create a Python file from
UI file.
The code does not have error checks, the subprocess
documentation should give
you enough details about how to add them.

Doing git clone without any thread
The following code creates a temporary directory, and then git clones any given
git repository into that.
#!/usr/bin/python3
import sys
import tempfile
import subprocess
from PyQt5 import QtWidgets
from mainwindow import Ui_MainWindow
class ExampleApp(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(ExampleApp, self).__init__(parent)
self.setupUi(self)
# Here we are telling to call git_clone method when
# someone clicks on the pushButton.
self.pushButton.clicked.connect(self.git_clone)
# Here is the actual method which does git clone
def git_clone(self):
git_url = self.lineEdit.text() # Get the git URL
tmpdir = tempfile.mkdtemp() # Creates a temporary directory
cmd = "git clone {0} {1}".format(git_url, tmpdir)
subprocess.check_output(cmd.split()) # Execute the command
self.textEdit.setText(tmpdir) # Show the output to the user
def main():
app = QtWidgets.QApplication(sys.argv)
form = ExampleApp()
form.show()
app.exec_()
if __name__ == '__main__':
main()
Doing git clone with a thread
In the below example we added a new CloneThread
class, it has a run method,
which gets called when the thread starts. At the end of the run, we are
emitting a signal to inform the main thread that the git clone operation has
finished.
#!/usr/bin/python3
import sys
import tempfile
import subprocess
from PyQt5 import QtWidgets
from PyQt5.QtCore import QThread, pyqtSignal
from mainwindow import Ui_MainWindow
class CloneThread(QThread):
signal = pyqtSignal('PyQt_PyObject')
def __init__(self):
QThread.__init__(self)
self.git_url = ""
# run method gets called when we start the thread
def run(self):
tmpdir = tempfile.mkdtemp()
cmd = "git clone {0} {1}".format(self.git_url, tmpdir)
subprocess.check_output(cmd.split())
# git clone done, now inform the main thread with the output
self.signal.emit(tmpdir)
class ExampleApp(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(ExampleApp, self).__init__(parent)
self.setupUi(self)
self.pushButton.setText("Git clone with Thread")
# Here we are telling to call git_clone method when
# someone clicks on the pushButton.
self.pushButton.clicked.connect(self.git_clone)
self.git_thread = CloneThread() # This is the thread object
# Connect the signal from the thread to the finished method
self.git_thread.signal.connect(self.finished)
def git_clone(self):
self.git_thread.git_url = self.lineEdit.text() # Get the git URL
self.pushButton.setEnabled(False) # Disables the pushButton
self.textEdit.setText("Started git clone operation.") # Updates the UI
self.git_thread.start() # Finally starts the thread
def finished(self, result):
self.textEdit.setText("Cloned at {0}".format(result)) # Show the output to the user
self.pushButton.setEnabled(True) # Enable the pushButton
def main():
app = QtWidgets.QApplication(sys.argv)
form = ExampleApp()
form.show()
app.exec_()
if __name__ == '__main__':
main()

The example looks like the above GIF. You can find the source code
here. You can find a bigger
example in the
journalist_gui
of the SecureDrop project.