Convert multiple .py file to .exe - python

I am trying to convert my python program to an executable (.exe) file using PyInstaller, I have 2 python fils: dummy_script.py and GUI.py.
Basically, GUI.py consists of a button that executes dummy_script.py.
dummy_script.py:
import sys
import time
def flush_then_wait():
sys.stdout.flush()
sys.stderr.flush()
time.sleep(0.5)
sys.stdout.write("Script stdout 1\n")
sys.stdout.write("Script stdout 2\n")
sys.stdout.write("Script stdout 3\n")
sys.stderr.write("Total time: 00:05:00\n")
sys.stderr.write("Total complete: 10%\n")
flush_then_wait()
sys.stdout.write("name=Martin\n")
sys.stdout.write("Script stdout 4\n")
sys.stdout.write("Script stdout 5\n")
sys.stderr.write("Total complete: 30%\n")
flush_then_wait()
sys.stderr.write("Elapsed time: 00:00:10\n")
sys.stderr.write("Elapsed time: 00:00:50\n")
sys.stderr.write("Total complete: 50%\n")
sys.stdout.write("country=Nederland\n")
flush_then_wait()
sys.stderr.write("Elapsed time: 00:01:10\n")
sys.stderr.write("Total complete: 100%\n")
sys.stdout.write("Script stdout 6\n")
sys.stdout.write("Script stdout 7\n")
sys.stdout.write("website=www.mfitzp.com\n")
flush_then_wait()
GUI.py:
from PyQt5.QtWidgets import (QApplication, QMainWindow, QPushButton, QPlainTextEdit,
QVBoxLayout, QWidget, QProgressBar)
from PyQt5.QtCore import QProcess
import sys
import re
# A regular expression, to extract the % complete.
progress_re = re.compile("Total complete: (\d+)%")
def simple_percent_parser(output):
"""
Matches lines using the progress_re regex,
returning a single integer for the % progress.
"""
m = progress_re.search(output)
if m:
pc_complete = m.group(1)
return int(pc_complete)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.p = None
self.btn = QPushButton("Execute")
self.btn.pressed.connect(self.start_process)
self.text = QPlainTextEdit()
self.text.setReadOnly(True)
self.progress = QProgressBar()
self.progress.setRange(0, 100)
l = QVBoxLayout()
l.addWidget(self.btn)
l.addWidget(self.progress)
l.addWidget(self.text)
w = QWidget()
w.setLayout(l)
self.setCentralWidget(w)
def message(self, s):
self.text.appendPlainText(s)
def start_process(self):
if self.p is None: # No process running.
self.message("Executing process")
self.p = QProcess() # Keep a reference to the QProcess (e.g. on self) while it's running.
self.p.readyReadStandardOutput.connect(self.handle_stdout)
self.p.readyReadStandardError.connect(self.handle_stderr)
self.p.stateChanged.connect(self.handle_state)
self.p.finished.connect(self.process_finished) # Clean up once complete.
self.p.start("python",["dummy_script.py"])
def handle_stderr(self):
data = self.p.readAllStandardError()
stderr = bytes(data).decode("utf8")
# Extract progress if it is in the data.
progress = simple_percent_parser(stderr)
if progress:
self.progress.setValue(progress)
self.message(stderr)
def handle_stdout(self):
data = self.p.readAllStandardOutput()
stdout = bytes(data).decode("utf8")
self.message(stdout)
def handle_state(self, state):
states = {
QProcess.NotRunning: 'Not running',
QProcess.Starting: 'Starting',
QProcess.Running: 'Running',
}
state_name = states[state]
self.message(f"State changed: {state_name}")
def process_finished(self):
self.message("Process finished.")
self.p = None
app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()
The concept used to execute dummy_script.py in GUI.py is:
p = QProcess()
p.start("python3", ['dummy_script.py'])
Now when I pack the whole program in an executable using PyInstaller, dummy_script.py is missing.
How can I make sure dummy_script.py gets included in the PyInstaller bundle?
Reference: https://www.pythonguis.com/tutorials/qprocess-external-programs/

