> ## Documentation Index
> Fetch the complete documentation index at: https://site.aspect.build/llms.txt
> Use this file to discover all available pages before exploring further.

# Python interpreter provisioning

> Bazel aspect_rules_py interpreter provisioning preview backed by python-build-standalone (PBS), replacing rules_python python.toolchain interpreter management.

<Warning>
  The interpreter provisioning feature is available under `py/unstable` and is a preview API. No compatibility guarantees are made across releases.
</Warning>

`aspect_rules_py` provides its own Python interpreter provisioning, backed by
[python-build-standalone](https://github.com/astral-sh/python-build-standalone)
(PBS). This replaces the need to use `rules_python`'s `python.toolchain()` for
interpreter management.

**No version manifests.** Unlike `rules_python`, there is no checked-in table of
SHA256 hashes or version metadata to maintain. Interpreter versions and checksums
are discovered automatically from PBS release artifacts and cached in your
`MODULE.bazel.lock`.

**Easy updates.** To get new interpreter versions, add a PBS release date—no
repinning or manifest regeneration required.

**No editorial decisions.** Any PBS release exposes every version it contains.
Need Python 3.8? Add an older release date that includes it.

**Windows and cross-platform support.** Nine platforms are registered out of the box, including Windows on x86\_64, aarch64, and i686, Linux with glibc and musl, and macOS.

## Quickstart

Add the following to your `MODULE.bazel`:

```py theme={null}
bazel_dep(name = "aspect_rules_py", version = "1.9.0")

interpreters = use_extension("@aspect_rules_py//py/unstable:extension.bzl", "python_interpreters")
interpreters.toolchain(
    is_default = True,
    python_version = "3.12",
)
interpreters.toolchain(python_version = "3.11")
use_repo(interpreters, "python_interpreters")
register_toolchains("@python_interpreters//:all")
```

The extension uses a set of default PBS release dates covering Python 3.8
through 3.15. The extension automatically selects the newest available build for each requested version.

## Configuring releases

By default, the extension ships with a small set of release dates covering the
full range of available Python versions. Use `configure()` to pin to specific
releases or to include versions that have been dropped from newer releases:

```py theme={null}
interpreters = use_extension("@aspect_rules_py//py/unstable:extension.bzl", "python_interpreters")
interpreters.configure(
    releases = ["20260303", "20241002"],
)

interpreters.toolchain(python_version = "3.12", is_default = True)
interpreters.toolchain(python_version = "3.8")  # Resolved from 20241002

use_repo(interpreters, "python_interpreters")
register_toolchains("@python_interpreters//:all")
```

When multiple releases contain the same Python minor version, the extension prefers the newest release. The module graph accepts only one `configure()` tag, and only the root module's tag takes effect. Dependency modules may include `configure()` without error, but the extension silently ignores it.

## Using the latest release

For development workflows where you always want the newest PBS release:

```py theme={null}
interpreters.configure(releases = ["latest"])
```

This resolves to the newest release using the GitHub releases API. Because the result depends on when it runs, this configuration marks the extension as non-reproducible—Bazel re-evaluates it on each invocation rather than caching it.

For CI and production builds, prefer explicit release dates.

## Using a mirror or fork

If you host PBS releases on a mirror or maintain your own fork:

```py theme={null}
interpreters.configure(
    releases = ["20260303"],
    base_url = "https://my-mirror.example.com/pbs/releases/download",
)
```

The `base_url` must point to a directory structure matching PBS releases, where
`{base_url}/{date}/SHA256SUMS` and `{base_url}/{date}/{asset}` are valid paths.

## Module scoping

Only the root module's `configure()` tag and `is_default` and `pre_release` flags on `toolchain()` take effect. This gives the root module full control over the build environment while allowing dependency modules to declare which Python versions they need.

The following table shows which settings each module type controls:

| Setting                           | Root module                          | Non-root module    |
| --------------------------------- | ------------------------------------ | ------------------ |
| `configure()`                     | Sets release search space and mirror | Silently ignored   |
| `toolchain(python_version = ...)` | Adds to global set                   | Adds to global set |
| `toolchain(is_default = True)`    | Honored                              | Silently ignored   |
| `toolchain(pre_release = True)`   | Honored                              | Silently ignored   |

If a dependency module requests a Python version that no root-module release includes, the build fails with a clear error message identifying which module requested it.

## Selecting Python versions

There are three layers of version control, from broadest to most specific.

### Default version (MODULE.bazel)

Bazel selects the `is_default = True` toolchain when nothing else overrides it:

```py theme={null}
interpreters.toolchain(python_version = "3.12", is_default = True)
```

### Build-wide default (.bazelrc)

Set `--@aspect_rules_py//py:python_version` in `.bazelrc` to lock the entire
build to a specific version. This is good practice even when it matches the
`is_default` toolchain—it makes the choice explicit and visible in version
control:

```sh theme={null}
# .bazelrc
common --@aspect_rules_py//py:python_version=3.12
```

You can also override this flag on the command line for one-off testing:

```sh theme={null}
bazel test //... --@aspect_rules_py//py:python_version=3.11
```

### Per-target overrides (python\_version attribute)

Individual `py_binary`, `py_venv_binary`, `py_test`, and `py_venv_test` targets
can pin to a specific version using the `python_version` attribute, which takes
precedence over the flag:

```py theme={null}
load("@aspect_rules_py//py:defs.bzl", "py_binary")

py_binary(
    name = "app",
    srcs = ["app.py"],
    python_version = "3.12",
)
```

Use this when a target genuinely requires a specific version, for example, a
library that only supports 3.12+, or a test matrix that exercises multiple
versions:

```py theme={null}
[py_venv_test(
    name = "test_py%s" % v.replace(".", ""),
    srcs = ["test.py"],
    main = "test.py",
    python_version = v,
) for v in ["3.11", "3.12", "3.13"]]
```

### Precedence

The most specific setting wins: `python_version` attribute > flag > default toolchain.

| Mechanism                               | Scope                  | Set by                     |
| --------------------------------------- | ---------------------- | -------------------------- |
| `is_default = True` on `toolchain()`    | Whole build (fallback) | `MODULE.bazel`             |
| `--@aspect_rules_py//py:python_version` | Whole build            | `.bazelrc` or command line |
| `python_version` attribute              | Single target          | `BUILD.bazel`              |

## Build configurations

PBS provides several build configurations that control how the Python interpreter
itself is compiled. The right choice depends on your priorities: binary size,
debug support, or experimental features like free-threading.

The default is `install_only`, which uses PGO+LTO compilation on platforms that
support it. PGO (Profile-Guided Optimization) and LTO (Link-Time Optimization)
produce a faster interpreter by letting the compiler optimize across the entire
program using real runtime behavior. This configuration suits most use cases.

Use `install_only_stripped` to reduce binary size by removing debug symbols,
which is useful in environments where disk space or distribution size matters.

The `freethreaded` configurations disable the GIL (Global Interpreter Lock),
allowing true parallel execution across threads. Free-threading is experimental
and was introduced in Python 3.13. Use these configurations only if you are
explicitly targeting the free-threaded build and your dependencies support it.

Select a configuration per toolchain using the `build_config` attribute:

```py theme={null}
interpreters.toolchain(
    python_version = "3.12",
    build_config = "install_only_stripped",
)
```

| Configuration           | Description                                            |
| ----------------------- | ------------------------------------------------------ |
| `install_only`          | Standard optimized build (default)                     |
| `install_only_stripped` | Same as `install_only` but with debug symbols stripped |
| `freethreaded+pgo+lto`  | Free-threaded (no GIL) build with PGO+LTO optimization |
| `freethreaded+debug`    | Free-threaded debug build                              |

## Platforms

These platforms are available by default:

| Platform                     | OS            | Architecture |
| ---------------------------- | ------------- | ------------ |
| `aarch64-apple-darwin`       | macOS         | ARM64        |
| `x86_64-apple-darwin`        | macOS         | x86\_64      |
| `aarch64-unknown-linux-gnu`  | Linux (glibc) | ARM64        |
| `x86_64-unknown-linux-gnu`   | Linux (glibc) | x86\_64      |
| `aarch64-unknown-linux-musl` | Linux (musl)  | ARM64        |
| `x86_64-unknown-linux-musl`  | Linux (musl)  | x86\_64      |
| `x86_64-pc-windows-msvc`     | Windows       | x86\_64      |
| `aarch64-pc-windows-msvc`    | Windows       | ARM64        |
| `i686-pc-windows-msvc`       | Windows       | x86 (32-bit) |

Not all Python versions are available on all platforms. The toolchain resolver silently skips unavailable combinations.

## Compatibility with rules\_python

This interpreter provisioning is designed to coexist with `rules_python`:

* Toolchain registration uses the standard `@bazel_tools//tools/python:toolchain_type`,
  so these interpreters work with all existing Python rules.
* Build transitions keep the `@rules_python//python/config_settings:python_version` flag in
  sync with `aspect_rules_py`'s own version flag.
* `aspect_rules_py` uses `py_runtime` and `py_runtime_pair` from `rules_python` to create
  the runtime providers.

You can migrate incrementally: replace `python.toolchain()` calls with
`interpreters.toolchain()` and remove the `rules_python` interpreter
configuration while keeping everything else unchanged.
