0

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/

2
  • Welcome to Stack Overflow. Please don't SHOUT. If you need to emphasize something you can do it with Markdown. Commented Feb 5, 2022 at 15:50
  • Side note, unless you have a specific need to use sys.stdout.write() and sys.stderr.write() directly, it's much easier (and more idiomatic) to simply use print(). That uses standard out by default, and you can print("foo", file=sys.stderr) for the error stream. Commented Feb 5, 2022 at 17:14

2 Answers 2

2

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.

Sign up to request clarification or add additional context in comments.

3 Comments

Thank You so much, but can you suggest me how can I implement dummy_script.py in GUI.py, what changes should I do in GUI.py
An example of how you might change GUI.py is already in my answer: import some or all of dummy_script into GUI.py, then call whatever function you need to call. What isn't shown is changes you might have to make to dummy_script.py. You'll probably want to wrap up its functionality (at least the part you want to execute from GUI.py) in a function.
With the example above, that might mean defining a run function that contains all of your prints and flushes. You'd import and call run() in GUI.py.
0

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')

1 Comment

As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.