From 13fe7fbfe8e3c5984a25afc9b2a7ec1fd84dd97c Mon Sep 17 00:00:00 2001 From: Liquid369 Date: Tue, 23 Jul 2024 14:05:37 -0500 Subject: [PATCH] Better type hinting, fstrings in threads and mainapp --- src/constants.py | 8 ++-- src/mainApp.py | 16 ++++---- src/mainWindow.py | 40 ++++++++++---------- src/qt/guiHeader.py | 6 +-- src/tabRewards.py | 84 ++++++++++++++++++++---------------------- src/threads.py | 24 +++++++++--- src/watchdogThreads.py | 4 +- src/workerThread.py | 5 ++- 8 files changed, 96 insertions(+), 91 deletions(-) diff --git a/src/constants.py b/src/constants.py index 222b648..bc38d39 100644 --- a/src/constants.py +++ b/src/constants.py @@ -7,7 +7,7 @@ import os from queue import Queue -wqueue = Queue() # type: Queue[str] +wqueue: Queue[str] = Queue() APPDATA_DIRNAME = ".PET4L-DATA" @@ -21,7 +21,7 @@ TESTNET_MAGIC_BYTE = 139 TESTNET_STAKE_MAGIC_BYTE = 73 DEFAULT_PROTOCOL_VERSION = 70915 -MINIMUM_FEE = 0.0001 # minimum PIV/kB +MINIMUM_FEE = 0.0001 # minimum PIV/kB SECONDS_IN_2_MONTHS = 60 * 24 * 60 * 60 MAX_INPUTS_NO_WARNING = 75 starting_width = 1033 @@ -50,8 +50,8 @@ trusted_RPC_Servers = [ ["https", "lithuania.fuzzbawls.pw:8080", "spmtUser", "WUss6sr8956S5Paex254"], ["https", "latvia.fuzzbawls.pw:8080", "spmtUser", "8X88u7TuefPm7mQaJY52"], - ["https", "charlotte.fuzzbawls.pw:8080", "spmtUser", "ZyD936tm9dvqmMP8A777"]] - + ["https", "charlotte.fuzzbawls.pw:8080", "spmtUser", "ZyD936tm9dvqmMP8A777"] +] HW_devices = [ # (model name, api index) diff --git a/src/mainApp.py b/src/mainApp.py index e0b9419..de9c74b 100644 --- a/src/mainApp.py +++ b/src/mainApp.py @@ -17,8 +17,8 @@ from misc import printDbg, initLogs, saveCacheSettings, readCacheSettings, getVersion from mainWindow import MainWindow from constants import user_dir, SECONDS_IN_2_MONTHS -from qt.dlg_configureRPCservers import ConfigureRPCservers_dlg -from qt.dlg_signmessage import SignMessage_dlg +from qt.dlg_configureRPCservers import ConfigureRPCserversDlg +from qt.dlg_signmessage import SignMessageDlg class ServiceExit(Exception): @@ -30,7 +30,7 @@ class ServiceExit(Exception): def service_shutdown(signum, frame): - print('Caught signal %d' % signum) + print(f'Caught signal {signum}') raise ServiceExit @@ -54,7 +54,7 @@ def __init__(self, imgDir, app, start_args): # Get version and title self.version = getVersion() - self.title = 'PET4L - PIVX Emergency Tool For Ledger - v.%s-%s' % (self.version['number'], self.version['tag']) + self.title = f'PET4L - PIVX Emergency Tool For Ledger - v.{self.version["number"]}-{self.version["tag"]}' # Open database self.db = Database(self) @@ -107,7 +107,7 @@ def initUI(self, imgDir): self.show() self.activateWindow() - def closeEvent(self, *args, **kwargs): + def closeEvent(self, event): # Terminate the running threads. # Set the shutdown flag on each thread to trigger a clean shutdown of each thread. self.mainWindow.myRpcWd.shutdown_flag.set() @@ -133,16 +133,16 @@ def closeEvent(self, *args, **kwargs): # Adios print("Bye Bye.") - return QMainWindow.closeEvent(self, *args, **kwargs) + return super().closeEvent(event) def onEditRPCServer(self): # Create Dialog - ui = ConfigureRPCservers_dlg(self) + ui = ConfigureRPCserversDlg(self) if ui.exec(): printDbg("Configuring RPC Servers...") def onSignVerifyMessage(self): # Create Dialog - ui = SignMessage_dlg(self.mainWindow) + ui = SignMessageDlg(self.mainWindow) if ui.exec(): printDbg("Sign/Verify message...") diff --git a/src/mainWindow.py b/src/mainWindow.py index 727b793..87ae791 100644 --- a/src/mainWindow.py +++ b/src/mainWindow.py @@ -13,7 +13,7 @@ from PyQt5.QtCore import pyqtSignal, Qt, QThread from PyQt5.QtGui import QPixmap, QColor, QPalette, QTextCursor, QFont, QIcon from PyQt5.QtWidgets import QWidget, QPushButton, QHBoxLayout, QGroupBox, QVBoxLayout, \ - QFileDialog, QTextEdit, QTabWidget, QLabel, QSplitter + QFileDialog, QTextEdit, QTabWidget, QLabel, QSplitter, QAction, QMenuBar from apiClient import ApiClient from constants import starting_height, DefaultCache, wqueue @@ -41,9 +41,8 @@ class MainWindow(QWidget): # signal: UTXO list loading percent (emitted by load_utxos_thread in tabRewards) sig_UTXOsLoading = pyqtSignal(int) - def __init__(self, parent, imgDir): - super(QWidget, self).__init__(parent) + super().__init__(parent) self.parent = parent self.imgDir = imgDir self.runInThread = ThreadFuns.runInThread @@ -79,7 +78,7 @@ def __init__(self, parent, imgDir): self.hwdevice = HWdevice(self) # -- init Api Client - self.apiClient = ApiClient(self.isTestnetRPC) + self.apiClient = ApiClient(self) # Pass 'self' as main_wnd reference # -- Create Queue to redirect stdout self.queue = wqueue @@ -257,7 +256,7 @@ def checkVersion(self, ctrl): (remote_version[0] == local_version[0] and remote_version[1] > local_version[1]) or \ (remote_version[0] == local_version[0] and remote_version[1] == local_version[1] and remote_version[2] > local_version[2]): - self.versionMess = 'New Version Available: %s ' % (self.gitVersion) + self.versionMess = f'New Version Available: {self.gitVersion} ' self.versionMess += '(download)' else: self.versionMess = "You have the latest version of PET4L" @@ -265,7 +264,7 @@ def checkVersion(self, ctrl): def updateVersion(self): if self.versionMess is not None: self.versionLabel.setText(self.versionMess) - printOK("Remote version: %s" % str(self.gitVersion)) + printOK(f"Remote version: {self.gitVersion}") def onChangeSelectedHW(self, i): # Clear status @@ -288,14 +287,13 @@ def onSaveConsole(self): timestamp = strftime('%Y-%m-%d_%H-%M-%S', gmtime(now())) options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog - fileName, _ = QFileDialog.getSaveFileName(self, "Save Logs to file", "PET4L_Logs_%s.txt" % timestamp, "All Files (*);; Text Files (*.txt)", options=options) + fileName, _ = QFileDialog.getSaveFileName(self, f"Save Logs to file PET4L_Logs_{timestamp}.txt", "All Files (*);; Text Files (*.txt)", options=options) try: if fileName: - printOK("Saving logs to %s" % fileName) - log_file = open(fileName, 'w+', encoding="utf-8") - log_text = self.consoleArea.toPlainText() - log_file.write(log_text) - log_file.close() + printOK(f"Saving logs to {fileName}") + with open(fileName, 'w+', encoding="utf-8") as log_file: + log_text = self.consoleArea.toPlainText() + log_file.write(log_text) except Exception as e: err_msg = "error writing Log file" @@ -315,14 +313,14 @@ def onToggleConsole(self): def showHWstatus(self): self.updateHWleds() - myPopUp_sb(self, "info", 'PET4L - hw check', "%s" % self.hwStatusMess) + myPopUp_sb(self, "info", 'PET4L - hw check', f"{self.hwStatusMess}") def showRPCstatus(self, server_index, fDebug): # Update displayed status only if selected server is not changed if server_index == self.header.rpcClientsBox.currentIndex(): self.updateRPCled(fDebug) if fDebug: - myPopUp_sb(self, "info", 'PET4L - rpc check', "%s" % self.rpcStatusMess) + myPopUp_sb(self, "info", 'PET4L - rpc check', f"{self.rpcStatusMess}") def updateHWleds(self): if self.hwStatus == 1: @@ -342,7 +340,7 @@ def updateHWstatus(self, ctrl): printDbg(str(e)) pass - printDbg("status:%s - mess: %s" % (self.hwStatus, self.hwStatusMess)) + printDbg(f"status:{self.hwStatus} - mess: {self.hwStatusMess}") def updateLastBlockLabel(self): text = '--' @@ -370,9 +368,9 @@ def updateLastBlockPing(self): color = "green" self.header.lastPingIcon.setPixmap(self.connGreen_icon) if self.rpcResponseTime is not None: - self.header.responseTimeLabel.setText("%.3f" % self.rpcResponseTime) - self.header.responseTimeLabel.setStyleSheet("color: %s" % color) - self.header.lastPingIcon.setStyleSheet("color: %s" % color) + self.header.responseTimeLabel.setText(f"{self.rpcResponseTime:.3f}") + self.header.responseTimeLabel.setStyleSheet(f"color: {color}") + self.header.lastPingIcon.setStyleSheet(f"color: {color}") def updateRPCled(self, fDebug=False): if self.rpcConnected: @@ -403,7 +401,7 @@ def updateRPClist(self): # Add public servers (italics) italicsFont = QFont("Times", italic=True) for s in public_servers: - url = s["protocol"] + "://" + s["host"].split(':')[0] + url = f"{s['protocol']}://{s['host'].split(':')[0]}" self.header.rpcClientsBox.addItem(url, s) self.header.rpcClientsBox.setItemData(self.getServerListIndex(s), italicsFont, Qt.FontRole) # Add Local Wallet (bold) @@ -413,7 +411,7 @@ def updateRPClist(self): self.header.rpcClientsBox.setItemData(self.getServerListIndex(custom_servers[0]), boldFont, Qt.FontRole) # Add custom servers for s in custom_servers[1:]: - url = s["protocol"] + "://" + s["host"].split(':')[0] + url = f"{s['protocol']}://{s['host'].split(':')[0]}" self.header.rpcClientsBox.addItem(url, s) # reset index if self.parent.cache['selectedRPC_index'] >= self.header.rpcClientsBox.count(): @@ -428,7 +426,7 @@ def updateRPClist(self): def updateRPCstatus(self, ctrl, fDebug=False): rpc_index, rpc_protocol, rpc_host, rpc_user, rpc_password = self.getRPCserver() if fDebug: - printDbg("Trying to connect to RPC %s://%s..." % (rpc_protocol, rpc_host)) + printDbg(f"Trying to connect to RPC {rpc_protocol}://{rpc_host}...") try: rpcClient = RpcClient(rpc_protocol, rpc_host, rpc_user, rpc_password) diff --git a/src/qt/guiHeader.py b/src/qt/guiHeader.py index fdaf92e..75f2b80 100644 --- a/src/qt/guiHeader.py +++ b/src/qt/guiHeader.py @@ -12,7 +12,7 @@ class GuiHeader(QWidget): def __init__(self, caller, *args, **kwargs): - QWidget.__init__(self) + super().__init__(*args, **kwargs) layout = QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) # --- 1) Check Box @@ -28,7 +28,7 @@ def __init__(self, caller, *args, **kwargs): self.button_checkRpc.setToolTip("try to connect to RPC server") self.centralBox.addWidget(self.button_checkRpc, 0, 2) self.rpcLed = QLabel() - self.rpcLed.setToolTip("%s" % caller.rpcStatusMess) + self.rpcLed.setToolTip(f"{caller.rpcStatusMess}") self.rpcLed.setPixmap(caller.ledGrayH_icon) self.centralBox.addWidget(self.rpcLed, 0, 3) self.lastPingBox = QWidget() @@ -65,7 +65,7 @@ def __init__(self, caller, *args, **kwargs): self.button_checkHw.setToolTip("try to connect to Hardware Wallet") self.centralBox.addWidget(self.button_checkHw, 1, 2) self.hwLed = QLabel() - self.hwLed.setToolTip("status: %s" % caller.hwStatusMess) + self.hwLed.setToolTip(f"status: {caller.hwStatusMess}") self.hwLed.setPixmap(caller.ledGrayH_icon) self.centralBox.addWidget(self.hwLed, 1, 3) layout.addLayout(self.centralBox) diff --git a/src/tabRewards.py b/src/tabRewards.py index 4e6c1c7..555751e 100644 --- a/src/tabRewards.py +++ b/src/tabRewards.py @@ -15,7 +15,7 @@ from misc import printDbg, printError, printException, getCallerName, getFunctionName, \ persistCacheSetting, myPopUp, myPopUp_sb, DisconnectedException, checkTxInputs from pivx_parser import ParseTx, IsPayToColdStaking, GetDelegatedStaker -from qt.gui_tabRewards import TabRewards_gui +from qt.gui_tabRewards import TabRewardsGui from threads import ThreadFuns from txCache import TxCache from utils import checkPivxAddr @@ -34,7 +34,7 @@ def __init__(self, caller): self.suggestedFee = MINIMUM_FEE # --- Initialize GUI - self.ui = TabRewards_gui(self.caller.imgDir) + self.ui = TabRewardsGui(self.caller.imgDir) self.caller.tabRewards = self.ui self.ui.btn_Copy.setIcon(self.caller.copy_icon) @@ -48,14 +48,14 @@ def __init__(self, caller): self.updateFee() # Connect GUI buttons - self.ui.addySelect.currentIndexChanged.connect(lambda: self.onChangeSelected()) - self.ui.rewardsList.box.itemClicked.connect(lambda: self.updateSelection()) - self.ui.btn_reload.clicked.connect(lambda: self.loadSelection()) - self.ui.btn_selectAllRewards.clicked.connect(lambda: self.onSelectAllRewards()) - self.ui.btn_deselectAllRewards.clicked.connect(lambda: self.onDeselectAllRewards()) - self.ui.btn_sendRewards.clicked.connect(lambda: self.onSendRewards()) - self.ui.btn_Cancel.clicked.connect(lambda: self.onCancel()) - self.ui.btn_Copy.clicked.connect(lambda: self.onCopy()) + self.ui.addySelect.currentIndexChanged.connect(self.onChangeSelected) + self.ui.rewardsList.box.itemClicked.connect(self.updateSelection) + self.ui.btn_reload.clicked.connect(self.loadSelection) + self.ui.btn_selectAllRewards.clicked.connect(self.onSelectAllRewards) + self.ui.btn_deselectAllRewards.clicked.connect(self.onDeselectAllRewards) + self.ui.btn_sendRewards.clicked.connect(self.onSendRewards) + self.ui.btn_Cancel.clicked.connect(self.onCancel) + self.ui.btn_Copy.clicked.connect(self.onCopy) # Connect Signals self.caller.sig_UTXOsLoading.connect(self.update_loading_utxos) @@ -72,7 +72,7 @@ def display_utxos(self): rewards = self.caller.parent.db.getRewardsList(self.curr_addr) if rewards is not None: - def item(value): + def item(value: str) -> QTableWidgetItem: item = QTableWidgetItem(value) item.setTextAlignment(Qt.AlignCenter) item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) @@ -93,17 +93,17 @@ def item(value): self.ui.rewardsList.box.showRow(row) if utxo['staker'] != "": self.ui.rewardsList.box.item(row, 2).setIcon(self.caller.coldStaking_icon) - self.ui.rewardsList.box.item(row, 2).setToolTip("Staked by %s" % utxo['staker']) + self.ui.rewardsList.box.item(row, 2).setToolTip(f"Staked by {utxo['staker']}") # make immature rewards unselectable if utxo['coinstake']: required = 16 if self.caller.isTestnetRPC else 101 if utxo['confirmations'] < required: - for i in range(0, 4): + for i in range(4): self.ui.rewardsList.box.item(row, i).setFlags(Qt.NoItemFlags) ttip = self.ui.rewardsList.box.item(row, i).toolTip() self.ui.rewardsList.box.item(row, i).setToolTip( - ttip + "\n(Immature - %d confirmations required)" % required) + ttip + f"\n(Immature - {required} confirmations required)") self.ui.rewardsList.box.resizeColumnsToContents() @@ -115,15 +115,12 @@ def item(value): if not self.caller.rpcConnected: self.ui.resetStatusLabel('PIVX wallet not connected') else: - self.ui.resetStatusLabel('Found no Rewards for %s' % self.curr_addr) + self.ui.resetStatusLabel(f'Found no Rewards for {self.curr_addr}') - def getSelection(self): + def getSelection(self) -> list: # Get selected rows indexes items = self.ui.rewardsList.box.selectedItems() - rows = set() - for i in range(0, len(items)): - row = items[i].row() - rows.add(row) + rows = {item.row() for item in items} indexes = list(rows) # Get UTXO info from DB for each selection = [] @@ -139,7 +136,7 @@ def loadSelection(self): printDbg("Checking HW device") if self.caller.hwStatus != 2: myPopUp_sb(self.caller, "crit", 'PET4L - hw device check', "Connect to HW device first") - printDbg("Unable to connect - hw status: %d" % self.caller.hwStatus) + printDbg(f"Unable to connect - hw status: {self.caller.hwStatus}") return None self.ui.addySelect.clear() @@ -159,7 +156,7 @@ def loadSelection_thread(self, ctrl): self.caller.parent.cache["intExt"] = persistCacheSetting('cache_intExt', intExt) for i in range(spathFrom, spathTo + 1): - path = "%d'/%d/%d" % (hwAcc, intExt, i) + path = f"{hwAcc}'/{intExt}/{i}" address = self.caller.hwdevice.scanForAddress(hwAcc, i, intExt, isTestnet) try: balance = self.caller.apiClient.getBalance(address) @@ -167,9 +164,9 @@ def loadSelection_thread(self, ctrl): print(e) balance = 0 - itemLine = "%s -- %s" % (path, address) - if (balance): - itemLine += " [%s PIV]" % str(balance) + itemLine = f"{path} -- {address}" + if balance: + itemLine += f" [{balance} PIV]" self.ui.addySelect.addItem(itemLine, [path, address, balance]) @@ -192,7 +189,7 @@ def load_utxos_thread(self, ctrl): # Get raw tx u['rawtx'] = TxCache(self.caller)[u['txid']] if u['rawtx'] is None: - printDbg("Unable to get raw TX with hash=%s from RPC server." % u['txid']) + printDbg(f"Unable to get raw TX with hash={u['txid']} from RPC server.") # Don't save UTXO if raw TX is unavailable utxos.remove(u) u['staker'] = "" @@ -263,7 +260,7 @@ def onSendRewards(self): self.caller.onCheckHw() if self.caller.hwStatus != 2: myPopUp_sb(self.caller, "crit", 'PET4L - hw device check', "Connect to HW device first") - printDbg("Unable to connect to hardware device. The device status is: %d" % self.caller.hwStatus) + printDbg(f"Unable to connect to hardware device. The device status is: {self.caller.hwStatus}") return None # SEND @@ -277,15 +274,15 @@ def SendRewards(self, inputs=None, gui=None): # re-connect signals try: self.caller.hwdevice.api.sigTxdone.disconnect() - except: + except Exception: pass try: self.caller.hwdevice.api.sigTxabort.disconnect() - except: + except Exception: pass try: self.caller.hwdevice.api.tx_progress.disconnect() - except: + except Exception: pass self.caller.hwdevice.api.sigTxdone.connect(gui.FinishSend) self.caller.hwdevice.api.sigTxabort.connect(gui.AbortSend) @@ -301,7 +298,7 @@ def SendRewards(self, inputs=None, gui=None): num_of_inputs = len(self.selectedRewards) else: # bulk send - num_of_inputs = sum([len(x['utxos']) for x in inputs]) + num_of_inputs = sum(len(x['utxos']) for x in inputs) ans = checkTxInputs(self.caller, num_of_inputs) if ans is None or ans == QMessageBox.No: # emit sigTxAbort and return @@ -310,9 +307,9 @@ def SendRewards(self, inputs=None, gui=None): # LET'S GO if inputs is None: - printDbg("Sending from PIVX address %s to PIVX address %s " % (self.curr_addr, self.dest_addr)) + printDbg(f"Sending from PIVX address {self.curr_addr} to PIVX address {self.dest_addr} ") else: - printDbg("Sweeping rewards to PIVX address %s " % self.dest_addr) + printDbg(f"Sweeping rewards to PIVX address {self.dest_addr} ") self.ui.rewardsList.statusLabel.hide() printDbg("Preparing transaction. Please wait...") self.ui.loadingLine.show() @@ -374,10 +371,9 @@ def FinishSend(self, serialized_tx, amount_to_send): decodedTx = ParseTx(tx_hex, self.caller.isTestnetRPC) destination = decodedTx.get("vout")[0].get("scriptPubKey").get("addresses")[0] amount = decodedTx.get("vout")[0].get("value") - message = '

