GitHub - FarhanAliRaza/taipan: Run Python anywhere. A single self-contained binary (Zig + embedded CPython) that runs PEP 723 scripts on machines with no Python installed.

GitHub

6 min read Original article ↗

CI

Run Python scripts anywhere. No Python required.

taipan is a single ~31 MB executable with CPython 3.14 inside. It runs Python files — including PEP 723 scripts with inline dependencies — on machines with nothing installed, and compiles scripts into standalone executables.

Highlights

  • No Python required — the interpreter ships inside the binary.
  • Inline dependencies — PEP 723 blocks are installed with uv and cached; one install per dependency set, ever.
  • ~10 ms warm startsabout 3× faster than uv run.
  • Standalone executablestaipan build bundles a script, or a whole package and its console script, into one file that runs offline.
  • Supports Linux, macOS, and Windows.

Installation

# On macOS and Linux.
curl -LsSf https://raw.githubusercontent.com/FarhanAliRaza/taipan/main/install.sh | sh
# On Windows.
irm https://raw.githubusercontent.com/FarhanAliRaza/taipan/main/install.ps1 | iex

Or download a binary for your platform from the releases page (rename it to taipan, and on Linux/macOS chmod +x it).

Usage

Run a script (taipan run script.py is the explicit equivalent):

taipan script.py [args...]

sys.argv, __file__, exit codes, tracebacks, threads, and every multiprocessing start method behave as they would under a regular CPython.

Declare dependencies inline with PEP 723:

# /// script
# dependencies = ["httpx"]
# ///

import httpx

print(httpx.get("https://example.com").status_code)
taipan example.py  # installs httpx on the first run; cached after that

Every later run — of this script or any script with the same dependency set — reuses the cached environment with no network and no uv invocation. Installs are delegated to uv, found via $TAIPAN_UV, then PATH; if neither exists, taipan downloads a static copy into its cache once (this needs curl and tar). Scripts without dependencies never touch uv.

uv uses the embedded interpreter, so it never downloads a Python of its own. This includes dependencies that publish no wheel: they are compiled from source against the same CPython 3.14 that runs your code, and need a C compiler (cc, or MSVC on Windows).

The exception is on Windows, where a package that embeds Python needs python314.lib, an import library taipan does not ship. For those builds uv falls back to its own interpreter and downloads one if it finds none. taipan tells you when this happens.

Standalone executables

Bundle a script, the interpreter, and its dependencies into one file:

taipan build app.py -o app
./app

The result runs on the same platform with no Python, uv, or network access. The output name defaults to the script name without .py (.exe added on Windows). Dependencies are resolved at build time, so the first build of a dependency set needs network; rebuilds are byte-identical.

Add sibling modules with --include-local and data files with --include:

taipan build app.py --include-local --include templates/ --include settings.json

--include-local bundles the .py files beside the entry script, keeping their relative layout so import helper and from pkg import x work; it skips .git, virtualenvs, __pycache__, and similar noise. It bundles what exists — it does not compute an import graph. --include paths (repeatable, file or directory) keep their basename next to the script; read them with Path(__file__).with_name("settings.json"). Duplicate paths warn at build time, and the last copy wins.

Building a package

taipan build also accepts a project directory or any requirement uv understands. taipan installs the package with its dependencies, and the executable runs one of its console scripts:

taipan build ./omniload -e omniload -o omniload   # from a local project
taipan build 'omniload==0.7.0' -e omniload        # from a package registry

Use -e to name the console script, the same name pip install would put on your PATH. Omit it if the package declares exactly one; if it declares several, taipan lists them and asks. Only console_scripts entry points are considered, not gui_scripts.

If no console script does what you want, -e also accepts an import target:

taipan build ./omniload -e omniload.main:main

The output name defaults to the console script's name.

A local project is reinstalled on every build, since its contents may have changed; uv's cache keeps this cheap. A pinned requirement reuses taipan's cached environment, so pin the version if you want rebuilds to fetch nothing.

How it works

The binary embeds a stripped CPython 3.14 and a bytecode-compiled standard library, extracted to a local cache on first use. Dependencies are installed by uv into content-addressed environments and precompiled once. Script bytecode is cached and startup modules are frozen into the runtime, so a warm start does almost no work. Threads and every multiprocessing start method are supported.

taipan build appends the entry script and its installed environment to a copy of the launcher, sealed with a digest that serves as both integrity check and cache key. For a package build, taipan generates the entry script. On the target, the first launch verifies and extracts the payload; later launches read only the 64-byte footer. A truncated or modified executable fails with a clear error.

The cache lives at ~/.cache/taipan (%LOCALAPPDATA%\taipan on Windows) and is disposable — deleting it costs one re-extraction on the next run. Set TAIPAN_CACHE to move it, or TAIPAN_UV to use a specific uv executable.

Comparison with uv run

uv run covers similar ground: it runs PEP 723 scripts and can download an interpreter on first use — and taipan itself uses uv for installs. If you have uv and network access, it is a good way to run scripts. taipan differs where that assumption breaks:

  • The interpreter is inside the binary, so a single file copy works on air-gapped and locked-down machines.
  • Warm starts skip environment revalidation: roughly 10 ms versus 30 ms per run.
  • taipan build produces one executable that needs nothing on the target. uv has no equivalent.

taipan is not a project manager. For lockfiles, multiple Python versions, and day-to-day work inside a project, use uv.

Limitations

  • Linux builds require glibc; musl (Alpine) is not supported yet.
  • Dependency sets are not locked — the first resolution wins and is cached. Pin versions in the PEP 723 block ("httpx==0.28.1") for reproducibility.
  • A dependency with no wheel is compiled against the build machine's system libraries, so the executable only runs on systems at least as new as the one that built it. Wheels from PyPI target manylinux and carry no such restriction.
  • Standard-library tracebacks show file and line but not source text, and code that expects stdlib modules to exist as ordinary files may fail. tkinter, idlelib, venv, and ensurepip are not included.
  • sys.executable points at the taipan launcher; it supports running scripts and CPython's multiprocessing worker protocol, not the full CPython CLI (no -m, no REPL).
  • PEP 723 requires-python is parsed but not enforced.
  • TLS uses the operating system's certificate store. If stdlib ssl cannot find certificates, set SSL_CERT_FILE; packages like httpx and requests bundle their own via certifi and are unaffected.

Building from source

Builds are native — the bundled CPython must match the host platform (Linux x86_64/arm64, macOS, Windows x86_64):

./tools/fetch_toolchain.sh
./vendor/zig/zig build

The executable lands in zig-out/bin/taipan. The toolchain script downloads pinned, checksum-verified copies of Zig and CPython into vendor/.