Pyinstaller bundled exe not working, but folder exe does [duplicate] - python-3.x

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!

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.

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.

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_())

Related

Adding textfile with --onefile option

the --add-data option works well without using --onefile option
I want the data text files to be compressed and merged into an exe file. But exe file only works when the data text file is in the same folder.
How can I merge the text file into the only one exe file??
When I used:
pyinstaller --add-data ="\GameUserSettings.ini;." file.py
it works.
pyinstaller --onefile --add-data ="\GameUserSettings.ini;." file.py
it can make the file.exe but it doesn't work well. and it works only when the text file is in the same folder.
When you add your text file with one-dir, Pyinstaller would put your file next to your executable and you will be fine when accessing your file by the script (e.g open("myfile.txt"). But when you create your executable with --onefile it would extract your file in a temp directory which is a separate directory, so calling open("myfile.txt") would result in a NotFound error because the file doesn't exist besides your executable. So you need to change the path to point to the temp directory. The sys._MEIPASS would return the temp directory so you need to locate your files inside it. You can find more info in here.
A function like this would solve the problem:
import sys
import os
def resource_path(relative_path):
if hasattr(sys, '_MEIPASS'):
return os.path.join(sys._MEIPASS, relative_path)
return os.path.join(os.path.abspath("."), relative_path)
Then you can access your file with source = resource_path("myfile.txt").

Python Setup gets IndexError?

I am trying to create a .exe file from my Python file. My setup file contains the following code (which was generated using the PyBuilder application):
# setup.py
from distutils.core import setup
import py2exe
import sys
sys.stdout = open('screen.txt','w')
sys.stderr = open('errors.txt','w')
setup(name='Crypto Calculator',
version='1.0',
author='RedCode',
data_files=[],
windows=[{'script':'Crypto Calculator.py',
'icon_resources':[(1,'')],
}])
print("---Done---")
When I run the command pyinstaller setup.pyw I get the IndexError: tuple out of range error. When I try running the pyinstaller command on my actual application's file, it still gives me the same error.
How can I correct this problem and successfully create a Python exe file.

Programmatically import Python files as modules within another Python file and run them

There's a highly upvoted StackOverflow thread which says that the best way to run Python files within another Python file is to import them as a module.
That works well for me, except that I'm having trouble doing it programmatically in a case where there are at least hundreds (if not thousands) of files to be run.
All of the files are in the same directory and share a common naming convention. I tried to run them like this:
import glob, os
for filename in glob.glob("*_decomp*"):
import filename
but that throws an error:
Traceback (most recent call last):
File "C:\Python35\lib\site-packages\IPython\core\interactiveshell.py", line
3066, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "", line 4, in
import filename
File "C:\Program Files\JetBrains\PyCharm Community Edition 2017.1.3\helpers\pydev_pydev_bundle\pydev_import_hook.py", line 21, in do_import
module = self._system_import(name, *args, **kwargs)
ImportError: No module named 'filename'
The variable filename is also underlined in red in the IDE, which negates my original hypothesis that it was simply a matter of needing to remove the .py file extension.
This works fine for printing:
import glob, os
for filename in glob.glob("*_decomp*"):
# import filename
print(filename)
So I'm not really sure what the problem with the earlier statement is or how to work around it. I can also do the import manually and that works fine, but again I'd like to do it programmatically so that I don't have to type all of the file names and because the file names will change over time.
Finally, I also tried it with [:-3] (i.e. filename[:-3]) to remove the file extension, but again that only works for print() and not import.
There are other ways of importing not covered by the SO link you give, for example (although I'm not holding this up as a canonical or even necessarily good way of importing, but it works for me) based on one of the examples I found via this SO question/answers Building a minimal plugin architecture in Python I wrote a simple plugin implementation below - it searches a folder called 'plugins' below wherever the .py file with this in it is. Each plugin has to implement a class called Plugin, they all get the same parameters.
path = 'plugins'
# find subdirs of the path - these are the groups
# for each group, load all the .py files, each provides one or more actions
searchdir = os.path.join(os.path.split(__file__)[0],path)
if os.access(searchdir, os.F_OK):
print "searchdir=",searchdir
print "results=",os.walk(searchdir)
(root, dirs, files) =os.walk(searchdir).next()
print root,dirs,files
for dir in dirs:
print "scanning dir",dir
self.groups[dir] = []
sys.path.insert(0, os.path.join(root,dir))
for f in sorted(os.listdir(os.path.join(root,dir))):
print "looking at",f
fname, ext = os.path.splitext(f)
if ext == '.py':
print "importing ",f
mod = __import__(fname)
try:
self.groups[dir].append(mod.PlugIn(group,cmdobj,config, jts_data, directives, current_config_props,allcomponents,globals))
except:
print "URGH! plugin instantiation error!"
raise
sys.path.pop(0)
else:
print "############# no plugins folder",searchdir

Python Py2exe Error After Compile

I have a pretty complicated application I am writing that integrates a lot of modules. Matplotlib was giving some errors until I explicitly included it in the py2exe file. I am getting this error after I compile and try to launch the program and I don't understand what it means. Any help appreciated!
Py2exe compilation file:
from distutils.core import setup
import py2exe, sys, os
sys.argv.append('py2exe')
setup(
options = {
'py2exe': {
'optimize': 2,
'includes' : ["matplotlib.backends.backend_tkagg"],
'packages' : ['matplotlib', 'pytz'],
}
},
windows = [{'script': "MYAPP.py", "icon_resources": [(1, "s.ico")]}],
zipfile = "shared.lib",
Error when launching after compile:
Traceback (most recent call last):
File "C:\Python34\lib\site-packages\pint\unit.py", line 756, in load_definitions
with closing(pkg_resources.resource_stream(__name__, file)) as fp:
File "C:\Python34\lib\site-packages\pkg_resources\__init__.py", line 1167, in resource_stream
self, resource_name
File "C:\Python34\lib\site-packages\pkg_resources\__init__.py", line 1602, in get_resource_stream
return io.BytesIO(self.get_resource_string(manager, resource_name))
File "C:\Python34\lib\site-packages\pkg_resources\__init__.py", line 1605, in get_resource_string
return self._get(self._fn(self.module_path, resource_name))
File "C:\Python34\lib\site-packages\pkg_resources\__init__.py", line 1683, in _get
return self.loader.get_data(path)
OSError: [Errno 0] Error: 'pint\\default_en.txt'
I think the problem here lies with py2exe's dealing with txt files. It looks like you're using the pint module and within pint is the file default_en.txt (which your compiled app is complaining about). When py2exe compiles pint it ignores the txt files contained within the pint package.
The workaround I used was within my script to state:
ureg = pint.UnitRegistry('\path\to\default_en.txt')
also, I added the following to my setup.py file for py2exe
"packages":["pkg_resources"],
from: http://pint.readthedocs.org/en/0.6/index.html
If you make a translation of the default units or define a completely new set, you don’t want to append the translated definitions so you just give the filename to the constructor:
from pint import UnitRegistry
ureg = UnitRegistry('/your/path/to/default_es.txt')
I made \path\to\ a location within my project and added default_en.txt and constants_en.txt to that folder (copied from pint folder). Then in py2exe I added these files to my data_files.
The real solution would be to figure out how to get py2exe include the txt files in module folders but I haven't yet figured that out.

Building executables for Python 3 and PyQt

I built a rather simple application in Python 3.1 using PyQt4. Being done, I want the application to be distributed to computers without either of those installed.
I almost exclusively care about Windows platforms, so my goal is to have a single executable file and maybe some resource files and .dlls in the end.
Having searched around, I came to the conclusion that
py2exe only supports Python up to version 2.7
pyinstaller only supports Python up to version 2.6
cx_Freeze does not work for me because I keep on getting the following error when trying to execute my successfully build binary:
Y:\Users\lulz\build\exe.win32-3.1>system_shutdown.exe
Traceback (most recent call last):
File "Y:\Program Files (x86)\Python\lib\site-packages\cx_Freeze\initscripts\Console3.py", line 27, in exec(code, m.__dict__)
File "Y:/Users/lulz/Documents/Coding/Python3/projects/System Shutdown/system_shutdown.pyw", line 5, in from PyQt4 import QtCore
File "ExtensionLoader_PyQt4_QtCore.py", line 16, in AttributeError: 'NoneType' object has no attribute 'modules'
So my problem is basically two problems here:
Is there another way but cx_Freeze to build binaries with my configuration?
If not, what might the cx_Freeze problem be?
I can provide more information on the second problem if necessary, like my call of cx_Freeze, my distutils setup script etc.
Thank you already for your help and comments.
You can fix this by appending one line of code to freeze.py in your cx_Freeze package.
It is described here:
http://www.mail-archive.com/cx-freeze-users#lists.sourceforge.net/msg00212.html
It worked for me at least :)
Cheers,
Almar
For Python 3.3 and later, there's a good resolution here:
py2exe - generate single executable file
Install py2exe:
pip install py2exe
Then add besides 'your_script.py' file, the following 'Make_exe.py' file:
from distutils.core import setup
import py2exe, sys
class Make_exe():
def __init__(self, python_script):
sys.argv.append('py2exe')
setup(
console=[{'script': python_script}],
zipfile = None,
options={
'py2exe':
{
'bundle_files': 1,
'compressed': True,
# Add includes if necessary, e.g.
'includes': ['lxml.etree', 'lxml._elementpath', 'gzip'],
}
}
)
if __name__ == '__main__':
Make_exe('your_script.py')
And if you want to make 'your_script.py' rebuild itself as 'your_script.exe' each time you run it in python, you can add to its main:
import subprocess
import sys
if __name__ == '__main__':
currentFile = sys.argv[0]
if currentFile.lower().endswith(".py"):
exitCode = subprocess.call("python Make_exe.py")
if exitCode==0 :
dirName = os.path.dirname(currentFile)
exeName = os.path.splitext(os.path.basename(currentFile))[0] + '.exe'
exePath = dirName + "/dist/" + exeName
cmd = [exePath] + sys.argv[1:]
print ("Executing command:\n %s" % cmd)
exitCode = subprocess.call(cmd)
sys.exit(exitCode)
else:
print ("This will be executed only within the new generated EXE File...")

Resources