How To Set Up Relative Paths To Make A Portable .exe Build In Pyinstaller With Python 3?
Solution 1:
Using --onefile
bundles all the datas together into the .exe
file.
When you execute the file, these files are "unpacked" to a temporary file location. On Windows, this is usually C:\Users\<You>\AppData\Local\Temp\MEIxxx
.
So, when you are developing your script, the data files (your text files in this example) will be located at
C:\\Users\\%username%\\PycharmProjects\\%project_folder%\\%project_folder%\txt_files\
but when the app is compiled, they will be extracted to the temporary directory mentioned above. So you need a way to tell the script whether you are developing, or it has been compiled. This is where you can use the 'frozen' flag (see the docs here)
An approach I have used before, is to create a utility function like this
def resolve_path(path):
if getattr(sys, "frozen", False):
# If the 'frozen' flag is set, we are in bundled-app mode!
resolved_path = os.path.abspath(os.path.join(sys._MEIPASS, path))
else:
# Normal development mode. Use os.getcwd() or __file__ as appropriate in your case...
resolved_path = os.path.abspath(os.path.join(os.getcwd(), path))
return resolved_path
Then whenever you want to use a path in your script, for example accessing your text files you can do
withopen(resolve_path("txt_files/file1.txt"), "r") as txt:
...
which should resolve the correct path whichever mode you are in.
A note on your .spec file
- You don't have to specify all the text files individually. You can of course, and you may have a good reason for doing so which is fine. But you could do
datas=[('txt_files', '.')]
which puts the contents of txt_files
directory in the root of your bundle. Be careful with this however, because now the paths to your text files will be <dev directory>\txt_files\file1.txt
but in the bundled app, they will be <MEIPASS directory>\file1.txt
. You may want to keep the 'relative' part of the path the same by doing
datas=[('txt_files', 'txt_files')]
which will mirror the file structure between your development folder and your bundled app.
- Also consider if you build with the spec file, remove the
COLLECT
part in order to produce a onefile bundled executable.
Solution 2:
I have the following FS
- GitRepoForMySomeProject
- MySomeProject
- main.py
- MyGitSubmodule
- ExeSettings.spec
- ExeVersion.py
- ExeMySomeProject.bat ("pyinstaller ExeSettings.spec")
- MySomeProject
In main.py are using some submodule which placed into subfolder "MyGitSubmodule/ReportGenerator.py"
from MyGitSubmodule import ReportGenerator as _report_
Here is my ExeSettings.spec file, with relative path pathex=['../MySomeProject']
# -*- mode: python -*-
block_cipher = None
a = Analysis(['MySomeProject/main.py'],
pathex=['../MySomeProject'],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
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='MySomeProject',
debug=False,
strip=False,
upx=True,
console=False,
icon='my.ico',
version='ExeVersion.py')
if forget to add this path, we will have
ModuleNotFoundError: No module named "MyGitSubmodule"
Post a Comment for "How To Set Up Relative Paths To Make A Portable .exe Build In Pyinstaller With Python 3?"