PyQt6 with Pyinstaller Runtime Error

110 views
Skip to first unread message

Nick

unread,
Aug 9, 2024, 9:43:19 AM8/9/24
to PyInstaller
Hello,

I am trying to create a windows desktop program gui with Pyqt6 and Pyinstaller. I got a simple hello world program that has uses a signal and threading to send updated time from localtime function.

Anyway, I get a runtime error from this line of code:
"engine.rootObjects()[0].setProperty('backend', backend)"

The error reads:
"Unhandled exception in script.
Failed to execute script 'main' due to unhandled exception: list index out of range
Traceback (most recent call last):
File "main.py", line 52, in <module>
IndexError: list index out of range"

I think it's because the rootObject list is dynamic, and doesn't have any list items until runtime. However, I don't know how to rectify a runtime error like this.

Code is bellow, and I added a few debugging lines since I built the program. So, that is why if you count, line 52 is no longer correct for the line of code in question. Any help would be greatly appreciated, thank you!

"
#############################################
#   main.py for PyQt6 Hello World           #
#                                           #
#############################################

import sys
import os
import threading
                    #gmtime is 6 hours later than here, depending on dst +/- 1 hr
from time           import strftime, sleep, localtime
from PyQt6.QtGui    import QGuiApplication
from PyQt6.QtQml    import QQmlApplicationEngine
from PyQt6.QtQuick  import QQuickWindow
from PyQt6.QtCore   import QObject, pyqtSignal

#currentTime = strftime("%H:%M:%S", gmtime())

class Backend(QObject):
    def __init__(self):
        QObject.__init__(self)

    updated = pyqtSignal(str, arguments=['updater'])

    def updater(self, currentTime):
        self.updated.emit(currentTime)

    def bootUp(self):
        t_thread = threading.Thread(target=self._bootUp)
        t_thread.daemon = True
        t_thread.start()

    def _bootUp(self):
        while True:
            currentTime = strftime("%H:%M:%S", localtime())
           
            print(currentTime)
            self.updater(currentTime)
            sleep(1) #use 0.1

QQuickWindow.setSceneGraphBackend('software')

app = QGuiApplication(sys.argv)

engine = QQmlApplicationEngine()
engine.quit.connect(app.quit)
engine.load('./UI/main.qml')

backend = Backend()

rootObjects = engine.rootObjects()
print("rootObjects type: ", type(rootObjects), "\n")
print("rootObjects len:  ", len(rootObjects), "\n")
print("rootObjects: ", rootObjects, "\n")
#engine.rootObjects()[0].setProperty('backend', backend)

rootObjects[0].setProperty('backend', backend)
#engine.rootObjects()[0].setProperty('currentTime', currentTime)

backend.bootUp()

sys.exit(app.exec())

bwoodsend

unread,
Aug 10, 2024, 4:02:44 AM8/10/24
to PyInstaller

Your bug is the line engine.load('./UI/main.qml') (although the issue is masked by the fact that Qt doesn’t raise an error if it can’t load a file). Relative paths to locate application resources are not portable. I suggest you change that line to:

qml_path = './UI/main.qml' if not os.path.exists(qml_path): raise FileNotFoundError(qml_path) engine.load(qml_path)

which will not fix your issue but it will give you a proper error message. Then read the docs for what qml_path should really be set to.

Nick

unread,
Aug 11, 2024, 9:18:45 AM8/11/24
to PyInstaller
Hello,

Why do you think the bug is in the line: "engine.load('./UI/main.qml')" ?

I tried what you said, and read the pyinstaller docs you linked, but this code is part of objects from PyQt6 not pyinstaller. So, I don't see what you're getting at. After I made the changes you mentioned, the error message is the same. "list index out of range" for the line "engine.rootObjects()[0].setProperty('backend', backend)".

I'm not sure why this part of the code caught your eye, but perhaps it's noteworthy to mention that I manually include the full path for the qml file in the spec file each time I build the exe with Pyinstaller. I build the spec file manually, manually enter the qml file path inside the spec file, and then build the exe with pyinstaller from the spec file.

bwoodsend

unread,
Aug 11, 2024, 2:56:02 PM8/11/24
to PyInstaller

I homed in on it because any piece of code that uses a relative path to locate an application resource is broken (it’s relative to your current working directory which could be anywhere, not the application root, but IDEs hide this bug by setting your current working directory to the location of your code) and that false assumption gets flushed out pretty quickly when you run PyInstaller on it. If you’re not convinced, try opening or cd-ing a terminal somewhere far away from your original python code and running python /full/path/to/your/code.py. I expect that you’ll get the same error.

To be clear, what I think is happening is that Qt can’t find that QML file, whether you correctly packaged it or not, for the reasons described above. Qt doesn’t raise an error (although it does print a warning) so the code continues on but it never managed to load any UI objects from the QML file so engine.rootObjects() is an empty list and that’s why you’re getting an IndexError on that line.

Reply all
Reply to author
Forward
0 new messages