Introduction
I am sure most of you would have used pyinstaller for generating executables. This article does not cover what a pyinstaller is. However, the focus of the article will be on how to add external data files to your pyinstaller binaries and their use cases.
Generate Pyinstaller binaries
Usually, we generate a one file executable in pyinstaller using the following command.
$pyinstaller --onefile --clean <file_name.py>
Adding data files to pyinstaller executables
Let’s say you have a help file for your application which needs to be packed along with your executable. Your application needs to read the file at runtime and display it to the user.
How do we do it? Though the file exists as part of our application during packaging, they don’t get packaged with the binary.Therefore, during execution, you may get the following error.
FileNotFoundError: [Errno 2] No such file or directory:C:\\Users\\Username\\AppData\\Local\\Temp\\_MEI41602\\help.md
[11200] Failed to execute script
Why does this happen?
This is because your pyinstaller executable looks for data files in a temp directory _MEIXXXX where XXXX is a random number. You need to ensure all your supporting data files are copied there.
Pyinstaller unpacks the data into a temporary folder and stores this directory path into the MEIPASS
variable. This attribute is set in the sys environment by the bootloader.
The MEIPASS variable will be available only when the program is running in bundled mode. This also helps us confirm if the program is running from a bundle or from the source code.
Getting back to adding data files
So where were we? Ohh yeah..We need to add data files such as help file or icon file or README files to pyinstaller.
That’s pretty simple.
Just use the add-data flag while building your binaries.The separator is different for windows and posix based systems.
Windows
$pyinstaller --onefile --clean --add-data "help.md;." <file_name.py>
Here we pass the file path to the add data flag with the path inside the bundle separated by a semi colon.
Linux/Mac
$pyinstaller --onefile --clean --add-data "help.md:." <file_name.py>
Here we pass the file path to the add data flag with the path inside the bundle separated by a colon.
Spec file
The data files can be added via spec files as well.
a = Analysis(...
datas=[ ('help.md', '.') ],
...
)
The datas
is a tuple
in which the first string says the existing file is help.md
. That file will be looked up relative to the location of the spec file and copied into the top level of the bundled app.In our case, help file is located in the current directory.
Now that we have added the help data file to your pyinstaller binaries, how do we read them and display to the user.
Well, we first need to ensure that the help file is readable from both source code and bundled mode. As mentioned earlier, we can confirm if our program is running from source code or pyinstaller.
def read_help_file():
# Check if MEIPASS attribute is available in sys else return current file path
bundle_dir = getattr(sys, '_MEIPASS', os.path.abspath(os.path.dirname(__file__)))
path_to_help = os.path.abspath(os.path.join(bundle_dir,'help.md'))
with open(path_to_help, "r+") as help_file:
print(help_file.read())
We use the getattr()
method to see if the sys
has MEIPASS attribute. If yes then the bundle_dir is set accordingly because the __file__
attribute is set to the bundle folder.
If not(when running from source code), it returns the path of the module it is placed.
The same logic can be extended to store, README , icon files, data files, text files etc.
Use Cases
You may add the following data files
- Icon files
- README text files
- Help files
- Markdown files
Summary
--add-data
flag can be used to add data files to pyinstaller binaries- The path separator while adding data files for windows is a “;” and for posix systems it is a “:”
- Any data file that is looked up from the source code can be added via
--add-data
flag