Enabling Easy Zipapp Installs on Windows
How to prepare a Windows system for a good PYZ experience.
Zipapps in a Nutshell
Zipapps are a way to distribute Python applications and all of their dependencies in a single binary file. This is comparable to statically linked golang apps or Java's ‘executable JARs’. Their main advantage is that distributing and installing them is quite simple.
Running Python code directly from ZIP archives is nothing new, PEP 273 made its debut in 2001, as part of Python 2.3 in the form of the zipimport
module.
PEP 441 builds on this and describes mechanisms to bundle full applications into a single ZIP file that can be made executable. It was approved in 2015 and a first implementation appeared in Python 3.5 via the zipapp
module.
See the PEP for details on how making a ZIP into an executable file works, but basically on POSIX systems the Python interpreter is called in a ‘bang path’ that is followed by the ZIP archive. The interpreter recognizes the ‘script’ is a whole application archive and acts accordingly. On Windows, zipapps MUST carry the .pyz
extension which is bound to the py
wrapper command, which in turn looks at the bang path and calls a matching Python interpreter from the installed set.
To display the bang path of a zipapp, use this command:
python3 -m zipapp --info foo.pyz
If you want to change the requested Python version to one that is actually installed or that you prefer, change the bang path as part of the installation process:
python3 -m zipapp -p '/usr/bin/env python3.8' -o ~/bin/foo foo.pyz
This can also be done on an ad-hoc basis, by explicitly calling the desired interpreter:
python3.8 foo.pyz … # POSIX
py -3.8 foo.pyz … # Windows
Well-known tools to build new zipapps, outside of the Python core, are pex (Twitter) and shiv (LinkedIn). See their documentation for details on bundling your own applications.
Setting Up Windows 10 for Zipapps
On Windows, because there is no ‘+x’ flag, things are a bit more complicated than on POSIX.
Zipapps MUST have a .pyz
extension,
for which the py
launcher is registered as the default application.
The net effect is that such files become executable and are handed over to the launcher
if you add a few environment settings to your machine.
In the user-specific environment settings, add a new PATHEXT
variable
(or extend an existing one), with the value %PATHEXT%;.PYZ
.
Also edit the PATH
one and add a new %LOCALAPPDATA%\bin
entry.
Save everything (click “OK”), open a new command window, and verify
the changes with
echo %PATHEXT% & echo %PATH%
Create the new bin directory by calling md %LOCALAPPDATA%\bin
.
Now you can place a zipapp file like foo.pyz
in that directory,
and it is immediately callable as foo
.
To get such a test subject, you can build shiv with itself:
git clone https://github.com/linkedin/shiv.git
cd shiv
py -3 -m venv --prompt shiv venv
venv\Scripts\activate.bat
python -m pip install -e .
shiv -e shiv.cli:main -o %LOCALAPPDATA%\bin\shiv.pyz .
deactivate
shiv --version
Variations
If that makes more sense to you, you can change the system-wide
variables instead of the user-specific ones, and choose paths that are
global for all users (like C:\usr\bin
or similar).
To make zipapps available network-wide, you can use %APPDATA%
to store the zipapps,
so you only have to maintain them once in case you regularly
work on several machines in the same network. Just make sure the same version of Python is used everywhere then.