The concept used to execute (dummy_script.py) in (GUI.py) is,
p = QProcess()
p.start("python3", ['dummy_script.py'])
PyInstaller is pretty good at finding and bundling dependencies, but it won't be able to figure out that GUI.py needs dummy_script.py if you run it this way.
A better approach would be to import the code you need and use it directly, e.g something like
from dummy_script import some_function
some_function()
Other options
If you simply modify GUI.py like this, PyInstaller should find dummy_script.py on its own.
If that is not practical for some reason, you should be able to declare it as a hidden import using a spec file. You may have a spec file from an earlier build, but if you need to create a new one you can do that with something like this:
pyi-makespec GUI.py
Then edit the spec file to add dummy_script to the list of hidden imports:
a = Analysis(['GUI.py'],
# ...
hiddenimports=['dummy_script'],
# ...,
)
Then build again from the modified spec file:
pyinstaller foo.spec
That may not work either, since you still aren't importing the other module. In that case you may need to declare it as a data file instead.

It looks like Chris has you covered with a great answer. If you have any further issues with pyinstaller.
Additional references:https://pyinstaller.readthedocs.io/en/stable/usage.html
I use the following:
pathex to find additional paths for imports
hiddenimports = missing or not visible in the python scripts. Sometimes modules have hidden imports themselves.
datas = all data from a module
Sometimes a hook is required, an example hook used for Hydra:
from PyInstaller.utils.hooks import
collect_data_files,collect_submodules
datas = collect_data_files('hydra.conf.hydra')
hiddenimports = collect_submodules('hydra')

Related

How can I add a path to the QProcess PATH environment variable? (PyQt5 on Python 3.7)

