PyQt5 Threading a Selenium Script - python

I'm having difficulties figuring out threading in PyQt5. I made a GUI for a script I use at work in order to share with my coworker. The script does selenium browser automation with some variables that it receives from the GUI. The script is located in a different .py file and it doesn't inherit from any class I just instantiate it in the mainapp.py when I click the query button. (Oh and the script doesn't return anything, I added a return True for testing purposes)
mainapp.py
class OutputCheckerSub(Ui_OutputChecker):
def __init__(self):
super().__init__()
........
........
# Connecting Query button
self.query_button.clicked.connect(self.run_output_script)
def run_output_script(self):
self.output_script.run_script()
......
This launches the script with the proper variables but crashes the GUI app.
I managed to get threading somewhat working (W/O setting the variables) but it crashes after 10 seconds.
multithreading.py
class OutputScriptThread(QThread):
signal = pyqtSignal(bool)
def __init__(self):
super().__init__()
def run(self):
output_checker_script = output_checker.OutputCheckerScript()
state = output_checker_script.run_script()
self.signal.emit(state)
mainapp.py with faulty threading
class OutputCheckerSub(Ui_OutputChecker):
def __init__(self):
super().__init__()
.......
.......
# Connecting Query button
self.query_button.clicked.connect(self.run_output_script)
self.outputcheck_thread = multithreading.OutputScriptThread()
def run_output_script(self):
self.outputcheck_thread.start()
.....
One more challenging part is passing variables from one class to another. currently my GUI directly modifies the variables of the instanciated script . If I get the threading working where should I set the variables ? From the Thread?

Related

Disconnect signals for QGis plugin with dialog created by Qt Designer

I'm working on a plugin for QGis 3.x. The UI was created with Qt Designer and the plugin code boilerplate by Plugin builder.
I do have 3 main files in my project :
my_plugin.py with the main MyPlugin class and initGui(), unload() and run() methods
my_plugin_dialog.py with a simple MyPluginDialog class, that inherits from QtWidgets.QDialog and the FORM_CLASS based on my .ui designer file. The class only contains __init__() method, itself calling self.setupUi()
an UI file created by Qt Designer my_plugin_dialog_base.ui
I encounter some issues related to duplicate signals when starting/closing the plugin dialog.
The most similar discussion on SO is the following : https://gis.stackexchange.com/questions/137160/qgis-plugin-triggers-function-twice
Yet, my problem is that my dialog object is created within the run() method (see below), so I can't access to my UI elements in the initGui() nor unload() methods.
So how can I disconnect signals, or define them so that closing the plugin also disconnects all my signals ?
Here is an excerpt of my_plugin.py content (created by Plugin Builder, except the last line related to connecting the signal and the slot) :
def initGui(self):
"""Create the menu entries and toolbar icons inside the QGIS GUI."""
icon_path = ':/plugins/my_plugin/icon.png'
self.add_action(
icon_path,
text=self.tr(u'Start My Plugin'),
callback=self.run,
parent=self.iface.mainWindow())
# will be set False in run()
self.first_start = True
def run(self):
"""Run method that performs all the real work"""
# Create the dialog with elements (after translation) and keep reference
# Only create GUI ONCE in callback, so that it will only load when the plugin is started
if self.first_start == True:
self.first_start = False
self.dlg = MyPluginDialog()
self.dlg.push_button_start.clicked.connect(self.on_pb_start) # connect signal to slot
And my_plugin_dialog.py:
FORM_CLASS, _ = uic.loadUiType(os.path.join(
os.path.dirname(__file__), 'small_etl_dialog_base.ui'))
class MyPluginDialog(QtWidgets.QDialog, FORM_CLASS):
def __init__(self, parent=None):
"""Constructor."""
super(MyPluginDialog, self).__init__(parent)
# Set up the user interface from Designer through FORM_CLASS.
# After self.setupUi() you can access any designer object by doing
# self.<objectname>, and you can use autoconnect slots - see
# http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html
# #widgets-and-dialogs-with-auto-connect
self.setupUi(self)
Maybe there is a proper way to access dialog objects inside the initGui() method ?
This is happening because you connect your signal every time the run method is called. However, you only create your dialog once guarded by a Boolean variable.
The solution is to only connect your signal when you create the dialog, so something like this would work better:
def run(self):
...
if self.first_start == True:
self.first_start = False
self.dlg = MyPluginDialog()
self.dlg.push_button_start.clicked.connect(self.on_pb_start)
...
Also, note that you do not necessarily need the self.first_start boolean guard. You could always check whether self.dlg is not None instead.
So, you could tidy this up a bit by something like this:
def run(self):
...
if not self.dlg:
self.dlg = MyPluginDialog()
self.dlg.push_button_start.clicked.connect(self.on_pb_start)
...

How to integrate python scripting in my python code

I am writing Python code (based on PyQt5 signals and slots). I need to make my code "Scriptable". By scriptable I mean the user can utilize the internal objects himself in a user-defined python script to develop/automate some functions,..etc. But I have no idea of how to implement it clearly.
I have tried using (exec) function in python in the following way:
user-def.py
def script_entry(main_object_in_my_code):
# Connecting signal from main_object_in_my_code (inherited from QObject) to other functions in this
# file. example:
main_object_in_my_code.event_1.connect(function_1)
#QtCore.pyqtSlot(str)
def function_1 (args):
#do user-defined logic using those args.
then in my script when user want to execute it, he inputs (as example)
source user-def.py
the main script reads the script and uses exec as the following:
with open(script_path) as f:
script = f.read()
exec(script, globals())
the problem is that events are triggered but function function_1 is not executed.
I am sure this is not the right way to do this. So, How can I implement my code to be (scriptable) using user defined scripts?
I would recomend to create a class and extend from it, let the 'user' call the functions when s/he needs.
If you are not in touch with class inheritance check this tutorial
source_def.py
class Main:
def __init__(self):
super(Main, self).__init__()
def script_entry(self, main_object_in_my_code):
main_object_in_my_code.event_1.connect( function_1 )
#QtCore.pyqtSlot(str)
def function_1( self, args ):
#this checks if the function is set
invert_op = getattr(self, "user_function", None)
if callable(user_function):
eval('self.user_function( args )')
user_def.py
from source_def import Main
class UserClass( Main ):
def __init__(self):
super(UserClass, self).__init__()
def user_function(self , args ):
print( args )
Try this

Can not emit signal from not threaded slot in pyqt

According this example : link ,I want to implement a thread based small application within a GUI. I have two files TestMain.py and TestManager.py (Worker class), which are connected with each other over sys.path.append('...'). In TestMain.py, I load my GUI, create thread and signal/slot connections, which act on functions in TestManager.py. In TestManager.py I do some calculations and want to visualize them in a QTextEditor at the same time. To do this, I have written following code:
TestMain.py
sys.path.append('../app/source')
from TestManager import *
class TestMain(QMainWindow):
def __init__(self, parent= None):
super(TestMain, self).__init__(parent)
self.manager= TestManager()
self.thread= QThread()
self.manager.moveToThread(self.thread)
self.manager.finished.connect(self.thread.quit)
self.thread.started.connect(self.manager.first_slot)
self.thread.start()
self.manager.printFirstSignal.connect(self.print_onTextEdit)
self.offlinePushButton.clicked.connect(self.offline_slot)
def offline_slot(self):
manager.do_some_calc(self)
def print_onTextEdit(self, str):
self.outputTextEdit.append(str)
Manager.py
sys.path.append('../app/source')
from TestMain import *
class TestManager(QObject): #QtCore.QThread
finished= pyqtSignal()
printFirstSignal= pyqtSignal(str)
def __init__(self, parent= None):
super(TestManager, self).__init__(parent)
def first_slot(self):
self.printFirstSignal.emit('TEST')
self.finished.emit()
def do_some_calc(self):
do_someting()
TestManager.first_slot("do_some_calc")
If I do self.thread.started.connect(self.manager.first_slot) in TestMain and emit the pyqtSignal in function first_slot() in TestManager, then I can update my GUI without any problem. But if I call a slot (e.g do_some_calc()) in TestManager and want to also update the GUI from there, than I get a segmentation fault. Even if I create a separate signal for this function, it does not work. Cause I call this function in TestMain self.offlinePushButton.clicked.connect(self.offline_slot)
My question is. How can I call any function in TestManager internally or from MainTest so that I can still update the GUI?
Thank you for any suggestion

PyQt / PySide Echo signals of another object without subclassing

I'm managing some long running tasks using signals and slots to track progress and such. I had a Run class that starts like this:
from PySide import QtCore, QtGui
class Run(QtCore.QObject):
running = QtCore.Signal(bool)
finished = QtCore.Signal(object)
status = QtCore.Signal(str)
message = QtCore.Signal(object)
progress = QtCore.Signal(float)
time_remaining_update = QtCore.Signal(str)
initialized = QtCore.Signal()
failed = QtCore.Signal(object)
pitch_update = QtCore.Signal(float)
def __init__(self, parent=None):
super().__init__(parent=parent)
#... rest of the class ...
I recently refactored some things and wrote a Task class which includes a Run instance as an attribute. I would like to "echo" many of the Run's signals upstream. This is my current solution:
class Task(QtCore.QObject):
#Signals echoed from Run
running = QtCore.Signal(bool)
status = QtCore.Signal(str)
message = QtCore.Signal(object)
progress = QtCore.Signal(float)
time_remaining_update = QtCore.Signal(str)
initialized = QtCore.Signal()
def __init__(self, run, parent=None):
super().__init__(parent=parent)
self.run = run
#Echo signals from run
signals = ['running', 'status', 'message', 'progress', 'time_remaining_update', 'initialized']
for signal in signals:
#Re-emit the signal
getattr(self.run, signal).connect(lambda *args: getattr(self, signal).emit(*args))
#... rest of class ...
I confirmed that the lambda *args: func(*args) pattern will avoid passing anything to func if nothing is passed to the lambda, which is necessary to obey the different signals' signatures.
Is this the best way to handle "echoing" or "bouncing" signals from one QObject to another? I'm basically trying to preserve some of the Run API while changing/adding other aspects with my Task API.
I realize that I can get all of the Run API by subclassing and overriding things I want to change, but I didn't want to subclass since Task handles restarting the run after failure. I'd like to keep the run logic separate from the code that handles restarting the run. There are also Run signals and methods that I don't want propagated by Task.
The only more elegant solution I can think of is to automate the creation of the Task signals... maybe make a echo_signals decorator that takes a QObject and a list of signal names that then does these things?
Is there a Qt feature for implementing this more elegantly? Any suggestions?
In Qt you can connect signals to other signals, unless I'm missing something regarding your example.

pyqt QThread blocking main thread

I'm trying to create a simple threaded application whereby i have a method which does some long processing and a widget that displays a loading bar and cancel button.
My problem is that no matter how i implement the threading it doesn't actually thread - the UI is locked up once the thread kicks in. I've read every tutorial and post about this and i'm now resorting on asking the community to try and solve my problem as i'm at a loss!
Initially i tried subclassing QThread until the internet said this was wrong. I then attempted the moveToThread approach but it made zero difference.
Initialization code:
loadingThreadObject = LoadThread(arg1)
loadingThread = PythonThread()
loadingThreadObject.moveToThread(loadingThread)
loadingThread.started.connect(loadingThreadObject.load)
loadingThread.start()
PythonThread class (apparently QThreads are bugged in pyQt and don't start unless you do this):
class PythonThread (QtCore.QThread):
def __init__(self, parent=None):
QtCore.QThread.__init__(self, parent)
def start(self):
QtCore.QThread.start(self)
def run(self):
QtCore.QThread.run(self)
LoadThread class:
class LoadThread (QtCore.QObject):
results = QtCore.Signal(tuple)
def __init__ (self, arg):
# Init QObject
super(QtCore.QObject, self).__init__()
# Store the argument
self.arg = arg
def load (self):
#
# Some heavy lifting is done
#
loaded = True
errors = []
# Emits the results
self.results.emit((loaded, errors))
Any help is greatly appreciated!
Thanks.
Ben.
The problem was with the SQL library I was using (a custom in-house solution) which turned out not to be thread safe and thus performed blocking queries.
If you are having a similar problem, first try removing the SQL calls and seeing if it still blocks. If that solves the blocking issue, try reintroducing your queries using raw SQL via MySQLdb (or the equivalent for the type of DB you're using). This will diagnose whether or not the problem is with your choice of SQL library.
The function connected to the started signal will run the thread which it was connected, the main GUI thread. However, a QThread's start() function executes its run() method in the thread after the thread is initialized so a subclass of QThread should be created and its run method should run LoadThread.load, the function you want to execute. Don't inherit from PythonThread, there's no need for that. The QThread subclass's start() method should be used to start the thread.
PS: Since in this case the subclass of QThread's run() method only calls LoadThread.load(), the run() method could be simply set to LoadThread.load:
class MyThread(QtCore.QThread):
run = LoadThread.load # x = y in the class block sets the class's x variable to y
An example:
import time
from PyQt4 import QtCore, QtGui
import sys
application = QtGui.QApplication(sys.argv)
class LoadThread (QtCore.QObject):
results = QtCore.pyqtSignal(tuple)
def __init__ (self, arg):
# Init QObject
super(QtCore.QObject, self).__init__()
# Store the argument
self.arg = arg
def load(self):
#
# Some heavy lifting is done
#
time.sleep(5)
loaded = True
errors = []
# Emits the results
self.results.emit((loaded, errors))
l = LoadThread("test")
class MyThread(QtCore.QThread):
run = l.load
thread = MyThread()
button = QtGui.QPushButton("Do 5 virtual push-ups")
button.clicked.connect(thread.start)
button.show()
l.results.connect(lambda:button.setText("Phew! Push ups done"))
application.exec_()

Categories

Resources