Skip to main content

A greedy Python standalone application bundler

Project description

shenzi

shenzi helps you create standalone Python applications from your development virtual environment. Using shenzi, you can create standalone folders which can be distributed to any machine, and the application will work (even when python is not installed on the target system).

The python packaging problem

Given a development environment (a virtual environment), we want to produce a single directory containing ALL the dependencies that the application needs. Other languages like rust and go provide easy way to create statically linked executables, which makes them very easy to distribute.
Python struggles in this area mainly because of how flexible it is when it comes to delegating work to C code (shared libraries on your system).

Out in the wild, python libraries regularly links to shared libraries in your system:

  • C Extensions
  • loading shared libraries using dlopen and equivalents

Even creating a development environment for some pip package might require you to install some system dependencies (a good example is weasyprint)
It becomes difficult to ship applications if we need to install system dependencies in target machines. Docker solves this problem by packaging everything in a single docker image.
shenzi does not compete with docker, if you can use docker, you should. shenzi is useful for shipping desktop applications.

Getting Started

First install shenzi in your virtual environment.

pip install shenzi

Initializing the workspace

If you have a project run using poetry, run

# only poetry package manager is supported
shenzi init

It will ask you some questions and generate shenzi_workspace.toml file. The TOML file looks like this.

# shenzi_workspace.toml
# all relative paths are relative to the directory containing this file

# you can add a list of binaries that your application calls
# something like calling aws cli. Shenzi would try to find all these in your path and add them to the distribution
binaries = ["tesseract"]

[packaging]
kind = "poetry"
config_file = "<relative-path-to-poetry.lock>"
# you can add the dependency groups you want in the distribution (dev, or other custom groups)
groups = ["main"]

[execution]
main = "<relative-path-to-main-python-script>"

Intercepting

You need to first configure shenzi to listen to all the imports that your python application makes. You can either do this by running your application in your development environment and testing it. Or running tests.

Running an application

In you main script, add the following lines

import os

if os.environ.get("SHENZI_INIT_DISCOVERY", "False") == "True":
    from shenzi.discovery import shenzi_init_discovery
    shenzi_init_discovery()

In pytest

If you are running tests in pytest, you can add this function in your root conftest.py

# root conftest.py


# this function is run by pytest in the beginning
def pytest_configure():
    from shenzi.discover import shenzi_init_discovery
    shenzi_init_discovery()

Run your application as you normally do/or run tests. shenzi will start intercepting all shared libraries that your code is importing.
You should run as much of your application code as possible, like running all the tests. This allows shenzi to detect every dependency linked to your application at runtime.

Once you stop the application, a file shenzi.json (called the manifest) will be dumped in the current directory. This file contains all the shared library loads that shenzi detected. It also contains some information about your virtual environment.
Now run the shenzi CLI with this manifest file

Building the application

From the directory containing shenzi_workspace.toml (your project's root directory), run this command:

RUST_LOG=INFO shenzi build ./shenzi.json

This can take a moment, after it is done, your application would be packaged in a dist folder.
You can ship this dist folder to any target machine and it should work out of the box. The only required dependency is bash.

Note: by default shenzi would try to validate if some warnings are actually errors. It needs to scan the whole file system to do that, it would print a log like this: shenzi will now validate if any of your warnings are errors, this can take time (it will scan your whole file system). You can skip this by passing --skip-warning-checks. If you feel its taking too long, you can skip it by passing --skip-warning-checks. You should however, at least have one successful build with all warnings validated.

Run dist/bootstrap.sh to run your application.

# bootstrap.sh is the entrypoint for your application
# you can run this from any directory generally
bash dist/bootstrap.sh

Note that if you don't specify main file in your shenzi_workspace.toml, shenzi would try to dynamically query that file, this can be annoying if you are running tests, so setting the file in workspace config is useful.

Next steps

You should at least read the doc which describes the structure of shenzi.json here.

If you use this, feel free to raise an issue on any problem, I need feedback for this :)

How is this different?

I will add a small comparison to PyInstaller, which I feel is the most mature tool in the ecosystem.
From what I've seen, PyInstaller statically analyses your python code (and does some imports too) to create the smallest possible packaged application. It is smarter than shenzi.

  • shenzi is much simpler. It tries to intercept all linker activity during runtime.
    • During packaging, shenzi will faithfully analyze all dependencies in the same order as done by the linker. Following the linker might solve a class of edge cases (not proved though, for all I know, this algorithm might end up performing very poorly)
  • It also packages everything in your python path (all data+code in your site-packages).
    • This makes shenzi faster in some cases (where you have complex applications, as we do not do any static analysis), but slower in others (mainly if your virtual environment is huge, and not all dependencies are used by your application normally)

Apart from that, there are some other internal differences that may or may not matter

  • The structure of the final application (described here). It's slightly similar to how pnpm organizes node_modules as far as I'm aware.
  • The bootstrap script in shenzi is pretty a simple bash script, it simply sets up the correct Python environment variables and starts the interpreter. PyInstaller has a very sophisticated bootstrapping CLI written in C

Supported Platforms

Currently only Mac and Linux are supported.
The project is very new right now, I've tested it on Ubuntu 20.04 and MacOS Sequoia with Python 3.9

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

shenzi-0.0.4.tar.gz (4.0 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

shenzi-0.0.4-py3-none-manylinux_2_28_x86_64.whl (4.6 MB view details)

Uploaded Python 3manylinux: glibc 2.28+ x86-64

shenzi-0.0.4-py3-none-macosx_10_9_universal2.whl (4.0 MB view details)

Uploaded Python 3macOS 10.9+ universal2 (ARM64, x86-64)

File details

Details for the file shenzi-0.0.4.tar.gz.

File metadata

  • Download URL: shenzi-0.0.4.tar.gz
  • Upload date:
  • Size: 4.0 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.5.4

File hashes

Hashes for shenzi-0.0.4.tar.gz
Algorithm Hash digest
SHA256 211222f6b32e5d13c77656b7011fcd3802f6aa90f8770b1afa146e9241d11839
MD5 ba6c038de6ddbf11d9d6bf2307e87b82
BLAKE2b-256 146f7e6859dc037e80196d1105b0de378888f36f0dff8e664cd9537115e72d39

See more details on using hashes here.

File details

Details for the file shenzi-0.0.4-py3-none-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for shenzi-0.0.4-py3-none-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 568bf16d6a3884126822ed2b86e13db58a1887e38b56d1ccac3ee7072239928b
MD5 535fb377ebcc7f2aff42ca30ab9860ae
BLAKE2b-256 c40cd8f923289be469b9dae54e8cdea39205f16871ad7aa1d2bd6043e06fe9f4

See more details on using hashes here.

File details

Details for the file shenzi-0.0.4-py3-none-macosx_10_9_universal2.whl.

File metadata

File hashes

Hashes for shenzi-0.0.4-py3-none-macosx_10_9_universal2.whl
Algorithm Hash digest
SHA256 eae69fcc81f1c61907ec9be6d6161bded9a5c15ccca95f4b4d5ca8a605781c98
MD5 f20809a90cb14e7ff3d7209cc70fbf9f
BLAKE2b-256 e968a58a68f3fdd9a2b1ae701def189ecfe369e9d8e98aa686c052526b38b9af

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page