Skip to content

Welcome to the documentation of pbs-installer

An installer for @indygreg's python-build-standalone

Installation

It's highly recommended to install pbs-installer with pipx:

pipx install pbs-installer

Or more conviniently, you can run with pipx directly:

pipx run pbs-installer --help

Usage

Core functions for the PBS Installer.

download(python_file, destination, client=None)

Download the given url to the destination.

Extras required

pbs-installer[download] must be installed to use this function.

Parameters:

Name Type Description Default
python_file PythonFile

The (url, checksum) tuple to download

required
destination StrPath

The file path to download to

required
client Client | None

A http.Client to use for downloading, or None to create a new one

None

Returns:

Type Description
str

The original filename of the downloaded file

Source code in src/pbs_installer/_install.py
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
def download(
    python_file: PythonFile, destination: StrPath, client: httpx.Client | None = None
) -> str:
    """Download the given url to the destination.

    Note: Extras required
        `pbs-installer[download]` must be installed to use this function.

    Parameters:
        python_file: The (url, checksum) tuple to download
        destination: The file path to download to
        client: A http.Client to use for downloading, or None to create a new one

    Returns:
        The original filename of the downloaded file
    """
    url, checksum = python_file
    logger.debug("Downloading url %s to %s", url, destination)
    try:
        import httpx
    except ModuleNotFoundError:
        raise RuntimeError("You must install httpx to use this function") from None

    if client is None:
        client = httpx.Client(trust_env=True, follow_redirects=True)

    filename = unquote(url.rsplit("/")[-1])
    hasher = hashlib.sha256()
    if not checksum:
        logger.warning("No checksum found for %s, this would be insecure", url)

    with open(destination, "wb") as f:
        with client.stream("GET", url, headers=_get_headers()) as resp:
            resp.raise_for_status()
            for chunk in resp.iter_bytes(chunk_size=8192):
                if checksum:
                    hasher.update(chunk)
                f.write(chunk)

    if checksum and hasher.hexdigest() != checksum:
        raise RuntimeError(f"Checksum mismatch. Expected {checksum}, got {hasher.hexdigest()}")
    return filename

Get the download URL matching the given requested version.

Parameters:

Name Type Description Default
request str

The version of Python to install, e.g. 3.8,3.10.4

required
arch str

The architecture to install, e.g. x86_64, arm64

THIS_ARCH
platform str

The platform to install, e.g. linux, macos

THIS_PLATFORM
implementation PythonImplementation

The implementation of Python to install, allowed values are 'cpython' and 'pypy'

'cpython'
build_dir bool

Whether to include the build/ directory from indygreg builds

False

Returns:

Type Description
tuple[PythonVersion, PythonFile]

A tuple of the PythonVersion and the download URL

Examples:

>>> get_download_link("3.10", "x86_64", "linux")
(PythonVersion(kind='cpython', major=3, minor=10, micro=13),
'https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-x86_64-unknown-linux-gnu-pgo%2Blto-full.tar.zst')
Source code in src/pbs_installer/_install.py
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
def get_download_link(
    request: str,
    arch: str = THIS_ARCH,
    platform: str = THIS_PLATFORM,
    implementation: PythonImplementation = "cpython",
    build_dir: bool = False,
) -> tuple[PythonVersion, PythonFile]:
    """Get the download URL matching the given requested version.

    Parameters:
        request: The version of Python to install, e.g. 3.8,3.10.4
        arch: The architecture to install, e.g. x86_64, arm64
        platform: The platform to install, e.g. linux, macos
        implementation: The implementation of Python to install, allowed values are 'cpython' and 'pypy'
        build_dir: Whether to include the `build/` directory from indygreg builds

    Returns:
        A tuple of the PythonVersion and the download URL

    Examples:
        >>> get_download_link("3.10", "x86_64", "linux")
        (PythonVersion(kind='cpython', major=3, minor=10, micro=13),
        'https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-x86_64-unknown-linux-gnu-pgo%2Blto-full.tar.zst')
    """
    from ._versions import PYTHON_VERSIONS

    for py_ver, urls in PYTHON_VERSIONS.items():
        if not py_ver.matches(request, implementation):
            continue

        matched = urls.get((platform, arch, not build_dir))
        if matched is not None:
            return py_ver, matched
        if build_dir and (matched := urls.get((platform, arch, False))) is not None:
            return py_ver, matched
    raise ValueError(
        f"Could not find a version matching version={request!r}, implementation={implementation}"
    )

install(request, destination, version_dir=False, client=None, arch=None, platform=None, implementation='cpython', build_dir=False)

Download and install the requested python version.

Extras required

pbs-installer[all] must be installed to use this function.

Parameters:

Name Type Description Default
request str