1. The problem explained
I instantiate a QProcess()-object just before the application shows its main window. The QProcess()-instance is stored in the self.__myProcess variable, and stays alive as long as you can see the main window.
The main window looks like this:
When you click on the button, the following code executes:
def __btn_clicked(self):
self.__add_openocd_to_env()
command = "openocd.exe" + '\r\n'
self.__myProcess.start(command)
The last two lines are quite clear: the command openocd.exe is passed to self.__myProcess and executes. What this executable actually does is not important here. In fact, I could use any random executable. The point is: if the executable is in my Windows PATH environment variable, it gets found and executed.
Imagine the executable is NOT in the PATH environment variable. Then the function self.__add_openocd_to_env() should fix that issue:
def __add_openocd_to_env(self):
env = self.__myProcess.processEnvironment()
env.insert("PATH", "C:\\Users\\Kristof\\programs\\openocd_0.10.0\\bin;" + env.value("PATH"))
self.__myProcess.setProcessEnvironment(env)
However, I've noticed it has no effect at all. I have tried a lot of different things in this function, but it just won't have any effect.
You can find the full code here:
If you have Python 3 installed with PyQt5, you can simply copy-paste the code into a .py module and run it. You should see the little window with the pushbutton. Of course you should change the path "C:\Users\Kristof.." to something valid on your computer. You can choose any executable you like for this test.
import sys
import os
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class CustomMainWindow(QMainWindow):
def __init__(self):
super(CustomMainWindow, self).__init__()
# -------------------------------- #
# QProcess() setup #
# -------------------------------- #
self.__myProcess = QProcess()
self.__myProcess.setProcessChannelMode(QProcess.MergedChannels)
self.__myProcess.readyRead.connect(self.__on_output)
self.__myProcess.errorOccurred.connect(self.__on_error)
self.__myProcess.finished.connect(self.__on_exit)
# -------------------------------- #
# Window setup #
# -------------------------------- #
self.setGeometry(100, 100, 800, 200)
self.setWindowTitle("QProcess test")
self.__frm = QFrame(self)
self.__frm.setStyleSheet("QWidget { background-color: #ffffff }")
self.__lyt = QVBoxLayout()
self.__lyt.setAlignment(Qt.AlignTop)
self.__frm.setLayout(self.__lyt)
self.setCentralWidget(self.__frm)
self.__myBtn = QPushButton("START QPROCESS()")
self.__myBtn.clicked.connect(self.__btn_clicked)
self.__myBtn.setFixedHeight(70)
self.__myBtn.setFixedWidth(200)
self.__lyt.addWidget(self.__myBtn)
self.show()
def __add_openocd_to_env(self):
env = self.__myProcess.processEnvironment()
env.insert("PATH", "C:\\Users\\Kristof\\programs\\openocd_0.10.0\\bin;" + env.value("PATH"))
self.__myProcess.setProcessEnvironment(env)
def __btn_clicked(self):
self.__add_openocd_to_env()
command = "openocd.exe" + '\r\n'
self.__myProcess.start(command)
def __on_output(self):
data = bytes(self.__myProcess.readAll()).decode().replace('\r\n', '\n')
print(data)
def __on_error(self, error):
print("")
print("Process error: {0}".format(str(error)))
print("")
def __on_exit(self, exitCode, exitStatus):
print("")
print("ExitCode = {0}".format(str(exitCode)))
print("ExitStatus = {0}".format(str(exitStatus)))
print("")
if __name__ == '__main__':
app = QApplication(sys.argv)
QApplication.setStyle(QStyleFactory.create('Fusion'))
myGUI = CustomMainWindow()
sys.exit(app.exec_())
2. My question
I know I could simply add "C:\Users\Kristof\programs\openocd_0.10.0\bin" to my Windows PATH environment variable before instantiating the QProcess(). But that's not the point. I want to know how to add it to the PATH environment variable for that one specific QProcess()-instance. If possible, it should not affect any other QProcess()-instances around in my software, nor should it affect any future QProcess()-instances I create later on.
3. System settings
I use the PyQt5 framework in Python 3.7 on Windows 10.
NOTE:
I've just tried to improve the QProcess() setup in the following way:
# -------------------------------- #
# QProcess() setup #
# -------------------------------- #
self.__myProcess = QProcess()
self.__myProcess.setProcessChannelMode(QProcess.MergedChannels)
self.__myProcess.readyRead.connect(self.__on_output)
self.__myProcess.errorOccurred.connect(self.__on_error)
self.__myProcess.finished.connect(self.__on_exit)
# NEW: initialize the environment variables for self.__myProcess:
env = QProcessEnvironment.systemEnvironment()
self.__myProcess.setProcessEnvironment(env)
I was hopefull ... but it still won't work :-(
Based on the comment of Mr. #JonBrave, I have written the following workaround:
import sys
import os
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class CustomMainWindow(QMainWindow):
def __init__(self):
super(CustomMainWindow, self).__init__()
# -------------------------------- #
# QProcess() setup #
# -------------------------------- #
self.__myProcess = QProcess()
self.__myProcess.setProcessChannelMode(QProcess.MergedChannels)
self.__myProcess.readyRead.connect(self.__on_output)
self.__myProcess.errorOccurred.connect(self.__on_error)
self.__myProcess.finished.connect(self.__on_exit)
# -------------------------------- #
# Window setup #
# -------------------------------- #
self.setGeometry(100, 100, 800, 200)
self.setWindowTitle("QProcess test")
self.__frm = QFrame(self)
self.__frm.setStyleSheet("QWidget { background-color: #ffffff }")
self.__lyt = QVBoxLayout()
self.__lyt.setAlignment(Qt.AlignTop)
self.__frm.setLayout(self.__lyt)
self.setCentralWidget(self.__frm)
self.__myBtn = QPushButton("START QPROCESS()")
self.__myBtn.clicked.connect(self.__btn_clicked)
self.__myBtn.setFixedHeight(70)
self.__myBtn.setFixedWidth(200)
self.__lyt.addWidget(self.__myBtn)
self.show()
def __add_openocd_to_env(self):
self.__oldEnv = os.environ["PATH"]
os.environ["PATH"] = "C:\\Users\\Kristof\\Dropbox (Personal)\\EMBEDOFFICE\\embedoffice\\resources\\programs\\openocd_0.10.0_dev00459\\bin;" + self.__oldEnv
def __remove_openocd_from_env(self):
os.environ["PATH"] = self.__oldEnv
def __btn_clicked(self):
self.__add_openocd_to_env()
command = "openocd.exe" + '\r\n'
self.__myProcess.start(command)
self.__myProcess.waitForStarted(-1)
self.__remove_openocd_from_env()
def __on_output(self):
data = bytes(self.__myProcess.readAll()).decode().replace('\r\n', '\n')
print(data)
def __on_error(self, error):
print("")
print("Process error: {0}".format(str(error)))
print("")
def __on_exit(self, exitCode, exitStatus):
print("")
print("ExitCode = {0}".format(str(exitCode)))
print("ExitStatus = {0}".format(str(exitStatus)))
print("")
if __name__ == '__main__':
app = QApplication(sys.argv)
QApplication.setStyle(QStyleFactory.create('Fusion'))
myGUI = CustomMainWindow()
sys.exit(app.exec_())
Basically I'm doing the following: just before ordering the QProcess()-instance to start a command, I add the executable path to the PATH environment variable that belongs to the whole Python session. Once the command has started, I can remove it again so it won't have an effect on other QProcess()-instances created in the future.
It works, but it will certainly require a lot of "bookkeeping" if I'm going to apply that approach in my software (many QProcess()-instances live in my software). If you find a better approach, please don't hesitate to share!
There is a solution using python subprocess.run() instead of QProcess.
In subprocess.run(), you can specify a set of environment variables (actually a dictionary) using the env parameter. The idea is to take a copy of your original environment, modify the PATH variable, and pass the modified environment to subprocess.run, as follows:
env = os.environ.copy()
env['PATH'] = "C:\\Users\\Kristof\\programs\\openocd_0.10.0\\bin" \
+ os.pathsep + env['PATH']
subprocess.run("openocd", env=env)
This still doesn't work: the remaining problem is that the environment (including the modified PATH variable) will be available in the subprocess but is not used to search for the openocd command. But that is easy to fix: subprocess.run also has a boolean shell parameter (default False) that tells it to run the command in a shell. Since the shell will run in the subprocess, it will use the modified PATH to search for openocd. So working code is:
env = os.environ.copy()
env['PATH'] = "C:\\Users\\Kristof\\programs\\openocd_0.10.0\\bin" \
+ os.pathsep + env['PATH']
subprocess.run("openocd", env=env, shell=True)
An alternative for shell=True is to use shutil.which (available in Python >= 3.3) to resolve the command. This will also work reliably when the command is given as a list of strings instead of a single string.
env = os.environ.copy()
env['PATH'] = "C:\\Users\\Kristof\\programs\\openocd_0.10.0\\bin" \
+ os.pathsep + env['PATH']
command = shutil.which("openocd", path = self.env.get('PATH', None))
subprocess.run([ command ], env=env)

