Skip to content

ArchiveBox/abx-pkg

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

582 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

abx-pkg Β  Β  Β  Β  πŸ“¦ aptΒ  brewΒ  pipΒ  uvΒ  npmΒ  pnpmΒ  yarnΒ  bunΒ  denoΒ  cargoΒ  gemΒ  gogetΒ  nixΒ  dockerΒ  bashΒ  puppeteerΒ  playwrightΒ  chromewebstoreΒ  ansibleΒ  pyinfra
Simple Python interfaces for package managers + installed binaries.


PyPI Python Version Django Version GitHub GitHub Last Commit


It's an ORM for your package managers, providing a nice python types for packages + installers.

This is a Python library for installing & managing packages locally with a variety of package managers.
It's designed for when requirements.txt isn't enough, and you have to detect or install dependencies at runtime. It's great for installing and managing MCP servers and their dependencies at runtime.

pip install abx-pkg
from abx_pkg import Binary, npm

curl = Binary(name="curl").load()
print(curl.abspath, curl.version, curl.exec(cmd=["--version"]))

npm.install("puppeteer")

πŸ“¦ Provides consistent interfaces for runtime dependency resolution & installation across multiple package managers & OSs ✨ Built with pydantic v2 for strong static typing guarantees and easy conversion to/from json 🌈 Usable with django >= 4.0, django-ninja, and OpenAPI + django-jsonform to build UIs & APIs πŸ¦„ Driver layer can be pyinfra / ansible / or built-in abx-pkg engine

Built by ArchiveBox to install & auto-update our extractor dependencies at runtime (chrome, wget, curl, etc.) on macOS/Linux/Docker.


Source Code: https://github.com/ArchiveBox/abx-pkg/
Documentation: https://github.com/ArchiveBox/abx-pkg/blob/main/README.md


from abx_pkg import Binary, apt, brew, pip, npm, env

# Provider singletons are available as simple imports β€” no manual instantiation needed
dependencies = [
    Binary(name='curl',       binproviders=[env, apt, brew]),
    Binary(name='yt-dlp',     binproviders=[env, pip, uv, apt, brew]),
    Binary(name='playwright', binproviders=[env, npm, pnpm]),
    Binary(name='chromium',   binproviders=[playwright, puppeteer, apt]),
    Binary(name='postgres',   binproviders=[docker, env, apt, brew]),
]
for binary in dependencies:
    binary = binary.install()

    print(binary.abspath, binary.version, binary.binprovider, binary.is_valid, binary.sha256)
    # Path(...) SemVer(...) EnvProvider()/AptProvider()/BrewProvider()/PipProvider()/NpmProvider() True '<sha256>'

    binary.exec(cmd=['--version'])   # curl 7.81.0 (x86_64-apple-darwin23.0) libcurl/7.81.0 ...

Tip

