Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
460 views
in Technique[技术] by (71.8m points)

python - Path error in PySide2 application after packaging with PyInstaller

I'm trying to package a PySide2 test application with the following structure:

.
├── main.py
├── main.spec
└── wizardUI
    ├── 10.toolBoxBtns.ui
    ├── 11.toolBoxShrCt.ui
    ├── 12.propertyBox.ui
    ├── 13.printing.ui
    ├── 14.settings.ui
    ├── 15.coclusion.ui
    ├── 1.welcomePage.ui
    ├── 2.graphicsScene.ui
    ├── 3.graphicsSceneText.ui
    ├── 4.textDialog.ui
    ├── 5.codeDialog.ui
    ├── 6.graphicsSceneBox.ui
    ├── 7.graphicsScenePixmap.ui
    ├── 8.graphicsSceneShrCt.ui
    ├── 9.toolbox.ui
    └── wizard.py

When I try to run an executable I get this error:

FileNotFoundError: No such file or directory:'/home/artem/Desktop/testUI/dist/main/wizardUI'

Here's my wizard.py file

from PySide2 import QtCore, QtWidgets
from PySide2.QtUiTools import QUiLoader
import os


class tutorWizard(QtWidgets.QWizard):
    """ Contains introduction tutorial """
    def __init__(self, parent=None):
        super(tutorWizard, self).__init__(parent)


        self.setWindowTitle("Introduction tutorial")
        pages = self.findPages()
        self.initPages(pages)

    def findPages(self):
        ui_files = []
        cnt = 1
        current_dir = os.path.dirname(os.path.realpath(__file__))
        while len(ui_files) != 15:
            for file in os.listdir(current_dir):
                if file.startswith("{}.".format(cnt)):
                    ui_files.append(os.path.join(current_dir, file))
                    cnt += 1
        return ui_files

    def initPages(self, files):
        loader = QUiLoader()
        for i in files:
            file = QtCore.QFile(str(i))
            file.open(QtCore.QFile.ReadOnly)

            file.reset()
            page = loader.load(file)

            file.close()

            self.addPage(page)

main.py is:

from PySide2.QtWidgets import QApplication
from wizardUI.wizard import tutorWizard
import sys


app = QApplication(sys.argv)
window = tutorWizard()
window.show()
sys.exit(app.exec_())

and .spec file is:

# -*- mode: python ; coding: utf-8 -*-

block_cipher = None


a = Analysis(['main.py'],
             pathex=['/home/artem/Desktop/testUI'],
             binaries=[],
             datas=[],
             hiddenimports=['PySide2.QtXml'],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          [],
          exclude_binaries=True,
          name='main',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          console=True )
coll = COLLECT(exe,
               a.binaries,
               a.zipfiles,
               a.datas,
               strip=False,
               upx=True,
               upx_exclude=[],
               name='main')

a.datas += Tree('/home/artem/Desktop/testUI/wizardUI')

Is there any way to solve this error without changig current_dir variable in wizard.py ?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Your code has the following problems:

  • You are adding the Tree() to a.datas after COLLECT so it will not be used in the compilation, you have to add it before.

  • You can no longer use __file__ to get the directory path, instead you must use sys._MEIPASS.

I will also give the following improvements:

  • For the .spec to be portable, I will use the SPECPATH variable.
  • I have added as a second parameter "wizardUI" to create a dictionary with the .ui, I have also excluded wizard.py.

Considering the above, the solution is as follows:

main.py

from PySide2.QtWidgets import QApplication
from wizardUI.wizard import tutorWizard
import sys


if __name__ == "__main__":

    app = QApplication(sys.argv)
    window = tutorWizard()
    window.show()
    sys.exit(app.exec_())

wizard.py

import os
import sys

from PySide2 import QtCore, QtWidgets, QtUiTools

# https://stackoverflow.com/a/42615559/6622587
if getattr(sys, 'frozen', False):
    # If the application is run as a bundle, the pyInstaller bootloader
    # extends the sys module by a flag frozen=True and sets the app 
    # path into variable _MEIPASS'.
    current_dir = os.path.join(sys._MEIPASS, "wizardUI")
else:
    current_dir = os.path.dirname(os.path.abspath(__file__))


