Eugene Sajine
Eugene Sajine

Reputation: 8200

QProcess.readAllStandardOutput() doesn't seem to read anything - PyQt

Here is the code sample:

class RunGui (QtGui.QMainWindow)

    def __init__(self, parent=None):

        ...
        QtCore.Qobject.connect(self.ui.actionNew, QtCore.SIGNAL("triggered()"), self.new_select)
        ...


    def normal_output_written(self, qprocess):
        self.ui.text_edit.append("caught outputReady signal") #works
        self.ui.text_edit.append(str(qprocess.readAllStandardOutput())) # doesn't work


    def new_select(self):
        ...
        dialog_np = NewProjectDialog()
        dialog_np.exec_()
        if dialog_np.is_OK:
            section = dialog_np.get_section()
            project = dialog_np.get_project()
            ...
            np = NewProject()
            np.outputReady.connect(lambda: self.normal_output_written(np.qprocess))
            np.errorReady.connect(lambda: self.error_output_written(np.qprocess))
            np.inputNeeded.connect(lambda: self.input_from_line_edit(np.qprocess))
            np.params = partial(np.create_new_project, section, project, otherargs)
            np.start()

class NewProject(QtCore.QThread):

    outputReady = QtCore.pyqtSignal(object)
    errorReady = QtCore.pyqtSignal(object)
    inputNeeded = QtCore.pyqtSignal(object)
    params = None
    message = ""

    def __init__(self):
        super(NewProject, self).__init__()
        self.qprocess = QtCore.QProcess()
        self.qprocess.moveToThread(self)
        self._inputQueue = Queue()

    def run(self):
        self.params()

    def create_new_project(self, section, project, otherargs):
        ...
        # PyDev for some reason skips the breakpoints inside the thread
        self.qprocess.start(command)
        self.qprocess.waitForReadyRead()
        self.outputReady.emit(self.qprocess) # works - I'm getting signal in RunGui.normal_output_written()
        print(str(self.qprocess.readAllStandardOutput())) # prints empty line
        .... # other actions inside the method requiring "command" to finish properly.

The idea is beaten to death - get the GUI to run scripts and communicate with the processes. The challenge in this particular example is that the script started in QProcess as command runs an app, that requires user input (confirmation) along the way. Therefore I have to be able to start the script, get all output and parse it, wait for the question to appear in the output and then communicate back the answer, allow it to finish and only then to proceed further with other actions inside create_new_project()

Upvotes: 0

Views: 3282

Answers (1)

jdi
jdi

Reputation: 92569

I don't know if this will fix your overall issue, but there are a few design issues I see here.

  1. You are passing around the qprocess between threads instead of just emitting your custom signals with the results of the qprocess
  2. You are using class-level attributes that should probably be instance attributes

Technically you don't even need the QProcess, since you are running it in your thread and actively using blocking calls. It could easily be a subprocess.Popen...but anyways, I might suggest changes like this:

class RunGui (QtGui.QMainWindow)
    
    ...

    def normal_output_written(self, msg):
        self.ui.text_edit.append(msg) 

    def new_select(self):
        ...
            np = NewProject()
            np.outputReady.connect(self.normal_output_written)
            np.params = partial(np.create_new_project, section, project, otherargs)
            np.start()

class NewProject(QtCore.QThread):

    outputReady = QtCore.pyqtSignal(object)
    errorReady = QtCore.pyqtSignal(object)
    inputNeeded = QtCore.pyqtSignal(object)

    def __init__(self):
        super(NewProject, self).__init__()

        self._inputQueue = Queue()
        self.params = None

    def run(self):
        self.params()

    def create_new_project(self, section, project, otherargs):
        ...
        qprocess = QtCore.QProcess()
        qprocess.start(command)
        if not qprocess.waitForStarted():
            # handle a failed command here
            return

        if not qprocess.waitForReadyRead():
            # handle a timeout or error here
            return

        msg = str(self.qprocess.readAllStandardOutput())
        self.outputReady.emit(msg) 

Don't pass around the QProcess. Just emit the data. And create it from within the threads method so that it is automatically owned by that thread. Your outside classes should really not have any knowledge of that QProcess object. It doesn't even need to be a member attribute since its only needed during the operation.

Also make sure you are properly checking that your command both successfully started, and is running and outputting data.

Update

To clarify some problems you might be having (per the comments), I wanted to suggest that QProcess might not be the best option if you need to have interactive control with processes that expect periodic user input. It should work find for running scripts that just produce output from start to finish, though really using subprocess would be much easier. For scripts that need user input over time, your best bet may be to use pexpect. It allows you to spawn a process, and then watch for various patterns that you know will indicate the need for input:

foo.py

import time

i = raw_input("Please enter something: ")
print "Output:", i
time.sleep(.1)
print "Another line"
time.sleep(.1)
print "Done"

test.py

import pexpect
import time

child = pexpect.spawn("python foo.py")
child.setecho(False)

ret = -1
while ret < 0:
    time.sleep(.05)
    ret = child.expect("Please enter something: ")

child.sendline('FOO')
while True:
    line = child.readline()
    if not line:
        break
    print line.strip()

# Output: FOO
# Another line
# Done

Upvotes: 2

Related Questions