πŸ”’ Stay safe from supply-chain attcaks with abx-pkg: We default to safe behavior (when providers allow):

  • min_release_age=7 (we only install packages that have been published for 7 days or longer)
  • postinstall_scripts=False (we don't run post-install scripts for packages by default)
  • install_root=~/.config/abx/lib (CLI defaults to installing packages in a dedicated prefix so host system stays clean)

You can customize these defaults on Binary or BinProvider, or with ABX_PKG_MIN_RELEASE_AGE/ABX_PKG_POSTINSTALL_SCRIPTS=/ABX_PKG_NO_CACHE/ABX_PKG_LIB_DIR (see Configuration below).


Usage

Install

pip install abx-pkg
# or
uv tool add abx-pkg

CLI

Installing abx-pkg also provides an abx-pkg CLI entrypoint:

abx-pkg --version

abx-pkg install yt-dlp
abx-pkg update yt-dlp
abx-pkg uninstall yt-dlp
abx-pkg load yt-dlp

Execute an installed binary via the configured providers

abx-pkg run yt-dlp --help                          # resolves yt-dlp via the configured providers and execs it
abx-pkg --binproviders=pip,brew run pip show black # restrict provider resolution (exercises PipProvider.exec)
abx-pkg --binproviders=pip --install run yt-dlp    # load first, then install via selected providers if needed
abx-pkg --binproviders=pip --update  run yt-dlp    # ensure the binary is available, then update before exec
abx-pkg --binproviders=pip --no-cache --install run yt-dlp  # bypass cached/current-state checks during resolution + install

abx-pkg options (e.g. --binproviders, --lib, --install, --update, --no-cache) must appear before the run subcommand; every argument after the binary name is forwarded verbatim to the underlying binary. run exits with the child's exit code, passes its stdout/stderr through unbuffered, and routes any abx-pkg install/load logs to stderr only β€” no headers, no footers, no parsing.

abx: auto-install-and-run shortcut

Think npx / uvx / pipx run β€” but for every package manager abx-pkg supports. abx is a thin alias for abx-pkg --install run ...: it resolves the binary via the configured providers, installs it if missing, then execs it with the forwarded arguments.

abx yt-dlp --help                               # auto-install (if needed) and run yt-dlp
abx --update yt-dlp --help                      # ensure the binary is available, then update before running
abx --binproviders=env,uv,pip,apt,brew yt-dlp   # restrict provider resolution

Options before the binary name (--lib, --binproviders, --dry-run, --debug, --no-cache, --update) are forwarded to abx-pkg; everything after the binary name is forwarded to the binary itself.

Per-Binary / per-BinProvider options as CLI flags

Every Binary / BinProvider configuration field is exposed as a CLI flag on the group and on subcommands (install, update, uninstall, load), and is also available to run / abx via group-level flags placed before the binary name. Providers that can't enforce a given option emit a warning to stderr and continue β€” no hard failure.

abx-pkg --min-version=1.2.3 --min-release-age=7 install yt-dlp
abx-pkg --postinstall-scripts=False --binproviders=apt,uv,pip install black
abx-pkg --no-cache install black
abx-pkg --install-root=/tmp/yt-dlp-root --bin-dir=/tmp/yt-dlp-bin install yt-dlp
abx-pkg --overrides='{"pip":{"install_args":["yt-dlp[default]"]}}' install yt-dlp
abx-pkg --install-timeout=600 --version-timeout=20 --euid=1000 install yt-dlp
abx --min-version=2024.1.1 --min-release-age=0 yt-dlp --help
Flag Type Meaning
--min-version=SEMVER str Minimum acceptable version (set on Binary.min_version).
--postinstall-scripts[=BOOL] bool Allow post-install scripts. Bare --postinstall-scripts = True. Providers that can't disable them warn-and-ignore.
--min-release-age=DAYS float Minimum days since publication. Non-supporting providers warn-and-ignore.
--no-cache[=BOOL] bool Skip cached/current-state checks and force fresh install/update/load probes. Bare --no-cache = True.
--overrides=JSON dict Per-provider Binary.overrides handler replacements (install_args, abspath, version, …).
--install-root=PATH Path Override the per-provider install directory.
--bin-dir=PATH Path Override the per-provider bin directory.
--euid=UID int Pin the UID used when providers shell out.
--install-timeout=SECONDS int Seconds to wait for install/update/uninstall subprocesses.
--version-timeout=SECONDS int Seconds to wait for version/metadata probes.
--dry-run[=BOOL] bool Show installer commands without executing them. Bare --dry-run = True.
--debug[=BOOL] bool Emit DEBUG logs to stderr. Bare --debug = True. Defaults to ABX_PKG_DEBUG or False.

Every value-taking flag also accepts the literal string None / null / nil / "" to reset to the provider's built-in default (or its env-var-backed default). The precedence is: explicit per-subcommand flag > group-level flag > environment variable > built-in default.

Select specific providers / re-order provider precedence

abx-pkg install --binproviders=env,uv,pip,apt,brew prettier
# or
env ABX_PKG_BINPROVIDERS=env,uv,pip,apt,brew abx-pkg install yt-dlp

Customize where installed packages are located

abx-pkg --lib=~/.config/abx/lib install yt-dlp   # the default behavior
abx-pkg --lib=./vendor install yt-dlp            # store all packages under $PWD/vendor
abx-pkg --lib=/tmp/abxlib install yt-dlp         # store all packages under /tmp/abxlib

# or
env ABX_PKG_LIB_DIR=/any/dir/path abx-pkg install yt-dlp

Run in "dry mode" to see what commands will do before executing

abx-pkg install --dry-run some-dangerous-package      # outputs commands that would be run without executing them
# or
env ABX_PKG_DRY_RUN=1 abx-pkg install some-dangerous-package

CLI result lines are written to stdout. Progress logging is written to stderr at INFO by default. Enable DEBUG logging with ABX_PKG_DEBUG=1 or --debug.


Python Library

Basic Usage

All built-in providers are available as lazy singletons β€” just import them by name:

from abx_pkg import apt, brew, pip, npm, env

apt.install('curl')
env.load('wget')

These are instantiated on first access and cached for reuse. If you need custom configuration, you can still instantiate provider classes directly:

from pathlib import Path
from abx_pkg import PipProvider

custom_pip = PipProvider(install_root=Path("/tmp/abx-pkg-venv"), min_release_age=0)

Use the Binary class to declare a package that can be installed by one of several ordered providers, with an optional version floor:

from abx_pkg import Binary, SemVer, env, brew

curl = Binary(
    name="curl",
    min_version=SemVer("8.0.0"),
    binproviders=[env, brew],
).install()

min_version is enforced after a provider resolves or installs a binary β€” provider discovery can still succeed, but the final Binary is rejected if the loaded version is below the floor. Use min_version=None to disable the check.

Pass no_cache=True to load() / install() / update() / uninstall() when you want to bypass cached/current-state checks. For install(), no_cache=True skips the initial load() check and forces a fresh install path. The equivalent CLI and env controls are --no-cache and ABX_PKG_NO_CACHE=1.

Advanced Usage

Define a reusable Binary subclass with per-provider overrides

from pydantic import InstanceOf
from abx_pkg import BinProvider, Binary, BinProviderName, BinName, HandlerDict, BrewProvider
from abx_pkg import env, pip, apt

class CustomBrewProvider(BrewProvider):
    name: BinProviderName = 'custom_brew'

    def get_macos_packages(self, bin_name: str, **context) -> list[str]:
        return ['yt-dlp'] if bin_name == 'ytdlp' else [bin_name]

class YtdlpBinary(Binary):
    name: BinName = 'ytdlp'
    description: str = 'YT-DLP (Replacement for YouTube-DL) Media Downloader'

    # define the providers this binary supports
    binproviders: list[InstanceOf[BinProvider]] = [env, pip, apt, CustomBrewProvider()]

    # customize installed package names for specific package managers
    overrides: dict[BinProviderName, HandlerDict] = {
        'pip': {'install_args': ['yt-dlp[default,curl-cffi]']},   # literal values
        'apt': {'install_args': lambda: ['yt-dlp', 'ffmpeg']},    # any pure Callable
        'custom_brew': {'install_args': 'self.get_macos_packages'},  # or a string ref to a method on self
    }


ytdlp = YtdlpBinary().install()
print(ytdlp.binprovider)    # EnvProvider(...) / PipProvider(...) / AptProvider(...) / CustomBrewProvider(...)
print(ytdlp.abspath)        # Path(...)
print(ytdlp.version)        # SemVer(...)
print(ytdlp.is_valid)       # True

# Lifecycle actions preserve the Binary type and refresh/clear loaded metadata as needed
ytdlp = ytdlp.update()
assert ytdlp.is_valid
ytdlp = ytdlp.uninstall()
assert ytdlp.abspath is None and ytdlp.version is None

Use Binary objects as a stable typed interface to interact with installed packages

from abx_pkg import Binary, apt, brew, env

# Use providers directly for package manager operations
apt.install('wget')
print(apt.PATH, apt.get_abspaths('wget'), apt.get_version('wget'))

# our Binary API provides a nice type-checkable, validated, serializable handle
ffmpeg = Binary(name='ffmpeg', binproviders=[env, apt, brew]).load()
print(ffmpeg)                       # Binary(name='ffmpeg', abspath=Path(...), version=SemVer(...), sha256='...')
print(ffmpeg.abspaths)              # show all matching binaries found via each provider PATH
print(ffmpeg.model_dump(mode='json'))  # JSON-ready dict
print(ffmpeg.model_json_schema())   # ... OpenAPI-ready JSON schema showing all available fields
from pydantic import InstanceOf
from abx_pkg import Binary, BinProvider, BrewProvider, EnvProvider

# You can also instantiate provider classes manually for custom configuration,
# or define binaries as classes for type checking
class CurlBinary(Binary):
    name: str = 'curl'
    binproviders: list[InstanceOf[BinProvider]] = [BrewProvider(), EnvProvider()]

curl = CurlBinary().install()
assert isinstance(curl, CurlBinary)                                 # CurlBinary is a unique type you can use in annotations now
print(curl.abspath, curl.version, curl.binprovider, curl.is_valid)  # Path(...) SemVer(...) BrewProvider()/EnvProvider() True
curl.exec(cmd=['--version'])                                        # curl 8.4.0 (x86_64-apple-darwin23.0) libcurl/8.4.0 ...

Customize binary resolution/install/other behavior via per-provider or per-binary overrides

import os
import platform
from pydantic import InstanceOf
from abx_pkg import BinProvider, Binary, BinProviderName, BinName, HandlerDict
from abx_pkg import env, apt

class DockerBinary(Binary):
    name: BinName = 'docker'
    binproviders: list[InstanceOf[BinProvider]] = [env, apt]

    overrides: dict[BinProviderName, HandlerDict] = {
        'env': {
            # prefer podman if installed, fall back to docker
            'abspath': lambda: os.which('podman') or os.which('docker') or os.which('docker-ce'),
        },
        'apt': {
            # vary the installed package name based on CPU architecture
            'install_args': {
                'amd64': ['docker'],
                'armv7l': ['docker-ce'],
                'arm64': ['docker-ce'],
            }.get(platform.machine(), 'docker'),
        },
    }

docker = DockerBinary().install()

Subclass BinProvider to add support for a new package manager

from pathlib import Path
from abx_pkg import (
    BinProvider,
    BinProviderName,
    BinName,
    HostBinPath,
    InstallArgs,
    SemVer,
    bin_abspath,
)

class CargoProvider(BinProvider):
    name: BinProviderName = 'cargo'
    INSTALLER_BIN: BinName = 'cargo'
    PATH: str = str(Path.home() / '.cargo/bin')

    def default_install_args_handler(self, bin_name: BinName, **context) -> InstallArgs:
        return [bin_name]

    def default_install_handler(
        self,
        bin_name: BinName,
        install_args: InstallArgs | None = None,
        postinstall_scripts: bool | None = None,
        min_release_age: float | None = None,
        min_version: SemVer | None = None,
        timeout: int | None = None,
    ) -> str:
        install_args = install_args or self.get_install_args(bin_name)
        installer = self._require_installer_bin()
        proc = self.exec(bin_name=installer, cmd=['install', *install_args], timeout=timeout)
        if proc.returncode != 0:
            self._raise_proc_error('install', install_args, proc)
        return proc.stdout.strip() or proc.stderr.strip()

    def default_abspath_handler(self, bin_name: BinName, **context) -> HostBinPath | None:
        return bin_abspath(bin_name, PATH=self.PATH)

    def default_version_handler(
        self,
        bin_name: BinName,
        abspath: HostBinPath | None = None,
        timeout: int | None = None,
        **context,
    ) -> SemVer | None:
        return self._version_from_exec(bin_name, abspath=abspath, timeout=timeout)


cargo = CargoProvider()
rg = cargo.install(bin_name='ripgrep')
print(rg.binprovider)    # CargoProvider(...)
print(rg.version)        # SemVer(...)

Configure python logging to customize the stderr/stdout logging

abx-pkg uses the standard Python logging module. By default it stays quiet unless your application configures logging explicitly.

import logging
from abx_pkg import Binary, env, configure_logging

configure_logging(logging.INFO)

python = Binary(name='python', binproviders=[env]).load()

To enable Rich logging:

pip install "abx-pkg[rich]"
import logging
from abx_pkg import Binary, EnvProvider, configure_rich_logging

configure_rich_logging(logging.DEBUG)

python = Binary(name='python', binproviders=[EnvProvider()]).load()

Debug logging is hardened so logging itself does not become the failure. If a provider/model object has a broken or overly-expensive repr(), abx-pkg falls back to a short ClassName(...) summary instead of raising while formatting log output.

configure_rich_logging(...) uses rich.logging.RichHandler under the hood, so log levels, paths, arguments, and command lines render with terminal colors when supported.

You can also manage it with standard logging primitives:

import logging

logging.basicConfig(level=logging.INFO)
logging.getLogger("abx_pkg").setLevel(logging.DEBUG)

Django integration: store BinProvider / Binary in DB models and render them in the Admin

With a few more packages, you get type-checked Django fields & forms that support BinProvider and Binary.

[!TIP] For the full Django experience, we recommend installing these 3 excellent packages:

Django model fields:

from django.db import models
from abx_pkg import BinProvider, Binary, SemVer
from django_pydantic_field import SchemaField

class Dependency(models.Model):
    label = models.CharField(max_length=63)
    default_binprovider: BinProvider = SchemaField()
    binaries: list[Binary] = SchemaField(default=[])
    min_version: SemVer = SchemaField(default=(0, 0, 1))

Saving a Binary using the model:

from abx_pkg import Binary, env

curl = Binary(name='curl').load()

obj = Dependency(
    label='runtime tools',
    default_binprovider=env,   # store BinProvider values directly
    binaries=[curl],            # store Binary/SemVer values directly
)
obj.save()

When fetching back from the DB, Binary fields are auto-deserialized and immediately usable:

obj = Dependency.objects.get(label='runtime tools')
assert obj.binaries[0].abspath == curl.abspath
obj.binaries[0].exec(cmd=['--version'])

For a full example see the bundled django_example_project/.

Django Admin integration:

Django Admin binaries list viewDjango Admin binaries detail view

# settings.py
INSTALLED_APPS = [
    # ...
    'admin_data_views',
    'abx_pkg',
]

ABX_PKG_GET_ALL_BINARIES = 'project.views.get_all_binaries'
ABX_PKG_GET_BINARY = 'project.views.get_binary'

ADMIN_DATA_VIEWS = {
    "NAME": "Environment",
    "URLS": [
        {
            "route": "binaries/",
            "view": "abx_pkg.views.binaries_list_view",
            "name": "binaries",
            "items": {
                "route": "<str:key>/",
                "view": "abx_pkg.views.binary_detail_view",
                "name": "binary",
            },
        },
    ],
}

If you override the default site admin, register the views manually:

from abx_pkg.admin import register_admin_views

custom_admin = YourSiteAdmin()
register_admin_views(custom_admin)

Configuration

All abx-pkg env vars are read once at import time and only apply when set. Explicit constructor kwargs always override these defaults.

Behavioral controls (apply across all providers):

Variable Default Effect
ABX_PKG_DRY_RUN / DRY_RUN 0 Flips the shared dry_run default. ABX_PKG_DRY_RUN wins if both are set. Provider subprocesses are logged and skipped, install() / update() return a placeholder, uninstall() returns True.
ABX_PKG_NO_CACHE 0 Flips the shared no_cache default. When enabled, install() skips the initial load() check and forces a fresh install path, while load() / update() / uninstall() bypass cached probe results.
ABX_PKG_DEBUG 0 Enables DEBUG-level CLI logging on stderr for abx-pkg / abx. The matching CLI flag is --debug. Default CLI logging level is INFO.
ABX_PKG_INSTALL_TIMEOUT 120 Seconds to wait for install() / update() / uninstall() handler subprocesses.
ABX_PKG_VERSION_TIMEOUT 10 Seconds to wait for version / metadata probes (--version, npm show, pip show, etc.).
ABX_PKG_POSTINSTALL_SCRIPTS unset Hydrates the provider-level default for the postinstall_scripts kwarg on every provider that supports it (pip, uv, npm, pnpm, yarn, bun, deno, brew, chromewebstore, puppeteer).
ABX_PKG_MIN_RELEASE_AGE 7 Hydrates the provider-level default (in days) for the min_release_age kwarg on every provider that supports it (pip, uv, npm, pnpm, yarn, bun, deno).
ABX_PKG_BINPROVIDERS all Comma-separated list of provider names to enable (and their order) for the abx-pkg CLI.

Install-root controls (one global default + one per-provider override):

Variable Applies to Effect
ABX_PKG_LIB_DIR providers that use managed roots Centralized install root. When set, each managed provider defaults its install_root to $ABX_PKG_LIB_DIR/<provider name> (e.g. <lib>/npm, <lib>/pip, <lib>/gem, <lib>/playwright). Accepts relative (./lib), tilde (~/.config/abx/lib), and absolute (/tmp/abxlib) paths.
ABX_PKG_<BINPROVIDER>_ROOT the matching provider's install_root Generic per-provider override; beats ABX_PKG_LIB_DIR/<provider name>. Examples: ABX_PKG_PIP_ROOT, ABX_PKG_UV_ROOT, ABX_PKG_NPM_ROOT, ABX_PKG_GOGET_ROOT, ABX_PKG_CHROMEWEBSTORE_ROOT. The <BINPROVIDER> token is the provider name uppercased.

Install-root precedence (most specific wins): explicit install_root= / provider alias kwarg > ABX_PKG_<NAME>_ROOT > ABX_PKG_LIB_DIR/<name> > provider-native global mode.

Provider-specific binary overrides:

Each provider also honors a <NAME>_BINARY=/abs/path/to/<name> env var to pin the exact executable it shells out to β€” PIP_BINARY, UV_BINARY, NPM_BINARY, PNPM_BINARY, YARN_BINARY, BUN_BINARY, DENO_BINARY, etc.

Per-Binary / per-BinProvider fields (constructor kwargs, most-specific wins):

  • min_version can be set on any individual Binary.
  • min_release_age can be set on Binary or BinProvider, or via ABX_PKG_MIN_RELEASE_AGE (days).
  • postinstall_scripts can be set on Binary or BinProvider, or via ABX_PKG_POSTINSTALL_SCRIPTS.
  • no_cache can be passed per-call to load() / install() / update() / uninstall(), or enabled globally for the CLI via ABX_PKG_NO_CACHE.
  • install_root / bin_dir can be set on any BinProvider with an isolated install location, or default to ABX_PKG_<NAME>_ROOT / ABX_PKG_LIB_DIR/<provider name>.
  • dry_run can be set on BinProvider or passed per-call to install() / update() / uninstall(), or via ABX_PKG_DRY_RUN / DRY_RUN.
  • install_timeout can be set on BinProvider or via ABX_PKG_INSTALL_TIMEOUT (seconds).
  • version_timeout can be set on BinProvider or via ABX_PKG_VERSION_TIMEOUT (seconds).
  • euid can be set on BinProvider to pin the UID used to sudo/drop into when running provider subprocesses; otherwise it's auto-detected from install_root ownership.
  • overrides is a dict[BinProviderName, HandlerDict] (on Binary) or dict[BinName, HandlerDict] (on BinProvider) mapping to per-binary / per-provider handler replacements (install_args, abspath, version, install, update, uninstall). See Advanced Usage for examples.

Precedence is always: explicit action kwarg > Binary(...) field > BinProvider(...) field > env var > built-in default.





API Reference

Built-in implementations: EnvProvider, AptProvider, BrewProvider, PipProvider, UvProvider, NpmProvider, PnpmProvider, YarnProvider, BunProvider, DenoProvider, CargoProvider, GemProvider, GoGetProvider, NixProvider, DockerProvider, PyinfraProvider, AnsibleProvider, BashProvider, ChromeWebstoreProvider, PuppeteerProvider, PlaywrightProvider

This type represents a provider of binaries, e.g. a package manager like apt / pip / npm, or env (which only resolves binaries already present in $PATH).

🧩 Shared API

Every provider exposes the same lifecycle surface:

  • load() / install() / update() / uninstall()
  • get_install_args() to resolve package names / formulae / image refs / module specs
  • get_abspath() / get_abspaths() / get_version() / get_sha256()

Shared base defaults come from abx_pkg/binprovider.py and apply unless a concrete provider overrides them:

INSTALLER_BIN = "env"
PATH = str(Path(sys.executable).parent)
postinstall_scripts = None           # some providers override this with ABX_PKG_POSTINSTALL_SCRIPTS
min_release_age = None               # some providers override this with ABX_PKG_MIN_RELEASE_AGE
install_timeout = 120                # or ABX_PKG_INSTALL_TIMEOUT=120
version_timeout = 10                 # or ABX_PKG_VERSION_TIMEOUT=10
dry_run = False                      # or ABX_PKG_DRY_RUN=1 / DRY_RUN=1
  • dry_run: use provider.get_provider_with_overrides(dry_run=True), pass dry_run=True directly to install() / update() / uninstall(), or set ABX_PKG_DRY_RUN=1 / DRY_RUN=1. If both env vars are set, ABX_PKG_DRY_RUN wins. Provider subprocesses are logged and skipped, install() / update() return a placeholder loaded binary, and uninstall() returns True without mutating the host.
  • no_cache: use --no-cache / ABX_PKG_NO_CACHE=1 on the CLI, or pass no_cache=True directly to load() / install() / update() / uninstall(). For install(), this skips the initial load() check and forces a fresh install path.
  • install_timeout: shared provider-level timeout used by install(), update(), and uninstall() handler execution paths. Can also be set with ABX_PKG_INSTALL_TIMEOUT.
  • version_timeout: shared provider-level timeout used by version / metadata probes such as --version, npm show, npm list, pip show, go version -m, and brew lookups. Can also be set with ABX_PKG_VERSION_TIMEOUT.
  • postinstall_scripts and min_release_age are standard provider/binary/action kwargs, but only supporting providers hydrate default values from ABX_PKG_POSTINSTALL_SCRIPTS and ABX_PKG_MIN_RELEASE_AGE.
  • Providers that do not support one of those controls leave the provider default as None. If you pass an explicit unsupported value during install() / update(), it is logged as a warning and ignored.
  • Precedence is: explicit action args > Binary(...) defaults > provider defaults.

For the full list of env vars that hydrate these defaults, see Configuration above.

Supported override keys are the same everywhere:

from pathlib import Path
from abx_pkg import PipProvider

provider = PipProvider(install_root=Path("/tmp/venv")).get_provider_with_overrides(
    overrides={
        "black": {
            "install_args": ["black==24.4.2"],
            "version": "self.default_version_handler",
            "abspath": "self.default_abspath_handler",
        },
    },
    dry_run=True,
    version_timeout=30,
)
  • install_args / packages: package-manager arguments for that provider. packages is the legacy alias.
  • abspath, version, install, update, uninstall: literal values, callables, or "self.method_name" references that replace the provider handler for a specific binary.

Providers with isolated install locations also expose a shared constructor surface:

  • install_root: shared alias for provider-specific roots such as pip_venv, npm_prefix, cargo_root, gem_home, gopath, nix_profile, docker_root, and brew_prefix.
  • bin_dir: shared alias for providers that separate package state from executable output, such as gem_bindir, gobin, and docker_shim_dir.
  • provider.install_root / provider.bin_dir: normalized computed properties you can inspect after construction, regardless of which provider-specific args were used.
  • Legacy provider-specific args still work. The shared aliases are additive, not replacements.
  • Providers that do not have an isolated install location reject install_root / bin_dir at construction time instead of silently ignoring them.
  • When an explicit install root or bin dir is configured, that provider-specific bin location wins during binary discovery and subprocess execution instead of being left behind ambient host PATH entries.

Supported BinProviders

🌍 EnvProvider (env)

Source: abx_pkg/binprovider.py β€’ Tests: tests/test_envprovider.py

INSTALLER_BIN = "which"
PATH = DEFAULT_ENV_PATH              # current PATH + current Python bin dir
  • Install root: none. env is read-only and only searches existing binaries on $PATH.
  • Auto-switching: none.
  • Security: min_release_age and postinstall_scripts are unsupported here and are ignored with a warning if explicitly passed to install() / update().
  • Overrides: abspath / version are the useful ones here. python has a built-in override to the current sys.executable and interpreter version.
  • Notes: install() / update() return explanatory no-op messages, and uninstall() returns False.

🐧 AptProvider (apt)

Source: abx_pkg/binprovider_apt.py β€’ Tests: tests/test_aptprovider.py

INSTALLER_BIN = "apt-get"
PATH = ""                            # populated from `dpkg -L bash` bin dirs
euid = 0                             # always runs as root
  • Install root: no hermetic prefix support. Installs into the host package database.
  • Auto-switching: tries PyinfraProvider first, then AnsibleProvider, then falls back to direct apt-get.
  • dry_run: shared behavior.
  • Security: min_release_age and postinstall_scripts=False are unsupported and are ignored with a warning if explicitly requested.
  • Overrides: in the direct shell fallback, install_args becomes apt-get install -y -qq --no-install-recommends ...; update() uses apt-get install --only-upgrade ....
  • Notes: direct mode runs apt-get update -qq at most once per day and requests privilege escalation when needed.

🍺 BrewProvider (brew)

Source: abx_pkg/binprovider_brew.py β€’ Tests: tests/test_brewprovider.py

INSTALLER_BIN = "brew"
PATH = "/home/linuxbrew/.linuxbrew/bin:/opt/homebrew/bin:/usr/local/bin"
brew_prefix = guessed host prefix    # /opt/homebrew, /usr/local, or linuxbrew
  • Install root: brew_prefix is still the host Homebrew prefix for package discovery and installation. If you pass install_root=..., abx-pkg uses <install_root>/bin as a managed shim/symlink dir for the resolved formula binaries, but Homebrew itself still installs into the host prefix.
  • Auto-switching: if postinstall_scripts=True, it prefers PyinfraProvider and then AnsibleProvider; otherwise it falls back to direct brew.
  • dry_run: shared behavior.
  • Security: min_release_age is unsupported and is ignored with a warning if explicitly requested. postinstall_scripts=False is supported for direct brew installs via --skip-post-install, and ABX_PKG_POSTINSTALL_SCRIPTS hydrates the provider default here.
  • Overrides: in the direct shell fallback, install_args maps to formula / cask args passed to brew install, brew upgrade, and brew uninstall.
  • Notes: direct mode runs brew update at most once per day. Explicit --skip-post-install args in install_args win over derived defaults.

🐍 PipProvider (pip)

Source: abx_pkg/binprovider_pip.py β€’ Tests: tests/test_pipprovider.py, tests/test_security_controls.py

INSTALLER_BIN = "pip"
PATH = ""                            # auto-built from global/user Python bin dirs
pip_venv = None                      # set this for hermetic installs
cache_dir = user_cache_path("pip", "abx-pkg") or <system temp>/pip-cache
pip_install_args = ["--no-input", "--disable-pip-version-check", "--quiet"]
pip_bootstrap_packages = ["pip", "setuptools"]
  • Install root: install_root=None uses the system/user Python environment. Set install_root=Path(...) or install_root=Path(...) for a hermetic venv rooted at <pip_venv>/bin, and that venv bin dir becomes the provider's active executable search path. When pip_venv is set, setup() creates the venv on first use via Python's built-in venv module and bootstraps the latest pip_bootstrap_packages into it.
  • Auto-switching: none. Shells out to pip directly. Honors PIP_BINARY=/abs/path/to/pip. Use UvProvider for uv-backed installs.
  • dry_run: shared behavior.
  • Security: supports postinstall_scripts=False (always) and min_release_age (on pip >= 26.0 or in a freshly bootstrapped pip_venv). Hydrated from ABX_PKG_POSTINSTALL_SCRIPTS and ABX_PKG_MIN_RELEASE_AGE. For stricter enforcement on hosts with older system pip, use UvProvider instead.
  • Overrides: install_args is passed as pip requirement specs; unpinned specs get a >=min_version floor when min_version is supplied.
  • Notes: postinstall_scripts=False adds pip --only-binary :all: (wheels only, no arbitrary sdist build scripts). min_release_age is enforced with pip --uploaded-prior-to=<ISO8601> on pip >= 26.0 (see pypa/pip#13625); older pip silently skips the flag. Explicit conflicting flags already present in install_args win over the derived defaults. get_version / get_abspath fall back to parsing pip show <package> output when the console script can't report its own version.

πŸš€ UvProvider (uv)

Source: abx_pkg/binprovider_uv.py β€’ Tests: tests/test_uvprovider.py

INSTALLER_BIN = "uv"
PATH = ""                            # prepends <uv_venv>/bin or uv_tool_bin_dir
uv_venv = None                       # None = global uv tool mode, Path(...) = hermetic venv
uv_tool_dir = None                   # mirrors $UV_TOOL_DIR (global mode only)
uv_tool_bin_dir = None               # mirrors $UV_TOOL_BIN_DIR (global mode only)
cache_dir = user_cache_path("uv", "abx-pkg") or <system temp>/uv-cache
uv_install_args = []
  • Install root: two modes, picked by whether uv_venv is set.
    • Hermetic venv mode (install_root=Path(...) or install_root=Path(...)): creates a real venv at the requested path via uv venv and installs packages into it with uv pip install --python <venv>/bin/python .... Binaries land in <uv_venv>/bin/<name>. This is the idiomatic "install a Python library + its CLI entrypoints into an isolated environment" path and matches PipProvider's pip_venv semantics.
    • Global tool mode (install_root=None): delegates to uv tool install which creates a fresh venv per tool under UV_TOOL_DIR (default ~/.local/share/uv/tools) and writes shims into UV_TOOL_BIN_DIR (default ~/.local/bin). Pass uv_tool_dir=Path(...) / bin_dir=Path(...) to override those dirs hermetically. This is the idiomatic "install a CLI tool globally" path.
  • Auto-switching: none. Honors UV_BINARY=/abs/path/to/uv. If uv isn't on the host, the provider is unavailable.
  • dry_run: shared behavior.
  • Security: supports both min_release_age and postinstall_scripts=False, and hydrates their provider defaults from ABX_PKG_MIN_RELEASE_AGE and ABX_PKG_POSTINSTALL_SCRIPTS. In both modes, postinstall_scripts=False becomes --no-build (wheels-only, no arbitrary sdist build scripts) and min_release_age becomes --exclude-newer=<ISO8601> (uv 0.4+). Explicit conflicting flags already present in install_args win over the derived defaults.
  • Overrides: install_args is passed as requirement specs; unpinned specs get a >=min_version floor when min_version is supplied.
  • Notes: update in venv mode is uv pip install --upgrade; update in global mode is uv tool install --force (re-installs the tool's venv). Uninstall in venv mode uses uv pip uninstall --python <venv>/bin/python; in global mode it uses uv tool uninstall <name>.

πŸ“¦ NpmProvider (npm)

Source: abx_pkg/binprovider_npm.py β€’ Tests: tests/test_npmprovider.py, tests/test_security_controls.py

INSTALLER_BIN = "npm"
PATH = ""                            # auto-built from npm local + global bin dirs
npm_prefix = None                    # None = global install, Path(...) = hermetic-ish prefix
cache_dir = user_cache_path("npm", "abx-pkg") or <system temp>/npm-cache
npm_install_args = ["--force", "--no-audit", "--no-fund", "--loglevel=error"]
  • Install root: install_root=None installs globally (walks up from the host's npm prefix / npm prefix -g to seed PATH). Set install_root=Path(...) or install_root=Path(...) to install under <prefix>/node_modules/.bin; that prefix bin dir becomes the provider's active executable search path.
  • Auto-switching: none. Shells out to npm directly and expects npm to be installed on the host. Honors NPM_BINARY=/abs/path/to/npm. Use PnpmProvider for pnpm.
  • dry_run: shared behavior.
  • Security: supports both postinstall_scripts=False and min_release_age, hydrated from ABX_PKG_POSTINSTALL_SCRIPTS and ABX_PKG_MIN_RELEASE_AGE. min_release_age requires an npm build that ships --min-release-age (detected once by probing npm install --help).
  • Overrides: install_args is passed as npm package specs; unpinned specs get rewritten to pkg@>=<min_version> when min_version is supplied.
  • Notes: postinstall_scripts=False adds --ignore-scripts; min_release_age adds --min-release-age=<days>. Explicit conflicting flags already present in install_args win over the derived defaults. get_version / get_abspath fall back to parsing npm show --json <package> and npm list --json --depth=0 output when the console script can't report its own version.

πŸ“¦ PnpmProvider (pnpm)

Source: abx_pkg/binprovider_pnpm.py β€’ Tests: tests/test_pnpmprovider.py

INSTALLER_BIN = "pnpm"
PATH = ""                            # auto-built from pnpm local + global bin dirs
pnpm_prefix = None                   # None = global install, Path(...) = hermetic-ish prefix
cache_dir = user_cache_path("pnpm", "abx-pkg") or <system temp>/pnpm-cache
pnpm_install_args = ["--loglevel=error"]
  • Install root: install_root=None installs globally. Set install_root=Path(...) or install_root=Path(...) to install under <prefix>/node_modules/.bin; that prefix bin dir becomes the provider's active executable search path.
  • Shells out to pnpm directly. Honors PNPM_BINARY=/abs/path/to/pnpm. Use NpmProvider for npm.
  • dry_run: shared behavior.
  • Security: supports both min_release_age and postinstall_scripts=False, and hydrates their provider defaults from ABX_PKG_MIN_RELEASE_AGE and ABX_PKG_POSTINSTALL_SCRIPTS. min_release_age requires pnpm 10.16+, and supports_min_release_age() returns False on older hosts (then it logs a warning and continues).
  • Overrides: install_args is passed as pnpm package specs; unpinned specs get rewritten to pkg@>=<min_version> when min_version is supplied.
  • Notes: pnpm has no --min-release-age CLI flag; this provider passes --config.minimumReleaseAge=<minutes> (the camelCase / kebab-case form pnpm exposes via its --config.<key>=<value> override). PNPM_HOME is auto-populated so pnpm add -g works without polluting the user's shell config.

🧢 YarnProvider (yarn)

Source: abx_pkg/binprovider_yarn.py β€’ Tests: tests/test_yarnprovider.py

INSTALLER_BIN = "yarn"
PATH = ""                            # prepends <yarn_prefix>/node_modules/.bin
yarn_prefix = None                   # workspace dir, defaults to ABX_PKG_YARN_ROOT or ~/.cache/abx-pkg/yarn
cache_dir = user_cache_path("yarn", "abx-pkg") or <system temp>/yarn-cache
yarn_install_args = []
  • Install root: Yarn 4 / Yarn Berry is workspace-based, so the provider always operates inside a project directory. Set install_root=Path(...) or install_root=Path(...) for a hermetic workspace; the workspace is auto-initialized with a stub package.json and .yarnrc.yml (nodeLinker: node-modules so binaries land in <workspace>/node_modules/.bin). When unset, the provider uses $ABX_PKG_YARN_ROOT or ~/.cache/abx-pkg/yarn.
  • Auto-switching: none. Honors YARN_BINARY=/abs/path/to/yarn. Both Yarn classic (1.x) and Yarn Berry (2+) work for basic install/update/uninstall, but only Yarn 4.10+ supports the security flags.
  • dry_run: shared behavior.
  • Security: supports both min_release_age and postinstall_scripts=False, and hydrates their provider defaults from ABX_PKG_MIN_RELEASE_AGE and ABX_PKG_POSTINSTALL_SCRIPTS. Both controls require Yarn 4.10+; on older hosts supports_min_release_age() / supports_postinstall_disable() return False and explicit values are logged-and-ignored.
  • Overrides: install_args is passed as Yarn package specs; unpinned specs get rewritten to pkg@>=<min_version> when min_version is supplied.
  • Notes: Yarn has no --ignore-scripts / --minimum-release-age CLI flags; the provider writes npmMinimalAgeGate: 7d (or whatever days value is configured) and enableScripts: false into <yarn_prefix>/.yarnrc.yml and additionally passes --mode skip-build to yarn add / yarn up when postinstall_scripts=False. Updates use yarn up <pkg> (Berry) or yarn upgrade <pkg> (classic). YARN_GLOBAL_FOLDER and YARN_CACHE_FOLDER are pointed at cache_dir so installs share a single cache across workspaces.

πŸ₯– BunProvider (bun)

Source: abx_pkg/binprovider_bun.py β€’ Tests: tests/test_bunprovider.py

INSTALLER_BIN = "bun"
PATH = ""                            # prepends <bun_prefix>/bin
bun_prefix = None                    # mirrors $BUN_INSTALL, None = ~/.bun (host-default)
cache_dir = user_cache_path("bun", "abx-pkg") or <system temp>/bun-cache
bun_install_args = []
  • Install root: bun_prefix=None writes into the host $BUN_INSTALL (default ~/.bun). Set bun_prefix=Path(...) or install_root=Path(...) to install under <prefix>/bin; the provider also creates <prefix>/install/global for the global node_modules dir, which is where bun puts the actual package state. The prefix bin dir becomes the provider's active executable search path.
  • Auto-switching: none. Honors BUN_BINARY=/abs/path/to/bun.
  • dry_run: shared behavior.
  • Security: supports both min_release_age and postinstall_scripts=False, and hydrates their provider defaults from ABX_PKG_MIN_RELEASE_AGE and ABX_PKG_POSTINSTALL_SCRIPTS. min_release_age requires Bun 1.3+, and supports_min_release_age() returns False on older hosts.
  • Overrides: install_args is passed as Bun package specs; unpinned specs get rewritten to pkg@>=<min_version> when min_version is supplied.
  • Notes: install/update use bun add -g (with --force as the update fallback). The provider passes --ignore-scripts for postinstall_scripts=False and --minimum-release-age=<seconds> (Bun's unit is seconds; this provider converts from days). Explicit conflicting flags already present in install_args win over the derived defaults.

πŸ¦• DenoProvider (deno)

Source: abx_pkg/binprovider_deno.py β€’ Tests: tests/test_denoprovider.py

INSTALLER_BIN = "deno"
PATH = ""                            # prepends <deno_root>/bin
deno_root = None                     # mirrors $DENO_INSTALL_ROOT, None = ~/.deno
deno_dir = None                      # mirrors $DENO_DIR for cache isolation
cache_dir = user_cache_path("deno", "abx-pkg") or <system temp>/deno-cache
deno_install_args = ["--allow-all"]
deno_default_scheme = "npm"          # 'npm' or 'jsr'
  • Install root: deno_root=None writes into the host $DENO_INSTALL_ROOT (default ~/.deno). Set deno_root=Path(...) or install_root=Path(...) for a hermetic root with executables under <deno_root>/bin. Set deno_dir=Path(...) to also isolate the module cache.
  • Auto-switching: none. Honors DENO_BINARY=/abs/path/to/deno.
  • dry_run: shared behavior.
  • Security: supports both min_release_age and postinstall_scripts=False / True, and hydrates their provider defaults from ABX_PKG_MIN_RELEASE_AGE and ABX_PKG_POSTINSTALL_SCRIPTS. min_release_age requires Deno 2.5+, and supports_min_release_age() returns False on older hosts.
  • Overrides: install_args is passed as deno install package specs and is auto-prefixed with npm: (or jsr: if deno_default_scheme="jsr") when an unqualified bare name is supplied. Already-qualified specs (npm:, jsr:, https://...) are passed through verbatim. Unpinned specs get rewritten to pkg@>=<min_version> when min_version is supplied.
  • Notes: install / update both run deno install -g --force --allow-all -n <bin_name> <pkg> because Deno's idiomatic update path is just a fresh global install. Deno's npm lifecycle scripts are opt-in (the opposite of npm), so the provider only adds --allow-scripts when postinstall_scripts=True. min_release_age is passed as --minimum-dependency-age=<minutes> (Deno's preferred unit; this provider converts from days). DENO_TLS_CA_STORE=system is set so installs work on hosts with corporate / sandboxed CA bundles.

πŸ§ͺ BashProvider (bash)

Source: abx_pkg/binprovider_bash.py β€’ Tests: tests/test_bashprovider.py

INSTALLER_BIN = "sh"
PATH = ""
bash_root = $ABX_PKG_BASH_ROOT or ~/.cache/abx-pkg/bash
bash_bin_dir = <bash_root>/bin
  • Install root: set bash_root / install_root for the managed state dir, and bash_bin_dir / bin_dir for the executable output dir.
  • Auto-switching: none.
  • dry_run: shared behavior.
  • Security: min_release_age and postinstall_scripts=False are unsupported and are ignored with a warning if explicitly requested.
  • Overrides: this provider is driven by literal per-binary shell overrides for install, update, and uninstall.
  • Notes: the provider exports INSTALL_ROOT, BIN_DIR, BASH_INSTALL_ROOT, and BASH_BIN_DIR into the shell environment for those commands.

πŸ¦€ CargoProvider (cargo)

Source: abx_pkg/binprovider_cargo.py β€’ Tests: tests/test_cargoprovider.py

INSTALLER_BIN = "cargo"
PATH = ""                            # prepends cargo_root/bin and cargo_home/bin
cargo_root = None                    # set this for hermetic installs
cargo_home = $CARGO_HOME or ~/.cargo
cargo_install_args = ["--locked"]
  • Install root: set install_root=Path(...) or install_root=Path(...) for isolated installs under <cargo_root>/bin; otherwise installs go through cargo_home.
  • Auto-switching: none.
  • dry_run: shared behavior.
  • Security: min_release_age and postinstall_scripts=False are unsupported and are ignored with a warning if explicitly requested.
  • Overrides: install_args is passed to cargo install; min_version becomes cargo install --version >=....
  • Notes: the provider also sets CARGO_HOME, CARGO_TARGET_DIR, and CARGO_INSTALL_ROOT when applicable.

πŸ’Ž GemProvider (gem)

Source: abx_pkg/binprovider_gem.py β€’ Tests: tests/test_gemprovider.py

INSTALLER_BIN = "gem"
PATH = DEFAULT_ENV_PATH
gem_home = None                      # defaults to $GEM_HOME or ~/.local/share/gem
gem_bindir = None                    # defaults to <gem_home>/bin
gem_install_args = ["--no-document"]
  • Install root: set gem_home or install_root, and optionally gem_bindir or bin_dir, for hermetic installs; otherwise it uses $GEM_HOME or ~/.local/share/gem.
  • Auto-switching: none.
  • dry_run: shared behavior.
  • Security: min_release_age and postinstall_scripts=False are unsupported and are ignored with a warning if explicitly requested.
  • Overrides: install_args maps to gem install ..., gem update ..., and gem uninstall ...; min_version becomes --version >=....
  • Notes: generated wrapper scripts are patched so they activate the configured GEM_HOME instead of the host default.

🐹 GoGetProvider (goget)

Source: abx_pkg/binprovider_goget.py β€’ Tests: tests/test_gogetprovider.py

INSTALLER_BIN = "go"
PATH = DEFAULT_ENV_PATH
gobin = None                         # defaults to <gopath>/bin
gopath = $GOPATH or ~/go
go_install_args = []
  • Install root: set gopath or install_root for the Go workspace, and gobin or bin_dir for the executable dir; otherwise installs land in <gopath>/bin.
  • Auto-switching: none.
  • dry_run: shared behavior.
  • Security: min_release_age and postinstall_scripts=False are unsupported and are ignored with a warning if explicitly requested.
  • Overrides: install_args is passed to go install ...; the default is ["<bin_name>@latest"].
  • Notes: update() is just install() again. Version detection prefers go version -m <binary> and falls back to the generic version probe. The provider name is goget, not go_get.

❄️ NixProvider (nix)

Source: abx_pkg/binprovider_nix.py β€’ Tests: tests/test_nixprovider.py

INSTALLER_BIN = "nix"
PATH = ""                            # prepends <nix_profile>/bin
nix_profile = $ABX_PKG_NIX_PROFILE or ~/.nix-profile
nix_state_dir = None                 # optional XDG state/cache isolation
nix_install_args = [
    "--extra-experimental-features", "nix-command",
    "--extra-experimental-features", "flakes",
]
  • Install root: set nix_profile=Path(...) or install_root=Path(...) for a custom profile; add nix_state_dir=Path(...) to isolate state/cache paths too.
  • Auto-switching: none.
  • dry_run: shared behavior.
  • Security: min_release_age and postinstall_scripts=False are unsupported and are ignored with a warning if explicitly requested.
  • Overrides: install_args is passed to nix profile install ...; default is ["nixpkgs#<bin_name>"].
  • Notes: update/uninstall operate on the resolved profile element name rather than reusing the full flake ref.

🐳 DockerProvider (docker)

Source: abx_pkg/binprovider_docker.py β€’ Tests: tests/test_dockerprovider.py

INSTALLER_BIN = "docker"
PATH = ""                            # prepends docker_shim_dir
docker_shim_dir = ($ABX_PKG_DOCKER_ROOT or ~/.cache/abx-pkg/docker) / "bin"
docker_run_args = ["--rm", "-i"]
  • Install root: partial only. Images are pulled into Docker's host-managed image store; the provider only controls the local shim dir and metadata dir. Use install_root=Path(...) for the shim/metadata root or bin_dir=Path(...) for the shim dir directly.
  • Auto-switching: none.
  • dry_run: shared behavior.
  • Security: min_release_age and postinstall_scripts=False are unsupported and are ignored with a warning if explicitly requested.
  • Overrides: install_args is a list of Docker image refs. The first item is treated as the main image and becomes the generated shim target.
  • Notes: default install args are ["<bin_name>:latest"]. install() / update() run docker pull, write metadata JSON, and create an executable wrapper that runs docker run .... Expects image refs as install args, typically via overrides on a Binary. It writes a local wrapper script for the binary and executes it via docker run ...; the binary version is parsed from the image tag, so semver-like tags work best.

🧩 ChromeWebstoreProvider (chromewebstore)

Source: abx_pkg/binprovider_chromewebstore.py β€’ Tests: tests/test_chromewebstoreprovider.py

INSTALLER_BIN = "node"
PATH = ""
extensions_root = $ABX_PKG_CHROMEWEBSTORE_ROOT or ~/.cache/abx-pkg/chromewebstore
extensions_dir = <extensions_root>/extensions
  • Install root: set extensions_root / install_root for the managed extension cache root, and extensions_dir / bin_dir for the unpacked extension output dir.
  • Auto-switching: none.
  • dry_run: shared behavior.
  • Security: min_release_age is unsupported and is ignored with a warning if explicitly requested. postinstall_scripts=False is supported as a standard kwarg and ABX_PKG_POSTINSTALL_SCRIPTS hydrates the provider default here, but there is no extra install-time toggle beyond the packaged JS runtime path this provider already uses.
  • Overrides: install_args are [webstore_id, "--name=<extension_name>"].
  • Notes: the packaged JS runtime under abx_pkg/js/chrome/ is used to download, unpack, and cache the extension, and the resolved binary path is the unpacked manifest.json. no_cache=True bypasses that metadata cache on the next install/update without deleting the unpacked extension tree.

🎭 PuppeteerProvider (puppeteer)

Source: abx_pkg/binprovider_puppeteer.py β€’ Tests: tests/test_puppeteerprovider.py

INSTALLER_BIN = "puppeteer-browsers"
PATH = ""
puppeteer_root = $ABX_PKG_PUPPETEER_ROOT or ~/.cache/abx-pkg/puppeteer
browser_bin_dir = <puppeteer_root>/bin
browser_cache_dir = <puppeteer_root>/cache
  • Install root: set puppeteer_root / install_root for the managed root, browser_bin_dir / bin_dir for symlinked executables, and browser_cache_dir for downloaded browser artifacts.
  • Auto-switching: bootstraps @puppeteer/browsers through NpmProvider and then uses that CLI for browser installs.
  • dry_run: shared behavior.
  • Security: min_release_age is unsupported for browser installs and is ignored with a warning if explicitly requested. postinstall_scripts=False is supported for the underlying npm bootstrap path, and ABX_PKG_POSTINSTALL_SCRIPTS hydrates the provider default here.
  • Overrides: install_args are passed through to @puppeteer/browsers install ..., with the provider appending its managed --path=<cache_dir>.
  • Notes: installed-browser resolution uses semantic version ordering, not lexicographic string sorting.

🎬 PlaywrightProvider (playwright)

Source: abx_pkg/binprovider_playwright.py β€’ Tests: tests/test_playwrightprovider.py

INSTALLER_BIN = "playwright"
PATH = ""
playwright_root = None           # when set, doubles as PLAYWRIGHT_BROWSERS_PATH
browser_bin_dir = <playwright_root>/bin  # symlink dir for resolved browsers
playwright_install_args = ["--with-deps"]
euid = 0                         # routes exec() through sudo-first-then-fallback
  • Install root: set playwright_root / install_root to pin both the abx-pkg managed root AND PLAYWRIGHT_BROWSERS_PATH to the same directory. Leave it unset to let playwright use its own OS-default browsers path (~/.cache/ms-playwright on Linux etc.) β€” in that case abx-pkg maintains no managed symlink dir or npm prefix at all, the playwright npm CLI bootstraps against the host's npm default, and load() returns the resolved executablePath() directly. browser_bin_dir / bin_dir overrides the symlink directory when playwright_root is pinned.
  • Auto-switching: bootstraps the playwright npm package through NpmProvider, then runs playwright install --with-deps <install_args> against it. Resolves each installed browser's real executable via the playwright-core Node.js API (chromium.executablePath() etc.) and writes a symlink into bin_dir when one is configured.
  • dry_run: shared behavior β€” the install handler short-circuits to a placeholder without touching the host.
  • Privilege handling: --with-deps installs system packages and requires root on Linux. euid defaults to 0, which routes every exec() call through the base BinProvider.exec sudo-first-then-fallback path β€” it tries sudo -n -- playwright install --with-deps ... first on non-root hosts, falls back to running the command directly if sudo fails or isn't available, and merges both stderr outputs into the final error if both attempts fail.
  • Security: min_release_age and postinstall_scripts=False are unsupported for browser installs and are ignored with a warning if explicitly requested.
  • Overrides: install_args are appended onto playwright install after playwright_install_args (defaults to ["--with-deps"]) and passed through verbatim β€” use whatever browser names / flags the playwright install CLI accepts (chromium, firefox, webkit, --no-shell, --only-shell, --force, etc.).
  • Notes: update() bumps the managed playwright npm package first (via NpmProvider.update) so its pinned browser versions refresh, then re-runs playwright install --force <install_args> to pull any new browser builds. uninstall() removes the relevant <bin_name>-*/ directories from playwright_root alongside the bin-dir symlink, since playwright uninstall only drops unused browsers on its own. Both update() and uninstall() leave playwright's OS-default cache untouched when playwright_root is unset.

πŸ› οΈ PyinfraProvider (pyinfra)

Source: abx_pkg/binprovider_pyinfra.py β€’ Tests: tests/test_pyinfraprovider.py

INSTALLER_BIN = "pyinfra"
PATH = os.environ.get("PATH", DEFAULT_PATH)
pyinfra_installer_module = "auto"
pyinfra_installer_kwargs = {}
  • Install root: no hermetic prefix support. It delegates to host package managers through pyinfra operations.
  • Auto-switching: installer_module="auto" resolves to operations.brew.packages on macOS and operations.server.packages on Linux.
  • dry_run: shared behavior.
  • Security: min_release_age and postinstall_scripts=False are unsupported and are ignored with a warning if explicitly requested.
  • Overrides: install_args is the package list passed to the selected pyinfra operation.
  • Notes: privilege requirements depend on the underlying package manager and selected module. When pyinfra tries a privileged sudo path and then falls back, both error outputs are preserved if the final attempt also fails.

πŸ“˜ AnsibleProvider (ansible)

Source: abx_pkg/binprovider_ansible.py β€’ Tests: tests/test_ansibleprovider.py

INSTALLER_BIN = "ansible"
PATH = os.environ.get("PATH", DEFAULT_PATH)
ansible_installer_module = "auto"
ansible_playbook_template = ANSIBLE_INSTALL_PLAYBOOK_TEMPLATE
  • Install root: no hermetic prefix support. It delegates to the host via ansible-runner.
  • Auto-switching: installer_module="auto" resolves to community.general.homebrew on macOS and ansible.builtin.package on Linux.
  • dry_run: shared behavior.
  • Security: min_release_age and postinstall_scripts=False are unsupported and are ignored with a warning if explicitly requested.
  • Overrides: install_args becomes the playbook loop input for the chosen Ansible module.
  • Notes: when using the Homebrew module, the provider auto-injects the detected brew search path into module kwargs. Privilege requirements still come from the underlying package manager, and failed sudo attempts are included in the final error if the fallback attempt also fails.

Represents a single binary dependency aka a package (e.g. wget, curl, ffmpeg). Each Binary can declare one or more BinProviders it supports, along with per-provider overrides.

Binarys implement the following interface:

  • load(), install(), update(), uninstall() -> Binary
  • binproviders
  • binprovider / loaded_binprovider
  • abspath / loaded_abspath
  • abspaths / loaded_abspaths
  • version / loaded_version
  • sha256 / loaded_sha256

Binary.install() and Binary.update() return a fresh loaded Binary. Binary.uninstall() returns a Binary with binprovider, abspath, version, and sha256 cleared after removal. Binary.load(), Binary.install(), and Binary.update() all enforce min_version consistently. All four lifecycle methods also accept no_cache=True to bypass cached/current-state checks.

from abx_pkg import Binary, SemVer, env, brew

curl = Binary(
    name="curl",
    min_version=SemVer("8.0.0"),
    binproviders=[env, brew],
).install()

print(curl.binprovider)   # EnvProvider(...) or BrewProvider(...)
print(curl.abspath)       # Path('/usr/local/bin/curl')
print(curl.version)       # SemVer(8, 4, 0)
print(curl.is_valid)      # True

curl = curl.update()
curl = curl.uninstall()

For reusable Binary subclasses with per-provider overrides, see Advanced Usage above.

from abx_pkg import SemVer

### Example: Use the SemVer type directly for parsing & verifying version strings
SemVer.parse('Google Chrome 124.0.6367.208+beta_234. 234.234.123')  # SemVer(124, 0, 6367)
SemVer.parse('2024.04.05')                                          # SemVer(2024, 4, 5)
SemVer.parse('1.9+beta')                                            # SemVer(1, 9, 0)
str(SemVer(1, 9, 0))                                                # '1.9.0'

These types are all meant to be used library-style to make writing your own apps easier.
e.g. you can use it to build things like playwright install --with-deps.






Development

abx-pkg uses uv for local development, dependency sync, linting, and tests.

git clone https://github.com/ArchiveBox/abx-pkg && cd abx-pkg

# setup the venv and install packages
uv sync --all-extras
source .venv/bin/activate

# run formatting/lint/type checks
uv run prek run --all-files

# run the full test suite from tests/
uv run pytest -sx tests/

# build distributions
uv build && uv publish --username=__token__
  • Tests live under tests/.
  • Use uv run pytest -sx tests/test_npmprovider.py or a specific node like uv run pytest -sx tests/test_npmprovider.py::TestNpmProvider::test_provider_dry_run_does_not_install_zx when iterating on one provider.


Note: this package used to be called pydantic-pkgr, it was renamed to abx-pkg on 2024-11-12.

Other Packages We Like

Sponsor this project

  •  
  •  

Packages

 
 
 

Contributors