Question :
settings.txt is stored and accessed within the compiled single file app, but it’s not being written to. This works prior to Pyinstaller compilation when the file is in the same directory as the script.
The app is compiled from the terminal:
pyinstaller script.spec script.py --windowed --onefile
a.datas
is set in the spec file as:
a.datas += [(‘settings.txt’,’/path/to/settings.txt’, "DATA”)]
and the file is read properly within the app:
with open(resource_path('settings.txt'), 'r') as f2
However, the file isn’t updated when attempting to overwrite the file:
def OnExit(self, event):
with open(resource_path('settings.txt'), 'w') as f2:
f2.write('update')
self.Destroy()
resource_path
is defined as:
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.environ.get("_MEIPASS2", os.path.abspath("."))
return os.path.join(base_path, relative_path)
Answer #1:
If you are on Windows, _MEIPASS
returns the “short” name for the path in case that any component of it is more than 8 characters long. So, to test that this is the issue, try to make it a one-folder
frozen app and then move it in a simple and short path: e.g., C:/test
.
If this is the issue, you can workaround the problem by retrieving the long path using something like:
if hasattr(sys, '_MEIPASS'):
import win32api
sys_meipass = win32api.GetLongPathName(sys._MEIPASS)
Answer #2:
I wanted to share my solution, which simultaneously addresses many issues with relative paths in general (see function __doc__string).
I have a module named top_level_locator.py
, with a modified function module_path
as seen in other answers which takes a relative_path
.
usage in other .py
files:
from top_level_locator import module_path
resource_location = module_path(relative_path = 'resource.ext')
import sys
from pathlib import Path
from inspect import getsourcefile
def module_path(relative_path):
"""
Combine top level path location, in this project app.py folder because it serves as main/entry_point, with user relative path.
NOTE: top_level_locator.py should be in same folder as entry_point.py(/main.py) script
- TEST this with executable
- TEST this without executable
NOTE: care with use of __file__ as it comes with unwarranted side effects when:
- running from IDLE (Python shell), no __file__ attribute
- freezers, e.g. py2exe & pyinstaller do not have __file__ attribute!
NOTE: care with use of sys.argv[0]
- unexpected result when you want current module path and get path where script/executable was run from!
NOTE: care with use of sys.executable
- if non-frozen application/module/script: python/path/python.exe
- else : standalone_application_executable_name.exe
"""
# 0 if this module next to your_entry_point.py (main.py) else += 1 for every directory deeper
n_deep = 1
print('sys.executable:', sys.executable)
print(' sys.argv[0]:', Path(sys.argv[0]).parents[n_deep].absolute() / sys.argv[0])
print(' __file__:', __file__)
print(' getsourcefile:', Path(getsourcefile(lambda:0)).parents[n_deep].absolute())
if hasattr(sys, "frozen"):
# retreive possible longpath if needed from _MEIPASS: import win32api;
# sys_meipass = win32api.GetLongPathName(sys._MEIPASS)
base_path = getattr(sys, '_MEIPASS', Path(sys.executable).parent)
print(' _MEIPASS:', base_path)
return Path(base_path).joinpath(relative_path)
return Path(getsourcefile(lambda:0)).parents[n_deep].absolute().joinpath(relative_path)
if __name__ == '__main__':
module_path()
In non-frozen applications the output will (should) be as such:
sys.executable: C:Users<usr_name>AppDataLocalProgramsPythonPython37python.exe
sys.argv[0]: c:Users<usr_name>Desktop<project_name><project_code_folder>app.py
__file__: c:Users<usr_name>Desktop<project_name><project_code_folder>utilstop_level_locator.py
getsourcefile: c:Users<usr_name>Desktop<project_name><project_code_folder>
In frozen applications:
sys.executable: C:Users<usr_name>Desktop<project_name>distapp.exe
sys.argv[0]: C:Users<usr_name>Desktop<project_name>distapp.exe
__file__: C:Users<usr_name>AppDataLocalTemp_MEI155562utilstop_level_locator.pyc
getsourcefile: C:Users<usr_name>Desktop<project_name>
_MEIPASS: C:Users<usr_name>AppDataLocalTemp_MEI155562