Broadcast signed transaction?

Destination address:
%s

' % destination - message += '

Amount: %s PIV
' % str(round(amount / 1e8, 8)) - message += 'Fees: %s PIV
Size: %d Bytes

' % ( - str(round(self.currFee / 1e8, 8)), len(tx_hex) / 2) + message = f'

Broadcast signed transaction?

Destination address:
{destination}

' + message += f'

Amount: {round(amount / 1e8, 8)} PIV
' + message += f'Fees: {round(self.currFee / 1e8, 8)} PIV
Size: {len(tx_hex) / 2} Bytes

' except Exception as e: printException(getCallerName(), getFunctionName(), "decoding exception", str(e)) message = '

Unable to decode TX- Broadcast anyway?

' @@ -392,7 +388,7 @@ def FinishSend(self, serialized_tx, amount_to_send): txid = self.caller.rpcClient.sendRawTransaction(tx_hex) if txid is None: raise Exception("Unable to send TX - connection to RPC server lost.") - printDbg("Transaction sent. ID: %s" % txid) + printDbg(f"Transaction sent. ID: {txid}") mess2_text = "

Transaction successfully sent.

" mess2 = QMessageBox(QMessageBox.Information, 'transaction Sent', mess2_text) mess2.setDetailedText(txid) @@ -431,15 +427,15 @@ def updateSelection(self, clicked_item=None): self.selectedRewards = self.getSelection() numOfInputs = len(self.selectedRewards) if numOfInputs: - for i in range(0, numOfInputs): - total += int(self.selectedRewards[i].get('satoshis')) + for reward in self.selectedRewards: + total += int(reward.get('satoshis')) # update suggested fee and selected rewards estimatedTxSize = (44 + numOfInputs * 148) * 1.0 / 1000 # kB feePerKb = self.caller.rpcClient.getFeePerKb() self.suggestedFee = round(feePerKb * estimatedTxSize, 8) - printDbg("estimatedTxSize is %s kB" % str(estimatedTxSize)) - printDbg("suggested fee is %s PIV (%s PIV/kB)" % (str(self.suggestedFee), str(feePerKb))) + printDbg(f"estimatedTxSize is {estimatedTxSize} kB") + printDbg(f"suggested fee is {self.suggestedFee} PIV ({feePerKb} PIV/kB)") self.ui.selectedRewardsLine.setText(str(round(total / 1e8, 8))) @@ -450,8 +446,6 @@ def updateSelection(self, clicked_item=None): def update_loading_utxos(self, percent): if percent < 100: - self.ui.resetStatusLabel('Checking explorer... %d%%' % percent) + self.ui.resetStatusLabel(f'Checking explorer... {percent}%') else: self.display_utxos() - - diff --git a/src/threads.py b/src/threads.py index 8ba0225..e248a5a 100644 --- a/src/threads.py +++ b/src/threads.py @@ -8,28 +8,41 @@ Based on project: https://github.com/Bertrand256/dash-masternode-tool """ + import threading import traceback from functools import partial from workerThread import WorkerThread +from typing import Callable, Any, Optional class ThreadFuns: @staticmethod - def runInThread(worker_fun, worker_fun_args, on_thread_finish=None, on_thread_exception=None, - skip_raise_exception=False): + def runInThread( + worker_fun: Callable[..., Any], + worker_fun_args: tuple, + on_thread_finish: Optional[Callable[[], None]] = None, + on_thread_exception: Optional[Callable[[Exception], None]] = None, + skip_raise_exception: bool = False + ) -> threading.Thread: """ Run a function inside a thread. :param worker_fun: reference to function to be executed inside a thread :param worker_fun_args: arguments passed to a thread function :param on_thread_finish: function to be called after thread finishes its execution + :param on_thread_exception: function to be called if an exception occurs in the thread :param skip_raise_exception: Exception raised inside the 'worker_fun' will be passed to the calling thread if: - on_thread_exception is a valid function (it's exception handler) - skip_raise_exception is False :return: reference to a thread object """ - def on_thread_finished_int(thread_arg, on_thread_finish_arg, skip_raise_exception_arg, on_thread_exception_arg): + def on_thread_finished_int( + thread_arg: WorkerThread, + on_thread_finish_arg: Optional[Callable[[], None]], + skip_raise_exception_arg: bool, + on_thread_exception_arg: Optional[Callable[[Exception], None]] + ): if thread_arg.worker_exception: if on_thread_exception_arg: on_thread_exception_arg(thread_arg.worker_exception) @@ -44,14 +57,13 @@ def on_thread_finished_int(thread_arg, on_thread_finish_arg, skip_raise_exceptio # starting thread from another thread causes an issue of not passing arguments' # values to on_thread_finished_int function, so on_thread_finish is not called st = traceback.format_stack() - print('Running thread from inside another thread. Stack: \n' + ''.join(st)) + print(f'Running thread from inside another thread. Stack: \n{"".join(st)}') thread = WorkerThread(worker_fun=worker_fun, worker_fun_args=worker_fun_args) # in Python 3.5 local variables sometimes are removed before calling on_thread_finished_int # so we have to bind that variables with the function ref - bound_on_thread_finished = partial(on_thread_finished_int, thread, on_thread_finish, skip_raise_exception, - on_thread_exception) + bound_on_thread_finished = partial(on_thread_finished_int, thread, on_thread_finish, skip_raise_exception, on_thread_exception) thread.finished.connect(bound_on_thread_finished) thread.daemon = True diff --git a/src/watchdogThreads.py b/src/watchdogThreads.py index 574c866..9bc016e 100644 --- a/src/watchdogThreads.py +++ b/src/watchdogThreads.py @@ -12,13 +12,13 @@ from misc import printOK -class CtrlObject(object): +class CtrlObject: pass class RpcWatchdog(QObject): def __init__(self, control_tab, timer_off=10, timer_on=120, *args, **kwargs): - QObject.__init__(self, *args, **kwargs) + super().__init__(*args, **kwargs) self.firstLoop = True self.shutdown_flag = Event() self.control_tab = control_tab diff --git a/src/workerThread.py b/src/workerThread.py index 6b07dd2..0f8ba50 100644 --- a/src/workerThread.py +++ b/src/workerThread.py @@ -13,7 +13,7 @@ from misc import printError -class CtrlObject(object): +class CtrlObject: pass @@ -23,7 +23,7 @@ class WorkerThread(QThread): """ def __init__(self, worker_fun, worker_fun_args): - QThread.__init__(self) + super().__init__() self.worker_fun = worker_fun self.worker_fun_args = worker_fun_args # prepare control object passed to external thread function @@ -44,4 +44,5 @@ def run(self): self.worker_result = self.worker_fun(self.ctrl_obj, *self.worker_fun_args) except Exception as e: printError("worker thread", "run", str(e)) + self.worker_exception = e self.stop()