0

I'm very new to PyQt and threading (only about a year). I apologize for the long post (most of the questions are at the end). I could use some assistance with why my worker thread is losing output when performing the work. This is PyQt5 and Python 3.5.

#Creates your own signal class to output stdout.
class EmittingStream(QtCore.QObject):
    textWritten = QtCore.pyqtSignal(str)
    def write(self, text):
        self.textWritten.emit(str(text))
    def flush(self):
        pass  

class MainWindow(QtWidgets.QMainWindow, Ui_mainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent=parent)
        self.setupUi(self)
        self.closeButton.clicked.connect(self.close)
        self.clearButton.clicked.connect(self.clear_all)
        self.submitButton.clicked.connect(self.submit_ips)
        self.addButton.clicked.connect(self.add_ip_on_button_press)
        #Keep Save button inactive until an IP(s) have been submitted.
        self.saveButton.clicked.connect(self.write_mem)
        self.saveButton.setEnabled(False)
        sys.stdout = EmittingStream(textWritten=self.normalOutputWritten)
        sys.stderr = EmittingStream(textWritten=self.errorOutputWritten)
        #Creates instance of WorkerThread in the main window
        self.workerThread = WorkerThread()
        #Creates isntance of WorkerThreadSave in the main window
        self.workerThreadSave = WorkerThreadSave()

    def __del__(self):
        #    Restore sys.stdout
        sys.stdout = sys.__stdout__    

    # Uses EmittingStream class to output stdout to outputTextBox
    def normalOutputWritten(self, text):
        """Append text to the QTextEdit."""
        cursor = self.outputTextBox.textCursor()
        cursor.movePosition(QtGui.QTextCursor.End)
        cursor.insertText(text)
        # self.outputTextBox.setTextCursor(cursor)
        self.outputTextBox.ensureCursorVisible()

    def errorOutputWritten(self, text):
        cursor = self.outputTextBox.textCursor()
        cursor.movePosition(QtGui.QTextCursor.End)
        cursor.insertText(text)
        self.outputTextBox.ensureCursorVisible()

    def submit_ips(self):
        msgBox = QtWidgets.QMessageBox()
        msgBox.setIcon(QtWidgets.QMessageBox.Warning)
        msgBox.setWindowTitle("Error")   
        if not hostiplist and not cidriplist:
            msgBox.setText("Ooops, Please enter an acceptible host or network IP Address.")
            msgBox.exec()
        else:
            print("***************  Process Started  ***************\n")
            self.submitButton.setEnabled(False)
            self.addButton.setEnabled(False)
            self.clearButton.setEnabled(False)
            self.saveButton.setEnabled(True)
            self.workerThread.start()

    #Performs Save Function through API
    def write_mem(self):
        print("***************  Saving Device(s) Started  ***************\n")
        self.saveButton.setEnabled(False)
        self.addButton.setEnabled(True)
        self.clearButton.setEnabled(True)
        self.submitButton.setEnabled(True)
        del hostiplist[:]
        del cidriplist[:]
        self.workerThreadSave.start()

The above code is obviously my main UI. When clicking the submit button it starts a separate thread to perform the processing and work (and also to not freeze the UI).

Unfortunately, I end up losing some of the print statements (I'm assuming my signals and slots aren't performing or the thread stops before the output is printed).

