I already soved this but am not feeling good about the solution I came up with. Here is the Issue: I created a command line utility that works fine.
eg: my main.py file is
def Parser():
description = textwrap.dedent(
'''Main Command Line Tool.
For GUI based tool use: `main-gui`''')
parser = argparse.ArgumentParser(description=description, prog="main")
parser.add_argument("-f", "--foo", type=str, required=True, help='foo help')
parser.add_argument("-b", "--bar", type=str, help='bar help')
...
# this will not be shown in help
parser.add_argument("--GUI", action="store_true", help=argparse.SUPPRESS)
return parser
def task(args):
...
print("I am doing the task, Alright!") # this means print will call sys.stdout.write function
if args.GUI:
if hasattr(sys.stdout, "set_progress"): # double check. But this is always True if args.GUI is True
sys.stdout.set_progress(value)
...
def main():
parser = Parser()
args = parser.parse_args()
result = task(args)
if __name__ == "__main__":
main()
Which I run as
main --foo Hello --bar World
Which prints certain text in the terminal/console/shell whatever. Basically sys.stdout
Now, I was asked to make GUI for the same so I used PySide6.
Here is my gui.py file:
import threading
from easydict import EasyDict
from PySide6.QtCore import ...
from PySide6.QtGui import ...
from PySide6.QtWidgets import ...
from main import main
class EmittingStream(QObject):
textWritten = Signal(str)
progress = Signal(int)
def write(self, text):
self.textWritten.emit(text)
def set_progress(self, value):
self.progress.emit(value)
def flush(self):
pass
class GUI(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
self.initArgs()
self.link_actions()
def initUI(self):
...
self.ui.progress_bar.setValue(0)
def initArgs(self):
self.args = EasyDict()
self.args.foo = None
self.args.bar = None
def link_actions(self):
...
self.ui.start_button.clicked.connect(self.start)
def setProgress(self, progress):
self.ui.progress_bar.setValue(progress)
def showOuput(self, s):
self.ui.output_textedit.append(s)
def clear_output_textedit(self):
self.ui.output_textedit.clear()
def get_args(self):
# Use the Gui text fields to access the foo and bar argument
# so self.arg.foo = Hello and so on for all arguments
# the arguments are exactly the same as the ones used for argparse
def start(self):
self.get_args()
command = ["main"]
command.extend(["--foo", self.args.foo])
command.extend(["--bar", self.args.bar])
## This specifically added in parser if the script is being called from GUI
## This doesn't show up when done main --help
command.extend(["--GUI"])
sys.argv = command
stream = EmittingStream()
sys.stdout = stream # This is what I beleive is Not good, But this work
stream.textWritten.connect(self.showOuput)
stream.progress.connect(self.setProgress)
analysis_thread = threading.Thread(target=main())
analysis_thread.start()
# Setting sys.stdout back to default
def __del__(self):
sys.stdout = sys.__stdout__
def gui():
app = QApplication(sys.argv)
window = GUI()
window.show()
sys.exit(app.exec())
if __name__ == "__main__":
gui()
I was able to tap into the hood of print function where it call sys.stdout.write function which overwrote it in EmittingStream. So whenever print is invoked in main.py, it sends a signal to GUI and I update the text in the field.
This works fine so I didn't touch it anymore. However, the thing that bothers me is changing the sys variable to do this.
This is not a duplicate of Passing sys.stdout as an argument to a process, but did give me idea about altering sys.stdout