Make a PEX from Python script

Python is a great language for scripting. But there is a problem with distributing working executable. If script uses any non built-in dependency, it can’t be just copied to the target host and executed.

One possible solution is using PEX - Python EXecutable. It packs the script with dependencies inside a single binary.

Install it with:

pip install pex

Example for copy-paste

This example will show how to pack script named tool, that has 1 dependency: click.

Script lives in tool.py:

import click


@click.command()
def main():
    """Example script."""
    click.echo('Hello World!')


if __name__ == '__main__':
    main()

First, PEX works with Python distributions. It means that there has to be setup.py. Good news, it’s relatively simple:

from setuptools import setup


setup(
    name='tool',
    version='0.0.1',
    py_modules=['tool'],
    install_requires=['click'],
    entry_points={
        'console_scripts': [
            'tool=tool:main',
        ]
    },
)

Let’s also have a Makefile so that build commands won’t be forgotten:

build: clean
    pex -o tool.pex . -e tool:main --validate-entry-point

deploy: build
    cp tool.pex /target/destination.pex

.PHONY: clean
clean:
    rm -rf *.egg-info build dist $${PEX_ROOT}/build/tool-*.whl

Notice clean command, that deletes PEX build cache, it’s extremely important, because PEX cache built wheels and skip updating if the version is the same.

Build pex binary with command

make

Now working directory looks like this:

-rw-rw-r--   1 deminp deminp  202 Oct 29 20:18 Makefile
-rw-rw-r--   1 deminp deminp  230 Oct 29 20:14 setup.py
-rwxrwxr-x   1 deminp deminp 558K Oct 29 20:18 tool.pex
-rw-rw-r--   1 deminp deminp  141 Oct 29 20:17 tool.py

Tools can be launched in 3 ways:

In virtual environment with click installed:

$ python tool.py
Hello World!

In virtual environment with distribution installed (i.e. pip install .)

$ tool
Hello World!

Without virtual environment using built PEX binary:

$ ./tool.pex
Hello World!