class WorkerThread(QThread):
    def __init__(self, parent=None):
        super(WorkerThread, self).__init__(parent)

    def run(self):
        #Post function for adding host IP's
        def post_object (f):
            self.api_path = "/api/objects/networkobjects"
            self.req = urllib.request.Request(str(server) + self.api_path, json.dumps(self.post_data).encode('utf-8'), headers)
            self.base64string = base64.encodestring(('%s:%s' % (username,password)).encode()).decode().replace('\n', '')
            self.req.add_header("Authorization", "Basic %s" % self.base64string)
            try:
                f = urllib.request.urlopen(self.req)#Had to add .request between the url.
                self.status_code = f.getcode()
                # print("Status code is {}.".format(self.status_code))
                if self.status_code == 201:
                    print ("The following object was created successfully: {}".format(self.post_data["name"]))
            except urllib.error.HTTPError as err: #Python3 requires urllib.error.HTTPError to work.
                print ("Error received from server: {}.  HTTP Status code: {}".format(err.reason, err.code))
                try:
                    print("Yes {}".format(json.loads(err.read))) #json.loads is not being sent...if you format the error it'll send that...not the error itself.
                    json_error = json.loads(err.read)
                    if json_error:
                        print (json.dumps(json_error,sort_keys=True,indent=4, separators=(',', ': '))) #Error won't print json.dumps error
                        print ("Error received from server: {}.  HTTP Status code: {}".format(err.reason, err.code))
                except ValueError:
                    pass
            finally:
                if f:  f.close()
                return (f) 

I run the post_object function within a for loop below:

try:
            #Starts for loop REST API adds.       
            for s in serverList:
                server = s
                # Operation for CIDR IPs
                for c in cidriplist:
                    self.network = c
                    self.patch_data_group = {
                    "members.add": [
                    {
                        "kind": "IPv4Network",
                        "value": str(self.network)
                    }]
                    }
                    patch_object_group(f)
                    jsonoutputlist.append(self.patch_data_group)

              #Operation for single host IPs
                for h in hostiplist:
                    self.host = h
                    self.post_data = {
                    "host": {
                        "kind": "IPv4Address",
                        "value": str(self.host)},
                    "kind": "object#NetworkObj",
                        "name": str(self.host),
                        "objectId": str(self.host)
                            }

                    self.patch_data_group = {
                        "members.add": [
                        {
                            "kind": "objectRef#NetworkObj",
                            "objectId": str(self.host)
                        }]
                        }
                    jsonoutputlist.append(self.post_data) 
                    post_object(f)                    
                    patch_object_group(f)
                    # print ("\n JSON Data format for change control: \n{}".format(self.patch_data_group))

            print("\n***************  IPs Add Complete  ***************\n")

        except: 
            (type, value, traceback) = sys.exc_info()
            sys.excepthook(type, value, traceback)

This is where the main issues arise. During the operation of the for loops, the print statements and return API call statements are dropped or cut off.

For example,

if self.status_code == 201:
                    print ("The following object was created successfully: {}".format(self.post_data["name"]))

The above statement only prints one line below the if statement...it won't allow me to do more than one line...and the one line print statement is usually cut off. As well as the json.dumps statements won't print at all...

Any help would be greatly appreciated.

Thanks,

Njordic
  • 41
  • 1
  • 8
  • Please add any critique to my Qthreading architecture...any help to get that more "up-to-date" would be appreciated as well. – Njordic Jul 06 '17 at 14:17
  • Please read the guidance on how to provide a [mcve]. – ekhumoro Jul 06 '17 at 19:58
  • Hard to follow exactly what is going on in your example. See if this link is any help: [link](https://stackoverflow.com/questions/16879971/example-of-the-right-way-to-use-qthread-in-pyqt) – aoh Jul 06 '17 at 23:20
  • I also echo the calls for a [mcve] (something that is actually runnable for debugging purposes). In the meantime, [this](https://stackoverflow.com/q/21071448/1994235) might be useful (looking back at that I think my answer is unnecessarily complex...but it works, so could be a starting point for you) – three_pineapples Jul 08 '17 at 13:11
  • I will take all of the input and appreciate the help! – Njordic Jul 09 '17 at 15:32
  • I have the same problem: printing from a QThread is not always reliable. If I add logging statements with the same text everywhere I print something, then I can see that everything works as expected. Somehow stdout is 'hogged' by the main thread or one of the QThreads, preventing the others from printing. – NZD Feb 11 '19 at 00:52

0 Answers0