python parallel processes return exit code

lets see if I can make this clear... I'm a total Python beginner so bear with me, this is my first python program (though I'm familiar with basic scripting in a few other languages). I've been searching around for hours and I'm sure the answer to this is fairly simple but I have yet to get it to work properly.
I'm writing a code that should launch multiple commandline processes, and when each one finishes I want to update a cell in a QTableWidget. The table has a row for each process to run, and each row has a cell for the "status" of the process.
I can run this no problem if I just do a for loop, spawning one process per row using subprocess.call() however this is too linear and I would like to fire them all off at the same time and not hang the program for each loop cycle. I've been digging through the subprocess documentation and am having a really hard time with it. I understand that I need to use subprocess.Popen (which will prevent my program from hanging while the process runs, and thus I can spawn multiple instances). Where I run into trouble is getting the exit code back so that I can update my table, without hanging the program - for instance using subprocess.wait() followed by a subprocess.returncode still just sticks until the process completes. I need a sort of "when process completes, check the exit code and run a function that updates the QTableWidget."
I did find these two posts that seemed to get me going in the right direction, but didn't quite get me there:
Understanding Popen.communicate
How to get exit code when using Python subprocess communicate method?
Hopefully that made sense. Here's a simplified version of my code, I realize it is half-baked and half-broken but I've been screwing around with it for over an hour and I've lost track of a few things...
import os, subprocess
ae_app = 'afterfx'
ae_path = os.path.join('C:/Program Files/Adobe/Adobe After Effects CC 2015/Support Files', ae_app + ".exe")
filename = "E:/Programming/Python/Archive tool/talk.jsx"
commandLine = 'afterfx -noui -r ' + filename
processList = [commandLine]
processes = []
for process in processList:
f = os.tmpfile()
aeProcess = subprocess.Popen(process, executable=ae_path, stdout=f)
processes.append((aeProcess, f))
for aeProcess, f in processes:
# this is where I need serious help...
aeProcess.wait()
print "the line is:"
print aeProcess.returncode
Spencer
You mentioned PyQt, so you can use PyQt's QProcess class.
def start_processes(self, process_list):
for cmd, args in process_list:
proc = QProcess(self)
proc.finished.connect(self.process_finished)
proc.start(cmd, args)
def process_finished(self, code, status):
# Do something
UPDATE: Added fully working example. Works properly for both PyQt4 and PyQt5 (to switch just comment line 3 and uncomment line 4)
sleeper.py
import sys
from time import sleep
from datetime import datetime as dt
if __name__ == '__main__':
x = int(sys.argv[1])
started = dt.now().time()
sleep(x)
ended = dt.now().time()
print('Slept for: {}, started: {}, ended: {}'.format(x, started, ended))
sys.exit(0)
main.py
import sys
from PyQt5 import QtCore, QtWidgets
# from PyQt4 import QtCore, QtGui as QtWidgets
class App(QtWidgets.QMainWindow):
cmd = r'python.exe C:\_work\test\sleeper.py {}'
def __init__(self):
super(App, self).__init__()
self.setGeometry(200, 200, 500, 300)
self.button = QtWidgets.QPushButton('Start processes', self)
self.button.move(20, 20)
self.editor = QtWidgets.QTextEdit(self)
self.editor.setGeometry(20, 60, 460, 200)
self.button.clicked.connect(self.start_proc)
def start_proc(self):
for x in range(5):
proc = QtCore.QProcess(self)
proc.finished.connect(self.finished)
proc.start(self.cmd.format(x))
def finished(self, code, status):
self.editor.append(str(self.sender().readAllStandardOutput()))
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
gui = App()
gui.show()
app.exec_()

