Pyinstaller - getting multiple exe files in a single compilation - python

I wanted to have multiple .exe files with common used libraries. for example, I have a project that should have a.py and b.py and both these scripts use another script like c.py
so I should have a.exe and b.exe.
curently i can get this like this:
pyinstaller a.py
and then
pyinstaller b.py
and after that copy contents of dist\a to dist\b and replace them so I will have both a.exe and b.exe in the same place without duplication of other compiled libraries. Is there a way to do this with a single command like pyinstaller a.py,b.py?

I just solved this problem that was bothering me.
you can make some .spec file and merge them, and run pyinstaller to build multi exe.
generate a spec file for each python file that you want build to exe.
pyi-makespec xxx.py
generated .spec file just like this:
a = Analysis(...)
pyz = PYZ(a.pure, a.zipped_data, ...)
exe = EXE(pyz,a.scripts, ...)
coll = COLLECT(exe,a.binaries, ...)
change variable name, just like this:
main = Analysis(...)
main_pyz = PYZ(main.pure, main.zipped_data, ...)
main_exe = EXE(main_pyz,main.scripts, ...)
coll = COLLECT(main_exe,main.binaries, ...)
process all .spec files like this and merge the contents of
coll into one
main = Analysis(...)
main_pyz = PYZ(main.pure, main.zipped_data, ...)
main_exe = EXE(main_pyz, main.scripts, ...)
sub = Analysis(...)
sub_pyz = PYZ(sub.pure, sub.zipped_data, ...)
sub_exe = EXE(sub_pyz, sub.scripts, ...)
...
coll = COLLECT(main_exe, sub_exe, ...)
finally, build it with the pyinstaller
pyinstller xxx.spec

Related

Bazel read temporary dynamically added file

I have a python script that downloads some json data and then uploads it somewehere else. The project is build and run using bazel and has the following simplified structure.
-scripts/
-exported_file_1.json
-main.py
-tests/
-BUILD
My issue is that when it comes to reading the exported files and loading them in memory, those files cannot be found using:
def get_filepath(filename):
"""Get the full path to file"""
cwd = os.path.dirname(__file__)
return os.path.join(cwd, filename)
If I manually add a json file to the project structure, declare it in the BUILD file to be visible, do a bazel build then it works fine
py_binary(
name = "main",
srcs = [
"main.py",
],
data = ["scripts/exported_file_1.json"],
python_version = "PY2",
visibility = ["//visibility:public"],
deps = [],
)
But how would one handle the case when your files are added dynamically?
Perhaps using glob might work?
Something like:
import os
import glob
def get_json_files():
cwd = os.path.dirname(__file__)
p=f"{cwd}/*.json"
return glob.glob(p)
for json_file in get_json_files():
print(f"found file: {json_file}")
Using glob in the BUILD file will allow the binary to find all .json files in the scripts directory:
py_binary(
name = "main",
srcs = [
"scripts/main.py",
],
data = glob(["scripts/*.json"]),
python_version = "PY2",
visibility = ["//visibility:public"],
deps = [],
)
This would still require the JSON files to be downloaded and present in the scripts/ directory when bazel run :main is executed.

Real path to config.ini in python binary *app in MacOS

