Skip to main content

Portable, offline-friendly Python package bundler and installer

Project description

portapkg

PyPI License PyPI Downloads CI Last commit Ruff

Portable, offline-friendly Python package bundler and installer.

Bundle a package + all its dependencies on an online machine, then install on an air-gapped machine with nothing but Python and pip.

Quick start

Online machine — bundle a package

# Install portapkg
pip install portapkg

# Bundle a package for all 6 platforms + 5 Python versions
portapkg bundle instrumation

# Bundle for specific platforms only
portapkg bundle instrumation --platforms win_amd64,macosx_13_0_arm64

# Bundle for specific platforms and python versions (faster!)
portapkg bundle instrumation --platforms win_amd64 --python-versions 313

# Bundle a snapshot of the current environment (single platform, most reliable)
portapkg bundle instrumation --snapshot

# List all bundles
portapkg list

# Show bundle details
portapkg info instrumation

# Update / re-fetch a bundle
portapkg update instrumation

Output bundle structure:

bundles/
└── instrumation/
    ├── manifest.json
    └── wheels/
        ├── instrumation-0.3.0-py3-none-any.whl
        ├── pyserial-3.5-py2.py3-none-any.whl
        └── ...

Offline machine — install from USB

Copy the bundles/ folder and portapkg.py to a USB drive, then on the offline machine:

# List available bundles
python portapkg.py list

# Install a bundled package (works without internet)
python portapkg.py install instrumation

# Install to a custom path
python portapkg.py install instrumation --target ./myenv/Lib/site-packages

The portapkg.py file is fully self-contained — stdlib only, no dependencies.

How it works

  1. Dependency resolution: pip download <package> resolves the full transitive dependency tree using pip's own resolver. For multi-platform bundles, the resolver also scans each wheel's METADATA for platform-conditional deps (e.g. pefile; sys_platform == "win32") and recursively resolves any missing deps found.
  2. Multi-platform fetch: For each dependency, wheels are downloaded across multiple platforms × Python versions using pip download --platform --python-version --only-binary=:all:.
  3. Layered fallback strategy:
    • Layer 1: Exact binary wheel for the target platform/Python.
    • Layer 2: Platform-constrained source distribution.
    • Layer 3: Unconstrained source distribution — catches sdist-only packages like Eel.
    • Layer 4: Version bump — queries pip index versions for newer releases with binary wheel coverage (e.g. msgpack gaining cp313 wheels in a later patch).
  4. Offline install: pip install --no-index --find-links <wheels_dir> installs from local files only.

Development

Setup

# Clone the repo
git clone https://github.com/your-org/portapkg-py && cd portapkg-py

# Install in editable mode with dev dependencies
pip install -e ".[dev]"

Running tests

# Run all tests
pytest

# Run tests with coverage
pytest --cov=portapkg --cov-report=term-missing

# Run a specific test file
pytest tests/test_platform.py

# Run a specific test
pytest tests/test_platform.py::TestParseWheelFilename

# Run linter
pip install ruff
ruff check portapkg/ tests/ portapkg.py

Project structure

portapkg/
├── portapkg.py                ← standalone offline installer (stdlib only)
├── portapkg/
│   ├── cli.py                 ← entrypoint: bundle/list/info/update
│   ├── bundler/
│   │   ├── fetch.py           ← multi-platform wheel download
│   │   ├── resolver.py        ← dependency resolution (pip download)
│   │   └── manifest.py        ← manifest.json read/write
│   └── installer/
│       ├── install.py         ← offline install logic
│       └── platform.py        ← OS/arch/python detection
├── tests/                     ← pytest test suite
├── pyproject.toml             ← packaging & tool config
└── .github/workflows/         ← CI / release workflows

CI / CD

  • CI (.github/workflows/test.yml): runs on push/PR to main — tests across 3 OS × 5 Python versions, plus linting with ruff.
  • Release (.github/workflows/publish.yml): manual workflow_dispatch — specify a version tag, and it builds wheels + sdist and creates a GitHub Release with the artifacts attached.

Platform matrix

Platform Tag
Windows x86_64 win_amd64
Windows x86 win32
Linux x86_64 manylinux2014_x86_64
Linux ARM64 manylinux2014_aarch64
macOS Apple Silicon macosx_13_0_arm64
macOS Intel macosx_13_0_x86_64

Python versions: 3.9, 3.10, 3.11, 3.12, 3.13

Error handling

  • Missing binary wheel: warns and falls back through layered strategy (source dist → unconstrained sdist → version bump)
  • Platform-conditional deps: automatically resolved by scanning wheel METADATA for Requires-Dist lines with sys_platform / platform_system / implementation_name markers, evaluated with packaging.markers.
  • No compatible wheel on offline machine: clear human-readable error with suggested fix (--snapshot)
  • Missing pip: detected before any install attempt
  • Missing bundle: tells you which directory to copy

Configuration

PORTAPKG_BUNDLES_DIR

By default, bundles are stored in ./bundles/ relative to the current working directory. You can override this with the PORTAPKG_BUNDLES_DIR environment variable:

export PORTAPKG_BUNDLES_DIR=/path/to/bundles
portapkg bundle instrumation   # stores in /path/to/bundles/instrumation/

Both the CLI (portapkg) and the standalone script (portapkg.py) respect this environment variable.

Requirements

  • Online machine: Python 3.9+, pip (any version)
  • Offline machine: Python 3.9+, pip (any version)
  • No third-party packages are used by portapkg itself — stdlib only.

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

portapkg-0.3.1.tar.gz (21.5 kB view details)

Uploaded Source

Built Distribution

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

portapkg-0.3.1-py3-none-any.whl (15.7 kB view details)

Uploaded Python 3

File details

Details for the file portapkg-0.3.1.tar.gz.

File metadata

  • Download URL: portapkg-0.3.1.tar.gz
  • Upload date:
  • Size: 21.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for portapkg-0.3.1.tar.gz
Algorithm Hash digest
SHA256 4f5d58a6db55661657d58574a6ea114b06a4adf9b6379524ae6b2906e8e99e33
MD5 11b05cec911f177f54110609fb61998a
BLAKE2b-256 98bec12192c29ebe7db81e74e3e4445f566cef758dc98f886fb2a6183d74e55b

See more details on using hashes here.

Provenance

The following attestation bundles were made for portapkg-0.3.1.tar.gz:

Publisher: publish.yml on abduznik/portapkg-py

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

File details

Details for the file portapkg-0.3.1-py3-none-any.whl.

File metadata

  • Download URL: portapkg-0.3.1-py3-none-any.whl
  • Upload date:
  • Size: 15.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for portapkg-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 7da9abab6f9acd8f37e8e44cf3543f063459ce9dbe7f0683ee2fe0d605c0b3ea
MD5 757f044b7bb0e11e3b929f2b566144c7
BLAKE2b-256 6836e29b10dcc2123813036af3d181e6343fa2d93ddf7219bea3d9dfb697b2ec

See more details on using hashes here.

Provenance

The following attestation bundles were made for portapkg-0.3.1-py3-none-any.whl:

Publisher: publish.yml on abduznik/portapkg-py

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