The version of Python to install, e.g. 3.8,3.10.4

required
destination StrPath

The directory to install to

required
version_dir bool

Whether to install to a subdirectory named with the python version

False
client Client | None

A httpx.Client to use for downloading

None
arch str | None

The architecture to install, e.g. x86_64, arm64

None
platform str | None

The platform to install, e.g. linux, macos

None
implementation PythonImplementation

The implementation of Python to install, allowed values are 'cpython' and 'pypy'

'cpython'
build_dir bool

Whether to include the build/ directory from indygreg builds

False

Examples:

>>> install("3.10", "./python")
Installing cpython@3.10.4 to ./python
>>> install("3.10", "./python", version_dir=True)
Installing cpython@3.10.4 to ./python/cpython@3.10.4
Source code in src/pbs_installer/_install.py
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
def install(
    request: str,
    destination: StrPath,
    version_dir: bool = False,
    client: httpx.Client | None = None,
    arch: str | None = None,
    platform: str | None = None,
    implementation: PythonImplementation = "cpython",
    build_dir: bool = False,
) -> None:
    """Download and install the requested python version.

    Note: Extras required
        `pbs-installer[all]` must be installed to use this function.

    Parameters:
        request: The version of Python to install, e.g. 3.8,3.10.4
        destination: The directory to install to
        version_dir: Whether to install to a subdirectory named with the python version
        client: A httpx.Client to use for downloading
        arch: The architecture to install, e.g. x86_64, arm64
        platform: The platform to install, e.g. linux, macos
        implementation: The implementation of Python to install, allowed values are 'cpython' and 'pypy'
        build_dir: Whether to include the `build/` directory from indygreg builds

    Examples:
        >>> install("3.10", "./python")
        Installing cpython@3.10.4 to ./python
        >>> install("3.10", "./python", version_dir=True)
        Installing cpython@3.10.4 to ./python/cpython@3.10.4
    """
    if platform is None:
        platform = THIS_PLATFORM
    if arch is None:
        arch = THIS_ARCH

    ver, python_file = get_download_link(
        request, arch=arch, platform=platform, implementation=implementation, build_dir=build_dir
    )
    if version_dir:
        destination = os.path.join(destination, str(ver))
    logger.debug("Installing %s to %s", ver, destination)
    os.makedirs(destination, exist_ok=True)
    with tempfile.NamedTemporaryFile() as tf:
        tf.close()
        original_filename = download(python_file, tf.name, client)
        install_file(tf.name, destination, original_filename, build_dir)

install_file(filename, destination, original_filename=None, build_dir=False)

Unpack the downloaded file to the destination.

Extras required

pbs-installer[install] must be installed to use this function.

Parameters:

Name Type Description Default
filename StrPath

The file to unpack

required
destination StrPath

The directory to unpack to

required
original_filename str | None

The original filename of the file, if it was renamed

None
build_dir bool

Whether to include the build/ directory from indygreg builds

False
Source code in src/pbs_installer/_install.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
def install_file(
    filename: StrPath,
    destination: StrPath,
    original_filename: str | None = None,
    build_dir: bool = False,
) -> None:
    """Unpack the downloaded file to the destination.

    Note: Extras required
        `pbs-installer[install]` must be installed to use this function.

    Parameters:
        filename: The file to unpack
        destination: The directory to unpack to
        original_filename: The original filename of the file, if it was renamed
        build_dir: Whether to include the `build/` directory from indygreg builds
    """

    from ._utils import unpack_tar, unpack_zip, unpack_zst

    if original_filename is None:
        original_filename = str(filename)
    logger.debug(
        "Extracting file %s to %s with original filename %s",
        filename,
        destination,
        original_filename,
    )
    filename = cast(str, filename)
    if original_filename.endswith(".zst"):
        unpack_zst(filename, destination)
    elif original_filename.endswith(".zip"):
        unpack_zip(filename, destination)
    else:
        unpack_tar(filename, destination)

CLI Usage

pbs-installer also ships with a CLI named pbs-install:

usage: pbs-install [-h] [--version-dir] -d DESTINATION [--arch {arm64,i686,x86_64}] [--platform {darwin,linux,windows}] [-v] [-l] version

Installer for Python Build Standalone

options:
  -h, --help            show this help message and exit
  -v, --verbose         Enable verbose logging
  -l, --list            List installable versions

Install Arguments:
  version               The version of Python to install, e.g. 3.8,3.10.4
  --version-dir         Install to a subdirectory named by the version
  -d DESTINATION, --destination DESTINATION
                        The directory to install to
  --arch {arm64,i686,x86_64}
                        Override the architecture to install
  --platform {darwin,linux,windows}
                        Override the platform to install