I have this code:
def _read_config(self):
config = configparser.ConfigParser()
config.sections()
# I tried
path_main = os.path.dirname(os.path.realpath(__file__))
# and this after create exec file with pyinstaller nothing changed
path_main = os.getcwd()
print(path_main)
file = os.path.join(path_main, "config.ini")
print(file)
config.read(file)
return config
When I run the code in MacOS using the terminal with python gui.py, it prints this:
/Users/telos/Desktop/Telos-Monitor-Tool/client
/Users/telos/Desktop/Telos-Monitor-Tool/client/config.ini
But when I do pyinstaller --onefile --windowed gui.py, I receive 1 app file, when I run it I get this:
/Users/telos
/Users/telos/config.ini
But the one file app and ``gui.py` is in the same directory.
So I have an error because the Python parser can't find config.ini.
As in comennt discasion advise me to use print(QtCore.QCoreApplication.applicationDirPath()) after recreating app, i have 2 file 1 gui.app, 2-nd gui.exec. gui.exec find config.ini fine and all work fine, but gui.app can't and send the error.
Any idea what is the problem?
Since you are using PyQt5 if you want to get the executable folder you can use:
QtCore.QCoreApplication.applicationDirPath()

python pyinstaller bundle image in GUI to onefile EXE?

I've successfully created an EXE file from python tkinter GUI which includes images. see the following code:
lblLogo=Label(main)
lblLogo.grid(row=3,column=11,rowspan=4)
try:
filelocation="C:/Documents/Python/ScreenPartNumberProgram/"
KasonLogo=PhotoImage(file=filelocation+"KasonLogo.png")
KasonLogo=KasonLogo.zoom(25,20)
KasonLogo=KasonLogo.subsample(50,50)
lblLogo.config(image=KasonLogo)
icon=PhotoImage(file=filelocation+"KasonLogo.png")
main.tk.call('wm','iconphoto',main._w,icon)
except:
print("warning: could not load photos")
lblLogo.config(text="[KasonLogo.png]")
the exe file opens and works perfectly on my computer. I used this command to get the .exe:
pyinstaller --onefile --noconsole myscript.py
The only problem is this file requires the image to be in the same folder or it doesn't show on the label. How can I get the image bundled into the EXE without requiring an external file?
Thanks in advance
edit:
I looked at the links provided,did more research, and still haven't solved the issue. The program works only if the image is in the same folder. I'd really like to make it work without requiring an external image file. the resource_path technique doesn't seem to work for me either...
I also tried modifying my code to use PIL's ImageTk and same issue. program loads image only if the external image file is in the folder with it.
HOLY COW ! After a few more days of headaches I FINALLY found an approach that works... here is what I did to get the program to work without requiring an external image file:
step 1: encode the image file (note that it must be a GIF. you can convert it using paint):
import base64
with open("MyPicture.gif", "rb") as image_file:
encoded_string = base64.b64encode(image_file.read())
print(encoded_string)#print string to copy it (see step 2)
step 2: the next step is to copy and paste the string into a separate myimages.py file and
store it as variable. for example:
imageString = b'R0lGODlhyADIAPcAAAA .....blah blah really long string.......'
step 3: then import the myimages.py into main program and call the variable
from myimages import *
pic=imageString#GIF decoded to string. imageString from myimages.py
render = PhotoImage(data=pic)
myLabel.config(image=render)
step 4: in the spec file: include the myimages.py as data
# -*- mode: python -*-
block_cipher = None
#instructions: pyinstaller --onefile --noconsole myProgram.spec
file_name = 'myProgram'
add_files=[('myimages.py','module')]
a = Analysis([file_name+'.py'],
pathex=['C:\\Program Files (x86)\\Python36-32\\Scripts'],
binaries=[],
datas=add_files,
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=exclude_list,
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
name=file_name,
debug=False,
strip=False,
upx=True,
runtime_tmpdir=None,
console=False )
step 5: finally you can compile to EXE:
pyinstaller --onefile --noconsole myProgram.spec
SIGH If anyone has a better solution, please post it here. until then I'm glad something works...
I ran into the same issue when I was wanting to include a .ico file to use as the icon for my tkinter app. Here's how I got it to work:
When building the .exe file with PyInstaller, I specified the --add-data option with the full path to my .ico.
When the app starts up, it has to unpack everything from the .exe in a temp directory, so I get the path to that temp directory and use it to specify the temp location of my .ico file.
Before the mainloop is kicked off, and using 'tempfile' to get the temp file directory and 'glob' to check for a file pattern match:
tempdir = tempfile.gettempdir()
icon_path = tempdir + '\\' + 'next_subdirectory_pattern' + '\\' + '*.ico'
icon_matches = glob.glob(icon_path)
if len(icon_matches) > 0:
logo = icon_matches[0]
else:
logo = ''
tk.Tk.iconbitmap(self, default=logo)
Probably not the most eloquent way to do it, but it works.
Here's how I did it...
To bundle the image with the executable:
Use the following command to save your icon to a folder called "icon" that will be generated in the "C:\Users<username>\AppData\Local\Temp_MEI123456" folder generated by pyinstaller at runtime:
--add-data=C:\absolute\path\to\your\ICON.ico;icon
My full command line, the string I type into the python terminal (i'm using pycharm IDE) to generate my EXE, looks like this:
pyinstaller -w -F -i=C:\absolute\path\to\your\ICON.ico --add-data=C:\absolute\path\to\your\ICON.ico;icon -n=MyEXEsNameV1 main.py
(For debug it is helpful to omit -w and add --debug=all)
doc reference: https://pyinstaller.readthedocs.io/en/stable/usage.html#options-group-what-to-bundle-where-to-search
To access the image in my python script main.py:
I used the following code to setup the tk window:
import tkinter as tk
from os import path
# main widget
self.root = tk.Tk(master)
self.root.title('Window Title Bar Name V1.0')
self.root.minsize(1234, 1234)
# get icon
try:
# Get the absolute path of the temp directory
path_to_icon = path.abspath(path.join(path.dirname(__file__), 'icon/GAUGE-ICON.ico'))
# set the icon of the tk window
self.root.iconbitmap(path_to_icon)
except:
pass
doc reference: https://pyinstaller.readthedocs.io/en/stable/runtime-information.html
In addition to accepted answer I want to add modified version:
from PIL import Image, ImageTk
from res import *
#res is a module with your base64 image string and in my case variable is icon_base64
from base64 import b64decode
pic_as_bytes=ImageTk.BytesIO(icon_base64)
my_image=Image.open(pic_as_bytes)
Please note this code will work in Python3, but could not in Python2 cause it needs differrent aproach and more conversions (the link below has exactly an example for Python2).
For more info please refer to https://github.com/Ariel-MN/Tkinter_base64_Images/blob/master/tkinter_base64_image-ico.py
I tried the following methods. The 2nd option worked for me.
Use the command you mentioned to generate exe and copy the exe file into the source folder of your project (Where the python file is).
I fix it using auto-py-to-exe which uses pyinstaller. There you
can see the command generated.
pyinstaller --noconfirm --onedir --windowed --add-data "path/assets/" "path/gui.py"
First, try using --onefile instead of --onedir. Mine didn't work
when I used --onefile. So I had to covert --onedir.

py2exe not recognizing jsonschema

I've been attempting to build a Windows executable with py2exe for a Python program that uses the jsonschema package, but every time I try to run the executable it fails with the following error:
File "jsonschema\__init__.pyc", line 18, in <module>
File "jsonschema\validators.pyc", line 163, in <module>
File "jsonschema\_utils.pyc", line 57, in load_schema
File "pkgutil.pyc", line 591, in get_data
IOError: [Errno 0] Error: 'jsonschema\\schemas\\draft3.json'
I've tried adding json and jsonschema to the package options for py2exe in setup.py and I also tried manually copying the jsonschema directory from its location in Python27\Libs\site-packages into library.zip, but neither of those work. I also attempted to use the solution found here (http://crazedmonkey.com/blog/python/pkg_resources-with-py2exe.html) that suggests extending py2exe to be able to copy files into the zip file, but that did not seem to work either.
I'm assuming this happens because py2exe only includes Python files in the library.zip, but I was wondering if there is any way for this to work without having to convert draft3.json and draft4.json into .py files in their original location.
Thank you in advance
Well after some more googling (I hate ugly) I got it working without patching the build_exe.py file. The key to the whole thing was the recipe at http://crazedmonkey.com/blog/python/pkg_resources-with-py2exe.html. My collector class looks like this:
from py2exe.build_exe import py2exe as build_exe
class JsonSchemaCollector(build_exe):
"""
This class Adds jsonschema files draft3.json and draft4.json to
the list of compiled files so it will be included in the zipfile.
"""
def copy_extensions(self, extensions):
build_exe.copy_extensions(self, extensions)
# Define the data path where the files reside.
data_path = os.path.join(jsonschema.__path__[0], 'schemas')
# Create the subdir where the json files are collected.
media = os.path.join('jsonschema', 'schemas')
full = os.path.join(self.collect_dir, media)
self.mkpath(full)
# Copy the json files to the collection dir. Also add the copied file
# to the list of compiled files so it will be included in the zipfile.
for name in os.listdir(data_path):
file_name = os.path.join(data_path, name)
self.copy_file(file_name, os.path.join(full, name))
self.compiled_files.append(os.path.join(media, name))
What's left is to add it to the core setup like this:
options = {"bundle_files": 1, # Bundle ALL files inside the EXE
"compressed": 2, # compress the library archive
"optimize": 2, # like python -OO
"packages": packages, # Packages needed by lxml.
"excludes": excludes, # COM stuff we don't want
"dll_excludes": skip} # Exclude unused DLLs
distutils.core.setup(
cmdclass={"py2exe": JsonSchemaCollector},
options={"py2exe": options},
zipfile=None,
console=[prog])
Some of the code is omitted since it's not relevant in this context but I think you get the drift.

Python's docx module giving AsserionError when .exe is created

I've a Python file titled my_python_file.py that makes, among other things, a .doc file using the python-docx module. The .doc is created perfectly and gives no problem. The problem comes when I build a .exe of my script and I try to make the .doc. An AssertionError problem appears.
This is my exe maker code (exe_maker.py):
from distutils.core import setup
import py2exe, sys, os
sys.argv.append('py2exe')
setup(
options = {'py2exe': {'bundle_files': 3, 'compressed': True, 'includes': ['lxml.etree', 'lxml._elementpath', 'gzip', 'docx']}},
windows = [{'script': "my_python_file.py"}],
zipfile = None,
)
It seems that moving the python script to a different location produces the error.
File "docx.pyc", line 1063, in savedocx
AssertionError
This is the savedocx line:
document = newdocument()
[...]
coreprops = coreproperties(title=title, subject=subject, creator=creator, keywords=keywords)
approps = appproperties()
contenttypes2 = contenttypes()
websettings2 = websettings()
wordrelationships2 = wordrelationships(relationships)
path_save = "C:\output"
savedocx(document, coreprops, approps, contenttypes2, websettings2, wordrelationships2, path_save)
The savedox is well writen as it works when it's not an .exe file.
How can I make the docx module work correctly? Do I've to add any other path/variable more when I make the exe?
Thanks in advance
I solved the problem by edditing the api.py file of docx egg folder which is located in the Python folder of the system.
Changing this:
_thisdir = os.path.split(__file__)[0]
_default_docx_path = os.path.join(_thisdir, 'templates', 'default.docx')
To this:
thisdir = os.getcwd()
_default_docx_path = os.path.join(thisdir, 'templates', 'default.docx')
The first one was taking the actual running program and adding it to the path to locate the templates folder.
C:\myfiles\myprogram.exe\templates\default.docx
The solution takes only the path, not the running program.
C:\myfiles\templates\default.docx
Instead of changing some library file, I find it easier and cleaner to tell python-docx explicitly where to look for the template, i.e.:
document = Document('whatever/path/you/choose/to/some.docx')
This effectively solves the py2exe and docx path problem.

Categories

Resources