Recently I wrote about how
deploy a python script without virtualenv. This
method takes advantage of the idea of installing the script’s
dependencies in a local directory using
pip and wraps them and our
code with a simple
shell script. The result was good, but you still
needed to deploy a directory with your source code, another one with
the dependencies and the wrapper script with those paths fixed, which
will be the one to run. Good, but not ideal.
Right after publishing that post, a colleague showed me the zipapp module, recently added in Python 3.5, which allows you to easily create an executable zipfile from your source code. The feature of running python code from a zipfile was added long time ago (refer to this article to find out more about the specific PEPs), but I wasn’t aware of its existence. As soon as I read through the module’s documentation, I decided to try and mix both ideas, bundling in a zipfile both the source code of a script and its dependencies.
All the literature I found achieved this modifying the
the script, but my goal was to do it without explicitly touching the
dependencies, just playing with the packaging process. The only
requirement for the
zipapp module to work is to have a
file on your sources folder with a valid entrypoint, which is the one
that python will run when you execute the file.
As running Python code from a zipfile can be done since Python 2.6,
Python 3 (in fact, 3.5 or greater) is only required to be used in the
line that calls the
zipapp module. Through this article I’m going to
python3 in the code examples for consistency, but it is only
needed at that precise step.
So imagine we have a script with the following layout:
repo ├── myscript │ ├── __main__.py │ └── myscript.py └── requirements.txt
If the main function of the script is called
example, the contents of the
__main__.py would be:
from myscript import entrypoint entrypoint()
For the script to be able to find the dependencies, we are going to
put them right in the sources directory. This way, they will be in the
PYTHONPATH, and we will not need to touch the code’s
To keep the original source code clean, we will create a
temporal folder, that we can remove after creating the executable
file. In this folder we will copy our source code, and we will install
our dependencies locally:
mkdir dist cp -r myscript/* dist pip3 install -r requirements.txt --upgrade --system --target dist
At this point, we should be able to run our program directly using the
following line, without needing to touch the
Finally, to create the executable zipfile, just run the following:
python3 -m zipapp -p '/usr/bin/env python3' -o myscript.bin dist
-mflag tells Python to run the
-pflag specifies the shebang of the header of the generated executable zipfile. If your script uses Python 2 instead of Python 3, just modify that line.
-oflag specifies the name of the output file. The extension is totally optional. I prefer to use no extension at all.
- The last parameter is the directory of the code you want to bundle.
The output of this command should leave you with a
file, perfectly portable and whose only requirement is the python
interpreter that you specified in the
-p argument on its creation.
You can even open the file with any text editor to see how it is basically a text file with our shebang, source code files and dependencies, all concatenated.