class tutorWizard(QtWidgets.QWizard):
    """ Contains introduction tutorial """

    def __init__(self, parent=None):
        super(tutorWizard, self).__init__(parent)

        self.setWindowTitle("Introduction tutorial")
        pages = self.findPages()
        self.initPages(pages)

    def findPages(self):
        ui_files = []
        cnt = 1
        while len(ui_files) < 15:
            for file in os.listdir(current_dir):
                if file.startswith("{}.".format(cnt)):
                    ui_files.append(os.path.join(current_dir, file))
                    cnt += 1
        return ui_files

    def initPages(self, files):
        loader = QtUiTools.QUiLoader()
        for i in files:
            file = QtCore.QFile(str(i))
            if file.open(QtCore.QFile.ReadOnly):
                page = loader.load(file)
                self.addPage(page)

main.spec

# -*- mode: python ; coding: utf-8 -*-

# https://stackoverflow.com/a/50402636/6622587
import os
spec_root = os.path.abspath(SPECPATH)

block_cipher = None

a = Analysis(['main.py'],
             pathex=[spec_root],
             binaries=[],
             datas=[],
             hiddenimports=['PySide2.QtXml', 'packaging.specifiers', 'packaging.requirements'],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          [],
          exclude_binaries=True,
          name='main',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          console=True )

a.datas += Tree(os.path.join(spec_root, 'wizardUI'), 'wizardUI', excludes=["*.py"])

coll = COLLECT(exe,
               a.binaries,
               a.zipfiles,
               a.datas,
               strip=False,
               upx=True,
               upx_exclude=[],
               name='main')

Another option is to use Qt Resource instead of data.

resource.qrc

<RCC>
  <qresource prefix="/">
    <file>wizardUI/1.welcomePage.ui</file>
    <file>wizardUI/2.graphicsScene.ui</file>
    <file>wizardUI/3.graphicsSceneText.ui</file>
    <file>wizardUI/4.textDialog.ui</file>
    <file>wizardUI/5.codeDialog.ui</file>
    <file>wizardUI/6.graphicsSceneBox.ui</file>
    <file>wizardUI/7.graphicsScenePixmap.ui</file>
    <file>wizardUI/8.graphicsSceneShrCt.ui</file>
    <file>wizardUI/9.toolbox.ui</file>
    <file>wizardUI/10.toolBoxBtns.ui</file>
    <file>wizardUI/11.toolBoxShrCt.ui</file>
    <file>wizardUI/12.propertyBox.ui</file>
    <file>wizardUI/13.printing.ui</file>
    <file>wizardUI/14.settings.ui</file>
    <file>wizardUI/15.coclusion.ui</file>
  </qresource>
</RCC>

Then convert it to .py using pyside2-rcc:

pyside2-rcc resource.qrc -o resource_rc.py

Then you have to modify the scripts:

main.py

from PySide2.QtWidgets import QApplication
from wizardUI.wizard import tutorWizard
import sys

import resource_rc

if __name__ == "__main__":

    app = QApplication(sys.argv)
    window = tutorWizard()
    window.show()
    sys.exit(app.exec_())

wizard.py

from PySide2 import QtCore, QtWidgets, QtUiTools


class tutorWizard(QtWidgets.QWizard):
    """ Contains introduction tutorial """

    def __init__(self, parent=None):
        super(tutorWizard, self).__init__(parent)

        self.setWindowTitle("Introduction tutorial")
        pages = self.findPages()
        self.initPages(pages)

    def findPages(self):
        ui_files = []
        cnt = 1
        while len(ui_files) < 15:
            it = QtCore.QDirIterator(":/wizardUI")
            while it.hasNext():
                filename = it.next()
                name = QtCore.QFileInfo(filename).fileName()
                if name.startswith("{}.".format(cnt)):
                    ui_files.append(filename)
                    cnt += 1                    
        return ui_files

    def initPages(self, files):
        loader = QtUiTools.QUiLoader()
        for i in files:
            file = QtCore.QFile(str(i))
            if file.open(QtCore.QFile.ReadOnly):
                page = loader.load(file)
                self.addPage(page)

And finally the structure of your project is as follows:

├── main.py
├── main.spec
├── resource.qrc
├── resource_rc.py
└── wizardUI
    ├── 10.toolBoxBtns.ui
    ├── 11.toolBoxShrCt.ui
    ├── 12.propertyBox.ui
    ├── 13.printing.ui
    ├── 14.settings.ui
    ├── 15.coclusion.ui
    ├── 1.welcomePage.ui
    ├── 2.graphicsScene.ui
    ├── 3.graphicsSceneText.ui
    ├── 4.textDialog.ui
    ├── 5.codeDialog.ui
    ├── 6.graphicsSceneBox.ui
    ├── 7.graphicsScenePixmap.ui
    ├── 8.graphicsSceneShrCt.ui
    ├── 9.toolbox.ui
    └── wizard.py

Both solutions are found here


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...