Skip to main content

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.

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.

Release history Release notifications | RSS feed

This release

0.3.1 This release

2 files

0.1.0

2 files

Supported by

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