Skip to main content

Use uv to build a pip within a standalone Python installation

Project description

py-app-standalone

py-app-standalone builds a standalone, relocatable Python installation with the given packages installed. It's like a modern alternative to PyInstaller that leverages the newer uv ecosystem.

It's a wrapper around uv that creates a standalone Python installation, runs uv pip install, and then makes the required platform-specific changes so you have a fully self-contained install directory (packagable and runnable from any directory on a machine of the same platform).

Background

Typically, Python installations are not relocatable or transferable between machines, even if they are on the same platform, because scripts and libraries contain absolute file paths (i.e., many scripts or libs include absolute paths that reference your home folder or system paths on your machine).

Now Gregory Szorc and Astral solved a lot of the challenge with standalone Python distributions. uv also supports relocatable venvs, so it's possible to move a venv. But at least currently, the actual Python installations created by uv can still have absolute paths inside them in the dynamic libraries or scripts, as discussed in this issue.

This tool is my quick attempt at fixing this.

It creates a fully self-contained installation of Python plus any desired packages. The idea is this pre-built binary build for a given platform can now packaged for use without any external dependencies, not even Python or uv. And the the directory is relocatable.

This should work for any platform. You just need to build on the same platform you want to run on.

Warning: Experimental! This is a new tool. I've used it on macOS and it's very lightly tested on Ubuntu and Windows, but obviously there are many possibilities for subtle incompatibilities within a given platform.

Alternatives

PyInstaller is the classic solution for this and has a lot of features beyond this little tool, but is far more complex and does not build on uv.

shiv and pex are mature options that focus on zipping up your app, but not the Python installation.

PyApp is a more recent effort on top of uv that creates a Rust-built standalone binary that downloads/installs Python and dependencies at runtime.

Usage

Requires uv to run. Do a uv self update to make sure you have a recent uv (I'm currently testing on v0.6.14).

As an example, to create a full standalone Python 3.13 environment with the cowsay package:

uvx py-app-standalone cowsay

Now the ./py-standalone directory will work without being tied to a specific machine, your home folder, or any other system-specific paths.

Binaries can now be put wherever and run:

$ uvx py-app-standalone cowsay

▶ uv python install --managed-python --install-dir /Users/levy/wrk/github/py-app-standalone/py-standalone 3.13
Installed Python 3.13.3 in 2.35s
 + cpython-3.13.3-macos-aarch64-none

⏱ Call to run took 2.37s

▶ uv venv --relocatable --python py-standalone/cpython-3.13.3-macos-aarch64-none py-standalone/bare-venv
Using CPython 3.13.3 interpreter at: py-standalone/cpython-3.13.3-macos-aarch64-none/bin/python3
Creating virtual environment at: py-standalone/bare-venv
Activate with: source py-standalone/bare-venv/bin/activate

⏱ Call to run took 590ms
Created relocatable venv config at: py-standalone/cpython-3.13.3-macos-aarch64-none/pyvenv.cfg

▶ uv pip install cowsay --python py-standalone/cpython-3.13.3-macos-aarch64-none --break-system-packages
Using Python 3.13.3 environment at: py-standalone/cpython-3.13.3-macos-aarch64-none
Resolved 1 package in 0.82ms
Installed 1 package in 2ms
 + cowsay==6.1

⏱ Call to run took 11.67ms
Found macos dylib, will update its id to remove any absolute paths: py-standalone/cpython-3.13.3-macos-aarch64-none/lib/libpython3.13.dylib

▶ install_name_tool -id @executable_path/../lib/libpython3.13.dylib py-standalone/cpython-3.13.3-macos-aarch64-none/lib/libpython3.13.dylib

⏱ Call to run took 34.11ms

Inserting relocatable shebangs on scripts in:
    py-standalone/cpython-3.13.3-macos-aarch64-none/bin/*
Replaced shebang in: py-standalone/cpython-3.13.3-macos-aarch64-none/bin/cowsay
...
Replaced shebang in: py-standalone/cpython-3.13.3-macos-aarch64-none/bin/pydoc3

Replacing all absolute paths in:
    py-standalone/cpython-3.13.3-macos-aarch64-none/bin/* py-standalone/cpython-3.13.3-macos-aarch64-none/lib/**/*.py:
    `/Users/levy/wrk/github/py-app-standalone/py-standalone` -> `py-standalone`
Replaced 27 occurrences in: py-standalone/cpython-3.13.3-macos-aarch64-none/lib/python3.13/_sysconfigdata__darwin_darwin.py
Replaced 27 total occurrences in 1 files total
Compiling all python files in: py-standalone...

Sanity checking if any absolute paths remain...
Great! No absolute paths found in the installed files.

✔ Success: Created standalone Python environment for packages ['cowsay'] at: py-standalone

$ ./py-standalone/cpython-3.13.3-macos-aarch64-none/bin/cowsay -t 'im moobile'
  __________
| im moobile |
  ==========
          \
           \
             ^__^
             (oo)\_______
             (__)\       )\/\
                 ||----w |
                 ||     ||

