Reading time: 25 minutes | Coding time: 10 minutes
In this article we are going to explore, how we can use PyInstaller to create standalone executables from our python applications. To illustrate the use of the PyInstaller application, we are going to take help of a simple Python based Expense Tracker Application. This application is used to create and view a log of expense made by a person over time. To follow along with the article i suggest you to get the source code for the application from the link given below:
git clone https://github.com/Arna-Maity/expense_tracker_api.git
The root directory of the Expense Tracker Application is as shown below:
Now, it might be a bit overwhelming in the beginning to look at so many files at once, but the directory we are really interested in, is the src/. That directory contains our 2 main Python source files:
Before going to use PyInstaller to create a standalone executable, we need to download and install all the python package dependencies. We can do this by, first navigating to the project's root directory and using the following command:
pip install -r requirements.txt
requirements.txt contains a list of all the dependencies required by the project.
The two Python files function as follows:
- spent.py: This file acts as the backend of our program which creates and retrieves expense logs from the database.
- spent_driver.py: This file creates a simple Command Line Interface (CLI) and imports the spent.py file and uses its functions to perform expense logging and retrieval.
The above information is actually necessary for using PyInstaller as it needs the top level python file as its argument to create the application. As software applications grow larger, the functionality of the application is broken down into separate Python files, and a top level Python file integrates the functionality of each of the separate files together into a single application.
As we can see from the above diagram, the top level python file for our application is spent_driver.py. Now lets start generating the actual executable for our application. THe most straightforward way to trigger PyInstaller is by using the following command while you're in the project's root directory.
If PyInstaller was successfully able to generate the executable, we will see completed successfully as the last line of the output as shown below:
Once PyInstaller successfully finshes executation, we can find a folder named dist whose contents are as shown below:
While the above process did generate an executable (you can test it by running the spent_driver executable listed in the dist directory as shown above), it also generated a bunch of other *.so files which the final executable depends upon, for functioning properly.This means that, whenever we want to distribute our application, we need to share the entire dist folder. This would not only be more cumbersome and difficult to maintain, but also more confusing and difficult to use for newcomers.
To overcome the above drawback, we can use the
--onefile option along with PyInstaller, to package all the dependency files together with the final executable into a single file. This is shown below:
pyinstaller src/spent_driver.py --onefile
NOTE: PLEASE DELETE THE OUTPUT GENERATED ON PYINSTALLER'S PREVIOUS RUN TO AVOID ANY ERRORS WHILE CREATING THE NEW EXECUTABLE.
The dist directory would look as follows:
The last command basically packaged all the dependencies and the final executable into a single file. This makes the application much easier to execute.
Executing our application:
Suppose, let's say we also have some accompanying files which are not part of the actual program logic but contain data, which the program relies upon to provide us with some information. These may include database files (*.db extension) or binary object files (*.bin extension). We can also include these files with the final executable using the
--add-data option with PyInstaller.
While, our application does depend on a database file (spent.db : Present in the root directory of our project.), it's not absolutely necessary to include the file with the final executable, as it is capable of creating a *.db file whenever called with the
init option as shown above.
To include separate database and binary files, use the following command:
pyinstaller /src/spent_driver.py --onefile --add-data 'spent.db:.'
The above command just includes the database file spent.db along with the final executable file into a single executable file.
Now, the output generated when running PyInstaller may seem unnecessarily verbose (Prints a lot of info on screen), we may redirect the output of the PyInstaller application using the standard redirection operators in linux. The PyInstaller application prints out all its output on the stderr console. So, if we do not need the output, we can simply redirect the stderr to /dev/null. This would basically, discard the output of PyInstaller and keep the screen clean.
pyinstaller /src/spent_driver.py --onefile --add-data 'spent.db:.' 2> /dev/null
2> is the operator for redirecting the stderr console.
Although, the above command can be used to keep the screen clean, it is generally not considered to be a good idea to just discard the output. Instead, we can redirect the output to a separate file. This is considered to be useful because, the output generated by PyInstaller is often useful to resolve issues, when the application fails to create a standalone executable.
So, a better option would be to redirect the output to a separate file such as build.log:
pyinstaller /src/spent_driver.py --onefile --add-data 'spent.db:.' 2> build.log
This does the same thing as the previous command, but instead of discarding the output, it stores the output in a separate build.log file.
This is how one can effectively use the PyInstaller application to create and distribute standalone executable files for different Python applications.