Learn how to package a Python app in this tutorial.
For the most part, once you’ve written your Python code, you simply deploy it to a server, install the environment, grab the dependencies and you’re done.
However, there are times when you may want to provide your app to someone else and don’t want the hassle of getting them setup with all the training around making sure they have Python on their machine and can run your app.
Perhaps it’s even because you don’t want the other party to have your precious source code. Python is an interpreted language, making this mostly unavoidable.
What if there were another way? … enter Nuitka!
What is Nuitka?
Nuitka can be understood as being a compiler for your python code. No, it technically isn’t a compiler. What it really does is convert your code to C and then compile that down to a binary for distribution.
Show me an example!
If you’re saying “This all sounds too good, don’t tell me.. Show Me!”, then get ready, because I plan to do just that!
Installing Nuitka to Package a Python App
As with most things Python, it’s quick to get straight to the point.
Head over to PyPi and search for Nuitka to make sure we have the latest version.
<a href="https://pypi.org/project/Nuitka/" target="_blank" rel="noreferrer noopener nofollow">https://pypi.org/project/Nuitka/</a>
N.B. Before performing the next step, make sure to setup a Python Virtual Environment so that all packages will be installed locally to this tutorial.
Continuing; This gives us an easy way to get going, pip install Nuitka
.
mkdir -p ~/src/tutorials/nuitka_testing
cd $_
virtualenv -p python3 venv
. venv/bin/activate
Now run the pip install Nuitka
:
$ pip install nuitka
Collecting nuitka
Downloading Nuitka-0.6.7.tar.gz (2.3 MB)
|████████████████████████████████| 2.3 MB 1.6 MB/s
Building wheels for collected packages: nuitka
Building wheel for nuitka (setup.py) ... done
Created wheel for nuitka: filename=Nuitka-0.6.7-py3-none-any.whl size=2117847 sha256=5ce6d2ef97e7fd72aa8980c8ba7d6cfdecaf6f7b8971fd397241070d8a0f6e2e
Stored in directory: /Users/ao/Library/Caches/pip/wheels/60/7f/ef/8c1ef8cf2b509e25ead8f221725a8f95db6d7af0fc67565fde
Successfully built nuitka
Installing collected packages: nuitka
Successfully installed nuitka-0.6.7
If you got stuff for some reason, read more about downloading Nuitka from the project’s website directly.
Testing out Nuitka
Nuitka is a Python module that we run against a project or python script.
This means we need a nice little test script to try it out.
Create a file called test1.py
and enter the following code in it:
import string
from random import *
characters = string.ascii_letters + string.punctuation + string.digits
password = "".join(choice(characters) for x in range(randint(12, 16)))
print(password)
This will generate a unique strong password for us, between 12 and 16 characters.
If we run the script using python, we get output similar to this:
$ python test1.py
KdcM[btk8JvW
Excellent!
So now let’s add Nuitka into the mix. Run the following:
python -m nuitka test1.py
This will take a moment and will not render any output to the screen.
If we execute a ls -lashp
then we will see what has been created:
$ ls -lashp
total 496
0 drwxr-xr-x 6 ao staff 192B ... ./
0 drwxr-xr-x 4 ao staff 128B ... ../
488 -rwxr-xr-x 1 ao staff 243K ... test1.bin
0 drwxr-xr-x 18 ao staff 576B ... test1.build/
8 -rw-r--r-- 1 ao staff 195B ... test1.py
0 drwxr-xr-x 6 ao staff 192B ... venv/
We can now execute ./test1.bin
directly and see the application run.
$ ./test1.bin
7'4^5`YNux5Z
Additional CLI arguments
While the default arguments work pretty well, if we want to add debug symbols, or package our application as a standalone app, there are a ton of additional arguments we can pass in.
Issue a python -m nuitka --help
to see all the options.
$ python -m nuitka --help
Usage: __main__.py [--module] [--run] [options] main_module.py
Options:
--version
-h, --help
--module
--standalone
--python-debug
--python-flag=PYTHON_FLAGS
--python-for-scons=PYTHON_SCONS
--warn-implicit-exceptions
--warn-unusual-code
--assume-yes-for-downloads
Control the inclusion of modules and packages:
--include-package=PACKAGE
--include-module=MODULE
--include-plugin-directory=MODULE/PACKAGE
--include-plugin-files=PATTERN
Control the recursion into imported modules:
--follow-stdlib, --recurse-stdlib
--nofollow-imports, --recurse-none
--follow-imports, --recurse-all
--follow-import-to=MODULE/PACKAGE, --recurse-to=MODULE/PACKAGE
--nofollow-import-to=MODULE/PACKAGE, --recurse-not-to=MODULE/PACKAGE
Immediate execution after compilation:
--run
--debugger, --gdb
--execute-with-pythonpath
Dump options for internal tree:
--xml
Code generation choices:
--full-compat
--file-reference-choice=FILE_REFERENCE_MODE
Output choices:
-o FILENAME
--output-dir=DIRECTORY
--remove-output
--no-pyi-file
Debug features:
--debug
--unstripped
--profile
--graph
--trace-execution
--recompile-c-only
--generate-c-only
--experimental=EXPERIMENTAL
Backend C compiler choice:
--clang
--mingw64
--msvc=MSVC
-j N, --jobs=N
--lto
Tracing features:
--show-scons
--show-progress
--show-memory
--show-modules
--verbose
Windows specific controls:
--windows-dependency-tool=DEPENDENCY_TOOL
--windows-disable-console
--windows-icon=ICON_PATH
Plugin control:
--plugin-enable=PLUGINS_ENABLED, --enable-plugin=PLUGINS_ENABLED
--plugin-disable=PLUGINS_DISABLED, --disable-plugin=PLUGINS_DISABLED
--plugin-no-detection
--plugin-list
--user-plugin=USER_PLUGINS
First let’s remove all the old stuff so that we can see what happens when a standalone
build occurs.
$ rm -rf test1.bin test1.build
$ ls -lashp
total 8
0 drwxr-xr-x 4 ao staff 128B ... ./
0 drwxr-xr-x 4 ao staff 128B ... ../
8 -rw-r--r-- 1 ao staff 195B ... test1.py
0 drwxr-xr-x 6 ao staff 192B ... venv/
How to Build a standalone Python App
python -m nuitka --standalone test1.py
This takes a moment or two, but when it’s done we see our distribution created.
$ ls -lashp
total 8
0 drwxr-xr-x 6 ao staff 192B ... ./
0 drwxr-xr-x 4 ao staff 128B ... ../
0 drwxr-xr-x 20 ao staff 640B ... test1.build/
0 drwxr-xr-x 65 ao staff 2.0K ... test1.dist/
8 -rw-r--r-- 1 ao staff 195B ... test1.py
0 drwxr-xr-x 6 ao staff 192B ... venv/
Let’s examine the build in more depth:
$ tree -L 2
.
├── test1.build
│ ├── @sources.tmp
│ ├── __constants.bin
│ ├── __constants.c
│ ├── __constants.o
│ ├── __constants_data.c
│ ├── __constants_data.o
│ ├── __frozen.c
│ ├── __frozen.o
│ ├── __helpers.c
│ ├── __helpers.h
│ ├── __helpers.o
│ ├── build_definitions.h
│ ├── module.__main__.c
│ ├── module.__main__.o
│ ├── scons-report.txt
│ └── static_src
├── test1.dist
│ ├── Python
│ ├── _asyncio.so
│ ├── _bisect.so
│ ├── _blake2.so
│ ├── _bz2.so
│ ├── _codecs_cn.so
│ ├── _codecs_hk.so
│ ├── _codecs_iso2022.so
│ ├── _codecs_jp.so
│ ├── _codecs_kr.so
│ ├── _codecs_tw.so
│ ├── _contextvars.so
│ ├── _crypt.so
│ ├── _csv.so
│ ├── _ctypes.so
│ ├── _curses.so
│ ├── _curses_panel.so
│ ├── _datetime.so
│ ├── _dbm.so
│ ├── _decimal.so
│ ├── _elementtree.so
│ ├── _gdbm.so
│ ├── _hashlib.so
│ ├── _heapq.so
│ ├── _json.so
│ ├── _lsprof.so
│ ├── _lzma.so
│ ├── _multibytecodec.so
│ ├── _multiprocessing.so
│ ├── _opcode.so
│ ├── _pickle.so
│ ├── _posixsubprocess.so
│ ├── _queue.so
│ ├── _random.so
│ ├── _scproxy.so
│ ├── _sha3.so
│ ├── _socket.so
│ ├── _sqlite3.so
│ ├── _ssl.so
│ ├── _struct.so
│ ├── _tkinter.so
│ ├── _uuid.so
│ ├── array.so
│ ├── audioop.so
│ ├── binascii.so
│ ├── fcntl.so
│ ├── grp.so
│ ├── libcrypto.1.1.dylib
│ ├── libgdbm.6.dylib
│ ├── liblzma.5.dylib
│ ├── libreadline.8.dylib
│ ├── libsqlite3.0.dylib
│ ├── libssl.1.1.dylib
│ ├── math.so
│ ├── mmap.so
│ ├── pyexpat.so
│ ├── readline.so
│ ├── select.so
│ ├── site
│ ├── termios.so
│ ├── test1
│ ├── unicodedata.so
│ └── zlib.so
├── test1.py
└── venv
├── bin
├── include
└── lib
8 directories, 78 files
From the above output, we see the build
directory contains C language code, while the dist
directory contains a self executable test1
application.
Closing remarks
I really like the idea of Nuitka
and the potential it brings to the table.
Being able to compile Python code would be a fantastic edge to the Python community. Albeit if only ever used to package a Python app and to distribute it.
Tell me what you think.