PyQt: Async console output not captured with every application

I am trying to start the Arduino IDE in command line mode and get the standard output back into my PyQt application like this:
def recvConsoleOutput(self, output):
print(output)
def upload(self):
[...]
cmd = [ARDUINO_PATH,
'--verbose',
'--upload', os.path.join(os.getcwd(), 'sketch', 'sketch.ino'),
'--port', self.serialPort.currentText(),
'--board', 'arduino:avr:leonardo']
cmd = ' '.join(cmd)
proc = AsyncProcess(cmd, self.recvConsoleOutput, parent=self)
proc.start()
It is called within a QMainWindow function and works flawlessly (Arduino IDE uploads the sketch!) except the fact, that it does not output any messages like it does when called by the regular windows command prompt.
The AsyncProcess class looks like this:
from PyQt5.QtCore import QObject
from PyQt5.Qt import QProcess
class AsyncProcess(QObject):
def __init__(self, path, slot, parent=None):
super().__init__(parent)
self.path = path
self.proc = None
self.slot = slot
def start(self):
self.proc = QProcess(self)
self.proc.readyReadStandardOutput.connect(self.readStandardOutput)
self.proc.readyReadStandardError.connect(self.readStandardError)
self.proc.start(self.path)
def readStandardOutput(self):
procOutput = self.proc.readAllStandardOutput()
self.slot(procOutput)
def readStandardError(self):
procOutput = self.proc.readAllStandardError()
self.slot(procOutput)
It works for some reason if I start "ping 127.0.0.1" instead of Arduino.
Any ideas?
Okay, mea culpa. At some point the developers of the IDE decided to output the information I needed only in the designated debugging version... I was actually calling the wrong executable...

Python os.dup2 redirect enables output buffering on windows python consoles

I'm using a strategy based around os.dup2 (similar to examples on this site) to redirect C/fortran level output into a temporary file for capturing.
The only problem I've noticed is, if you use this code from an interactive shell in windows (either python.exe or ipython) it has the strange side effect of enabling output buffering in the console.
Before capture sys.stdout is some kind of file object that returns True for istty(). Typing print('hi') causes hi to be output directly.
After capture sys.stdout points to exactly the same file object but print('hi') no longer shows anything until sys.stdout.flush() is called.
Below is a minimal example script "test.py"
import os, sys, tempfile
class Capture(object):
def __init__(self):
super(Capture, self).__init__()
self._org = None # Original stdout stream
self._dup = None # Original system stdout descriptor
self._file = None # Temporary file to write stdout to
def start(self):
self._org = sys.stdout
sys.stdout = sys.__stdout__
fdout = sys.stdout.fileno()
self._file = tempfile.TemporaryFile()
self._dup = None
if fdout >= 0:
self._dup = os.dup(fdout)
os.dup2(self._file.fileno(), fdout)
def stop(self):
sys.stdout.flush()
if self._dup is not None:
os.dup2(self._dup, sys.stdout.fileno())
os.close(self._dup)
sys.stdout = self._org
self._file.seek(0)
out = self._file.readlines()
self._file.close()
return out
def run():
c = Capture()
c.start()
os.system('echo 10')
print('20')
x = c.stop()
print(x)
if __name__ == '__main__':
run()
Opening a command prompt and running the script works fine. This produces the expected output:
python.exe test.py
Running it from a python shell does not:
python.exe
>>> import test.py
>>> test.run()
>>> print('hello?')
No output is shown until stdout is flushed:
>>> import sys
>>> sys.stdout.flush()
Does anybody have any idea what's going on?
Quick info:
The issue appears on Windows, not on linux (so probably not on mac).
Same behaviour in both Python 2.7.6 and Python 2.7.9
The script should capture C/fortran output, not just python output
It runs without errors on windows, but afterwards print() no longer flushes
I could confirm a related problem with Python 2 in Linux, but not with Python 3
The basic problem is
>>> sys.stdout is sys.__stdout__
True
Thus you are using the original sys.stdout object all the time. And when you do the first output, in Python 2 it executes the isatty() system call once for the underlying file, and stores the result.
You should open an altogether new file and replace sys.stdout with it.
Thus the proper way to write the Capture class would be
import sys
import tempfile
import time
import os
class Capture(object):
def __init__(self):
super(Capture, self).__init__()
def start(self):
self._old_stdout = sys.stdout
self._stdout_fd = self._old_stdout.fileno()
self._saved_stdout_fd = os.dup(self._stdout_fd)
self._file = sys.stdout = tempfile.TemporaryFile(mode='w+t')
os.dup2(self._file.fileno(), self._stdout_fd)
def stop(self):
os.dup2(self._saved_stdout_fd, self._stdout_fd)
os.close(self._saved_stdout_fd)
sys.stdout = self._old_stdout
self._file.seek(0)
out = self._file.readlines()
self._file.close()
return out
def run():
c = Capture()
c.start()
os.system('echo 10')
x = c.stop()
print(x)
time.sleep(1)
print("finished")
run()
With this program, in both Python 2 and Python 3, the output will be:
['10\n']
finished
with the first line appearing on the terminal instantaneously, and the second after one second delay.
This would fail for code that import stdout from sys, however. Luckily not much code does that.