$ # Now let's confirm it runs in a different location!
$ mv ./py-standalone /tmp

$ /tmp/py-standalone/cpython-3.13.3-macos-aarch64-none/bin/cowsay -t 'udderly moobile'
  _______________
| udderly moobile |
  ===============
               \
                \
                  ^__^
                  (oo)\_______
                  (__)\       )\/\
                      ||----w |
                      ||     ||

$

How it Works

It uses a true (not venv) Python installation with the given packages installed, with zero absolute paths encoded in any of the Python scripts or libraries.

After setting this up we:

  • Ensure all scripts in bin/ have relocatable shebangs (normally they are absolute)

  • Clean up a few places source directories are baked into paths

  • Do slightly different things on macOS, Linux, and Windows to make the binary libs are relocatable.

With those changes, it seems to work. So in theory, the resulting binary folder should be installable as at any location on a machine with compatible architecture.

More Notes

  • The good thing is this does work to encapsulate binary builds and libraries, as long as the binaries are included in the pip. It doesn't the problem of external dependencies that traditionally need to be installed outside the Python ecosystem (like ffmpeg). (For this, pixi seems promising.)

  • This by default pre-compiles all files to create __pycache__ .pyc files. This means the build should start faster and could run on a read-only filesystem. Use --source-only to have a source-only build.

  • For now, we assume you are packaging a pip already on PyPI but of course the same approach could work for unpublished code.


This project was built from simple-modern-uv.

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

py_app_standalone-0.2.0.tar.gz (23.7 kB view details)

Uploaded Source

Built Distribution

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

py_app_standalone-0.2.0-py3-none-any.whl (12.3 kB view details)

Uploaded Python 3

File details

Details for the file py_app_standalone-0.2.0.tar.gz.

File metadata

  • Download URL: py_app_standalone-0.2.0.tar.gz
  • Upload date:
  • Size: 23.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for py_app_standalone-0.2.0.tar.gz
Algorithm Hash digest
SHA256 41bdf7c1a5197c8ca2c8d5f6955f853954c1228f758392953ce5f13d0462a1da
MD5 e7be6a63b1c9ee9e15c88260daae26c4
BLAKE2b-256 1c92ad64ea6147cd887ea9403be88d70b90da7ffd04350a98a6dc0bdccccaf44

See more details on using hashes here.

Provenance

The following attestation bundles were made for py_app_standalone-0.2.0.tar.gz:

Publisher: publish.yml on jlevy/py-app-standalone

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file py_app_standalone-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for py_app_standalone-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 752741552be9e0657d11898742b861908e05025c930ec12eb2a78ce7db7f6128
MD5 1db20abceda9619631947f582ab0b11d
BLAKE2b-256 dd16fe9c175e48bb919b95d7aa0b0da925284acf225e5ba929c8587282eb201b

See more details on using hashes here.

Provenance

The following attestation bundles were made for py_app_standalone-0.2.0-py3-none-any.whl:

Publisher: publish.yml on jlevy/py-app-standalone

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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