Skip to main content

portablepy

Bundle a Python app into a ZIP or tar.gz with its dependencies and a launcher. Share the archive, extract it, and run it. The bundle sets up its own environment and installs dependencies offline.

Python itself isn't included. The person running the app needs a matching CPython version, operating system, and architecture.

Install

Requires CPython 3.14 or later. Install it in a virtual environment:

python -m pip install portablepy

Build an app

portablepy build ./my-app --run "python -m my_app"
portablepy build ./script.py --run "python script.py" --output app.zip
portablepy build ./my_app-1.0-py3-none-any.whl --run "my-console-command"

Build on the operating system and architecture your users have. portablepy uses the app's .venv when available, or its own Python interpreter otherwise. Use --python PATH to choose one.

Without --output, the filename includes the app name, platform, architecture, and Python version. Windows gets a ZIP; Linux and macOS get a tar.gz. Use --replace to replace an existing archive after the new build passes validation.

Run the bundle

Extract the whole archive somewhere writable, then double-click run.cmd on Windows or run.command on macOS. Linux bundles include run.sh; your file manager may need permission to run executable scripts.

You can also use a terminal:

python run.py
python run.py --help

Arguments go straight to your app. With --compile all, the launcher is run.pyc; use python run.pyc instead. The double-click launcher picks the right file automatically.

The first launch checks the bundled files and creates a private .venv using the included wheels. Later launches reuse it. Moving the bundle or updating it rebuilds the environment as needed. Python needs its standard venv and ensurepip modules.

If extraction removes executable permissions on macOS or Linux, run chmod +x run.command or chmod +x run.sh.

Dependencies and files

For packaged projects and wheels, portablepy reads the package's dependency metadata. For scripts, it follows local imports and looks up dependencies in the selected Python environment. Install your app's dependencies there before building.

A requirements.txt beside a script or in a loose app directory supplies explicit dependencies. You can also pass them yourself:

portablepy build ./my-app --run "python main.py" --requirements requirements.txt
portablepy build ./my-app --run "python -m my_app" --extra cli
portablepy build ./script.py --run "python script.py" --requirement "requests>=2"

Explicit requirements replace import-based dependency detection. Dynamic imports and plugins may need this. Use --no-index --find-links ./wheels to build from local packages only.

The bundle includes the selected app's local helpers and resources. Common cache, environment, build, and editor files are excluded, along with .env files. Add --exclude patterns to leave out other files.

To see what's included before building:

portablepy inspect ./my-app --run "python -m my_app"
portablepy inspect ./my-app --run "python -m my_app" --resolve

inspect reports files, dependencies, and estimated size as JSON. --resolve also downloads or builds the wheels for a fuller report. Building checks wheel integrity and tests installation in a clean offline environment; it doesn't start your app.

Keep writable data

Use --include SOURCE=data/DESTINATION for default settings, databases, or other files your app changes:

portablepy build ./my-app --run "python -m my_app --config {data}/settings.json" --include "settings.json=data/settings.json"

Files and directories both work. Defaults live in seeds/ and are copied into data/ only when missing. Extracting an update into the same bundle folder keeps existing data. When moving to a fresh folder, copy data/ over before launching.

Launch commands support {bundle}, {app}, {data}, {python}, and {bin} placeholders. The app runs from its application folder, which is named after a content hash. Use {app} instead of hardcoding that folder name.

Commands are argument lists, so use forward slashes for paths and quote arguments containing spaces. Shell pipelines and activation commands aren't supported.

Save build settings

Put defaults and optional profiles in pyproject.toml:

[tool.portablepy]
source = 'my-app'
run = 'python -m my_app --config {data}/settings.json'
include = ['defaults/settings.json=data/settings.json']

[tool.portablepy.profiles.release]
compile = 'all'
strip-source = true
portablepy build
portablepy build --profile release
portablepy inspect --profile release --resolve

portablepy looks for the nearest pyproject.toml, starting from your source or current directory. Use --config PATH to choose one. Configured paths are relative to that file; the default output stays in your current directory.

Profiles override defaults, and command-line options override profiles. Lists replace earlier lists. Use --keep-source, --use-index, or --no-replace to turn off those profile settings.

Compile to bytecode

portablepy build ./my-app --run "python -m my_app" --compile all --strip-source
  • --compile app compiles your application files.
  • --compile all also compiles dependency wheels and creates run.pyc.
  • --strip-source removes compiled .py files, including run.py with all. Sources stay by default.

Compilation keeps assertions and docstrings at optimization level 0. Native extensions, resources, and dependency licenses stay in the bundle. Some packages need their source files at runtime, so test your app before distributing a source-stripped build. Bytecode is specific to the Python version and doesn't hide your code securely.

Check a bundle

portablepy verify app.zip
portablepy verify ./extracted-app
python run.py --portable-info
python run.py --portable-setup
python run.py --portable-verify

Use run.pyc for bundles built with --compile all.

--portable-info shows bundle details without setting anything up. --portable-setup prepares the environment without starting the app. --portable-verify checks bundled files and leaves writable data alone.

Each archive includes checksums and gets a .sha256 sidecar. These detect damaged or changed files; they don't prove who published the bundle.

Development

Create a .venv with python -m venv .venv. Activate it with .venv\Scripts\Activate.ps1 in PowerShell or source .venv/bin/activate in a Unix shell, then run:

python -m pip install -e ".[dev]"
python -m pytest --cov=portablepy --cov-branch
python -m ruff check .
python -m ruff format --check .
python -m mypy

Release files for portablepy 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for portablepy 0.1.0
File Size Uploaded
portablepy-0.1.0.tar.gz 43.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for portablepy 0.1.0
File Interpreter ABI Platform
portablepy-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 79.5 kB

Release files / portablepy-0.1.0.tar.gz

Download URL portablepy-0.1.0.tar.gz
Size 43.5 kB
Tags Source
SHA-256 checksum
How to use checksums
e1e8f008ce1ef66baea533972a2ea8e8a23f61d697505ce4849e4d56010370cf
BLAKE2b-256 checksum
How to use checksums
65d6959b0da27a0dc209da9401d08553bcfdeeb47dcb9cb2a03a6a30b37349ea
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.

Transparency log

Release files / portablepy-0.1.0-py3-none-any.whl

Download URL portablepy-0.1.0-py3-none-any.whl
Size 36.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4838f5a67ec74d56c87508d6c4306707f63b95da042ea4925d7bfcd9a392f99b
BLAKE2b-256 checksum
How to use checksums
7ee2d968014c7aca7a4712fc9421aa446e8ed4532b2b53cfc27b720bf925c78d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.

Transparency log

Release history Release notifications | RSS feed

0.2.0

2 release files

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page