16

I'm trying to export my .py script to .exe using PyInstaller, which has dependencies on .ui files which were created using Qt Designer.

I can confirm that my .py script works just fine when running it through PyCharm - I'm able to see the GUI I've created with the .ui files.

However, when I export my .py script to .exe and launch it, I recieve the following errors in the command line:

C:\Users\giranm>"C:\Users\giranm\PycharmProjects\PyQt Tutorial\dist\secSearch_demo.exe"
Traceback (most recent call last):
  File "secSearch_demo.py", line 13, in <module>
  File "site-packages\PyQt4\uic\__init__.py", line 208, in loadUiType
  File "site-packages\PyQt4\uic\Compiler\compiler.py", line 140, in compileUi
  File "site-packages\PyQt4\uic\uiparser.py", line 974, in parse
  File "xml\etree\ElementTree.py", line 1186, in parse
  File "xml\etree\ElementTree.py", line 587, in parse
FileNotFoundError: [Errno 2] No such file or directory: 'C:\\Users\\giranm\\securitySearchForm.ui'
Failed to execute script secSearch_demo

For some reason, the .exe file is looking for the .ui file within the path - C:\Users\giranm\

However, having done some research already, I was told that I needed to use os.getcwd() and ensure that I have the full path in my script. Even with the code below, I still get errors trying to locate the .ui files.

PyInstaller: IOError: [Errno 2] No such file or directory:

# import relevant modules etc...

cwd = os.getcwd()
securitySearchForm = os.path.join(cwd, "securitySearchForm.ui")
popboxForm = os.path.join(cwd, "popbox.ui")

Ui_MainWindow, QtBaseClass = uic.loadUiType(securitySearchForm)
Ui_PopBox, QtSubClass = uic.loadUiType(popboxForm)

# remainder of code below.  

I'm aware that one can convert .ui files to .py and import them into the main routine using pyuic4. However, I will be making multiple edits to the .ui files and thus it is not feasible for me to keep converting them.

Is there anyway to fix this so that I can create a standalone .exe?

I'm fairly new to using PyQT4 and PyInstaller - any help would be much appreciated!

Community
  • 1
  • 1
giran
  • 368
  • 1
  • 2
  • 11

3 Answers3

18

After scratching my head all weekend and looking further on SO, I managed to compile the standalone .exe as expected using the UI files.

Firstly, I defined the following function using this answer

Bundling data files with PyInstaller (--onefile)

# Define function to import external files when using PyInstaller.
def resource_path(relative_path):
    """ Get absolute path to resource, works for dev and for PyInstaller """
    try:
        # PyInstaller creates a temp folder and stores path in _MEIPASS
        base_path = sys._MEIPASS
    except Exception:
        base_path = os.path.abspath(".")

    return os.path.join(base_path, relative_path)

Next I imported the .UI files using this function and variables for the required classes.

# Import .ui forms for the GUI using function resource_path()
securitySearchForm = resource_path("securitySearchForm.ui")
popboxForm = resource_path("popbox.ui")

Ui_MainWindow, QtBaseClass = uic.loadUiType(securitySearchForm)
Ui_PopBox, QtSubClass = uic.loadUiType(popboxForm)

I then had to create a resource file (.qrc) using Qt Designer and embed images/icons using this resource file. Once done, I used pyrcc4 to convert the .qrc file to .py file, which would be imported in the main script.

Terminal

C:\Users\giranm\PycharmProjects\PyQt Tutorial>pyrcc4 -py3 resources.qrc -o resources_rc.py

Python

import resources_rc

Once I have confirmed the main .py script works, I then created a .spec file using PyInstaller.

Terminal

C:\Users\giranm\PycharmProjects\PyQt Tutorial>pyi-makespec --noconsole --onefile secSearch_demo.py

As per PyInstaller's guide, I've added data files by modifying the above .spec file.

https://pythonhosted.org/PyInstaller/spec-files.html#adding-data-files

Finally, I then compiled the .exe using the .spec file from above.

Community
  • 1
  • 1
giran
  • 368
  • 1
  • 2
  • 11
  • This is still the thing everytime you want to `open()` a file you bundled in witjh `--add-data` atribute. The root path to the file is `sys._MEIPASS` following with the relative path in your project. – n1_ Feb 27 '19 at 14:02
  • Newbie in Python deployment here: Could you edit this answer to specify in which files these pieces of code should be. – Gwendal Grelier Mar 19 '19 at 10:04
5

Another method, tested on Ubuntu 20.04 is to add the .ui file to the data section in the spec file. First generate a spec file with pyinstaller --onefile hello.py. Then update the spec file and run pyinstaller hello.spec.

a = Analysis(['hello.py'],
             ...
             datas=[('mainwindow.ui', '.')],
             ...

The next step is to update the current directory in your Python file. To do this, the os.chdir(sys._MEIPASS) command has to be used. Wrap it in a try-catch for development use when _MEIPASS is not set.

import os
import sys

# Needed for Wayland applications
os.environ["QT_QPA_PLATFORM"] = "xcb"
# Change the current dir to the temporary one created by PyInstaller
try:
    os.chdir(sys._MEIPASS)
    print(sys._MEIPASS)
except:
    pass

from PySide2.QtUiTools import QUiLoader
from PySide2.QtWidgets import QApplication
from PySide2.QtCore import QFile, QIODevice

if __name__ == "__main__":
    app = QApplication(sys.argv)

    ui_file_name = "mainwindow.ui"
    ui_file = QFile(ui_file_name)
    if not ui_file.open(QIODevice.ReadOnly):
        print(f"Cannot open {ui_file_name}: {ui_file.errorString()}")
        sys.exit(-1)
    loader = QUiLoader()
    window = loader.load(ui_file)
    ui_file.close()
    if not window:
        print(loader.errorString())
        sys.exit(-1)
    window.show()

    sys.exit(app.exec_())
Moritz
  • 2,987
  • 3
  • 21
  • 34
  • This is quite simple and worked like a charm for me. No need to go and change all of my loadUi()'s to meet pyinstallers *odd* MSWindows OS requirements! I was already using the data files list in the spec file. The folder version worked, but not the single file version... Thanks @Moritz! – Arana May 23 '22 at 15:12
4

You can simply use:

uic.loadUi(r'E:\Development\Python\your_ui.ui', self)

Use the full path, and use pyinstaller with standard arguments, and it works fine. The r prefix makes sure the backslashes are interpreted literally.

antonagestam
  • 4,532
  • 3
  • 32
  • 44