Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.aspect.build/llms.txt

Use this file to discover all available pages before exploring further.

The interpreter provisioning feature is available under py/unstable and is a preview API. No compatibility guarantees are made across releases.
aspect_rules_py provides its own Python interpreter provisioning, backed by 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:
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:
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:
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:
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:
SettingRoot moduleNon-root module
configure()Sets release search space and mirrorSilently ignored
toolchain(python_version = ...)Adds to global setAdds to global set
toolchain(is_default = True)HonoredSilently ignored
toolchain(pre_release = True)HonoredSilently 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:
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:
# .bazelrc
common --@aspect_rules_py//py:python_version=3.12
You can also override this flag on the command line for one-off testing:
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:
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_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.
MechanismScopeSet by
is_default = True on toolchain()Whole build (fallback)MODULE.bazel
--@aspect_rules_py//py:python_versionWhole build.bazelrc or command line
python_version attributeSingle targetBUILD.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:
interpreters.toolchain(
    python_version = "3.12",
    build_config = "install_only_stripped",
)
ConfigurationDescription
install_onlyStandard optimized build (default)
install_only_strippedSame as install_only but with debug symbols stripped
freethreaded+pgo+ltoFree-threaded (no GIL) build with PGO+LTO optimization
freethreaded+debugFree-threaded debug build

Platforms

These platforms are available by default:
PlatformOSArchitecture
aarch64-apple-darwinmacOSARM64
x86_64-apple-darwinmacOSx86_64
aarch64-unknown-linux-gnuLinux (glibc)ARM64
x86_64-unknown-linux-gnuLinux (glibc)x86_64
aarch64-unknown-linux-muslLinux (musl)ARM64
x86_64-unknown-linux-muslLinux (musl)x86_64
x86_64-pc-windows-msvcWindowsx86_64
aarch64-pc-windows-msvcWindowsARM64
i686-pc-windows-msvcWindowsx86 (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.