Python, Quickly and Glade, showing stdout in a TextView

I've spent ages looking for a way to do this, and I've so far come up with nothing. :(
I'm trying to make a GUI for a little CLI program that I've made - so I thought using Ubuntu's "Quickly" would be the easiest way. Basically it appears to use Glade for making the GUI. I know that I need to run my CLI backend in a subprocess and then send the stdout and stderr to a textview. But I can't figure out how to do this.
This is the code that Glade/Quickly created for the Dialog box that I want the output to appear into:
from gi.repository import Gtk # pylint: disable=E0611
from onice_lib.helpers import get_builder
import gettext
from gettext import gettext as _
gettext.textdomain('onice')
class BackupDialog(Gtk.Dialog):
__gtype_name__ = "BackupDialog"
def __new__(cls):
"""Special static method that's automatically called by Python when
constructing a new instance of this class.
Returns a fully instantiated BackupDialog object.
"""
builder = get_builder('BackupDialog')
new_object = builder.get_object('backup_dialog')
new_object.finish_initializing(builder)
return new_object
def finish_initializing(self, builder):
"""Called when we're finished initializing.
finish_initalizing should be called after parsing the ui definition
and creating a BackupDialog object with it in order to
finish initializing the start of the new BackupDialog
instance.
"""
# Get a reference to the builder and set up the signals.
self.builder = builder
self.ui = builder.get_ui(self)
self.test = False
def on_btn_cancel_now_clicked(self, widget, data=None):
# TODO: Send SIGTERM to the subprocess
self.destroy()
if __name__ == "__main__":
dialog = BackupDialog()
dialog.show()
Gtk.main()
If I put this in the finish_initializing function
backend_process = subprocess.Popen(["python", <path to backend>], stdout=subprocess.PIPE, shell=False)
then the process starts and runs as another PID, which is what I want, but now how do I send backend_process.stdout to the TextView? I can write to the textview with:
BackupDialog.ui.backup_output.get_buffer().insert_at_cursor("TEXT")
But I just need to know how to have this be called each time there is a new line of stdout.
But I just need to know how to have this be called each time there is a new line of stdout.
You could use GObject.io_add_watch to monitor the subprocess output or create a separate thread to read from the subprocess.
# read from subprocess
def read_data(source, condition):
line = source.readline() # might block
if not line:
source.close()
return False # stop reading
# update text
label.set_text('Subprocess output: %r' % (line.strip(),))
return True # continue reading
io_id = GObject.io_add_watch(proc.stdout, GObject.IO_IN, read_data)
Or using a thread:
# read from subprocess in a separate thread
def reader_thread(proc, update_text):
with closing(proc.stdout) as file:
for line in iter(file.readline, b''):
# execute update_text() in GUI thread
GObject.idle_add(update_text, 'Subprocess output: %r' % (
line.strip(),))
t = Thread(target=reader_thread, args=[proc, label.set_text])
t.daemon = True # exit with the program
t.start()
Complete code examples.

Categories