If any of your Python scripts uses external files (JSON, text or any configuration files) and you wish to include those files in the executable, follow these steps on a Windows system.
Considering that there's a Python script app.py
and it reads a JSON file config.json
, our goal is to add the config.json
file in some suitable directory where it can be accessed by the application while it is running (as an .exe
).
This answer is applicable even if app.py
does not read config.json
directly. config.json
may be read by any of the modules used by app.py
and the answer would still help.
app.py
config.json
Step 1: Resolving the path of the JSON file while the application is running
When the application is running, files are copied to a temporary location on your Windows system C:\Users\<You>\AppData\Local\Temp\MEIxxx
. So, app.py
needs to read the JSON file from this temporary location. But how would the app know at runtime in which directory it has to search for the files? From this excellent answer, in app.py
, we'll have a function,
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
# While reading the JSON file
with open(resolve_path("config.json"), "r") as jsonfile:
# Access the contents here
Now app.py
knows where to look for the file. Before that, we need to instruct PyInstaller to copy config.json
into that temporary files directory.
Note, you need to use the resolve_path
wherever you are using a relative path in your Python Scripts.
Step 2: Make and edit the .spec
file to copy config.json
As we wish to create an executable of app.py
, we'll first create a .spec
file for it, (referring to @Stefano Giraldi's answer)
pyi-makespec --onefile --windowed --name appexe app.py
Open the resulting appexe.spec
file, and you'll notice these contents,
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(
...
datas=[],
...
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
...
)
Create a new list files
and pass it to the datas
argument,
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
files = [
( 'config.json' , '.' )
]
a = Analysis(
...
datas=files,
...
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
...
)
The tuple ('config.json', '.')
denotes the source and destination paths of the file. The destination path is relative to the temporary file directory.
Step 3: Build the executable
Finally, we can run the .spec
file to build the installer,
pyinstaller --clean appexe.spec
The resulting installer should now run without any FileNotFoundError
s.